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

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