#include #include #if BOOST_VERSION < 104400 #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 4)) #undef BOOST_HAS_RVALUE_REFS #endif #endif #include #include #include #include "Dim.h" #include "Event.h" #include "Shell.h" #include "StateMachineDim.h" #include "Connection.h" #include "Configuration.h" #include "Console.h" #include "Converter.h" #include "tools.h" #include "LocalControl.h" namespace ba = boost::asio; namespace bs = boost::system; namespace dummy = ba::placeholders; using namespace std; // ------------------------------------------------------------------------ class ConnectionFSC : public Connection { boost::asio::streambuf fBuffer; bool fIsVerbose; bool fDump; ofstream fDumpStream; protected: virtual void UpdateTemp(float, const vector &) { } virtual void UpdateHum(float, const vector&) { } virtual void UpdateVolt(float, const vector&) { } virtual void UpdateCur(float, const vector&) { } /* virtual void UpdateError() { if (!fIsVerbose) return; Out() << endl << kRed << "Error received:" << endl; Out() << fError; if (fIsHexOutput) Out() << Converter::GetHex(fError, 16) << endl; } */ void Dump(const string &str) { if (!fDumpStream.is_open()) { fDumpStream.open("socket_dump-fsc.txt", ios::app); if (!fDumpStream) { //ostringstream str; //str << "Open file " << name << ": " << strerror(errno) << " (errno=" << errno << ")"; //Error(str); return; } } fDumpStream << str << endl; } private: void HandleReceivedData(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; /* "status: 00000538 \n" "time_s: 764.755 \n" "VOLTAGES \n" " \n" "enable:11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00001111 \n" " done:11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00001111 \n" "values:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 \n" "RESISTANCES \n" " \n" "enable:11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 \n" " done:11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 \n" "values: \n" "1000.16 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" "3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" "1197.07 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" " 558.59 677.92 817.26 989.39 1200.35 1503.06 1799.90 2204.18 \n" "3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" "3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" "3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" "3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 3199.99 \n" "end.\n"; */ /* const unsigned int TIME_OFF = 3; const unsigned int VOLT_OFF = 30; const unsigned int CURR_OFF = 70; const unsigned int HUMI_OFF = 110; const unsigned int TEMP_OFF = 134; */ if (fDump) { ostringstream msg; msg << "--- " << Time().GetAsStr() << " --- received " << bytes_received << " bytes."; Dump(msg.str()); } istream is(&fBuffer); int state = 0; bool values = false; vector volt; vector resist; int status=-1; float time=0; string buffer; while (getline(is, buffer, '\n')) { if (fIsVerbose) Out() << buffer << endl; if (fDump) Dump(buffer); buffer = Tools::Trim(buffer); if (buffer.empty()) continue; if (buffer.substr(0, 4)=="end.") break; if (buffer.substr(0, 8)=="status: ") { status = atoi(buffer.c_str()+8); continue; } if (buffer.substr(0, 8)=="time_s: ") { time = atof(buffer.c_str()+8); continue; } if (buffer.substr(0, 8)=="VOLTAGES") { state = 1; continue; } if (buffer.substr(0, 11)=="RESISTANCES") { state = 2; continue; } if (state==1 && buffer.substr(0, 7)=="values:") { istringstream in(buffer.substr(7)); while (1) { int v; in >> v; if (!in) break; volt.push_back(v); } continue; } if (state==2 && buffer.substr(0, 7)=="values:") { values = true; continue; } if (state==2 && !values) continue; istringstream in(buffer); while (1) { float f; in >> f; if (!in) break; resist.push_back(f); } } StartRead(); } void StartRead() { ba::async_read_until(*this, fBuffer, "end.\n", boost::bind(&ConnectionFSC::HandleReceivedData, this, dummy::error, dummy::bytes_transferred, 0)); // FIXME: Add timeout here } // This is called when a connection was established void ConnectionEstablished() { PostMessage("m", 1); fBuffer.prepare(10000); StartRead(); } /* void HandleReadTimeout(const bs::error_code &error) { if (error==ba::error::basic_errors::operation_aborted) return; if (error) { ostringstream str; str << "Read timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl; Error(str); PostClose(); 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. 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 (fInTimeout.expires_at() > ba::deadline_timer::traits_type::now()) return; Error("Timeout reading data from "+URL()); PostClose(); } */ public: ConnectionFSC(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), fIsVerbose(true), fDump(false) { SetLogStream(&imp); } void SetVerbose(bool b) { fIsVerbose = b; } void SetDumpStream(bool b) { fDump = b; } }; // ------------------------------------------------------------------------ #include "DimDescriptionService.h" class ConnectionDimFSC : public ConnectionFSC { private: DimDescribedService fDimTemp; DimDescribedService fDimHum; DimDescribedService fDimVolt; DimDescribedService fDimCurrent; void Update(DimDescribedService &svc, vector data, float time) const { data.insert(data.begin(), time); svc.setData(data.data(), data.size()*sizeof(float)); svc.updateService(); } void UpdateTemp(float time, const vector &temp) { Update(fDimTemp, temp, time); } void UpdateHum(float time, const vector &hum) { Update(fDimHum, hum, time); } void UpdateVolt(float time, const vector &volt) { Update(fDimVolt, volt, time); } void UpdateCur(float time, const vector &curr) { Update(fDimCurrent, curr, time); } public: ConnectionDimFSC(ba::io_service& ioservice, MessageImp &imp) : ConnectionFSC(ioservice, imp), fDimTemp ("FSC_CONTROL/TEMPERATURE", "F:1;F:64", ""), fDimHum ("FSC_CONTROL/HUMIDITY", "F:1;F:40", ""), fDimVolt ("FSC_CONTROL/VOLTAGE", "F:1;F:40", ""), fDimCurrent("FSC_CONTROL/CURRENT", "F:1;F:4", "") { } // A B [C] [D] E [F] G H [I] J K [L] M N O P Q R [S] T U V W [X] Y Z }; // ------------------------------------------------------------------------ template class StateMachineFSC : public T, public ba::io_service, public ba::io_service::work { int Wrap(boost::function f) { f(); return T::GetCurrentState(); } boost::function Wrapper(boost::function func) { return boost::bind(&StateMachineFSC::Wrap, this, func); } private: S fFSC; enum states_t { kStateDisconnected = 1, kStateConnected = 2, }; int Disconnect() { // Close all connections fFSC.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 fFSC.PostClose(false); // Now wait until all connection have been closed and // all pending handlers have been processed poll(); if (evt.GetBool()) fFSC.SetEndpoint(evt.GetString()); // Now we can reopen the connection fFSC.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(); return fFSC.IsConnected() ? kStateConnected : kStateDisconnected; } 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; fFSC.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int SetDumpStream(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetDumpStream", 1)) return T::kSM_FatalError; fFSC.SetDumpStream(evt.GetBool()); return T::GetCurrentState(); } public: StateMachineFSC(ostream &out=cout) : T(out, "FSC_CONTROL"), ba::io_service::work(static_cast(*this)), fFSC(*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 AddStateName(kStateDisconnected, "Disconnected", "FSC board not connected via ethernet."); AddStateName(kStateConnected, "Connected", "Ethernet connection to FSC established."); // Verbosity commands T::AddEvent("SET_VERBOSE", "B:1") (boost::bind(&StateMachineFSC::SetVerbosity, this, _1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); T::AddEvent("DUMP_STREAM", "B:1") (boost::bind(&StateMachineFSC::SetDumpStream, this, _1)) ("" ""); // Conenction commands AddEvent("DISCONNECT", kStateConnected) (boost::bind(&StateMachineFSC::Disconnect, this)) ("disconnect from ethernet"); AddEvent("RECONNECT", "O", kStateDisconnected, kStateConnected) (boost::bind(&StateMachineFSC::Reconnect, this, _1)) ("(Re)connect ethernet connection to FTM, a new address can be given" "|[host][string]:new ethernet address in the form "); fFSC.StartConnect(); } void SetEndpoint(const string &url) { fFSC.SetEndpoint(url); } bool SetConfiguration(const Configuration &conf) { SetEndpoint(conf.Get("addr")); fFSC.SetVerbose(!conf.Get("quiet")); return true; } }; // ------------------------------------------------------------------------ void RunThread(StateMachineImp *io_service) { // This is necessary so that the StateMachien Thread can signal the // Readline to exit io_service->Run(); Readline::Stop(); } /* template int RunDim(Configuration &conf) { WindowLog wout; ReadlineColor::PrintBootMsg(wout, conf.GetName(), false); if (conf.Has("log")) if (!wout.OpenLogFile(conf.Get("log"))) wout << kRed << "ERROR - Couldn't open log-file " << conf.Get("log") << ": " << strerror(errno) << endl; // Start io_service.Run to use the StateMachineImp::Run() loop // Start io_service.run to only use the commandHandler command detaching StateMachineFSC io_service(wout); if (!io_service.SetConfiguration(conf)) return -1; io_service.Run(); return 0; } */ template int RunShell(Configuration &conf) { static T shell(conf.GetName().c_str(), conf.Get("console")!=1); WindowLog &win = shell.GetStreamIn(); WindowLog &wout = shell.GetStreamOut(); if (conf.Has("log")) if (!wout.OpenLogFile(conf.Get("log"))) win << kRed << "ERROR - Couldn't open log-file " << conf.Get("log") << ": " << strerror(errno) << endl; StateMachineFSC io_service(wout); if (!io_service.SetConfiguration(conf)) return -1; shell.SetReceiver(io_service); boost::thread t(boost::bind(RunThread, &io_service)); // boost::thread t(boost::bind(&StateMachineFSC::Run, &io_service)); if (conf.Has("exec")) { const vector v = conf.Get>("exec"); for (vector::const_iterator it=v.begin(); it!=v.end(); it++) shell.Execute(*it); } shell.Run(); // Run the shell io_service.Stop(); // Signal Loop-thread to stop // io_service.Close(); // Obsolete, done by the destructor // Wait until the StateMachine has finished its thread // before returning and destroying the dim objects which might // still be in use. t.join(); return 0; } void SetupConfiguration(Configuration &conf) { const string n = conf.GetName()+".log"; po::options_description config("Program options"); config.add_options() ("dns", var("localhost"), "Dim nameserver host name (Overwites DIM_DNS_NODE environment variable)") ("log,l", var(n), "Write log-file") ("no-dim,d", po_bool(), "Disable dim services") ("console,c", var(), "Use console (0=shell, 1=simple buffered, X=simple unbuffered)") ("exec,e", vars(), "Execute one or more scrips at startup") ; po::options_description control("FTM control options"); control.add_options() ("addr,a", var("localhost:5000"), "Network address of FTM") ("quiet,q", po_bool(), "Disable printing contents of all received messages (except dynamic data) in clear text.") ; conf.AddEnv("dns", "DIM_DNS_NODE"); conf.AddOptions(config); 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 ftmctrl controls the FSC (FACT Slow Control) board.\n" "\n" "The default is that the program is started without 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: fscctrl [-c type] [OPTIONS]\n" " or: fscctrl [OPTIONS]\n"; cout << endl; } void PrintHelp() { /* Additional help text which is printed after the configuration options goes here */ /* cout << "bla bla bla" << endl << endl; cout << endl; cout << "Environment:" << endl; cout << "environment" << endl; cout << endl; cout << "Examples:" << endl; cout << "test exam" << endl; cout << endl; cout << "Files:" << endl; cout << "files" << endl; cout << endl; */ } int main(int argc, const char* argv[]) { Configuration conf(argv[0]); conf.SetPrintUsage(PrintUsage); SetupConfiguration(conf); po::variables_map vm; try { vm = conf.Parse(argc, argv); } #if BOOST_VERSION > 104000 catch (po::multiple_occurrences &e) { cerr << "Program options invalid due to: " << e.what() << " of '" << e.get_option_name() << "'." << endl; return -1; } #endif catch (exception& e) { cerr << "Program options invalid due to: " << e.what() << endl; return -1; } if (conf.HasVersion() || conf.HasPrint()) return -1; if (conf.HasHelp()) { PrintHelp(); return -1; } Dim::Setup(conf.Get("dns")); //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; }