#include #include "FACT.h" #include "Dim.h" #include "Event.h" #include "StateMachineDim.h" #include "StateMachineAsio.h" #include "Connection.h" #include "LocalControl.h" #include "Configuration.h" #include "Console.h" #include "tools.h" #include "HeadersSQM.h" namespace ba = boost::asio; namespace bs = boost::system; namespace dummy = ba::placeholders; using namespace std; class ConnectionSQM : public Connection { protected: virtual void Update(const SQM::Data &) { } private: bool fIsVerbose; bool fFirstMessage; bool fValid; uint16_t fTimeout; boost::asio::streambuf fBuffer; boost::asio::deadline_timer fTrigger; void HandleRead(const boost::system::error_code& err, size_t bytes_received) { // 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."); // 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(false);//err!=ba::error::basic_errors::operation_aborted); return; } istream is(&fBuffer); string buffer; if (!getline(is, buffer, '\n')) { Fatal("Received message does not contain \\n... closing connection."); PostClose(false); return; } buffer = buffer.substr(0, buffer.size()-1); if (fIsVerbose) { Out() << Time().GetAsStr("%H:%M:%S.%f") << "[" << buffer.size() << "]: " << buffer << "|" << endl; // Out() << Time().GetAsStr("%H:%M:%S.%f") << "[ " << vec.size() << "]: "; // for (auto it=vec.begin(); it!=vec.end(); it++) // Out() << *it << "|"; // Out() << endl; } vector vec; boost::split(vec, buffer, boost::is_any_of(",")); try { if (vec.size()!=6) throw runtime_error("Unknown number of fields in received data"); if (vec[0]!="r") throw runtime_error("Not a proper answer"); SQM::Data data; data.mag = stof(vec[1]); data.freq = stol(vec[2]); data.counts = stol(vec[3]); data.period = stof(vec[4]); data.temp = stof(vec[5]); Update(data); fValid = true; } catch (const exception &e) { if (fFirstMessage) Warn("Parsing first message failed ["+string(e.what())+"]"); else { Error("Parsing received message failed ["+string(e.what())+"]"); Error("Received: "+buffer); PostClose(false); return; } } // Send next request in fTimeout milliseconds calculated from // the last request onwards. fTrigger.expires_at(fTrigger.expires_at()+boost::posix_time::milliseconds(fTimeout)); fTrigger.async_wait(boost::bind(&ConnectionSQM::HandleRequestTrigger, this, dummy::error)); fFirstMessage = false; } void HandleReadTimeout(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 << "ReadTimeout of " << URL() << " failed: " << 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. fValid = false; PostClose(true); return; } // This is called if the deadline has been shifted if (error==ba::error::basic_errors::operation_aborted) 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; ostringstream str; str << "No valid answer received from " << URL() << " within " << ceil(fTimeout*3/2) << "ms"; Error(str); PostClose(false); fInTimeout.expires_from_now(boost::posix_time::milliseconds(1000)); fInTimeout.async_wait(boost::bind(&ConnectionSQM::HandleReadTimeout, this, dummy::error)); } void HandleRequestTrigger(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 << "RequestTrigger failed of " << URL() << " failed: " << 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 (fTrigger.expires_at() > ba::deadline_timer::traits_type::now()) return; StartReadReport(); } void StartReadReport() { PostMessage(string("rx\n"), 3); // Do not schedule two reads if (!fFirstMessage) { async_read_until(*this, fBuffer, '\n', boost::bind(&ConnectionSQM::HandleRead, this, dummy::error, dummy::bytes_transferred)); } fInTimeout.expires_from_now(boost::posix_time::milliseconds(fTimeout*3/2)); fInTimeout.async_wait(boost::bind(&ConnectionSQM::HandleReadTimeout, this, dummy::error)); } private: // This is called when a connection was established void ConnectionEstablished() { fValid = false; fFirstMessage = true; // Empty a possible buffer first before we start reading // otherwise reading and writing might not be consecutive async_read_until(*this, fBuffer, '\n', boost::bind(&ConnectionSQM::HandleRead, this, dummy::error, dummy::bytes_transferred)); // If there was no immediate answer, send a request fTrigger.expires_at(Time()+boost::posix_time::milliseconds(1000)); fTrigger.async_wait(boost::bind(&ConnectionSQM::HandleRequestTrigger, this, dummy::error)); } public: static const uint16_t kMaxAddr; public: ConnectionSQM(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), fIsVerbose(true), fTimeout(0), fTrigger(ioservice) { SetLogStream(&imp); } void SetVerbose(bool b) { fIsVerbose = b; Connection::SetVerbose(b); } void SetTimeout(uint16_t t) { fTimeout = t; } int GetState() const { if (!IsConnected()) return SQM::State::kDisconnected; return fValid ? SQM::State::kValid : SQM::State::kConnected; } }; // ------------------------------------------------------------------------ #include "DimDescriptionService.h" class ConnectionDimWeather : public ConnectionSQM { private: DimDescribedService fDim; public: ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) : ConnectionSQM(ioservice, imp), fDim("SQM_CONTROL/DATA", "F:1;I:1;I:1;F:1;F:1", "Data received from sky quality meter" "|Mag[mag/arcsec^2]:Magnitude (0 means upper brightness limit)" "|Freq[Hz]:Frequency of sensor" "|Counts:Period of sensor (counts occur at 14.7456MHz/32)" "|Period[s]:Period of sensor" "|Temp[deg C]:Sensor temperature in deg C") { } void Update(const SQM::Data &data) { fDim.Update(data); } }; // ------------------------------------------------------------------------ template class StateMachineSQMControl : public StateMachineAsio { private: S fSQM; 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 Disconnect() { // Close all connections fSQM.PostClose(false); return T::GetCurrentState(); } int Reconnect(const EventImp &evt) { // Close all connections to supress the warning in SetEndpoint fSQM.PostClose(false); // Now wait until all connection have been closed and // all pending handlers have been processed ba::io_service::poll(); if (evt.GetBool()) fSQM.SetEndpoint(evt.GetString()); // Now we can reopen the connection fSQM.PostClose(true); return T::GetCurrentState(); } int SetVerbosity(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1)) return T::kSM_FatalError; fSQM.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int Send(const string &cmd) { const string tx = cmd+"\r\n"; fSQM.PostMessage(tx, tx.size()); return T::GetCurrentState(); } int SendCommand(const EventImp &evt) { return Send(evt.GetString()); } int Execute() { return fSQM.GetState(); } public: StateMachineSQMControl(ostream &out=cout) : StateMachineAsio(out, "SQM_CONTROL"), fSQM(*this, *this) { // State names T::AddStateName(SQM::State::kDisconnected, "Disconnected", "No connection to Sky Quality Meter"); T::AddStateName(SQM::State::kConnected, "Connected", "Connection established, but no valid message received"); T::AddStateName(SQM::State::kValid, "Valid", "Valid message received"); // Commands //T::AddEvent("SEND_COMMAND", "C") // (bind(&StateMachineSQMControl::SendCommand, this, placeholders::_1)) // ("Send command to SQM"); // Verbosity commands T::AddEvent("SET_VERBOSE", "B") (bind(&StateMachineSQMControl::SetVerbosity, this, placeholders::_1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); //T::AddEvent("ENABLE") // (bind(&StateMachineSQMControl::Send, this, "veto_60")) // ("Enable trigger signal once a second vetoed at every exact minute"); //T::AddEvent("DISABLE") // (bind(&StateMachineSQMControl::Send, this, "veto_on")) // ("Diable trigger output"); // Conenction commands T::AddEvent("DISCONNECT") (bind(&StateMachineSQMControl::Disconnect, this)) ("disconnect from ethernet"); T::AddEvent("RECONNECT", "O") (bind(&StateMachineSQMControl::Reconnect, this, placeholders::_1)) ("(Re)connect ethernet connection to SQM, a new address can be given" "|[host][string]:new ethernet address in the form "); } int EvalOptions(Configuration &conf) { fSQM.SetVerbose(!conf.Get("quiet")); fSQM.SetTimeout(conf.Get("request-interval")); fSQM.SetDebugTx(conf.Get("debug-tx")); fSQM.SetEndpoint(conf.Get("addr")); fSQM.StartConnect(); return -1; } }; // ------------------------------------------------------------------------ #include "Main.h" template int RunShell(Configuration &conf) { return Main::execute>(conf); } void SetupConfiguration(Configuration &conf) { po::options_description control("SQM control"); control.add_options() ("no-dim,d", po_switch(), "Disable dim services") ("addr,a", var("10.0.100.208:10001"), "Network address of the lid controling Arduino including port") ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.") ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.") ("request-interval", var(5000), "How often to request a report [milliseconds].") ; 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 sqmctrl is an interface to the Sky Quality Meter.\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: sqmctrl [-c type] [OPTIONS]\n" " or: sqmctrl [OPTIONS]\n"; cout << endl; } void PrintHelp() { // Main::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); Main::SetupConfiguration(conf); SetupConfiguration(conf); if (!conf.DoParse(argc, argv, PrintHelp)) return 127; // 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); } return 0; }