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

Last change on this file since 14751 was 14680, checked in by tbretz, 12 years ago
Fixed the timing around the movement; a conditional sign in Execute was wrong; added descriptions for OPEN and CLOSE
File size: 19.4 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 resports both 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::kClosed)
568 (bind(&StateMachineLidControl::Open, this))
569 ("Open the lids");
570
571 T::AddEvent("CLOSE", Lid::State::kInconsistent, 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.