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

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