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

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