#include #include "Dim.h" #include "Event.h" #include "Shell.h" #include "StateMachineDim.h" #include "Connection.h" #include "LocalControl.h" #include "Configuration.h" #include "Console.h" #include "Converter.h" #include "tools.h" #include "HeadersAgilent.h" namespace ba = boost::asio; namespace bs = boost::system; namespace bapla = ba::placeholders; using namespace std; using namespace Agilent; // ------------------------------------------------------------------------ class ConnectionAgilent : public Connection { boost::asio::streambuf fBuffer; bool fIsVerbose; bool fDump; ofstream fDumpStream; int fState; float fMeasuredVoltage; float fMeasuredCurrent; protected: virtual void UpdateDim(const vector &) { } void Dump(const string &str) { if (!fDumpStream.is_open()) { fDumpStream.open("socket_dump-agilent.txt", ios::app); if (!fDumpStream) { //ostringstream str; //str << "Open file " << name << ": " << strerror(errno) << " (errno=" << errno << ")"; //Error(str); return; } } fDumpStream << str << endl; } boost::asio::deadline_timer fCheckStatusTimer; boost::asio::deadline_timer fAntiFloddingTimer; void PostStatusRequest() { if (IsConnected()) { PostMessage(string("*IDN?\n")); PostMessage(string("meas:volt?\n")); PostMessage(string("meas:curr?\n")); } } void RequestStatus() { PostStatusRequest(); fCheckStatusTimer.expires_from_now(boost::posix_time::seconds(60)); fCheckStatusTimer.async_wait(boost::bind(&ConnectionAgilent::HandleRequest, this, bapla::error)); } void HandleRequest(const bs::error_code &error) { // 125: Operation canceled (bs::error_code(125, bs::system_category)) if (error && error!=ba::error::basic_errors::operation_aborted) { ostringstream str; str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl; Error(str); PostClose(false); return; } if (!is_open()) { // For example: Here we could schedule a new accept if we // would not want to allow two connections at the same time. PostClose(true); return; } // Check whether the deadline has passed. We compare the deadline // against the current time since a new asynchronous operation // may have moved the deadline before this actor had a chance // to run. if (fCheckStatusTimer.expires_at() > ba::deadline_timer::traits_type::now()) return; RequestStatus(); } private: int fLineCounter; void Start_async_read_until() { // Bind Received Data Handler to the event: "received character" // this Handler will try to parse the incoming data ba::async_read_until(*this, fBuffer, "\n", boost::bind(&ConnectionAgilent::ReceivedStatusHandler, this, bapla::error, bapla::bytes_transferred, 0)); // FIXME: Add timeout here } void ReceivedStatusHandler(const bs::error_code& err, size_t bytes_received, int /*type*/) { // Do not schedule a new read if the connection failed. if (bytes_received==0 || err) { if (err==ba::error::eof) Warn("Connection closed by remote host (FTM)."); // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category)) // 125: Operation canceled if (err && err!=ba::error::eof && // Connection closed by remote host err!=ba::error::basic_errors::not_connected && // Connection closed by remote host err!=ba::error::basic_errors::operation_aborted) // Connection closed by us { ostringstream str; str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl; Error(str); } PostClose(err!=ba::error::basic_errors::operation_aborted); return; } if (fIsVerbose) { Out() << kBold << "Received (" << bytes_received << " bytes):" << endl; } // FIXME: the piece of code below causes a Seg Fault in case // The Agilent is not listening during program start, then after a while is // listening and after connection established. // It sends: 1.) a string and 2.) and empty line (just an pressed) // // then the agilent_ctrl segfaults ... was able to reproduce it. // gdp just gave me the hint, that Time().GetAdStr() caused the Seg Fault in a way... // Is Time threadsafe? /* if (fDump) { ostringstream msg; msg << "--- " << Time().GetAsStr() << " --- received " << bytes_received << " bytes."; Dump(msg.str()); } */ istream is(&fBuffer); fLineCounter++; if (fLineCounter == 1) { // this is the Agilent identity string, do nothing string s; getline(is,s, '\n'); Out() << "ID string: " << s << endl; } else if (fLineCounter == 2) { // this should be a float containing the measured voltage is >> fMeasuredVoltage; Out() << "voltage: " << fMeasuredVoltage << endl; } else if (fLineCounter >= 3) { // this should be a float containing the measured voltage is >> fMeasuredCurrent; Out() << "current: " << fMeasuredCurrent << endl; fLineCounter = 0; // data should contain two floats: // * measured output voltage in volts // * measured ouput current in amperes vector data(2); data[0] = fMeasuredVoltage; data[1] = fMeasuredCurrent; UpdateDim(data); } // read the buffer empty ... // FIXME: I surely misunderstand that damn fBuffer thing. // I thought it should be emtpy by now... string buffer; while (getline(is,buffer, '\n')) { buffer = Tools::Trim(buffer); if (buffer.empty()) continue; } if (fMeasuredVoltage > 1.0) { fState = State::kVoltage_On; } else { fState = State::kVoltage_Off; } Start_async_read_until(); } // This is called when a connection was established void ConnectionEstablished() { // DN 07.08.2012: The next line is imho not needed. // But I'm in a train and cannot test it. fState = State::kConnected; Start_async_read_until(); RequestStatus(); fLineCounter = 0; fBuffer.prepare(1000); } public: ConnectionAgilent(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), fIsVerbose(true), fDump(true), fCheckStatusTimer(ioservice), fAntiFloddingTimer(ioservice) { SetLogStream(&imp); fState = State::kDisconnected; fMeasuredVoltage=-1; fMeasuredCurrent=-1; } void SetVerbose(bool b) { fIsVerbose = b; } void SetDumpStream(bool b) { fDump = b; } void SetOutput(bool b) { if (b) { // check if the previous 'outp off' is some time ago if (fAntiFloddingTimer.expires_at() < ba::deadline_timer::traits_type::now()) { if (IsConnected()) PostMessage(string("outp on\n")); } } else { if (IsConnected()) PostMessage(string("outp off\n")); // start a Timer, which runs out in 60sec making sure, that the // camera can't be switched off&on on short time scales. // sending repetetive outp off is possible // sending repetivitve outp on is also posible // switching off immediately after switching on is also possible. fAntiFloddingTimer.expires_from_now(boost::posix_time::seconds(60)); } RequestStatus(); } int GetState() const { // fState is set in ReceivedStatusHandler return fState; } }; // ------------------------------------------------------------------------ #include "DimDescriptionService.h" class ConnectionDimAgilent : public ConnectionAgilent { private: DimDescribedService fDim; void Update(DimDescribedService &svc, vector data) const { svc.Update(data); } void UpdateDim(const vector &data) { Update(fDim, data); } public: ConnectionDimAgilent(ba::io_service& ioservice, MessageImp &imp) : ConnectionAgilent(ioservice, imp), fDim("AGILENT_CONTROL/DATA", "F:1;F:1", "|U[V]: output voltage" "|I[A]: output current") { // nothing happens here. } }; // ------------------------------------------------------------------------ template class StateMachineAgilent : public T, public ba::io_service, public ba::io_service::work { private: S fAgilent; int Disconnect() { // Close all connections fAgilent.PostClose(false); /* // Now wait until all connection have been closed and // all pending handlers have been processed poll(); */ return T::GetCurrentState(); } int Reconnect(const EventImp &evt) { // Close all connections to supress the warning in SetEndpoint fAgilent.PostClose(false); // Now wait until all connection have been closed and // all pending handlers have been processed poll(); if (evt.GetBool()) fAgilent.SetEndpoint(evt.GetString()); // Now we can reopen the connection fAgilent.PostClose(true); return T::GetCurrentState(); } int Execute() { // Dispatch (execute) at most one handler from the queue. In contrary // to run_one(), it doesn't wait until a handler is available // which can be dispatched, so poll_one() might return with 0 // handlers dispatched. The handlers are always dispatched/executed // synchronously, i.e. within the call to poll_one() poll_one(); if ( fAgilent.IsConnected() ) return fAgilent.GetState(); else return State::kDisconnected; } bool CheckEventSize(size_t has, const char *name, size_t size) { if (has==size) return true; ostringstream msg; msg << name << " - Received event has " << has << " bytes, but expected " << size << "."; T::Fatal(msg); return false; } int SetVerbosity(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1)) return T::kSM_FatalError; fAgilent.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int SetDumpStream(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetDumpStream", 1)) return T::kSM_FatalError; fAgilent.SetDumpStream(evt.GetBool()); return T::GetCurrentState(); } int SetOutput(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetOutput", 1)) return T::kSM_FatalError; fAgilent.SetOutput(evt.GetBool()); return T::GetCurrentState(); } public: StateMachineAgilent(ostream &out=cout) : T(out, "AGILENT_CONTROL"), ba::io_service::work(static_cast(*this)), fAgilent(*this, *this) { // ba::io_service::work is a kind of keep_alive for the loop. // It prevents the io_service to go to stopped state, which // would prevent any consecutive calls to run() // or poll() to do nothing. reset() could also revoke to the // previous state but this might introduce some overhead of // deletion and creation of threads and more. // State names T::AddStateName(State::kDisconnected, "Disconnected", "Agilent not connected via ethernet."); T::AddStateName(State::kConnected, "Connected", "Ethernet connection to Agilent established."); T::AddStateName(State::kVoltage_On, "Voltage_On", "The measured output voltage is higher than 1.0V"); T::AddStateName(State::kVoltage_Off, "Voltage_Off", "The measured output voltage is lower than 1.0V"); // Verbosity commands T::AddEvent("SET_VERBOSE", "B:1") (bind(&StateMachineAgilent::SetVerbosity, this, placeholders::_1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no)"); T::AddEvent("DUMP_STREAM", "B:1") (bind(&StateMachineAgilent::SetDumpStream, this, placeholders::_1)) ("" ""); // Conenction commands T::AddEvent("DISCONNECT", State::kConnected) (bind(&StateMachineAgilent::Disconnect, this)) ("disconnect from ethernet"); T::AddEvent("RECONNECT", "O", State::kDisconnected, State::kConnected) (bind(&StateMachineAgilent::Reconnect, this, placeholders::_1)) ("(Re)connect ethernet connection to Agilent, a new address can be given" "|[host][string]:new ethernet address in the form "); T::AddEvent("OUTPUT", "B:1") (bind(&StateMachineAgilent::SetOutput, this, placeholders::_1)) ("set output on or off" "|[state][boolean]: output setting (1;0 or 'on';'off')"); fAgilent.StartConnect(); } void SetEndpoint(const string &url) { fAgilent.SetEndpoint(url); } int EvalOptions(Configuration &conf) { fAgilent.SetVerbose(!conf.Get("quiet")); SetEndpoint(conf.Get("addr")); return -1; } }; // ------------------------------------------------------------------------ #include "Main.h" template int RunShell(Configuration &conf) { return Main::execute>(conf); } void SetupConfiguration(Configuration &conf) { po::options_description control("agilent_ctrl control options"); control.add_options() ("no-dim", po_bool(), "Disable dim services") // ("addr,a", var("localhost:8080"), "network address of Agilent") ("addr,a", var("10.0.100.220:5025"), "network address of Agilent") ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.") ; conf.AddOptions(control); } /* Extract usage clause(s) [if any] for SYNOPSIS. Translators: "Usage" and "or" here are patterns (regular expressions) which are used to match the usage synopsis in program output. An example from cp (GNU coreutils) which contains both strings: Usage: cp [OPTION]... [-T] SOURCE DEST or: cp [OPTION]... SOURCE... DIRECTORY or: cp [OPTION]... -t DIRECTORY SOURCE... */ void PrintUsage() { cout << "The agilentctrl controls the FACT camera power supply.\n" "The powersupply is made by Agilent, so we call it 'The Agilent'. \n" "Since FACT uses three Agilent Power Supplies with different output voltages\n" "one might still not now which one ist controlled by this Program, so:\n" "\n" "This program controls the 48V Agilent." "\n" "The default is that the program is started with user intercation. " "All actions are supposed to arrive as DimCommands. Using the -c " "option, a local shell can be initialized. With h or help a short " "help message about the usuage can be brought to the screen.\n" "\n" "Usage: agilentctrl [-c type] [OPTIONS]\n" " or: agilentctrl [OPTIONS]\n"; cout << endl; } void PrintHelp() { Main::PrintHelp>(); } int main(int argc, const char* argv[]) { Configuration conf(argv[0]); conf.SetPrintUsage(PrintUsage); Main::SetupConfiguration(conf); SetupConfiguration(conf); if (!conf.DoParse(argc, argv, PrintHelp)) return 127; //try { // No console access at all if (!conf.Has("console")) { if (conf.Get("no-dim")) return RunShell(conf); else return RunShell(conf); } // Cosole access w/ and w/o Dim if (conf.Get("no-dim")) { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } else { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } } /*catch (std::exception& e) { cerr << "Exception: " << e.what() << endl; return -1; }*/ return 0; }