source: trunk/FACT++/src/agilentctrl.cc@ 17689

Last change on this file since 17689 was 17669, checked in by tbretz, 11 years ago
Implemented the possibility to start more than one agilentctrl to read from all agilents.
File size: 17.7 KB
Line 
1#include <functional>
2
3#include "Dim.h"
4#include "Event.h"
5#include "StateMachineDim.h"
6#include "StateMachineAsio.h"
7#include "Connection.h"
8#include "LocalControl.h"
9#include "Configuration.h"
10
11#include "tools.h"
12
13#include "HeadersAgilent.h"
14
15namespace ba = boost::asio;
16namespace bs = boost::system;
17namespace dummy = ba::placeholders;
18
19using namespace std;
20using namespace Agilent;
21
22// ------------------------------------------------------------------------
23
24class ConnectionAgilent : public Connection
25{
26public:
27 static string fMode;
28
29private:
30 bool fIsVerbose;
31 bool fDebugRx;
32
33 uint16_t fInterval;
34
35 boost::asio::deadline_timer fTimeout;
36 boost::asio::deadline_timer fTimeoutPowerCycle;
37 boost::asio::streambuf fBuffer;
38
39 Data fData;
40
41 Time fLastReceived;
42 Time fLastCommand;
43
44protected:
45
46 virtual void UpdateDim(const Data &)
47 {
48 }
49
50 void RequestStatus()
51 {
52 if (IsConnected())
53 PostMessage(string("*IDN?\nvolt?\nmeas:volt?\nmeas:curr?\ncurr?\n"));
54
55 fTimeout.expires_from_now(boost::posix_time::seconds(fInterval));
56 fTimeout.async_wait(boost::bind(&ConnectionAgilent::HandleStatusTimer,
57 this, dummy::error));
58 }
59
60
61 void HandleStatusTimer(const bs::error_code &error)
62 {
63 // 125: Operation canceled (bs::error_code(125, bs::system_category))
64 if (error && error!=ba::error::basic_errors::operation_aborted)
65 {
66 ostringstream str;
67 str << "Status request timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
68 Error(str);
69
70 PostClose(false);
71 return;
72 }
73
74 if (!is_open())
75 {
76 // For example: Here we could schedule a new accept if we
77 // would not want to allow two connections at the same time.
78 PostClose(true);
79 return;
80 }
81
82 // Check whether the deadline has passed. We compare the deadline
83 // against the current time since a new asynchronous operation
84 // may have moved the deadline before this actor had a chance
85 // to run.
86 if (fTimeout.expires_at() > ba::deadline_timer::traits_type::now())
87 return;
88
89 RequestStatus();
90 }
91
92 void HandlePowerCycle(const bs::error_code &error)
93 {
94 // 125: Operation canceled (bs::error_code(125, bs::system_category))
95 if (error && error!=ba::error::basic_errors::operation_aborted)
96 {
97 ostringstream str;
98 str << "Power cycle timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
99 Error(str);
100
101 PostClose(false);
102 return;
103 }
104
105 if (!is_open())
106 {
107 // For example: Here we could schedule a new accept if we
108 // would not want to allow two connections at the same time.
109 PostClose(true);
110 return;
111 }
112
113 // Check whether the deadline has passed. We compare the deadline
114 // against the current time since a new asynchronous operation
115 // may have moved the deadline before this actor had a chance
116 // to run.
117 if (fTimeout.expires_at() > ba::deadline_timer::traits_type::now())
118 return;
119
120 SetPower(true);
121 }
122
123private:
124 void StartRead(int line=0)
125 {
126 ba::async_read_until(*this, fBuffer, "\n",
127 boost::bind(&ConnectionAgilent::HandleReceivedData, this,
128 dummy::error, dummy::bytes_transferred, line+1));
129 }
130
131 void HandleReceivedData(const bs::error_code& err, size_t bytes_received, int line)
132 {
133
134 // Do not schedule a new read if the connection failed.
135 if (bytes_received==0 || err)
136 {
137 if (err==ba::error::eof)
138 Warn("Connection closed by remote host (FTM).");
139
140 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
141 // 125: Operation canceled
142 if (err && err!=ba::error::eof && // Connection closed by remote host
143 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
144 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
145 {
146 ostringstream str;
147 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
148 Error(str);
149 }
150 PostClose(err!=ba::error::basic_errors::operation_aborted);
151 return;
152 }
153
154
155 if (fDebugRx)
156 {
157 Out() << kBold << "Received (" << bytes_received << ", " << fBuffer.size() << " bytes):" << endl;
158 Out() << "-----\n" << string(ba::buffer_cast<const char*>(fBuffer.data()), bytes_received) << "-----\n";
159 }
160
161 istream is(&fBuffer);
162
163 string str;
164 getline(is, str, '\n');
165
166 try
167 {
168 switch (line)
169 {
170 case 1: Out() << "ID: " << str << endl; break;
171 case 2: fData.fVoltageSet = stof(str); break;
172 case 3: fData.fVoltageMeasured = stof(str); break;
173 case 4: fData.fCurrentMeasured = stof(str); break;
174 case 5: fData.fCurrentLimit = stof(str); break;
175 default:
176 return;
177 }
178 }
179 catch (const exception &e)
180 {
181 Error("String conversion failed for '"+str+" ("+e.what()+")");
182 return;
183 }
184
185 if (line==5)
186 {
187 if (fIsVerbose)
188 {
189 Out() << "Voltage: " << fData.fVoltageMeasured << "V/" << fData.fVoltageSet << "V\n";
190 Out() << "Current: " << fData.fCurrentMeasured << "A/" << fData.fCurrentLimit << "A\n" << endl;
191 }
192
193 UpdateDim(fData);
194
195 fLastReceived = Time();
196
197 line = 0;
198
199 }
200
201 StartRead(line);
202 }
203
204
205 // This is called when a connection was established
206 void ConnectionEstablished()
207 {
208 fBuffer.prepare(1000);
209
210 StartRead();
211 RequestStatus();
212 }
213
214public:
215
216 ConnectionAgilent(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
217 fIsVerbose(true), fDebugRx(false), fTimeout(ioservice), fTimeoutPowerCycle(ioservice)
218 {
219 SetLogStream(&imp);
220 }
221
222 void SetVerbose(bool b)
223 {
224 fIsVerbose = b;
225 }
226
227 void SetDebugRx(bool b)
228 {
229 fDebugRx = b;
230 }
231
232 void SetInterval(uint16_t i)
233 {
234 fInterval = i;
235 }
236
237 bool SetPower(bool on)
238 {
239 if (!IsConnected())
240 return false;
241
242 if (fLastCommand+boost::posix_time::seconds(59)>Time())
243 {
244 Error("Last power command within the last 59 seconds... ignored.");
245 return false;
246 }
247
248 PostMessage("outp "+string(on?"on":"off")+"\n*IDN?\nvolt?\nmeas:volt?\nmeas:curr?\ncurr?\n");
249 fLastCommand = Time();
250
251 // Stop any pending power cycling
252 fTimeoutPowerCycle.cancel();
253
254 return true;
255 }
256
257 void PowerCycle(uint16_t seconds)
258 {
259 if (!SetPower(false))
260 return;
261
262 fTimeoutPowerCycle.expires_from_now(boost::posix_time::seconds(seconds));
263 fTimeoutPowerCycle.async_wait(boost::bind(&ConnectionAgilent::HandlePowerCycle,
264 this, dummy::error));
265 }
266
267 int GetState()
268 {
269 if (!IsConnected())
270 return State::kDisconnected;
271
272 if (fLastReceived+boost::posix_time::seconds(fInterval*2)<Time())
273 return State::kDisconnected;
274
275 if (fData.fCurrentMeasured<0)
276 return State::kConnected;
277
278 if (fData.fVoltageMeasured<0.1)
279 return State::kVoltageOff;
280
281 if (fData.fVoltageMeasured<fData.fVoltageSet-0.1)
282 return State::kVoltageLow;
283
284 if (fData.fVoltageMeasured>fData.fVoltageSet+0.1)
285 return State::kVoltageHigh;
286
287 return State::kVoltageOn;
288 }
289};
290
291string ConnectionAgilent::fMode;
292
293// ------------------------------------------------------------------------
294
295#include "DimDescriptionService.h"
296
297class ConnectionDimAgilent : public ConnectionAgilent
298{
299private:
300
301 DimDescribedService fDim;
302
303 void UpdateDim(const Data &data)
304 {
305 fDim.Update(data);
306 }
307
308public:
309 ConnectionDimAgilent(ba::io_service& ioservice, MessageImp &imp) :
310 ConnectionAgilent(ioservice, imp),
311 fDim("AGILENT_CONTROL_"+fMode+"/DATA", "F:1;F:1;F:1;F:1",
312 "|U_nom[V]: Nominal output voltage"
313 "|U_mes[V]: Measured output voltage"
314 "|I_max[A]: Current limit"
315 "|I_mes[A]: Measured current")
316 {
317 // nothing happens here.
318 }
319};
320
321// ------------------------------------------------------------------------
322
323template <class T, class S>
324class StateMachineAgilent : public StateMachineAsio<T>
325{
326private:
327 S fAgilent;
328
329 int Disconnect()
330 {
331 // Close all connections
332 fAgilent.PostClose(false);
333
334 /*
335 // Now wait until all connection have been closed and
336 // all pending handlers have been processed
337 poll();
338 */
339
340 return T::GetCurrentState();
341 }
342
343 int Reconnect(const EventImp &evt)
344 {
345 // Close all connections to supress the warning in SetEndpoint
346 fAgilent.PostClose(false);
347
348 // Now wait until all connection have been closed and
349 // all pending handlers have been processed
350 ba::io_service::poll();
351
352 if (evt.GetBool())
353 fAgilent.SetEndpoint(evt.GetString());
354
355 // Now we can reopen the connection
356 fAgilent.PostClose(true);
357
358 return T::GetCurrentState();
359 }
360
361 int Execute()
362 {
363 return fAgilent.GetState();
364 }
365
366 bool CheckEventSize(size_t has, const char *name, size_t size)
367 {
368 if (has==size)
369 return true;
370
371 ostringstream msg;
372 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
373 T::Fatal(msg);
374 return false;
375 }
376
377 int SetVerbosity(const EventImp &evt)
378 {
379 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
380 return T::kSM_FatalError;
381
382 fAgilent.SetVerbose(evt.GetBool());
383
384 return T::GetCurrentState();
385 }
386
387 int SetDebugRx(const EventImp &evt)
388 {
389 if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1))
390 return T::kSM_FatalError;
391
392 fAgilent.SetDebugRx(evt.GetBool());
393
394 return T::GetCurrentState();
395 }
396
397 int SetPower(const EventImp &evt)
398 {
399 if (!CheckEventSize(evt.GetSize(), "SetPower", 1))
400 return T::kSM_FatalError;
401
402 fAgilent.SetPower(evt.GetBool());
403
404 return T::GetCurrentState();
405 }
406
407 int PowerCycle(const EventImp &evt)
408 {
409 if (!CheckEventSize(evt.GetSize(), "PowerCyle", 2))
410 return T::kSM_FatalError;
411
412 if (evt.GetShort()<60)
413 {
414 T::Warn("Power cycle delays of less than 60s not allowed.");
415 return T::GetCurrentState();
416 }
417
418 fAgilent.PowerCycle(evt.GetShort());
419
420 return T::GetCurrentState();
421 }
422
423
424public:
425 StateMachineAgilent(ostream &out=cout) :
426 StateMachineAsio<T>(out, "AGILENT_CONTROL_"+S::fMode), fAgilent(*this, *this)
427 {
428 // State names
429 T::AddStateName(State::kDisconnected, "Disconnected",
430 "Agilent not connected via ethernet.");
431 T::AddStateName(State::kConnected, "Connected",
432 "Ethernet connection to Agilent established, but not data received yet.");
433
434 T::AddStateName(State::kVoltageOff, "VoltageOff",
435 "The measured output voltage is lower than 0.1V");
436 T::AddStateName(State::kVoltageLow, "VoltageLow",
437 "The measured output voltage is higher than 0.1V, but lower than the command voltage");
438 T::AddStateName(State::kVoltageOn, "VoltageOn",
439 "The measured output voltage is higher than 0.1V and comparable to the command voltage");
440 T::AddStateName(State::kVoltageHigh, "VoltageHigh",
441 "The measured output voltage is higher than the command voltage!");
442
443 // Verbosity commands
444 T::AddEvent("SET_VERBOSE", "B:1")
445 (bind(&StateMachineAgilent::SetVerbosity, this, placeholders::_1))
446 ("set verbosity state"
447 "|verbosity[bool]:disable or enable verbosity for received data (yes/no)");
448
449 T::AddEvent("SET_DEBUG_RX", "B:1")
450 (bind(&StateMachineAgilent::SetVerbosity, this, placeholders::_1))
451 ("set debug state"
452 "|debug[bool]:disable or enable verbosity for received raw data (yes/no)");
453
454 T::AddEvent("SET_POWER", "B:1")
455 (bind(&StateMachineAgilent::SetPower, this, placeholders::_1))
456 ("Enable or disable power output"
457 "|output[bool]:set power output to 'on' or 'off'");
458
459 T::AddEvent("POWER_CYCLE", "S:1")
460 (bind(&StateMachineAgilent::PowerCycle, this, placeholders::_1))
461 ("Power cycle the power output"
462 "|delay[short]:Defines the delay between switching off and on.");
463
464
465 // Conenction commands
466 T::AddEvent("DISCONNECT", State::kConnected)
467 (bind(&StateMachineAgilent::Disconnect, this))
468 ("disconnect from ethernet");
469
470 T::AddEvent("RECONNECT", "O", State::kDisconnected, State::kConnected)
471 (bind(&StateMachineAgilent::Reconnect, this, placeholders::_1))
472 ("(Re)connect ethernet connection to Agilent, a new address can be given"
473 "|[host][string]:new ethernet address in the form <host:port>");
474
475 fAgilent.StartConnect();
476 }
477
478 void SetEndpoint(const string &url)
479 {
480 fAgilent.SetEndpoint(url);
481 }
482
483 int EvalOptions(Configuration &conf)
484 {
485 fAgilent.SetVerbose(!conf.Get<bool>("quiet"));
486 fAgilent.SetDebugRx(conf.Get<bool>("debug-rx"));
487 fAgilent.SetInterval(conf.Get<uint16_t>("interval"));
488
489 SetEndpoint(conf.Get<string>("addr.", S::fMode));
490
491 const std::vector<std::string> opts = conf.GetWildcardOptions("addr.*");
492 for (auto it=opts.begin(); it!=opts.end(); it++)
493 conf.Get<string>(*it);
494
495 return -1;
496 }
497};
498
499// ------------------------------------------------------------------------
500
501#include "Main.h"
502
503template<class T, class S, class R>
504int RunShell(Configuration &conf)
505{
506 return Main::execute<T, StateMachineAgilent<S, R>>(conf);
507}
508
509void SetupConfiguration(Configuration &conf)
510{
511 po::options_description control("agilent_ctrl control options");
512 control.add_options()
513 ("no-dim", po_bool(), "Disable dim services")
514 ("mode,m", var<string>()->required(), "Mode (e.g. 24V, 50V, 80V)")
515 ("addr.*", var<string>(), "Network address of Agilent specified by mode")
516 ("debug-rx", po_bool(false), "Enable raw debug output wehen receiving data")
517 ("interval", var<uint16_t>(15), "Interval in seconds in which the Agilent status is requested")
518 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
519 ;
520
521 po::positional_options_description p;
522 p.add("mode", 1); // The first positional options
523
524 conf.AddOptions(control);
525 conf.SetArgumentPositions(p);
526}
527
528/*
529 Extract usage clause(s) [if any] for SYNOPSIS.
530 Translators: "Usage" and "or" here are patterns (regular expressions) which
531 are used to match the usage synopsis in program output. An example from cp
532 (GNU coreutils) which contains both strings:
533 Usage: cp [OPTION]... [-T] SOURCE DEST
534 or: cp [OPTION]... SOURCE... DIRECTORY
535 or: cp [OPTION]... -t DIRECTORY SOURCE...
536 */
537void PrintUsage()
538{
539 cout <<
540 "The agilentctrl controls the FACT Agilent power supplies.\n\n"
541 "\n"
542 "The default is that the program is started without user intercation. "
543 "All actions are supposed to arrive as DimCommands. Using the -c "
544 "option, a local shell can be initialized. With h or help a short "
545 "help message about the usuage can be brought to the screen.\n"
546 "\n"
547 "Usage: agilentctrl [-c type] [OPTIONS] mode\n"
548 " or: agilentctrl [OPTIONS] mode\n";
549 cout << endl;
550}
551
552void PrintHelp()
553{
554 Main::PrintHelp<StateMachineAgilent<StateMachine, ConnectionAgilent>>();
555}
556
557int main(int argc, const char* argv[])
558{
559 Configuration conf(argv[0]);
560 conf.SetPrintUsage(PrintUsage);
561 Main::SetupConfiguration(conf);
562 SetupConfiguration(conf);
563
564 if (!conf.DoParse(argc, argv, PrintHelp))
565 return 127;
566
567 ConnectionAgilent::fMode = conf.Get<string>("mode");
568
569 //try
570 {
571 // No console access at all
572 if (!conf.Has("console"))
573 {
574 if (conf.Get<bool>("no-dim"))
575 return RunShell<LocalStream, StateMachine, ConnectionAgilent>(conf);
576 else
577 return RunShell<LocalStream, StateMachineDim, ConnectionDimAgilent>(conf);
578 }
579 // Cosole access w/ and w/o Dim
580 if (conf.Get<bool>("no-dim"))
581 {
582 if (conf.Get<int>("console")==0)
583 return RunShell<LocalShell, StateMachine, ConnectionAgilent>(conf);
584 else
585 return RunShell<LocalConsole, StateMachine, ConnectionAgilent>(conf);
586 }
587 else
588 {
589 if (conf.Get<int>("console")==0)
590 return RunShell<LocalShell, StateMachineDim, ConnectionDimAgilent>(conf);
591 else
592 return RunShell<LocalConsole, StateMachineDim, ConnectionDimAgilent>(conf);
593 }
594 }
595 /*catch (std::exception& e)
596 {
597 cerr << "Exception: " << e.what() << endl;
598 return -1;
599 }*/
600
601 return 0;
602}
Note: See TracBrowser for help on using the repository browser.