source: trunk/FACT++/src/lidctrl.cc@ 18538

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