source: trunk/FACT++/src/gudectrl.cc@ 20115

Last change on this file since 20115 was 20022, checked in by tbretz, 4 years ago
Fixed some compiler warnings by letting the compiler decide the exact type.
File size: 18.2 KB
Line 
1#include <boost/array.hpp>
2#include <boost/property_tree/ptree.hpp>
3#include <boost/property_tree/json_parser.hpp>
4
5#include <string> // std::string
6#include <algorithm> // std::transform
7#include <cctype> // std::tolower
8
9#include "FACT.h"
10#include "Dim.h"
11#include "Event.h"
12#include "Shell.h"
13#include "StateMachineDim.h"
14#include "StateMachineAsio.h"
15#include "Connection.h"
16#include "LocalControl.h"
17#include "Configuration.h"
18#include "Console.h"
19
20#include "tools.h"
21
22#include "HeadersGude.h"
23
24namespace ba = boost::asio;
25namespace bs = boost::system;
26namespace pt = boost::property_tree;
27namespace dummy = ba::placeholders;
28
29using namespace std;
30using namespace Gude;
31
32// ------------------------------------------------------------------------
33
34class ConnectionGude : public Connection
35{
36 uint16_t fInterval;
37
38 bool fIsVerbose;
39
40 string fSite;
41
42protected:
43
44 Time fLastReport;
45 Time fLastReception;
46
47 boost::asio::streambuf fBuffer;
48 string fData;
49
50 array<float, 12*21> fReadings;
51 array<string, 12> fSensors;
52 array<string, 21> fUnits;
53
54 virtual void UpdateGude(const array<float, 12*21> &)
55 {
56 }
57
58 void ProcessAnswer(string s)
59 {
60 try
61 {
62 std::stringstream ss;
63 ss << s;
64
65 pt::ptree tree;
66 pt::read_json(ss, tree);
67
68 const auto &sensor_values = tree.get_child("sensor_values");
69
70 for (const auto &obj : sensor_values)
71 {
72 switch (obj.second.get_child("type").get_value<uint32_t>())
73 {
74 case 8:
75
76 {
77 const auto &values = obj.second.get_child("values");
78
79 int isensor = 0;
80 for (const auto &sensor : values)
81 {
82 if (isensor==12)
83 throw runtime_error("Values from more sensors than expected!");
84
85 int ivalue = 0;
86 for (const auto &value : sensor.second)
87 {
88 if (ivalue==21)
89 throw runtime_error("More values than expected!");
90
91 //std::stringstream ss2;
92 //pt::write_json(ss2, value.second.get_child("v").get_value<float>());
93
94 const auto idx = ivalue*12+isensor;
95 fReadings[idx] = value.second.get_child("v").get_value<float>();
96
97 ivalue++;
98 }
99 isensor++;
100 }
101 }
102 default:
103 continue;
104
105 }
106 }
107
108
109
110 const auto &outputs = tree.get_child("outputs");
111
112 int isensor = 0;
113 for (const auto &obj : outputs)
114 {
115 if (isensor==12)
116 throw runtime_error("More outputs than expected!");
117
118 const string name = obj.second.get_child("name").get_value<string>();
119
120 if (name!=fSensors[isensor])
121 Info("New name for sensor "+to_string(isensor)+": "+name);
122
123 fSensors[isensor++] = name;
124 }
125
126
127
128 const auto &sensor_descr = tree.get_child("sensor_descr");
129
130 for (const auto &obj : sensor_descr)
131 {
132 switch (obj.second.get_child("type").get_value<uint32_t>())
133 {
134 case 8:
135 {
136 const auto &fields = obj.second.get_child("fields");
137
138 int ifield = 0;
139 for (const auto &field : fields)
140 {
141 if (ifield==21)
142 throw runtime_error("More fields than expected!");
143
144 const string name = field.second.get_child("name").get_value<string>();
145 const string unit = field.second.get_child("unit").get_value<string>();
146
147 fUnits[ifield] = name;
148 if (!unit.empty())
149 fUnits[ifield] += " [" + unit + "]";
150
151 ifield++;
152 }
153 }
154 default:
155 continue;
156
157 }
158 }
159
160 UpdateGude(fReadings);
161
162 fLastReport = Time();
163 }
164 catch (std::exception const& e)
165 {
166 Error(string("Parsing JSON failed: ")+e.what());
167 }
168 }
169
170 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
171 {
172 // Do not schedule a new read if the connection failed.
173 if (bytes_received==0 || err)
174 {
175 if (err==ba::error::eof)
176 {
177 // Does the message contain a header?
178 const size_t p1 = fData.find("\r\n\r\n");
179 if (p1!=string::npos)
180 ProcessAnswer(fData.substr(p1));
181 else
182 Warn("Received message lacks a header!");
183 fData = "";
184
185 PostClose(false);
186
187 return;
188 }
189
190 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
191 // 125: Operation canceled
192 if (err && err!=ba::error::eof && // Connection closed by remote host
193 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
194 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
195 {
196 ostringstream str;
197 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
198 Error(str);
199 }
200 PostClose(err!=ba::error::basic_errors::operation_aborted);
201 return;
202 }
203
204 fLastReception = Time();
205
206 istream is(&fBuffer);
207
208 string buffer;
209 if (!getline(is, buffer, '\n'))
210 {
211 Fatal("Received message does not contain \\n... closing connection.");
212 PostClose(false);
213 return;
214 }
215
216 if (fIsVerbose)
217 Out() << buffer << endl;
218
219 fData += buffer;
220 fData += '\n';
221
222 StartReadLine();
223 }
224
225 void StartReadLine()
226 {
227 async_read_until(*this, fBuffer, '\n',
228 boost::bind(&ConnectionGude::HandleRead, this,
229 dummy::error, dummy::bytes_transferred));
230 }
231
232 ba::deadline_timer fKeepAlive;
233
234 void PostRequest()
235 {
236 const string cmd =
237 "GET "+fSite+" HTTP/1.1\r\n"
238 "\r\n";
239
240 if (GetDebugTx())
241 Out() << cmd << endl;
242
243 PostMessage(cmd);
244 }
245
246 void Request()
247 {
248 PostRequest();
249
250 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval/2));
251 fKeepAlive.async_wait(boost::bind(&ConnectionGude::HandleRequest,
252 this, dummy::error));
253 }
254
255 void HandleRequest(const bs::error_code &error)
256 {
257 // 125: Operation canceled (bs::error_code(125, bs::system_category))
258 if (error && error!=ba::error::basic_errors::operation_aborted)
259 {
260 ostringstream str;
261 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
262 Error(str);
263
264 PostClose(false);
265 return;
266 }
267
268 if (!is_open())
269 {
270 // For example: Here we could schedule a new accept if we
271 // would not want to allow two connections at the same time.
272 PostClose(true);
273 return;
274 }
275
276 // Check whether the deadline has passed. We compare the deadline
277 // against the current time since a new asynchronous operation
278 // may have moved the deadline before this actor had a chance
279 // to run.
280 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
281 return;
282
283 Request();
284 }
285
286
287private:
288 // This is called when a connection was established
289 void ConnectionEstablished()
290 {
291 Request();
292 StartReadLine();
293 }
294
295public:
296 ConnectionGude(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
297 fIsVerbose(true), fLastReport(Time::none), fLastReception(Time::none), fKeepAlive(ioservice)
298 {
299 SetLogStream(&imp);
300 }
301
302 void SetVerbose(bool b)
303 {
304 fIsVerbose = b;
305 Connection::SetVerbose(b);
306 }
307
308 void SetInterval(uint16_t i)
309 {
310 fInterval = i;
311 }
312
313 void SetSite(const string &site)
314 {
315 fSite = site;
316 }
317
318 void Print(bool all=true)
319 {
320 for (int k=0; k<(all?21:6); k+=3)
321 {
322 Out() << setw(17) << ' ';
323 for (int i=k; i<k+3; i++)
324 Out() << setw(30) << fUnits[i];
325 Out() << endl;
326
327 for (int i=0; i<12; i++)
328 {
329 Out() << setw(17) << fSensors[i];
330 for (int j=k; j<k+3; j++)
331 Out() << setw(30) << fReadings[i+j*12];
332 Out() << endl;
333 }
334
335 Out() << endl;
336 }
337 }
338
339 int GetState() const
340 {
341 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
342 return 3;
343
344 if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
345 return 2;
346
347 return 1;
348
349 }
350};
351
352// ------------------------------------------------------------------------
353
354#include "DimDescriptionService.h"
355
356class ConnectionDimGude : public ConnectionGude
357{
358private:
359
360 DimDescribedService fDimGude;
361
362 virtual void UpdateGude(const array<float, 12*21> &data)
363 {
364 fDimGude.setData(data.data(), sizeof(float)*data.size());
365 fDimGude.Update();
366 }
367
368public:
369 ConnectionDimGude(ba::io_service& ioservice, MessageImp &imp) :
370 ConnectionGude(ioservice, imp),
371 fDimGude("GUDE_CONTROL/DATA",
372 "F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12;F:12",
373 "|Voltage[V]"
374 "|Current[A]"
375 "|Frequency[Hz]"
376 "|PhaseIU[degree]"
377 "|ActivePower[W]"
378 "|ReactivePower[VAR]"
379 "|ApparentPower[VA]"
380 "|Powerfactor"
381 "|AbsActEnergyNonRes[kWh]"
382 "|AbsReactEnergyNonRes[kVARh]"
383 "|AbsActEnergyRes[kWh]"
384 "|AbsReactEnergyRes[kVARh]"
385 "|RelativeTime[s]"
386 "|FwdActEnergyNonRes[kWh]"
387 "|FwdReactEnergyNonRes[kVARh]"
388 "|FwdActEnergyRes[kWh]"
389 "|FwdReactEnergyRes[kVARh]"
390 "|RevActEnergyNonRes[kWh]"
391 "|RevReactEnergyNonRes[kVARh]"
392 "|RevActEnergyRes[kWh]"
393 "|RevReactEnergyRes[kVARh]")
394 {
395 }
396};
397
398// ------------------------------------------------------------------------
399
400template <class T, class S>
401class StateMachineGude : public StateMachineAsio<T>
402{
403private:
404 S fGude;
405
406 bool CheckEventSize(size_t has, const char *name, size_t size)
407 {
408 if (has==size)
409 return true;
410
411 ostringstream msg;
412 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
413 T::Fatal(msg);
414 return false;
415 }
416
417 int SetVerbosity(const EventImp &evt)
418 {
419 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
420 return T::kSM_FatalError;
421
422 fGude.SetVerbose(evt.GetBool());
423
424 return T::GetCurrentState();
425 }
426
427 int Print()
428 {
429 fGude.Print(false);
430 return T::GetCurrentState();
431 }
432
433 int PrintAll()
434 {
435 fGude.Print(true);
436 return T::GetCurrentState();
437 }
438/*
439 int Disconnect()
440 {
441 // Close all connections
442 fGude.PostClose(false);
443
444 return T::GetCurrentState();
445 }
446
447 int Reconnect(const EventImp &evt)
448 {
449 // Close all connections to supress the warning in SetEndpoint
450 fGude.PostClose(false);
451
452 // Now wait until all connection have been closed and
453 // all pending handlers have been processed
454 ba::io_service::poll();
455
456 if (evt.GetBool())
457 fGude.SetEndpoint(evt.GetString());
458
459 // Now we can reopen the connection
460 fGude.PostClose(true);
461
462 return T::GetCurrentState();
463 }
464*/
465 int Execute()
466 {
467 return fGude.GetState();
468 }
469
470public:
471 StateMachineGude(ostream &out=cout) :
472 StateMachineAsio<T>(out, "GUDE_CONTROL"), fGude(*this, *this)
473 {
474 // State names
475 T::AddStateName(State::kDisconnected, "NoConnection",
476 "No connection to web-server could be established recently");
477
478 T::AddStateName(State::kConnected, "Invalid",
479 "Connection to webserver can be established, but received data is not recent or invalid");
480
481 T::AddStateName(State::kReceiving, "Valid",
482 "Connection to webserver can be established, receint data received");
483
484 // Verbosity commands
485 T::AddEvent("SET_VERBOSE", "B")
486 (bind(&StateMachineGude::SetVerbosity, this, placeholders::_1))
487 ("set verbosity state"
488 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
489
490 T::AddEvent("PRINT", "")
491 (bind(&StateMachineGude::Print, this))
492 ("Print last readings");
493
494 T::AddEvent("PRINT_ALL", "")
495 (bind(&StateMachineGude::PrintAll, this))
496 ("Print all readings");
497/*
498 // Conenction commands
499 AddEvent("DISCONNECT")
500 (bind(&StateMachineGude::Disconnect, this))
501 ("disconnect from ethernet");
502
503 AddEvent("RECONNECT", "O")
504 (bind(&StateMachineGude::Reconnect, this, placeholders::_1))
505 ("(Re)connect ethernet connection to FTM, a new address can be given"
506 "|[host][string]:new ethernet address in the form <host:port>");
507*/
508 }
509
510 int EvalOptions(Configuration &conf)
511 {
512 fGude.SetVerbose(!conf.Get<bool>("quiet"));
513 fGude.SetInterval(conf.Get<uint16_t>("interval"));
514 fGude.SetDebugTx(conf.Get<bool>("debug-tx"));
515 fGude.SetSite(conf.Get<string>("url"));
516 fGude.SetEndpoint(conf.Get<string>("addr"));
517 fGude.StartConnect();
518
519 return -1;
520 }
521};
522
523
524
525// ------------------------------------------------------------------------
526
527#include "Main.h"
528
529
530template<class T, class S, class R>
531int RunShell(Configuration &conf)
532{
533 return Main::execute<T, StateMachineGude<S, R>>(conf);
534}
535
536void SetupConfiguration(Configuration &conf)
537{
538 po::options_description control("Bias Crate temperature readout");
539 control.add_options()
540 ("no-dim,d", po_switch(), "Disable dim services")
541 ("addr,a", var<string>("10.0.100.238:80"), "Network address of the hardware")
542 ("url,u", var<string>("/statusjsn.js?components=81921"), "File name and path to load")
543 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
544 ("interval,i", var<uint16_t>(5), "Interval between two updates on the server in seconds")
545 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
546 ;
547
548 conf.AddOptions(control);
549}
550
551/*
552 Extract usage clause(s) [if any] for SYNOPSIS.
553 Translators: "Usage" and "or" here are patterns (regular expressions) which
554 are used to match the usage synopsis in program output. An example from cp
555 (GNU coreutils) which contains both strings:
556 Usage: cp [OPTION]... [-T] SOURCE DEST
557 or: cp [OPTION]... SOURCE... DIRECTORY
558 or: cp [OPTION]... -t DIRECTORY SOURCE...
559 */
560void PrintUsage()
561{
562 cout <<
563 "The gudectrl is an interface to the Gude sensors.\n"
564 "\n"
565 "The default is that the program is started without user intercation. "
566 "All actions are supposed to arrive as DimCommands. Using the -c "
567 "option, a local shell can be initialized. With h or help a short "
568 "help message about the usuage can be brought to the screen.\n"
569 "\n"
570 "Usage: gudectrl [-c type] [OPTIONS]\n"
571 " or: gudectrl [OPTIONS]\n";
572 cout << endl;
573}
574
575void PrintHelp()
576{
577// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
578
579 /* Additional help text which is printed after the configuration
580 options goes here */
581
582 /*
583 cout << "bla bla bla" << endl << endl;
584 cout << endl;
585 cout << "Environment:" << endl;
586 cout << "environment" << endl;
587 cout << endl;
588 cout << "Examples:" << endl;
589 cout << "test exam" << endl;
590 cout << endl;
591 cout << "Files:" << endl;
592 cout << "files" << endl;
593 cout << endl;
594 */
595}
596
597int main(int argc, const char* argv[])
598{
599 Configuration conf(argv[0]);
600 conf.SetPrintUsage(PrintUsage);
601 Main::SetupConfiguration(conf);
602 SetupConfiguration(conf);
603
604 if (!conf.DoParse(argc, argv, PrintHelp))
605 return 127;
606
607 //try
608 {
609 // No console access at all
610 if (!conf.Has("console"))
611 {
612 if (conf.Get<bool>("no-dim"))
613 return RunShell<LocalStream, StateMachine, ConnectionGude>(conf);
614 else
615 return RunShell<LocalStream, StateMachineDim, ConnectionDimGude>(conf);
616 }
617 // Cosole access w/ and w/o Dim
618 if (conf.Get<bool>("no-dim"))
619 {
620 if (conf.Get<int>("console")==0)
621 return RunShell<LocalShell, StateMachine, ConnectionGude>(conf);
622 else
623 return RunShell<LocalConsole, StateMachine, ConnectionGude>(conf);
624 }
625 else
626 {
627 if (conf.Get<int>("console")==0)
628 return RunShell<LocalShell, StateMachineDim, ConnectionDimGude>(conf);
629 else
630 return RunShell<LocalConsole, StateMachineDim, ConnectionDimGude>(conf);
631 }
632 }
633 /*catch (std::exception& e)
634 {
635 cerr << "Exception: " << e.what() << endl;
636 return -1;
637 }*/
638
639 return 0;
640}
Note: See TracBrowser for help on using the repository browser.