source: trunk/FACT++/src/temperature.cc@ 16542

Last change on this file since 16542 was 16469, checked in by tbretz, 11 years ago
Made compile also with the root version on gate.
File size: 14.8 KB
Line 
1#if BOOST_VERSION < 104600
2#include <assert.h>
3#endif
4
5#include <boost/bind.hpp>
6#include <boost/array.hpp>
7
8#include <boost/property_tree/ptree.hpp>
9#include <boost/property_tree/json_parser.hpp>
10
11#include <string>
12
13#include "FACT.h"
14#include "Dim.h"
15#include "Event.h"
16#include "StateMachineDim.h"
17#include "Connection.h"
18#include "LocalControl.h"
19#include "Configuration.h"
20#include "Console.h"
21
22#include "tools.h"
23
24#include "HeadersTemperature.h"
25
26namespace ba = boost::asio;
27namespace bs = boost::system;
28namespace pt = boost::property_tree;
29namespace dummy = ba::placeholders;
30
31using namespace std;
32
33class ConnectionPowerSwitch : public Connection
34{
35protected:
36 bool fIsValid;
37
38private:
39 uint16_t fInterval;
40
41 bool fIsVerbose;
42 bool fDebugRx;
43
44 string fSite;
45 string fRdfData;
46
47 boost::array<char, 4096> fArray;
48
49 string fNextCommand;
50
51 Time fLastReport;
52
53 int fStatus;
54
55 virtual void Update(const vector<float> &)
56 {
57 }
58
59
60 void ProcessAnswer()
61 {
62 if (fDebugRx)
63 {
64 Out() << "------------------------------------------------------" << endl;
65 Out() << fRdfData << endl;
66 Out() << "------------------------------------------------------" << endl;
67 }
68
69 const size_t p1 = fRdfData.find("\r\n\r\n");
70 if (p1==string::npos)
71 {
72 Warn("HTTP header not found.");
73 PostClose(false);
74 return;
75 }
76
77 fRdfData.erase(0, p1+4);
78
79 vector<float> temp(3);
80 try
81 {
82 std::stringstream ss;
83 ss << fRdfData;
84
85 pt::ptree tree;
86 pt::read_json(ss, tree);
87
88 const pt::ptree sub2 = tree.get_child("sensor_values.").begin()->second;
89 const pt::ptree sub3 = sub2.get_child("values").begin()->second.begin()->second;
90
91 temp[0] = sub3.get_child("v").get_value<float>();
92
93 auto sub = sub3.get_child("st.").begin();
94
95 temp[1] = sub++->second.get_value<float>();
96 temp[2] = sub->second.get_value<float>();
97 }
98 catch (std::exception const& e)
99 {
100 Warn("Parsing of JSON failed: "+string(e.what()));
101
102 fStatus = Temperature::State::kConnected;
103
104 PostClose(false);
105 return;
106 }
107
108 fRdfData = "";
109
110 Update(temp);
111
112 ostringstream msg;
113 msg << "T=" << temp[0] << "°C"
114 << " Tmin=" << temp[1] << "°C"
115 << " Tmax=" << temp[2] << "°C";
116 Message(msg);
117
118 fStatus = Temperature::State::kValid;
119
120 fLastReport = Time();
121 PostClose(false);
122 }
123
124 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
125 {
126 // Do not schedule a new read if the connection failed.
127 if (bytes_received==0 || err)
128 {
129 if (err==ba::error::eof)
130 {
131 if (!fRdfData.empty())
132 ProcessAnswer();
133 return;
134 }
135
136 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
137 // 125: Operation canceled
138 if (err && err!=ba::error::eof && // Connection closed by remote host
139 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
140 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
141 {
142 ostringstream str;
143 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
144 Error(str);
145 }
146 PostClose(err!=ba::error::basic_errors::operation_aborted);
147
148 fRdfData = "";
149 return;
150 }
151
152 fRdfData += string(fArray.data(), bytes_received);
153
154 // Does the message contain a header?
155 const size_t p1 = fRdfData.find("\r\n\r\n");
156 if (p1!=string::npos)
157 {
158 // Does the answer also contain the body?
159 const size_t p2 = fRdfData.find("\r\n\r\n", p1+4);
160 if (p2!=string::npos)
161 ProcessAnswer();
162 }
163
164 // Go on reading until the web-server closes the connection
165 StartReadReport();
166 }
167
168 boost::asio::streambuf fBuffer;
169
170 void StartReadReport()
171 {
172 async_read_some(ba::buffer(fArray),
173 boost::bind(&ConnectionPowerSwitch::HandleRead, this,
174 dummy::error, dummy::bytes_transferred));
175 }
176
177 boost::asio::deadline_timer fKeepAlive;
178
179 void HandleRequest(const bs::error_code &error)
180 {
181 // 125: Operation canceled (bs::error_code(125, bs::system_category))
182 if (error && error!=ba::error::basic_errors::operation_aborted)
183 {
184 ostringstream str;
185 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
186 Error(str);
187
188 PostClose(false);
189 return;
190 }
191
192 if (!is_open())
193 {
194 // For example: Here we could schedule a new accept if we
195 // would not want to allow two connections at the same time.
196 PostClose(true);
197 return;
198 }
199
200 // Check whether the deadline has passed. We compare the deadline
201 // against the current time since a new asynchronous operation
202 // may have moved the deadline before this actor had a chance
203 // to run.
204 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
205 return;
206
207 Request();
208 }
209
210
211private:
212 // This is called when a connection was established
213 void ConnectionEstablished()
214 {
215 Request();
216 StartReadReport();
217 }
218
219public:
220 static const uint16_t kMaxAddr;
221
222public:
223 ConnectionPowerSwitch(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
224 fIsValid(false), fIsVerbose(true), fDebugRx(false), fLastReport(Time::none),
225 fStatus(Temperature::State::kDisconnected), fKeepAlive(ioservice)
226 {
227 SetLogStream(&imp);
228 }
229
230 void SetVerbose(bool b)
231 {
232 fIsVerbose = b;
233 }
234
235 void SetDebugRx(bool b)
236 {
237 fDebugRx = b;
238 Connection::SetVerbose(b);
239 }
240
241 void SetInterval(uint16_t i)
242 {
243 fInterval = i;
244 }
245
246 void SetSite(const string &site)
247 {
248 fSite = site;
249 }
250
251 void Post(const string &post)
252 {
253 fNextCommand = post;
254 }
255
256 void Request()
257 {
258 string cmd = "GET " + fSite;
259
260 if (!fNextCommand.empty())
261 cmd += "?" + fNextCommand;
262
263 cmd += " HTTP/1.1\r\n";
264 cmd += "\r\n";
265
266 PostMessage(cmd);
267
268 fNextCommand = "";
269
270 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval));
271 fKeepAlive.async_wait(boost::bind(&ConnectionPowerSwitch::HandleRequest,
272 this, dummy::error));
273 }
274
275 int GetInterval() const
276 {
277 return fInterval;
278 }
279
280 int GetState() const
281 {
282 // Timeout
283 if (!fLastReport.IsValid() || Time()>fLastReport+boost::posix_time::seconds(fInterval*3))
284 return Temperature::State::kDisconnected;
285
286 return fStatus;
287 }
288};
289
290const uint16_t ConnectionPowerSwitch::kMaxAddr = 0xfff;
291
292// ------------------------------------------------------------------------
293
294#include "DimDescriptionService.h"
295
296class ConnectionDimPowerSwitch : public ConnectionPowerSwitch
297{
298private:
299 DimDescribedService fDim;
300
301public:
302 ConnectionDimPowerSwitch(ba::io_service& ioservice, MessageImp &imp) :
303 ConnectionPowerSwitch(ioservice, imp),
304 fDim("TEMPERATURE/DATA", "F:1;F:1;F:1",
305 "Temperature readout from power switch"
306 "|T[degC]:Current temperature"
307 "|Tmin[degC]:24h minimum"
308 "|Tmax[degC]:24h maximum")
309 {
310 }
311
312 void Update(const vector<float> &temp)
313 {
314 fDim.Update(temp);
315 }
316};
317
318// ------------------------------------------------------------------------
319
320template <class T, class S>
321class StateMachinePowerControl : public T, public ba::io_service, public ba::io_service::work
322{
323private:
324 S fPower;
325 Time fLastCommand;
326
327 bool CheckEventSize(size_t has, const char *name, size_t size)
328 {
329 if (has==size)
330 return true;
331
332 ostringstream msg;
333 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
334 T::Fatal(msg);
335 return false;
336 }
337
338 int SetVerbosity(const EventImp &evt)
339 {
340 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
341 return T::kSM_FatalError;
342
343 fPower.SetVerbose(evt.GetBool());
344
345 return T::GetCurrentState();
346 }
347
348 int SetDebugRx(const EventImp &evt)
349 {
350 if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1))
351 return T::kSM_FatalError;
352
353 fPower.SetDebugRx(evt.GetBool());
354
355 return T::GetCurrentState();
356 }
357
358 int Execute()
359 {
360 // Dispatch (execute) at most one handler from the queue. In contrary
361 // to run_one(), it doesn't wait until a handler is available
362 // which can be dispatched, so poll_one() might return with 0
363 // handlers dispatched. The handlers are always dispatched/executed
364 // synchronously, i.e. within the call to poll_one()
365 poll_one();
366
367 return fPower.GetState();
368 }
369
370
371public:
372 StateMachinePowerControl(ostream &out=cout) :
373 T(out, "TEMPERATURE"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
374 fPower(*this, *this)
375 {
376 // ba::io_service::work is a kind of keep_alive for the loop.
377 // It prevents the io_service to go to stopped state, which
378 // would prevent any consecutive calls to run()
379 // or poll() to do nothing. reset() could also revoke to the
380 // previous state but this might introduce some overhead of
381 // deletion and creation of threads and more.
382
383 // State names
384 T::AddStateName(Temperature::State::kDisconnected, "NoConnection",
385 "No connection to web-server could be established recently");
386
387 T::AddStateName(Temperature::State::kConnected, "Connected",
388 "Connection established, but no valid data received");
389
390 T::AddStateName(Temperature::State::kValid, "Valid",
391 "Connection established, received data valid");
392
393 // Verbosity commands
394 T::AddEvent("SET_VERBOSE", "B:1")
395 (bind(&StateMachinePowerControl::SetVerbosity, this, placeholders::_1))
396 ("Set verbosity state"
397 "|verbosity[bool]:disable or enable verbosity for interpreted data (yes/no)");
398
399 T::AddEvent("SET_DEBUG_RX", "B:1")
400 (bind(&StateMachinePowerControl::SetDebugRx, this, placeholders::_1))
401 ("Set debux-rx state"
402 "|debug[bool]:dump received text and parsed text to console (yes/no)");
403
404 }
405
406 int EvalOptions(Configuration &conf)
407 {
408 fPower.SetVerbose(!conf.Get<bool>("quiet"));
409 fPower.SetInterval(conf.Get<uint16_t>("interval"));
410 fPower.SetDebugTx(conf.Get<bool>("debug-tx"));
411 fPower.SetDebugRx(conf.Get<bool>("debug-rx"));
412 fPower.SetSite(conf.Get<string>("url"));
413 fPower.SetEndpoint(conf.Get<string>("addr"));
414 fPower.StartConnect();
415
416 return -1;
417 }
418};
419
420// ------------------------------------------------------------------------
421
422#include "Main.h"
423
424
425template<class T, class S, class R>
426int RunShell(Configuration &conf)
427{
428 return Main::execute<T, StateMachinePowerControl<S, R>>(conf);
429}
430
431void SetupConfiguration(Configuration &conf)
432{
433 po::options_description control("Lid control");
434 control.add_options()
435 ("no-dim,d", po_switch(), "Disable dim services")
436 ("addr,a", var<string>("10.0.100.234:80"), "Network address of the lid controling Arduino including port")
437 ("url,u", var<string>("/statusjsn.js?components=18179&_=1365876572736"), "File name and path to load")
438 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
439 ("interval,i", var<uint16_t>(60), "Interval between two updates on the server in seconds")
440 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
441 ("debug-rx", po_bool(), "Enable debugging for received data.")
442 ;
443
444 conf.AddOptions(control);
445}
446
447/*
448 Extract usage clause(s) [if any] for SYNOPSIS.
449 Translators: "Usage" and "or" here are patterns (regular expressions) which
450 are used to match the usage synopsis in program output. An example from cp
451 (GNU coreutils) which contains both strings:
452 Usage: cp [OPTION]... [-T] SOURCE DEST
453 or: cp [OPTION]... SOURCE... DIRECTORY
454 or: cp [OPTION]... -t DIRECTORY SOURCE...
455 */
456void PrintUsage()
457{
458 cout <<
459 "The temperature is an interface to readout the temperature from the power switch.\n"
460 "\n"
461 "The default is that the program is started without user intercation. "
462 "All actions are supposed to arrive as DimCommands. Using the -c "
463 "option, a local shell can be initialized. With h or help a short "
464 "help message about the usuage can be brought to the screen.\n"
465 "\n"
466 "Usage: temperature [-c type] [OPTIONS]\n"
467 " or: temperature [OPTIONS]\n";
468 cout << endl;
469}
470
471void PrintHelp()
472{
473// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
474
475 /* Additional help text which is printed after the configuration
476 options goes here */
477
478 /*
479 cout << "bla bla bla" << endl << endl;
480 cout << endl;
481 cout << "Environment:" << endl;
482 cout << "environment" << endl;
483 cout << endl;
484 cout << "Examples:" << endl;
485 cout << "test exam" << endl;
486 cout << endl;
487 cout << "Files:" << endl;
488 cout << "files" << endl;
489 cout << endl;
490 */
491}
492
493int main(int argc, const char* argv[])
494{
495 Configuration conf(argv[0]);
496 conf.SetPrintUsage(PrintUsage);
497 Main::SetupConfiguration(conf);
498 SetupConfiguration(conf);
499
500 if (!conf.DoParse(argc, argv, PrintHelp))
501 return 127;
502
503 // No console access at all
504 if (!conf.Has("console"))
505 {
506 if (conf.Get<bool>("no-dim"))
507 return RunShell<LocalStream, StateMachine, ConnectionPowerSwitch>(conf);
508 else
509 return RunShell<LocalStream, StateMachineDim, ConnectionDimPowerSwitch>(conf);
510 }
511 // Cosole access w/ and w/o Dim
512 if (conf.Get<bool>("no-dim"))
513 {
514 if (conf.Get<int>("console")==0)
515 return RunShell<LocalShell, StateMachine, ConnectionPowerSwitch>(conf);
516 else
517 return RunShell<LocalConsole, StateMachine, ConnectionPowerSwitch>(conf);
518 }
519 else
520 {
521 if (conf.Get<int>("console")==0)
522 return RunShell<LocalShell, StateMachineDim, ConnectionDimPowerSwitch>(conf);
523 else
524 return RunShell<LocalConsole, StateMachineDim, ConnectionDimPowerSwitch>(conf);
525 }
526
527 return 0;
528}
Note: See TracBrowser for help on using the repository browser.