source: trunk/FACT++/src/pwrctrl.cc@ 15005

Last change on this file since 15005 was 14988, checked in by tbretz, 12 years ago
If there was no report yet at all, state disconnected is returned.
File size: 17.2 KB
Line 
1#include <boost/bind.hpp>
2#include <boost/array.hpp>
3
4#include <string>
5
6#include <QtXml/QDomDocument>
7
8#include "FACT.h"
9#include "Dim.h"
10#include "Event.h"
11#include "StateMachineDim.h"
12#include "Connection.h"
13#include "LocalControl.h"
14#include "Configuration.h"
15#include "Console.h"
16
17#include "tools.h"
18
19#include "HeadersPower.h"
20
21namespace ba = boost::asio;
22namespace bs = boost::system;
23namespace dummy = ba::placeholders;
24
25using namespace std;
26
27class ConnectionInterlock : public Connection
28{
29protected:
30 bool fIsValid;
31
32private:
33 uint16_t fInterval;
34
35 bool fIsVerbose;
36 bool fDebugRx;
37
38 string fSite;
39 string fRdfData;
40
41 boost::array<char, 4096> fArray;
42
43 string fNextCommand;
44
45 Time fLastReport;
46
47 Power::Status fStatus;
48
49 virtual void Update(const Power::Status &)
50 {
51 }
52
53
54 void ProcessAnswer()
55 {
56 if (fDebugRx)
57 {
58 Out() << "------------------------------------------------------" << endl;
59 Out() << fRdfData << endl;
60 Out() << "------------------------------------------------------" << endl;
61 }
62
63 const size_t p1 = fRdfData.find("\r\n\r\n");
64 if (p1==string::npos)
65 {
66 Warn("HTTP header not found.");
67 PostClose(false);
68 return;
69 }
70
71 fRdfData.erase(0, p1+4);
72 fRdfData.insert(0, "<?xml version=\"1.0\"?>\n");
73
74 QDomDocument doc;
75 if (!doc.setContent(QString(fRdfData.c_str()), false))
76 {
77 Warn("Parsing of html failed.");
78 PostClose(false);
79 return;
80 }
81
82 if (fDebugRx)
83 Out() << "Parsed:\n-------\n" << doc.toString().toStdString() << endl;
84
85 const QDomNodeList imageElems = doc.elementsByTagName("span");
86
87 for (unsigned int i=0; i<imageElems.length(); i++)
88 {
89 const QDomElement e = imageElems.item(i).toElement();
90
91 const QDomNamedNodeMap att = e.attributes();
92
93 if (fStatus.Set(att))
94 fIsValid = true;
95 }
96
97 if (fIsVerbose)
98 fStatus.Print(Out());
99
100 Update(fStatus);
101
102 fRdfData = "";
103
104 fLastReport = Time();
105 PostClose(false);
106 }
107
108 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
109 {
110 // Do not schedule a new read if the connection failed.
111 if (bytes_received==0 || err)
112 {
113 if (err==ba::error::eof)
114 {
115 if (!fRdfData.empty())
116 ProcessAnswer();
117 return;
118 }
119
120 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
121 // 125: Operation canceled
122 if (err && err!=ba::error::eof && // Connection closed by remote host
123 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
124 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
125 {
126 ostringstream str;
127 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
128 Error(str);
129 }
130 PostClose(err!=ba::error::basic_errors::operation_aborted);
131
132 fRdfData = "";
133 return;
134 }
135
136 fRdfData += string(fArray.data(), bytes_received);
137
138 // Does the message contain a header?
139 const size_t p1 = fRdfData.find("\r\n\r\n");
140 if (p1!=string::npos)
141 {
142 // Does the answer also contain the body?
143 const size_t p2 = fRdfData.find("\r\n\r\n", p1+4);
144 if (p2!=string::npos)
145 ProcessAnswer();
146 }
147
148 // Go on reading until the web-server closes the connection
149 StartReadReport();
150 }
151
152 boost::asio::streambuf fBuffer;
153
154 void StartReadReport()
155 {
156 async_read_some(ba::buffer(fArray),
157 boost::bind(&ConnectionInterlock::HandleRead, this,
158 dummy::error, dummy::bytes_transferred));
159 }
160
161 boost::asio::deadline_timer fKeepAlive;
162
163 void HandleRequest(const bs::error_code &error)
164 {
165 // 125: Operation canceled (bs::error_code(125, bs::system_category))
166 if (error && error!=ba::error::basic_errors::operation_aborted)
167 {
168 ostringstream str;
169 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
170 Error(str);
171
172 PostClose(false);
173 return;
174 }
175
176 if (!is_open())
177 {
178 // For example: Here we could schedule a new accept if we
179 // would not want to allow two connections at the same time.
180 PostClose(true);
181 return;
182 }
183
184 // Check whether the deadline has passed. We compare the deadline
185 // against the current time since a new asynchronous operation
186 // may have moved the deadline before this actor had a chance
187 // to run.
188 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
189 return;
190
191 Request();
192 }
193
194
195private:
196 // This is called when a connection was established
197 void ConnectionEstablished()
198 {
199 Request();
200 StartReadReport();
201 }
202
203public:
204 static const uint16_t kMaxAddr;
205
206public:
207 ConnectionInterlock(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
208 fIsValid(false), fIsVerbose(true), fDebugRx(false), fLastReport(Time::none), fKeepAlive(ioservice)
209 {
210 SetLogStream(&imp);
211 }
212
213 void SetVerbose(bool b)
214 {
215 fIsVerbose = b;
216 }
217
218 void SetDebugRx(bool b)
219 {
220 fDebugRx = b;
221 Connection::SetVerbose(b);
222 }
223
224 void SetInterval(uint16_t i)
225 {
226 fInterval = i;
227 }
228
229 void SetSite(const string &site)
230 {
231 fSite = site;
232 }
233
234 void Post(const string &post)
235 {
236 fNextCommand = post;
237 }
238
239 void Request()
240 {
241 string cmd = "GET " + fSite;
242
243 if (!fNextCommand.empty())
244 cmd += "?" + fNextCommand;
245
246 cmd += " HTTP/1.1\r\n";
247 cmd += "\r\n";
248
249 PostMessage(cmd);
250
251 fNextCommand = "";
252
253 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval));
254 fKeepAlive.async_wait(boost::bind(&ConnectionInterlock::HandleRequest,
255 this, dummy::error));
256 }
257
258 int GetInterval() const
259 {
260 return fInterval;
261 }
262
263 int GetState() const
264 {
265 using namespace Power::State;
266
267 // Timeout
268 if (!fLastReport.IsValid() || fLastReport+boost::posix_time::seconds(fInterval*2)<Time())
269 return Power::State::kDisconnected;
270
271 // No data received yet
272 if (!fIsValid)
273 return Power::State::kConnected;
274
275 /*
276 bool fWaterFlowOk;
277 bool fWaterLevelOk;
278 bool fPwrBiasOn;
279 bool fPwr24VOn;
280 bool fPwrPumpOn;
281 bool fPwrDriveOn;
282 bool fDriveMainSwitchOn;
283 bool fDriveFeedbackOn;
284 */
285
286 if (!fStatus.fWaterLevelOk || (fStatus.fPwrPumpOn && !fStatus.fWaterFlowOk))
287 return kCoolingFailure;
288
289 const int rc =
290 (fStatus.fPwrBiasOn ? kBiasOn : 0) |
291 (fStatus.fPwrPumpOn ? kCameraOn : 0) |
292 (fStatus.fDriveFeedbackOn ? kDriveOn : 0);
293
294 return rc==0 ? kSystemOff : rc;
295 }
296};
297
298const uint16_t ConnectionInterlock::kMaxAddr = 0xfff;
299
300// ------------------------------------------------------------------------
301
302#include "DimDescriptionService.h"
303
304class ConnectionDimWeather : public ConnectionInterlock
305{
306private:
307 DimDescribedService fDim;
308
309public:
310 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
311 ConnectionInterlock(ioservice, imp),
312 fDim("PWR_CONTROL/DATA", "C:1;C:1;C:1;C:1;C:1;C:1;C:1;C:1",
313 "|water_lvl[bool]:Water level ok"
314 "|water_flow[bool]:Water flowing"
315 "|pwr_24V[bool]:24V power enabled"
316 "|pwr_pump[bool]:Pump power enabled"
317 "|pwr_bias[bool]:Bias power enabled"
318 "|pwr_drive[bool]:Drive power enabled (command value)"
319 "|main_drive[bool]:Drive manual main switch on"
320 "|feedback_drive[bool]:Drive power on (feedback value)")
321 {
322 }
323
324 void Update(const Power::Status &status)
325 {
326 fDim.setQuality(status.GetVal());
327 fDim.Update(status);
328 }
329};
330
331// ------------------------------------------------------------------------
332
333template <class T, class S>
334class StateMachinePowerControl : public T, public ba::io_service, public ba::io_service::work
335{
336private:
337 S fPower;
338 Time fLastCommand;
339
340 bool CheckEventSize(size_t has, const char *name, size_t size)
341 {
342 if (has==size)
343 return true;
344
345 ostringstream msg;
346 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
347 T::Fatal(msg);
348 return false;
349 }
350
351 int SetVerbosity(const EventImp &evt)
352 {
353 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
354 return T::kSM_FatalError;
355
356 fPower.SetVerbose(evt.GetBool());
357
358 return T::GetCurrentState();
359 }
360
361 int SetDebugRx(const EventImp &evt)
362 {
363 if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1))
364 return T::kSM_FatalError;
365
366 fPower.SetDebugRx(evt.GetBool());
367
368 return T::GetCurrentState();
369 }
370
371 int Post(const EventImp &evt)
372 {
373 fPower.Post(evt.GetText());
374 return T::GetCurrentState();
375 }
376
377 int SetCameraPower(const EventImp &evt)
378 {
379 if (!CheckEventSize(evt.GetSize(), "SetCameraPower", 1))
380 return T::kSM_FatalError;
381
382 fLastCommand = Time();
383 fPower.Post(evt.GetBool() ? "cam_on=Camera+ON" : "cam_off=Camera+OFF");
384 return T::GetCurrentState();
385 }
386
387 int ToggleDrive()
388 {
389 fLastCommand = Time();
390 fPower.Post("dt=Drive+ON%2FOFF");
391 return T::GetCurrentState();
392
393 }
394
395 int Execute()
396 {
397 // Dispatch (execute) at most one handler from the queue. In contrary
398 // to run_one(), it doesn't wait until a handler is available
399 // which can be dispatched, so poll_one() might return with 0
400 // handlers dispatched. The handlers are always dispatched/executed
401 // synchronously, i.e. within the call to poll_one()
402 poll_one();
403
404 return fPower.GetState();
405 }
406
407
408public:
409 StateMachinePowerControl(ostream &out=cout) :
410 T(out, "PWR_CONTROL"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
411 fPower(*this, *this)
412 {
413 // ba::io_service::work is a kind of keep_alive for the loop.
414 // It prevents the io_service to go to stopped state, which
415 // would prevent any consecutive calls to run()
416 // or poll() to do nothing. reset() could also revoke to the
417 // previous state but this might introduce some overhead of
418 // deletion and creation of threads and more.
419
420 // State names
421 T::AddStateName(Power::State::kDisconnected, "NoConnection",
422 "No connection to web-server could be established recently");
423
424 T::AddStateName(Power::State::kConnected, "Connected",
425 "Connection established, but status still not known");
426
427 T::AddStateName(Power::State::kSystemOff, "PowerOff",
428 "Camera, Bias and Drive power off");
429
430 T::AddStateName(Power::State::kBiasOn, "BiasOn",
431 "Camera and Drive power off, Bias on");
432
433 T::AddStateName(Power::State::kDriveOn, "DriveOn",
434 "Camera and Bias power off, Drive on");
435
436 T::AddStateName(Power::State::kCameraOn, "CameraOn",
437 "Drive and Bias power off, Camera on");
438
439 T::AddStateName(Power::State::kBiasOff, "BiasOff",
440 "Camera and Drive power on, Bias off");
441
442 T::AddStateName(Power::State::kDriveOff, "DriveOff",
443 "Camera and Bias power on, Drive off");
444
445 T::AddStateName(Power::State::kCameraOff, "CameraOff",
446 "Drive and Bias power on, Camera off");
447
448 T::AddStateName(Power::State::kSystemOn, "SystemOn",
449 "Camera, Bias and drive power on");
450
451
452 // Verbosity commands
453 T::AddEvent("SET_VERBOSE", "B:1")
454 (bind(&StateMachinePowerControl::SetVerbosity, this, placeholders::_1))
455 ("Set verbosity state"
456 "|verbosity[bool]:disable or enable verbosity for interpreted data (yes/no)");
457
458 T::AddEvent("SET_DEBUG_RX", "B:1")
459 (bind(&StateMachinePowerControl::SetDebugRx, this, placeholders::_1))
460 ("Set debux-rx state"
461 "|debug[bool]:dump received text and parsed text to console (yes/no)");
462
463 T::AddEvent("CAMERA_POWER", "B:1")
464 (bind(&StateMachinePowerControl::SetCameraPower, this, placeholders::_1))
465 ("Switch camera power"
466 "|power[bool]:Switch camera power 'on' or 'off'");
467
468 T::AddEvent("TOGGLE_DRIVE")
469 (bind(&StateMachinePowerControl::ToggleDrive, this))
470 ("Toggle drive power");
471
472 T::AddEvent("POST", "C")
473 (bind(&StateMachinePowerControl::Post, this, placeholders::_1))
474 ("set verbosity state"
475 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
476 }
477
478 int EvalOptions(Configuration &conf)
479 {
480 fPower.SetVerbose(!conf.Get<bool>("quiet"));
481 fPower.SetInterval(conf.Get<uint16_t>("interval"));
482 fPower.SetDebugTx(conf.Get<bool>("debug-tx"));
483 fPower.SetDebugRx(conf.Get<bool>("debug-rx"));
484 fPower.SetSite(conf.Get<string>("url"));
485 fPower.SetEndpoint(conf.Get<string>("addr"));
486 fPower.StartConnect();
487
488 return -1;
489 }
490};
491
492// ------------------------------------------------------------------------
493
494#include "Main.h"
495
496
497template<class T, class S, class R>
498int RunShell(Configuration &conf)
499{
500 return Main::execute<T, StateMachinePowerControl<S, R>>(conf);
501}
502
503void SetupConfiguration(Configuration &conf)
504{
505 po::options_description control("Lid control");
506 control.add_options()
507 ("no-dim,d", po_switch(), "Disable dim services")
508 ("addr,a", var<string>(""), "Network address of the lid controling Arduino including port")
509 ("url,u", var<string>(""), "File name and path to load")
510 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
511 ("interval,i", var<uint16_t>(5), "Interval between two updates on the server in seconds")
512 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
513 ("debug-rx", po_bool(), "Enable debugging for received data.")
514 ;
515
516 conf.AddOptions(control);
517}
518
519/*
520 Extract usage clause(s) [if any] for SYNOPSIS.
521 Translators: "Usage" and "or" here are patterns (regular expressions) which
522 are used to match the usage synopsis in program output. An example from cp
523 (GNU coreutils) which contains both strings:
524 Usage: cp [OPTION]... [-T] SOURCE DEST
525 or: cp [OPTION]... SOURCE... DIRECTORY
526 or: cp [OPTION]... -t DIRECTORY SOURCE...
527 */
528void PrintUsage()
529{
530 cout <<
531 "The lidctrl is an interface to the LID control hardware.\n"
532 "\n"
533 "The default is that the program is started without user intercation. "
534 "All actions are supposed to arrive as DimCommands. Using the -c "
535 "option, a local shell can be initialized. With h or help a short "
536 "help message about the usuage can be brought to the screen.\n"
537 "\n"
538 "Usage: lidctrl [-c type] [OPTIONS]\n"
539 " or: lidctrl [OPTIONS]\n";
540 cout << endl;
541}
542
543void PrintHelp()
544{
545// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
546
547 /* Additional help text which is printed after the configuration
548 options goes here */
549
550 /*
551 cout << "bla bla bla" << endl << endl;
552 cout << endl;
553 cout << "Environment:" << endl;
554 cout << "environment" << endl;
555 cout << endl;
556 cout << "Examples:" << endl;
557 cout << "test exam" << endl;
558 cout << endl;
559 cout << "Files:" << endl;
560 cout << "files" << endl;
561 cout << endl;
562 */
563}
564
565int main(int argc, const char* argv[])
566{
567 Configuration conf(argv[0]);
568 conf.SetPrintUsage(PrintUsage);
569 Main::SetupConfiguration(conf);
570 SetupConfiguration(conf);
571
572 if (!conf.DoParse(argc, argv, PrintHelp))
573 return 127;
574
575 // No console access at all
576 if (!conf.Has("console"))
577 {
578 if (conf.Get<bool>("no-dim"))
579 return RunShell<LocalStream, StateMachine, ConnectionInterlock>(conf);
580 else
581 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
582 }
583 // Cosole access w/ and w/o Dim
584 if (conf.Get<bool>("no-dim"))
585 {
586 if (conf.Get<int>("console")==0)
587 return RunShell<LocalShell, StateMachine, ConnectionInterlock>(conf);
588 else
589 return RunShell<LocalConsole, StateMachine, ConnectionInterlock>(conf);
590 }
591 else
592 {
593 if (conf.Get<int>("console")==0)
594 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
595 else
596 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
597 }
598
599 return 0;
600}
Note: See TracBrowser for help on using the repository browser.