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

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