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

Last change on this file since 15040 was 14978, checked in by tbretz, 12 years ago
Added include for array.hpp - needed for newer boost versions
File size: 19.5 KB
Line 
1#include <boost/bind.hpp>
2#include <boost/array.hpp>
3
4#include <string> // std::string
5#include <algorithm> // std::transform
6#include <cctype> // std::tolower
7
8#include <QtXml/QDomDocument>
9
10#include "FACT.h"
11#include "Dim.h"
12#include "Event.h"
13#include "StateMachineDim.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!="Unknown") ||
169 (fLid2.status!="Open" && fLid2.status!="Closed" && fLid2.status!="Unknown"))
170 {
171 Warn("Lid status unknown ("+fLid1.status+"/"+fLid2.status+")");
172 PostClose(false);
173 return;
174 }
175
176 fLastReport = Time();
177 PostClose(false);
178 }
179
180 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
181 {
182 // Do not schedule a new read if the connection failed.
183 if (bytes_received==0 || err)
184 {
185 if (err==ba::error::eof)
186 {
187 //Warn("Connection closed by remote host.");
188 ProcessAnswer();
189 return;
190 }
191
192 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
193 // 125: Operation canceled
194 if (err && err!=ba::error::eof && // Connection closed by remote host
195 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
196 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
197 {
198 ostringstream str;
199 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
200 Error(str);
201 }
202 PostClose(err!=ba::error::basic_errors::operation_aborted);
203
204 fRdfData = "";
205 return;
206 }
207
208 fRdfData += string(fArray.data(), bytes_received);
209
210 //cout << "." << flush;
211
212 // Does the message contain a header?
213 const size_t p1 = fRdfData.find("\r\n\r\n");
214 if (p1!=string::npos)
215 {
216 // Does the answer also contain the body?
217 const size_t p2 = fRdfData.find("\r\n\r\n", p1+4);
218 if (p2!=string::npos)
219 {
220 ProcessAnswer();
221 }
222 }
223
224 // Go on reading until the web-server closes the connection
225 StartReadReport();
226 }
227
228 boost::asio::streambuf fBuffer;
229
230 void StartReadReport()
231 {
232 async_read_some(ba::buffer(fArray),
233 boost::bind(&ConnectionLid::HandleRead, this,
234 dummy::error, dummy::bytes_transferred));
235 }
236
237 boost::asio::deadline_timer fKeepAlive;
238
239 void PostRequest(string cmd, const string &args="")
240 {
241 cmd += " "+fSite+" HTTP/1.1\r\n"
242 //"Connection: Keep-Alive\r\n"
243 ;
244
245 ostringstream msg;
246 msg << args.length();
247
248 cmd += "Content-Length: ";
249 cmd += msg.str();
250 cmd +="\r\n";
251
252 if (args.length()>0)
253 cmd += "\r\n"+args + "\r\n";
254
255 cmd += "\r\n";
256
257 //cout << "Post: " << cmd << endl;
258 PostMessage(cmd);
259 }
260
261 void HandleRequest(const bs::error_code &error)
262 {
263 // 125: Operation canceled (bs::error_code(125, bs::system_category))
264 if (error && error!=ba::error::basic_errors::operation_aborted)
265 {
266 ostringstream str;
267 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
268 Error(str);
269
270 PostClose(false);
271 return;
272 }
273
274 if (!is_open())
275 {
276 // For example: Here we could schedule a new accept if we
277 // would not want to allow two connections at the same time.
278 PostClose(true);
279 return;
280 }
281
282 // Check whether the deadline has passed. We compare the deadline
283 // against the current time since a new asynchronous operation
284 // may have moved the deadline before this actor had a chance
285 // to run.
286 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
287 return;
288
289 Request();
290 }
291
292
293private:
294 // This is called when a connection was established
295 void ConnectionEstablished()
296 {
297 Request();
298 StartReadReport();
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 // Inconsistency
361 if (fLid1.status!=fLid2.status)
362 return State::kInconsistent;
363
364 // Unknown
365 if (fLid1.status=="Unknown")
366 return State::kUnknown;
367
368 // Closed
369 if (fLid1.status=="Closed")
370 return State::kClosed;
371
372 // Open
373 if (fLid1.status=="Open")
374 return State::kOpen;
375
376 return State::kConnected;
377 }
378};
379
380const uint16_t ConnectionLid::kMaxAddr = 0xfff;
381
382// ------------------------------------------------------------------------
383
384#include "DimDescriptionService.h"
385
386class ConnectionDimWeather : public ConnectionLid
387{
388private:
389 DimDescribedService fDim;
390
391public:
392 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
393 ConnectionLid(ioservice, imp),
394 fDim("LID_CONTROL/DATA", "S:2;F:2;F:2",
395 "|status[bool]:Lid1/2 open or closed"
396 "|I[A]:Lid1/2 current"
397 "|P[dac]:Lid1/2 hall sensor position in averaged dac counts")
398 {
399 }
400
401 void Update(const Lid &l1, const Lid &l2)
402 {
403 struct DimData
404 {
405 int16_t status[2];
406 float current[2];
407 float position[2];
408
409 DimData() { status[0] = status[1] = -1; }
410
411 } __attribute__((__packed__));
412
413 DimData data;
414
415 if (l1.status=="Open")
416 data.status[0] = 1;
417 if (l1.status=="Closed")
418 data.status[0] = 0;
419
420 if (l2.status=="Open")
421 data.status[1] = 1;
422 if (l2.status=="Closed")
423 data.status[1] = 0;
424
425 data.current[0] = l1.current;
426 data.current[1] = l2.current;
427
428 data.position[0] = l1.position;
429 data.position[1] = l2.position;
430
431 fDim.Update(data);
432 }
433};
434
435// ------------------------------------------------------------------------
436
437template <class T, class S>
438class StateMachineLidControl : public T, public ba::io_service, public ba::io_service::work
439{
440private:
441 S fLid;
442 Time fLastCommand;
443
444 uint16_t fTimeToMove;
445
446 bool CheckEventSize(size_t has, const char *name, size_t size)
447 {
448 if (has==size)
449 return true;
450
451 ostringstream msg;
452 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
453 T::Fatal(msg);
454 return false;
455 }
456
457 int SetVerbosity(const EventImp &evt)
458 {
459 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
460 return T::kSM_FatalError;
461
462 fLid.SetVerbose(evt.GetBool());
463
464 return T::GetCurrentState();
465 }
466
467 int Post(const EventImp &evt)
468 {
469 fLid.Post(evt.GetText());
470 return T::GetCurrentState();
471 }
472
473 int Open()
474 {
475 fLastCommand = Time();
476 fLid.Post("Button5=");
477 return Lid::State::kMoving;
478 }
479 int Close()
480 {
481 fLastCommand = Time();
482 fLid.Post("Button6=");
483 return Lid::State::kMoving;
484
485 }
486 /*
487 int MoveMotor(const EventImp &evt, int mid)
488 {
489 if (!CheckEventSize(evt.GetSize(), "MoveMotor", 2))
490 return T::kSM_FatalError;
491
492 if (evt.GetUShort()>0xfff)
493 {
494 ostringstream msg;
495 msg << "Position " << evt.GetUShort() << " for motor " << mid+1 << " out of range [0,1023].";
496 T::Error(msg);
497 return T::GetCurrentState();
498 }
499
500 fLid.MoveMotor(mid, evt.GetUShort());
501
502 return T::GetCurrentState();
503 }*/
504
505 int Execute()
506 {
507 // Dispatch (execute) at most one handler from the queue. In contrary
508 // to run_one(), it doesn't wait until a handler is available
509 // which can be dispatched, so poll_one() might return with 0
510 // handlers dispatched. The handlers are always dispatched/executed
511 // synchronously, i.e. within the call to poll_one()
512 poll_one();
513
514 const int rc = fLid.GetState();
515
516 if (T::GetCurrentState()==Lid::State::kMoving &&
517 (rc==Lid::State::kConnected || rc==Lid::State::kDisconnected) &&
518 fLastCommand+boost::posix_time::seconds(fTimeToMove+fLid.GetInterval()) > Time())
519 {
520 return Lid::State::kMoving;
521 }
522
523 return rc==Lid::State::kConnected ? T::GetCurrentState() : rc;
524 }
525
526
527public:
528 StateMachineLidControl(ostream &out=cout) :
529 T(out, "LID_CONTROL"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
530 fLid(*this, *this)
531 {
532 // ba::io_service::work is a kind of keep_alive for the loop.
533 // It prevents the io_service to go to stopped state, which
534 // would prevent any consecutive calls to run()
535 // or poll() to do nothing. reset() could also revoke to the
536 // previous state but this might introduce some overhead of
537 // deletion and creation of threads and more.
538
539 // State names
540 T::AddStateName(Lid::State::kDisconnected, "NoConnection",
541 "No connection to web-server could be established recently");
542
543 T::AddStateName(Lid::State::kConnected, "Connected",
544 "Connection established, but status still not known");
545
546 T::AddStateName(Lid::State::kInconsistent, "Inconsistent",
547 "Both lids show different states");
548
549 T::AddStateName(Lid::State::kUnknown, "Unknown",
550 "Arduino reports at least one lids in an unknown status");
551
552 T::AddStateName(Lid::State::kClosed, "Closed",
553 "Both lids are closed");
554
555 T::AddStateName(Lid::State::kOpen, "Open",
556 "Both lids are open");
557
558 T::AddStateName(Lid::State::kMoving, "Moving",
559 "Lids are supposed to move, waiting for next status");
560
561
562 // Verbosity commands
563 T::AddEvent("SET_VERBOSE", "B")
564 (bind(&StateMachineLidControl::SetVerbosity, this, placeholders::_1))
565 ("set verbosity state"
566 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
567
568 T::AddEvent("OPEN", Lid::State::kInconsistent, Lid::State::kUnknown, Lid::State::kClosed)
569 (bind(&StateMachineLidControl::Open, this))
570 ("Open the lids");
571
572 T::AddEvent("CLOSE", Lid::State::kInconsistent, Lid::State::kUnknown, Lid::State::kOpen)
573 (bind(&StateMachineLidControl::Close, this))
574 ("Close the lids");
575
576 T::AddEvent("POST", "C")
577 (bind(&StateMachineLidControl::Post, this, placeholders::_1))
578 ("set verbosity state"
579 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
580 }
581
582 int EvalOptions(Configuration &conf)
583 {
584 fLid.SetVerbose(!conf.Get<bool>("quiet"));
585 fLid.SetInterval(conf.Get<uint16_t>("interval"));
586 fLid.SetDebugTx(conf.Get<bool>("debug-tx"));
587 fLid.SetSite(conf.Get<string>("url"));
588 fLid.SetEndpoint(conf.Get<string>("addr"));
589 fLid.StartConnect();
590
591 fTimeToMove = conf.Get<uint16_t>("time-to-move");
592
593 return -1;
594 }
595};
596
597// ------------------------------------------------------------------------
598
599#include "Main.h"
600
601
602template<class T, class S, class R>
603int RunShell(Configuration &conf)
604{
605 return Main::execute<T, StateMachineLidControl<S, R>>(conf);
606}
607
608void SetupConfiguration(Configuration &conf)
609{
610 po::options_description control("Lid control");
611 control.add_options()
612 ("no-dim,d", po_switch(), "Disable dim services")
613 ("addr,a", var<string>(""), "Network address of the lid controling Arduino including port")
614 ("url,u", var<string>(""), "File name and path to load")
615 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
616 ("interval,i", var<uint16_t>(5), "Interval between two updates on the server in seconds")
617 ("time-to-move", var<uint16_t>(20), "Expected minimum time the lid taks to open/close")
618 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
619 ;
620
621 conf.AddOptions(control);
622}
623
624/*
625 Extract usage clause(s) [if any] for SYNOPSIS.
626 Translators: "Usage" and "or" here are patterns (regular expressions) which
627 are used to match the usage synopsis in program output. An example from cp
628 (GNU coreutils) which contains both strings:
629 Usage: cp [OPTION]... [-T] SOURCE DEST
630 or: cp [OPTION]... SOURCE... DIRECTORY
631 or: cp [OPTION]... -t DIRECTORY SOURCE...
632 */
633void PrintUsage()
634{
635 cout <<
636 "The lidctrl is an interface to the LID control hardware.\n"
637 "\n"
638 "The default is that the program is started without user intercation. "
639 "All actions are supposed to arrive as DimCommands. Using the -c "
640 "option, a local shell can be initialized. With h or help a short "
641 "help message about the usuage can be brought to the screen.\n"
642 "\n"
643 "Usage: lidctrl [-c type] [OPTIONS]\n"
644 " or: lidctrl [OPTIONS]\n";
645 cout << endl;
646}
647
648void PrintHelp()
649{
650// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
651
652 /* Additional help text which is printed after the configuration
653 options goes here */
654
655 /*
656 cout << "bla bla bla" << endl << endl;
657 cout << endl;
658 cout << "Environment:" << endl;
659 cout << "environment" << endl;
660 cout << endl;
661 cout << "Examples:" << endl;
662 cout << "test exam" << endl;
663 cout << endl;
664 cout << "Files:" << endl;
665 cout << "files" << endl;
666 cout << endl;
667 */
668}
669
670int main(int argc, const char* argv[])
671{
672 Configuration conf(argv[0]);
673 conf.SetPrintUsage(PrintUsage);
674 Main::SetupConfiguration(conf);
675 SetupConfiguration(conf);
676
677 if (!conf.DoParse(argc, argv, PrintHelp))
678 return 127;
679
680 // No console access at all
681 if (!conf.Has("console"))
682 {
683 if (conf.Get<bool>("no-dim"))
684 return RunShell<LocalStream, StateMachine, ConnectionLid>(conf);
685 else
686 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
687 }
688 // Cosole access w/ and w/o Dim
689 if (conf.Get<bool>("no-dim"))
690 {
691 if (conf.Get<int>("console")==0)
692 return RunShell<LocalShell, StateMachine, ConnectionLid>(conf);
693 else
694 return RunShell<LocalConsole, StateMachine, ConnectionLid>(conf);
695 }
696 else
697 {
698 if (conf.Get<int>("console")==0)
699 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
700 else
701 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
702 }
703
704 return 0;
705}
Note: See TracBrowser for help on using the repository browser.