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

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