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

Last change on this file since 14476 was 14451, checked in by tbretz, 12 years ago
File size: 22.0 KB
Line 
1#include <boost/bind.hpp>
2
3#include <string> // std::string
4#include <algorithm> // std::transform
5#include <cctype> // std::tolower
6
7#include <QtXml/QDomDocument>
8
9#include "FACT.h"
10#include "Dim.h"
11#include "Event.h"
12#include "StateMachineDim.h"
13#include "Connection.h"
14#include "LocalControl.h"
15#include "Configuration.h"
16#include "Console.h"
17
18#include "tools.h"
19
20#include "HeadersLid.h"
21
22namespace ba = boost::asio;
23namespace bs = boost::system;
24namespace dummy = ba::placeholders;
25
26using namespace std;
27
28class ConnectionLid : public Connection
29{
30protected:
31
32 struct Lid
33 {
34 bool fc_ok;
35 bool bp_on;
36 bool il_on;
37 bool fl_ok;
38 bool fp_en;
39 bool x_on;
40 bool y_on;
41 bool z_on;
42
43 Lid() { }
44
45 bool Set(const QDomNamedNodeMap &map)
46 {
47 if (!map.contains("id") || !map.contains("value"))
48 return false;
49
50 QString item = map.namedItem("id").nodeValue();
51 QString value = map.namedItem("value").nodeValue();
52
53 if (item==(QString("FC_OK")))
54 {
55 fc_ok = value.toInt();
56 return true;
57 }
58
59 if (item==(QString("BP_ON")))
60 {
61 bp_on = value.toInt();
62 return true;
63 }
64
65 if (item==(QString("IL_ON")))
66 {
67 il_on = value.toInt();
68 return true;
69 }
70
71 if (item==(QString("FL_OK")))
72 {
73 fl_ok = value.toInt();
74 return true;
75 }
76
77 if (item==(QString("FP_EN")))
78 {
79 fp_en = value.toInt();
80 return true;
81 }
82
83 if (item==(QString("X_ON")))
84 {
85 x_on = value.toInt();
86 return true;
87 }
88
89 if (item==(QString("Y_ON")))
90 {
91 y_on = value.toInt();
92 return true;
93 }
94
95 if (item==(QString("Z_ON")))
96 {
97 z_on = value.toInt();
98 return true;
99 }
100
101 return false;
102 }
103
104 void Print(ostream &out)
105 {
106 out << "FC_OK=" << fc_ok << endl;
107 out << "BP_ON=" << bp_on << endl;
108 out << "IL_ON=" << il_on << endl;
109 out << "FL_OK=" << fl_ok << endl;
110 out << "FP_EN=" << fp_en << endl;
111 out << "X_ON =" << x_on << endl;
112 out << "Y_ON =" << y_on << endl;
113 out << "Z_ON =" << z_on << endl;
114 }
115
116 };
117
118private:
119 uint16_t fInterval;
120
121 bool fIsVerbose;
122
123 string fSite;
124 string fRdfData;
125
126 boost::array<char, 4096> fArray;
127
128 string fNextCommand;
129
130 Time fLastReport;
131
132 Lid fLid1;
133
134 virtual void Update(const Lid &)
135 {
136 }
137
138
139 void ProcessAnswer()
140 {
141 if (fIsVerbose)
142 {
143 Out() << "------------------------------------------------------" << endl;
144 Out() << fRdfData << endl;
145 Out() << "------------------------------------------------------" << endl;
146 }
147
148 /*
149 fRdfData =
150 "HTTP/1.1 200 OK\n"
151 "Content-Type: text/html\n"
152 "Connnection: close\r\n"
153 "\r\n"
154 "<!DOCTYPE HTML>\n"
155 "<html><head>\n"
156 "<meta http-equiv=\"refresh\" content=\"1 URL=index.html\"/>\n"
157 "<title>FACT camera power interlock control</title>\n"
158 "</head><body>\n"
159 "<FORM NAME=\"Form\">"
160 "<P/><INPUT TYPE=\"SUBMIT\" NAME=\"D2off\" VALUE=\"Disable Pump\"/>--> FC_OFF HIGH for 0.5 seconds <br/>\n"
161 "<P/><INPUT TYPE=\"SUBMIT\" NAME=\"D2on\" VALUE=\"Enable Pump\" />--> FC_ON HIGH for 3.0 seconds <br/>\n"
162 "<P/><INPUT TYPE=\"SUBMIT\" NAME=\"toggle_x\" VALUE=\"Toggle X\" />--> ... well it toggles X <br/>\n"
163 "<P/><INPUT TYPE=\"SUBMIT\" NAME=\"toggle_y\" VALUE=\"Toggle Y\" />--> ... well it toggles Y <br/>\n"
164 "FC_OK :2 is <font color=\"red\" size=\"20\"><span id=\"FC_OK\" value=\"0\">LOW </span></font><br />\n"
165 "BP_ON :3 is <font color=\"red\" size=\"20\"><span id=\"BP_ON\" value=\"0\">LOW </span></font><br />\n"
166 "IL_ON :14 is <font color=\"green\" size=\"20\"><span id=\"IL_ON\" value=\"1\">HIGH </span></font><br />\n"
167 "FL_OK :15 is <font color=\"green\" size=\"20\"><span id=\"FL_OK\" value=\"1\">HIGH </span></font><br />\n"
168 "FP_EN :16 is <font color=\"red\" size=\"20\"><span id=\"FP_EN\" value=\"0\">LOW </span></font><br />\n"
169 "X_ON :17 is <font color=\"red\" size=\"20\"><span id=\"X_ON\" value=\"0\">LOW </span></font><br />\n"
170 "Y_ON :18 is <font color=\"red\" size=\"20\"><span id=\"Y_ON\" value=\"0\">LOW </span></font><br />\n"
171 "Z_OK :19 is <font color=\"red\" size=\"20\"><span id=\"Z_OK\" value=\"0\">LOW </span></font><br />\n"
172 "</FORM>\n"
173 "</body></html>\n";
174 */
175
176 const size_t p1 = fRdfData.find("\r\n\r\n");
177 if (p1==string::npos)
178 {
179 Warn("HTTP header not found.");
180 PostClose(false);
181 return;
182 }
183
184 fRdfData.erase(0, p1+4);
185 fRdfData.insert(0, "<?xml version=\"1.0\"?>\n");
186
187 QDomDocument doc;
188 if (!doc.setContent(QString(fRdfData.c_str()), false))
189 {
190 Warn("Parsing of html failed.");
191 PostClose(false);
192 return;
193 }
194
195 if (fIsVerbose)
196 {
197 Out() << "Parsed:\n-------\n" << doc.toString().toStdString() << endl;
198 Out() << "------------------------------------------------------" << endl;
199 }
200
201 const QDomNodeList imageElems = doc.elementsByTagName("span"); // "input"
202
203 /*
204 // elementById
205 for (unsigned int i=0; i<imageElems.length(); i++)
206 {
207 QDomElement e = imageElems.item(i).toElement();
208 Out() << "<" << e.tagName().toStdString() << " ";
209
210 QDomNamedNodeMap att = e.attributes();
211
212 for (int j=0; j<att.size(); j++)
213 {
214 Out() << att.item(j).nodeName().toStdString() << "=";
215 Out() << att.item(j).nodeValue().toStdString() << " ";
216 }
217 Out() << "> " << e.text().toStdString() << endl;
218 }*/
219
220 for (unsigned int i=0; i<imageElems.length(); i++)
221 {
222 const QDomElement e = imageElems.item(i).toElement();
223
224 const QDomNamedNodeMap att = e.attributes();
225
226 fLid1.Set(att);
227 }
228
229 if (fIsVerbose)
230 {
231 fLid1.Print(Out());
232 Out() << "------------------------------------------------------" << endl;
233 }
234
235 Update(fLid1);
236
237 fRdfData = "";
238
239 /*
240 if ((fLid1.status!="Open" && fLid1.status!="Closed" && fLid1.status!="Unknown") ||
241 (fLid2.status!="Open" && fLid2.status!="Closed" && fLid2.status!="Unknown"))
242 {
243 Warn("Lid status unknown ("+fLid1.status+"/"+fLid2.status+")");
244 PostClose(false);
245 return;
246 }*/
247
248 fLastReport = Time();
249 PostClose(false);
250 }
251
252 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
253 {
254 // Do not schedule a new read if the connection failed.
255 if (bytes_received==0 || err)
256 {
257 if (err==ba::error::eof)
258 {
259 ProcessAnswer();
260 return;
261 }
262
263 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
264 // 125: Operation canceled
265 if (err && err!=ba::error::eof && // Connection closed by remote host
266 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
267 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
268 {
269 ostringstream str;
270 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
271 Error(str);
272 }
273 PostClose(err!=ba::error::basic_errors::operation_aborted);
274
275 fRdfData = "";
276 return;
277 }
278
279 fRdfData += string(fArray.data(), bytes_received);
280
281 //cout << "." << flush;
282
283 // Does the message contain a header?
284 const size_t p1 = fRdfData.find("\r\n\r\n");
285 if (p1!=string::npos)
286 {
287 // Does the answer also contain the body?
288 const size_t p2 = fRdfData.find("\r\n\r\n", p1+4);
289 if (p2!=string::npos)
290 {
291 ProcessAnswer();
292 }
293 }
294
295 // Go on reading until the web-server closes the connection
296 StartReadReport();
297 }
298
299 boost::asio::streambuf fBuffer;
300
301 void StartReadReport()
302 {
303 async_read_some(ba::buffer(fArray),
304 boost::bind(&ConnectionLid::HandleRead, this,
305 dummy::error, dummy::bytes_transferred));
306 }
307
308 boost::asio::deadline_timer fKeepAlive;
309
310 void PostRequest(string cmd, const string &args="")
311 {
312 cmd += " "+fSite+" HTTP/1.1\r\n"
313 //"Connection: Keep-Alive\r\n"
314 ;
315
316 ostringstream msg;
317 msg << args.length();
318
319 cmd += "Content-Length: ";
320 cmd += msg.str();
321 cmd += "\r\n";
322
323 if (args.length()>0)
324 cmd += "\r\n" + args + "\r\n";
325
326 cmd += "\r\n";
327
328 //cout << "Post: " << cmd << endl;
329 PostMessage(cmd);
330 }
331
332 void HandleRequest(const bs::error_code &error)
333 {
334 // 125: Operation canceled (bs::error_code(125, bs::system_category))
335 if (error && error!=ba::error::basic_errors::operation_aborted)
336 {
337 ostringstream str;
338 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
339 Error(str);
340
341 PostClose(false);
342 return;
343 }
344
345 if (!is_open())
346 {
347 // For example: Here we could schedule a new accept if we
348 // would not want to allow two connections at the same time.
349 PostClose(true);
350 return;
351 }
352
353 // Check whether the deadline has passed. We compare the deadline
354 // against the current time since a new asynchronous operation
355 // may have moved the deadline before this actor had a chance
356 // to run.
357 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
358 return;
359
360 Request();
361 }
362
363
364private:
365 // This is called when a connection was established
366 void ConnectionEstablished()
367 {
368 Request();
369 StartReadReport();
370 }
371
372public:
373 static const uint16_t kMaxAddr;
374
375public:
376 ConnectionLid(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
377 fIsVerbose(true), fLastReport(Time::none), fKeepAlive(ioservice)
378 {
379 SetLogStream(&imp);
380 }
381
382 void SetVerbose(bool b)
383 {
384 fIsVerbose = b;
385 Connection::SetVerbose(b);
386 }
387
388 void SetInterval(uint16_t i)
389 {
390 fInterval = i;
391 }
392
393 void SetSite(const string &site)
394 {
395 fSite = site;
396 }
397
398 void Post(const string &post)
399 {
400 fNextCommand = post;
401 //PostRequest("POST", post);
402 }
403
404 void Request()
405 {
406 PostRequest("POST", fNextCommand);
407 fNextCommand = "";
408
409 //fLid1.status = "";
410 //fLid2.status = "";
411
412 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval));
413 fKeepAlive.async_wait(boost::bind(&ConnectionLid::HandleRequest,
414 this, dummy::error));
415 }
416
417 int GetInterval() const
418 {
419 return fInterval;
420 }
421
422 int GetState() const
423 {
424 using namespace Lid;
425
426 // Timeout
427 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)<Time())
428 return State::kDisconnected;
429
430 /*
431 // Inconsistency
432 if (fLid1.status!=fLid2.status)
433 return State::kInconsistent;
434
435 // Unknown
436 if (fLid1.status=="Unknown")
437 return State::kUnknown;
438
439 // Closed
440 if (fLid1.status=="Closed")
441 return State::kClosed;
442
443 // Open
444 if (fLid1.status=="Open")
445 return State::kOpen;
446 */
447 return State::kConnected;
448 }
449};
450
451const uint16_t ConnectionLid::kMaxAddr = 0xfff;
452
453// ------------------------------------------------------------------------
454
455#include "DimDescriptionService.h"
456
457class ConnectionDimWeather : public ConnectionLid
458{
459private:
460 //DimDescribedService fDim;
461
462public:
463 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
464 ConnectionLid(ioservice, imp)/*,
465 fDim("PWR_CONTROL/DATA", "S:2;F:2;F:2",
466 "|status[bool]:Lid1/2 open or closed"
467 "|I[A]:Lid1/2 current"
468 "|P[dac]:Lid1/2 hall sensor position in averaged dac counts")*/
469 {
470 }
471
472 void Update(const Lid &l1)
473 {
474 /*
475 struct DimData
476 {
477 int16_t status[2];
478 float current[2];
479 float position[2];
480
481 DimData() { status[0] = status[1] = -1; }
482
483 } __attribute__((__packed__));
484
485 DimData data;
486
487 if (l1.status=="Open")
488 data.status[0] = 1;
489 if (l1.status=="Closed")
490 data.status[0] = 0;
491
492 if (l2.status=="Open")
493 data.status[1] = 1;
494 if (l2.status=="Closed")
495 data.status[1] = 0;
496
497 data.current[0] = l1.current;
498 data.current[1] = l2.current;
499
500 data.position[0] = l1.position;
501 data.position[1] = l2.position;
502
503 fDim.Update(data);*/
504 }
505};
506
507// ------------------------------------------------------------------------
508
509template <class T, class S>
510class StateMachineLidControl : public T, public ba::io_service, public ba::io_service::work
511{
512private:
513 S fLid;
514 Time fLastCommand;
515
516 bool CheckEventSize(size_t has, const char *name, size_t size)
517 {
518 if (has==size)
519 return true;
520
521 ostringstream msg;
522 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
523 T::Fatal(msg);
524 return false;
525 }
526
527 int SetVerbosity(const EventImp &evt)
528 {
529 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
530 return T::kSM_FatalError;
531
532 fLid.SetVerbose(evt.GetBool());
533
534 return T::GetCurrentState();
535 }
536
537 int Post(const EventImp &evt)
538 {
539 fLid.Post(evt.GetText());
540 return T::GetCurrentState();
541 }
542
543 int Open()
544 {
545 fLastCommand = Time();
546 fLid.Post("Button5=");
547 return Lid::State::kMoving;
548 }
549 int Close()
550 {
551 fLastCommand = Time();
552 fLid.Post("Button6=");
553 return Lid::State::kMoving;
554
555 }
556 /*
557 int MoveMotor(const EventImp &evt, int mid)
558 {
559 if (!CheckEventSize(evt.GetSize(), "MoveMotor", 2))
560 return T::kSM_FatalError;
561
562 if (evt.GetUShort()>0xfff)
563 {
564 ostringstream msg;
565 msg << "Position " << evt.GetUShort() << " for motor " << mid+1 << " out of range [0,1023].";
566 T::Error(msg);
567 return T::GetCurrentState();
568 }
569
570 fLid.MoveMotor(mid, evt.GetUShort());
571
572 return T::GetCurrentState();
573 }*/
574
575 int Execute()
576 {
577 // Dispatch (execute) at most one handler from the queue. In contrary
578 // to run_one(), it doesn't wait until a handler is available
579 // which can be dispatched, so poll_one() might return with 0
580 // handlers dispatched. The handlers are always dispatched/executed
581 // synchronously, i.e. within the call to poll_one()
582 poll_one();
583
584 const int rc = fLid.GetState();
585
586 if (T::GetCurrentState()==Lid::State::kMoving &&
587 (rc==Lid::State::kConnected || rc==Lid::State::kDisconnected) &&
588 fLastCommand+boost::posix_time::seconds(6*fLid.GetInterval()) < Time())
589 return Lid::State::kMoving;
590
591 return rc==Lid::State::kConnected ? T::GetCurrentState() : rc;
592 }
593
594
595public:
596 StateMachineLidControl(ostream &out=cout) :
597 T(out, "LID_CONTROL"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
598 fLid(*this, *this)
599 {
600 // ba::io_service::work is a kind of keep_alive for the loop.
601 // It prevents the io_service to go to stopped state, which
602 // would prevent any consecutive calls to run()
603 // or poll() to do nothing. reset() could also revoke to the
604 // previous state but this might introduce some overhead of
605 // deletion and creation of threads and more.
606
607 // State names
608 T::AddStateName(Lid::State::kDisconnected, "NoConnection",
609 "No connection to web-server could be established recently");
610
611 T::AddStateName(Lid::State::kConnected, "Connected",
612 "Connection established, but status still not known");
613
614 T::AddStateName(Lid::State::kInconsistent, "Inconsistent",
615 "Both lids show different states");
616
617 T::AddStateName(Lid::State::kUnknown, "Unknown",
618 "Arduino resports both lids in an unknown status");
619
620 T::AddStateName(Lid::State::kClosed, "Closed",
621 "Both lids are closed");
622
623 T::AddStateName(Lid::State::kOpen, "Open",
624 "Both lids are open");
625
626
627 // Verbosity commands
628 T::AddEvent("SET_VERBOSE", "B")
629 (bind(&StateMachineLidControl::SetVerbosity, this, placeholders::_1))
630 ("set verbosity state"
631 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
632
633 T::AddEvent("OPEN", Lid::State::kInconsistent, Lid::State::kClosed)
634 (bind(&StateMachineLidControl::Open, this));
635
636 T::AddEvent("CLOSE", Lid::State::kInconsistent, Lid::State::kOpen)
637 (bind(&StateMachineLidControl::Close, this));
638
639 T::AddEvent("POST", "C")
640 (bind(&StateMachineLidControl::Post, this, placeholders::_1))
641 ("set verbosity state"
642 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
643 }
644
645 int EvalOptions(Configuration &conf)
646 {
647 fLid.SetVerbose(!conf.Get<bool>("quiet"));
648 fLid.SetInterval(conf.Get<uint16_t>("interval"));
649 fLid.SetDebugTx(conf.Get<bool>("debug-tx"));
650 fLid.SetSite(conf.Get<string>("url"));
651 fLid.SetEndpoint(conf.Get<string>("addr"));
652 fLid.StartConnect();
653
654 return -1;
655 }
656};
657
658// ------------------------------------------------------------------------
659
660#include "Main.h"
661
662
663template<class T, class S, class R>
664int RunShell(Configuration &conf)
665{
666 return Main::execute<T, StateMachineLidControl<S, R>>(conf);
667}
668
669void SetupConfiguration(Configuration &conf)
670{
671 po::options_description control("Lid control");
672 control.add_options()
673 ("no-dim,d", po_switch(), "Disable dim services")
674 ("addr,a", var<string>(""), "Network address of the lid controling Arduino including port")
675 ("url,u", var<string>(""), "File name and path to load")
676 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
677 ("interval,i", var<uint16_t>(5), "Interval between two updates on the server in seconds")
678 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
679 ;
680
681 conf.AddOptions(control);
682}
683
684/*
685 Extract usage clause(s) [if any] for SYNOPSIS.
686 Translators: "Usage" and "or" here are patterns (regular expressions) which
687 are used to match the usage synopsis in program output. An example from cp
688 (GNU coreutils) which contains both strings:
689 Usage: cp [OPTION]... [-T] SOURCE DEST
690 or: cp [OPTION]... SOURCE... DIRECTORY
691 or: cp [OPTION]... -t DIRECTORY SOURCE...
692 */
693void PrintUsage()
694{
695 cout <<
696 "The lidctrl is an interface to the LID control hardware.\n"
697 "\n"
698 "The default is that the program is started without user intercation. "
699 "All actions are supposed to arrive as DimCommands. Using the -c "
700 "option, a local shell can be initialized. With h or help a short "
701 "help message about the usuage can be brought to the screen.\n"
702 "\n"
703 "Usage: lidctrl [-c type] [OPTIONS]\n"
704 " or: lidctrl [OPTIONS]\n";
705 cout << endl;
706}
707
708void PrintHelp()
709{
710// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
711
712 /* Additional help text which is printed after the configuration
713 options goes here */
714
715 /*
716 cout << "bla bla bla" << endl << endl;
717 cout << endl;
718 cout << "Environment:" << endl;
719 cout << "environment" << endl;
720 cout << endl;
721 cout << "Examples:" << endl;
722 cout << "test exam" << endl;
723 cout << endl;
724 cout << "Files:" << endl;
725 cout << "files" << endl;
726 cout << endl;
727 */
728}
729
730int main(int argc, const char* argv[])
731{
732 Configuration conf(argv[0]);
733 conf.SetPrintUsage(PrintUsage);
734 Main::SetupConfiguration(conf);
735 SetupConfiguration(conf);
736
737 if (!conf.DoParse(argc, argv, PrintHelp))
738 return 127;
739
740 // No console access at all
741 if (!conf.Has("console"))
742 {
743 if (conf.Get<bool>("no-dim"))
744 return RunShell<LocalStream, StateMachine, ConnectionLid>(conf);
745 else
746 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
747 }
748 // Cosole access w/ and w/o Dim
749 if (conf.Get<bool>("no-dim"))
750 {
751 if (conf.Get<int>("console")==0)
752 return RunShell<LocalShell, StateMachine, ConnectionLid>(conf);
753 else
754 return RunShell<LocalConsole, StateMachine, ConnectionLid>(conf);
755 }
756 else
757 {
758 if (conf.Get<int>("console")==0)
759 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
760 else
761 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
762 }
763
764 return 0;
765}
Note: See TracBrowser for help on using the repository browser.