source: trunk/FACT++/src/sqmctrl.cc@ 17918

Last change on this file since 17918 was 17918, checked in by tbretz, 10 years ago
First version of the control for the sky quality meter.
File size: 13.2 KB
Line 
1#include <boost/algorithm/string.hpp>
2
3#include "FACT.h"
4#include "Dim.h"
5#include "Event.h"
6#include "StateMachineDim.h"
7#include "StateMachineAsio.h"
8#include "Connection.h"
9#include "LocalControl.h"
10#include "Configuration.h"
11#include "Console.h"
12
13#include "tools.h"
14
15#include "HeadersSQM.h"
16
17namespace ba = boost::asio;
18namespace bs = boost::system;
19namespace dummy = ba::placeholders;
20
21using namespace std;
22
23class ConnectionSQM : public Connection
24{
25protected:
26 virtual void Update(const SQM::Data &)
27 {
28 }
29
30private:
31 bool fIsVerbose;
32 bool fMsgReceived;
33 bool fMsgValid;
34
35 uint16_t fTimeout;
36
37 boost::asio::streambuf fBuffer;
38
39 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
40 {
41 // Do not schedule a new read if the connection failed.
42 if (bytes_received==0 || err)
43 {
44 if (err==ba::error::eof)
45 Warn("Connection closed by remote host.");
46
47 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
48 // 125: Operation canceled
49 if (err && err!=ba::error::eof && // Connection closed by remote host
50 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
51 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
52 {
53 ostringstream str;
54 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
55 Error(str);
56 }
57 PostClose(err!=ba::error::basic_errors::operation_aborted);
58 return;
59 }
60
61 istream is(&fBuffer);
62
63 string buffer;
64 if (!getline(is, buffer, '\n'))
65 {
66 Error("Received message does not contain \\n... closing connection.");
67 PostClose(false);
68 return;
69 }
70
71 buffer = buffer.substr(0, buffer.size()-1);
72
73 if (fIsVerbose)
74 Out() << buffer << endl;
75
76 vector<string> vec;
77 boost::split(vec, buffer, boost::is_any_of(","));
78
79 try
80 {
81 if (vec.size()!=6)
82 throw runtime_error("Unknown number of fields in received data");
83
84 if (vec[0]!="r")
85 throw runtime_error("Not a 'reading request' answer.");
86
87 SQM::Data data;
88
89 data.mag = stof(vec[1]);
90 data.freq = stol(vec[2]);
91 data.counts = stol(vec[3]);
92 data.period = stof(vec[4]);
93 data.temp = stof(vec[5]);
94
95 Update(data);
96 }
97 catch (const exception &e)
98 {
99 fMsgValid = false;
100 Error("Parsing received data failed ["+string(e.what())+"]");
101 Error("Received: "+buffer);
102 PostClose(false);
103 return;
104 }
105
106 fMsgValid = true;
107 fMsgReceived = true;
108 }
109
110 void HandleReadTimeout(const bs::error_code &error)
111 {
112 // 125: Operation canceled (bs::error_code(125, bs::system_category))
113 if (error && error!=ba::error::basic_errors::operation_aborted)
114 {
115 ostringstream str;
116 str << "Read timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
117 Error(str);
118
119 PostClose(true);
120 return;
121 }
122
123 if (!is_open())
124 {
125 // For example: Here we could schedule a new accept if we
126 // would not want to allow two connections at the same time.
127 PostClose(true);
128 return;
129 }
130
131 // Check whether the deadline has passed. We compare the deadline
132 // against the current time since a new asynchronous operation
133 // may have moved the deadline before this actor had a chance
134 // to run.
135 if (fInTimeout.expires_at() > ba::deadline_timer::traits_type::now())
136 return;
137
138 if (fMsgReceived)
139 {
140 StartReadReport();
141 return;
142 }
143
144 ostringstream str;
145 str << "No answer received from " << URL() << ".";
146 Error(str);
147
148 PostClose(true);
149 }
150
151 void StartReadReport()
152 {
153 fMsgReceived = false;
154
155 PostMessage(string("rx\n"), 3);
156 async_read_until(*this, fBuffer, '\n',
157 boost::bind(&ConnectionSQM::HandleRead, this,
158 dummy::error, dummy::bytes_transferred));
159
160 fInTimeout.expires_from_now(boost::posix_time::milliseconds(fTimeout));
161 fInTimeout.async_wait(boost::bind(&ConnectionSQM::HandleReadTimeout,
162 this, dummy::error));
163 }
164
165private:
166 // This is called when a connection was established
167 void ConnectionEstablished()
168 {
169 StartReadReport();
170 }
171
172public:
173 static const uint16_t kMaxAddr;
174
175public:
176 ConnectionSQM(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
177 fIsVerbose(true)
178 {
179 SetLogStream(&imp);
180 }
181
182 void SetVerbose(bool b)
183 {
184 fIsVerbose = b;
185 Connection::SetVerbose(b);
186 }
187
188 void SetTimeout(uint16_t t)
189 {
190 fTimeout = t;
191 }
192
193 int GetState() const
194 {
195 if (!is_open())
196 return SQM::State::kDisconnected;
197
198 return fMsgValid ? SQM::State::kValid : SQM::State::kConnected;
199 }
200};
201
202// ------------------------------------------------------------------------
203
204#include "DimDescriptionService.h"
205
206class ConnectionDimWeather : public ConnectionSQM
207{
208private:
209 DimDescribedService fDim;
210
211public:
212 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
213 ConnectionSQM(ioservice, imp),
214 fDim("SQM_CONTROL/DATA", "F:1;I:1;I:1;F:1;F:1",
215 "Data received from sky quality meter"
216 "|mag[mag/arcsec^2]:Magnitude (0 means upper brightness limit)"
217 "|freq[Hz]:Frequency of sensor"
218 "|counts:Period of sensor (counts occur at 14.7456MHz/32)"
219 "|period[s]:Period of sensor"
220 "|temp[deg C]:Sensor temperature in deg C")
221 {
222 }
223
224 void Update(const SQM::Data &data)
225 {
226 fDim.Update(data);
227 }
228};
229
230// ------------------------------------------------------------------------
231
232template <class T, class S>
233class StateMachineSQMControl : public StateMachineAsio<T>
234{
235private:
236 S fSQM;
237
238 bool CheckEventSize(size_t has, const char *name, size_t size)
239 {
240 if (has==size)
241 return true;
242
243 ostringstream msg;
244 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
245 T::Fatal(msg);
246 return false;
247 }
248
249 int Disconnect()
250 {
251 // Close all connections
252 fSQM.PostClose(false);
253
254 return T::GetCurrentState();
255 }
256
257 int Reconnect(const EventImp &evt)
258 {
259 // Close all connections to supress the warning in SetEndpoint
260 fSQM.PostClose(false);
261
262 // Now wait until all connection have been closed and
263 // all pending handlers have been processed
264 ba::io_service::poll();
265
266 if (evt.GetBool())
267 fSQM.SetEndpoint(evt.GetString());
268
269 // Now we can reopen the connection
270 fSQM.PostClose(true);
271
272 return T::GetCurrentState();
273 }
274
275 int SetVerbosity(const EventImp &evt)
276 {
277 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
278 return T::kSM_FatalError;
279
280 fSQM.SetVerbose(evt.GetBool());
281
282 return T::GetCurrentState();
283 }
284
285 int Send(const string &cmd)
286 {
287 const string tx = cmd+"\r\n";
288 fSQM.PostMessage(tx, tx.size());
289 return T::GetCurrentState();
290 }
291
292 int SendCommand(const EventImp &evt)
293 {
294 return Send(evt.GetString());
295 }
296
297 int Execute()
298 {
299 return fSQM.GetState();
300 }
301
302
303public:
304 StateMachineSQMControl(ostream &out=cout) :
305 StateMachineAsio<T>(out, "SQM_CONTROL"), fSQM(*this, *this)
306 {
307 // State names
308 T::AddStateName(SQM::State::kDisconnected, "Disconnected",
309 "No connection to Sky Quality Meter");
310
311 T::AddStateName(SQM::State::kConnected, "Connected",
312 "Connection established, but no valid message received");
313
314 T::AddStateName(SQM::State::kValid, "Valid",
315 "Valid message received");
316
317 // Commands
318 //T::AddEvent("SEND_COMMAND", "C")
319 // (bind(&StateMachineSQMControl::SendCommand, this, placeholders::_1))
320 // ("Send command to SQM");
321
322 // Verbosity commands
323 T::AddEvent("SET_VERBOSE", "B")
324 (bind(&StateMachineSQMControl::SetVerbosity, this, placeholders::_1))
325 ("set verbosity state"
326 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
327
328 //T::AddEvent("ENABLE")
329 // (bind(&StateMachineSQMControl::Send, this, "veto_60"))
330 // ("Enable trigger signal once a second vetoed at every exact minute");
331
332 //T::AddEvent("DISABLE")
333 // (bind(&StateMachineSQMControl::Send, this, "veto_on"))
334 // ("Diable trigger output");
335
336 // Conenction commands
337 T::AddEvent("DISCONNECT")
338 (bind(&StateMachineSQMControl::Disconnect, this))
339 ("disconnect from ethernet");
340
341 T::AddEvent("RECONNECT", "O")
342 (bind(&StateMachineSQMControl::Reconnect, this, placeholders::_1))
343 ("(Re)connect ethernet connection to SQM, a new address can be given"
344 "|[host][string]:new ethernet address in the form <host:port>");
345
346 }
347
348 int EvalOptions(Configuration &conf)
349 {
350 fSQM.SetVerbose(!conf.Get<bool>("quiet"));
351 fSQM.SetTimeout(conf.Get<uint16_t>("request-interval"));
352 fSQM.SetDebugTx(conf.Get<bool>("debug-tx"));
353 fSQM.SetEndpoint(conf.Get<string>("addr"));
354 fSQM.StartConnect();
355
356 return -1;
357 }
358};
359
360// ------------------------------------------------------------------------
361
362#include "Main.h"
363
364
365template<class T, class S, class R>
366int RunShell(Configuration &conf)
367{
368 return Main::execute<T, StateMachineSQMControl<S, R>>(conf);
369}
370
371void SetupConfiguration(Configuration &conf)
372{
373 po::options_description control("SQM control");
374 control.add_options()
375 ("no-dim,d", po_switch(), "Disable dim services")
376 ("addr,a", var<string>("10.0.100.208:10001"), "Network address of the lid controling Arduino including port")
377 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
378 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
379 ("request-interval", var<uint16_t>(5000), "How often to request a report [milliseconds].")
380 ;
381
382 conf.AddOptions(control);
383}
384
385/*
386 Extract usage clause(s) [if any] for SYNOPSIS.
387 Translators: "Usage" and "or" here are patterns (regular expressions) which
388 are used to match the usage synopsis in program output. An example from cp
389 (GNU coreutils) which contains both strings:
390 Usage: cp [OPTION]... [-T] SOURCE DEST
391 or: cp [OPTION]... SOURCE... DIRECTORY
392 or: cp [OPTION]... -t DIRECTORY SOURCE...
393 */
394void PrintUsage()
395{
396 cout <<
397 "The sqmctrl is an interface to the Sky Quality Meter.\n"
398 "\n"
399 "The default is that the program is started without user intercation. "
400 "All actions are supposed to arrive as DimCommands. Using the -c "
401 "option, a local shell can be initialized. With h or help a short "
402 "help message about the usuage can be brought to the screen.\n"
403 "\n"
404 "Usage: sqmctrl [-c type] [OPTIONS]\n"
405 " or: sqmctrl [OPTIONS]\n";
406 cout << endl;
407}
408
409void PrintHelp()
410{
411// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
412
413 /* Additional help text which is printed after the configuration
414 options goes here */
415
416 /*
417 cout << "bla bla bla" << endl << endl;
418 cout << endl;
419 cout << "Environment:" << endl;
420 cout << "environment" << endl;
421 cout << endl;
422 cout << "Examples:" << endl;
423 cout << "test exam" << endl;
424 cout << endl;
425 cout << "Files:" << endl;
426 cout << "files" << endl;
427 cout << endl;
428 */
429}
430
431int main(int argc, const char* argv[])
432{
433 Configuration conf(argv[0]);
434 conf.SetPrintUsage(PrintUsage);
435 Main::SetupConfiguration(conf);
436 SetupConfiguration(conf);
437
438 if (!conf.DoParse(argc, argv, PrintHelp))
439 return 127;
440
441 // No console access at all
442 if (!conf.Has("console"))
443 {
444 if (conf.Get<bool>("no-dim"))
445 return RunShell<LocalStream, StateMachine, ConnectionSQM>(conf);
446 else
447 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
448 }
449 // Cosole access w/ and w/o Dim
450 if (conf.Get<bool>("no-dim"))
451 {
452 if (conf.Get<int>("console")==0)
453 return RunShell<LocalShell, StateMachine, ConnectionSQM>(conf);
454 else
455 return RunShell<LocalConsole, StateMachine, ConnectionSQM>(conf);
456 }
457 else
458 {
459 if (conf.Get<int>("console")==0)
460 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
461 else
462 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
463 }
464
465 return 0;
466}
Note: See TracBrowser for help on using the repository browser.