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

Last change on this file since 16380 was 16054, checked in by tbretz, 11 years ago
Print a red warning in case of Cooling failure.
File size: 17.6 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 // Timeout
266 if (!fLastReport.IsValid() || Time()>fLastReport+boost::posix_time::seconds(fInterval*3))
267 return Power::State::kDisconnected;
268
269 // No data received yet
270 if (!fIsValid)
271 return Power::State::kConnected;
272
273 /*
274 bool fWaterFlowOk;
275 bool fWaterLevelOk;
276 bool fPwrBiasOn;
277 bool fPwr24VOn;
278 bool fPwrPumpOn;
279 bool fPwrDriveOn;
280 bool fDriveMainSwitchOn;
281 bool fDriveFeedbackOn;
282 */
283
284 if (!fStatus.fWaterLevelOk || (fStatus.fPwrPumpOn && !fStatus.fWaterFlowOk))
285 return Power::State::kCoolingFailure;
286
287 const int rc =
288 (fStatus.fPwrBiasOn ? Power::State::kBiasOn : 0) |
289 (fStatus.fPwrPumpOn ? Power::State::kCameraOn : 0) |
290 (fStatus.fDriveFeedbackOn ? Power::State::kDriveOn : 0);
291
292 return rc==0 ? Power::State::kSystemOff : rc;
293 }
294};
295
296const uint16_t ConnectionInterlock::kMaxAddr = 0xfff;
297
298// ------------------------------------------------------------------------
299
300#include "DimDescriptionService.h"
301
302class ConnectionDimWeather : public ConnectionInterlock
303{
304private:
305 DimDescribedService fDim;
306
307public:
308 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
309 ConnectionInterlock(ioservice, imp),
310 fDim("PWR_CONTROL/DATA", "C:1;C:1;C:1;C:1;C:1;C:1;C:1;C:1",
311 "|water_lvl[bool]:Water level ok"
312 "|water_flow[bool]:Water flowing"
313 "|pwr_24V[bool]:24V power enabled"
314 "|pwr_pump[bool]:Pump power enabled"
315 "|pwr_bias[bool]:Bias power enabled"
316 "|pwr_drive[bool]:Drive power enabled (command value)"
317 "|main_drive[bool]:Drive manual main switch on"
318 "|feedback_drive[bool]:Drive power on (feedback value)")
319 {
320 }
321
322 void Update(const Power::Status &status)
323 {
324 fDim.setQuality(status.GetVal());
325 fDim.Update(status);
326 }
327};
328
329// ------------------------------------------------------------------------
330
331template <class T, class S>
332class StateMachinePowerControl : public T, public ba::io_service, public ba::io_service::work
333{
334private:
335 S fPower;
336 Time fLastCommand;
337
338 bool CheckEventSize(size_t has, const char *name, size_t size)
339 {
340 if (has==size)
341 return true;
342
343 ostringstream msg;
344 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
345 T::Fatal(msg);
346 return false;
347 }
348
349 int SetVerbosity(const EventImp &evt)
350 {
351 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
352 return T::kSM_FatalError;
353
354 fPower.SetVerbose(evt.GetBool());
355
356 return T::GetCurrentState();
357 }
358
359 int SetDebugRx(const EventImp &evt)
360 {
361 if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1))
362 return T::kSM_FatalError;
363
364 fPower.SetDebugRx(evt.GetBool());
365
366 return T::GetCurrentState();
367 }
368
369 int Post(const EventImp &evt)
370 {
371 fPower.Post(evt.GetText());
372 return T::GetCurrentState();
373 }
374
375 int SetCameraPower(const EventImp &evt)
376 {
377 if (!CheckEventSize(evt.GetSize(), "SetCameraPower", 1))
378 return T::kSM_FatalError;
379
380 fLastCommand = Time();
381 fPower.Post(evt.GetBool() ? "cam_on=Camera+ON" : "cam_off=Camera+OFF");
382 return T::GetCurrentState();
383 }
384
385 int ToggleDrive()
386 {
387 fLastCommand = Time();
388 fPower.Post("dt=Drive+ON%2FOFF");
389 return T::GetCurrentState();
390
391 }
392
393 int Execute()
394 {
395 // Dispatch (execute) at most one handler from the queue. In contrary
396 // to run_one(), it doesn't wait until a handler is available
397 // which can be dispatched, so poll_one() might return with 0
398 // handlers dispatched. The handlers are always dispatched/executed
399 // synchronously, i.e. within the call to poll_one()
400 poll_one();
401
402 const int rc = fPower.GetState();
403
404 if (rc==Power::State::kCoolingFailure && T::GetCurrentState()!=Power::State::kCoolingFailure)
405 T::Error("Power control unit reported cooling failure.");
406
407 return fPower.GetState();
408 }
409
410
411public:
412 StateMachinePowerControl(ostream &out=cout) :
413 T(out, "PWR_CONTROL"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
414 fPower(*this, *this)
415 {
416 // ba::io_service::work is a kind of keep_alive for the loop.
417 // It prevents the io_service to go to stopped state, which
418 // would prevent any consecutive calls to run()
419 // or poll() to do nothing. reset() could also revoke to the
420 // previous state but this might introduce some overhead of
421 // deletion and creation of threads and more.
422
423 // State names
424 T::AddStateName(Power::State::kDisconnected, "NoConnection",
425 "No connection to web-server could be established recently");
426
427 T::AddStateName(Power::State::kConnected, "Connected",
428 "Connection established, but status still not known");
429
430 T::AddStateName(Power::State::kSystemOff, "PowerOff",
431 "Camera, Bias and Drive power off");
432
433 T::AddStateName(Power::State::kBiasOn, "BiasOn",
434 "Camera and Drive power off, Bias on");
435
436 T::AddStateName(Power::State::kDriveOn, "DriveOn",
437 "Camera and Bias power off, Drive on");
438
439 T::AddStateName(Power::State::kCameraOn, "CameraOn",
440 "Drive and Bias power off, Camera on");
441
442 T::AddStateName(Power::State::kBiasOff, "BiasOff",
443 "Camera and Drive power on, Bias off");
444
445 T::AddStateName(Power::State::kDriveOff, "DriveOff",
446 "Camera and Bias power on, Drive off");
447
448 T::AddStateName(Power::State::kCameraOff, "CameraOff",
449 "Drive and Bias power on, Camera off");
450
451 T::AddStateName(Power::State::kSystemOn, "SystemOn",
452 "Camera, Bias and drive power on");
453
454 T::AddStateName(Power::State::kCoolingFailure, "CoolingFailure",
455 "The cooling unit has failed, the interlock has switched off");
456
457 // Verbosity commands
458 T::AddEvent("SET_VERBOSE", "B:1")
459 (bind(&StateMachinePowerControl::SetVerbosity, this, placeholders::_1))
460 ("Set verbosity state"
461 "|verbosity[bool]:disable or enable verbosity for interpreted data (yes/no)");
462
463 T::AddEvent("SET_DEBUG_RX", "B:1")
464 (bind(&StateMachinePowerControl::SetDebugRx, this, placeholders::_1))
465 ("Set debux-rx state"
466 "|debug[bool]:dump received text and parsed text to console (yes/no)");
467
468 T::AddEvent("CAMERA_POWER", "B:1")
469 (bind(&StateMachinePowerControl::SetCameraPower, this, placeholders::_1))
470 ("Switch camera power"
471 "|power[bool]:Switch camera power 'on' or 'off'");
472
473 T::AddEvent("TOGGLE_DRIVE")
474 (bind(&StateMachinePowerControl::ToggleDrive, this))
475 ("Toggle drive power");
476
477 T::AddEvent("POST", "C")
478 (bind(&StateMachinePowerControl::Post, this, placeholders::_1))
479 ("set verbosity state"
480 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
481 }
482
483 int EvalOptions(Configuration &conf)
484 {
485 fPower.SetVerbose(!conf.Get<bool>("quiet"));
486 fPower.SetInterval(conf.Get<uint16_t>("interval"));
487 fPower.SetDebugTx(conf.Get<bool>("debug-tx"));
488 fPower.SetDebugRx(conf.Get<bool>("debug-rx"));
489 fPower.SetSite(conf.Get<string>("url"));
490 fPower.SetEndpoint(conf.Get<string>("addr"));
491 fPower.StartConnect();
492
493 return -1;
494 }
495};
496
497// ------------------------------------------------------------------------
498
499#include "Main.h"
500
501
502template<class T, class S, class R>
503int RunShell(Configuration &conf)
504{
505 return Main::execute<T, StateMachinePowerControl<S, R>>(conf);
506}
507
508void SetupConfiguration(Configuration &conf)
509{
510 po::options_description control("Lid control");
511 control.add_options()
512 ("no-dim,d", po_switch(), "Disable dim services")
513 ("addr,a", var<string>(""), "Network address of the lid controling Arduino including port")
514 ("url,u", var<string>(""), "File name and path to load")
515 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
516 ("interval,i", var<uint16_t>(5), "Interval between two updates on the server in seconds")
517 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
518 ("debug-rx", po_bool(), "Enable debugging for received data.")
519 ;
520
521 conf.AddOptions(control);
522}
523
524/*
525 Extract usage clause(s) [if any] for SYNOPSIS.
526 Translators: "Usage" and "or" here are patterns (regular expressions) which
527 are used to match the usage synopsis in program output. An example from cp
528 (GNU coreutils) which contains both strings:
529 Usage: cp [OPTION]... [-T] SOURCE DEST
530 or: cp [OPTION]... SOURCE... DIRECTORY
531 or: cp [OPTION]... -t DIRECTORY SOURCE...
532 */
533void PrintUsage()
534{
535 cout <<
536 "The pwrctrl is an interface to the LID control hardware.\n"
537 "\n"
538 "The default is that the program is started without user intercation. "
539 "All actions are supposed to arrive as DimCommands. Using the -c "
540 "option, a local shell can be initialized. With h or help a short "
541 "help message about the usuage can be brought to the screen.\n"
542 "\n"
543 "Usage: pwrctrl [-c type] [OPTIONS]\n"
544 " or: pwrctrl [OPTIONS]\n";
545 cout << endl;
546}
547
548void PrintHelp()
549{
550// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
551
552 /* Additional help text which is printed after the configuration
553 options goes here */
554
555 /*
556 cout << "bla bla bla" << endl << endl;
557 cout << endl;
558 cout << "Environment:" << endl;
559 cout << "environment" << endl;
560 cout << endl;
561 cout << "Examples:" << endl;
562 cout << "test exam" << endl;
563 cout << endl;
564 cout << "Files:" << endl;
565 cout << "files" << endl;
566 cout << endl;
567 */
568}
569
570int main(int argc, const char* argv[])
571{
572 Configuration conf(argv[0]);
573 conf.SetPrintUsage(PrintUsage);
574 Main::SetupConfiguration(conf);
575 SetupConfiguration(conf);
576
577 if (!conf.DoParse(argc, argv, PrintHelp))
578 return 127;
579
580 // No console access at all
581 if (!conf.Has("console"))
582 {
583 if (conf.Get<bool>("no-dim"))
584 return RunShell<LocalStream, StateMachine, ConnectionInterlock>(conf);
585 else
586 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
587 }
588 // Cosole access w/ and w/o Dim
589 if (conf.Get<bool>("no-dim"))
590 {
591 if (conf.Get<int>("console")==0)
592 return RunShell<LocalShell, StateMachine, ConnectionInterlock>(conf);
593 else
594 return RunShell<LocalConsole, StateMachine, ConnectionInterlock>(conf);
595 }
596 else
597 {
598 if (conf.Get<int>("console")==0)
599 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
600 else
601 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
602 }
603
604 return 0;
605}
Note: See TracBrowser for help on using the repository browser.