#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 "HeadersPFmini.h" namespace ba = boost::asio; namespace bs = boost::system; namespace dummy = ba::placeholders; using namespace std; class ConnectionPFmini : public Connection { protected: virtual void Update(const PFmini::Data &) { } private: bool fIsVerbose; uint16_t fInterval; bool fReceived; int fState; vector fBuffer; 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 && err!=ba::error::eof)) { // 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); return; } const uint16_t chk0 = Tools::Fletcher16(fBuffer.data(), 2); const uint16_t chk1 = uint16_t(fBuffer[2]); if (chk0!=chk1) { ostringstream out; out << "Checksum error ("; out << hex << setfill('0'); out << setw(4) << fBuffer[0] << ":"; out << setw(4) << fBuffer[1] << "|"; out << setw(4) << fBuffer[2] << "!="; out << setw(4) << chk1 << ")"; Error(out); PostClose(false); return; } PFmini::Data data; data.hum = 110*fBuffer[0]/1024.; data.temp = 110*fBuffer[1]/1024.-20; Update(data); ostringstream msg; msg << fixed << setprecision(1) << "H=" << data.hum << "% T=" << data.temp << "°C" ; Message(msg); fState = PFmini::State::kReceiving; fReceived = true; } boost::asio::deadline_timer fKeepAlive; 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 << "Timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl; Error(str); PostClose(false); 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 (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now()) return; // Re-open connection PostClose(true); } 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 << "Read timeout of " << URL() << " timed out: " << error.message() << " (" << error << ")";// << endl; Error(str); PostClose(false); return; } if (!fReceived) PostClose(false); } void Request() { fReceived = false; string cmd = "GET / HTTP/1.1\r\n\r\n"; PostMessage(cmd); fBuffer.resize(6); AsyncRead(ba::buffer(fBuffer)); AsyncWait(fInTimeout, 3000, &Connection::HandleReadTimeout); fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval)); fKeepAlive.async_wait(boost::bind(&ConnectionPFmini::HandleRequest, this, dummy::error)); } // This is called when a connection was established void ConnectionEstablished() { // Keep state kReceiving if (fState class StateMachinePFminiControl : public StateMachineAsio { private: S fPFmini; Time fLastCommand; 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 fPFmini.PostClose(false); return T::GetCurrentState(); } int Reconnect(const EventImp &evt) { // Close all connections to supress the warning in SetEndpoint fPFmini.PostClose(false); // Now wait until all connection have been closed and // all pending handlers have been processed ba::io_service::poll(); if (evt.GetBool()) fPFmini.SetEndpoint(evt.GetString()); // Now we can reopen the connection fPFmini.PostClose(true); return T::GetCurrentState(); } int SetVerbosity(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1)) return T::kSM_FatalError; fPFmini.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int Execute() { return fPFmini.GetState(); } public: StateMachinePFminiControl(ostream &out=cout) : StateMachineAsio(out, "PFMINI_CONTROL"), fPFmini(*this, *this) { // State names T::AddStateName(PFmini::State::kDisconnected, "Disconnected", "No connection to web-server could be established recently"); T::AddStateName(PFmini::State::kConnected, "Connected", "Connection established, but status still not known"); T::AddStateName(PFmini::State::kReceiving, "Receiving", "Connection established, receiving reports"); // Commands // Verbosity commands T::AddEvent("SET_VERBOSE", "B") (bind(&StateMachinePFminiControl::SetVerbosity, this, placeholders::_1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); // Conenction commands T::AddEvent("DISCONNECT") (bind(&StateMachinePFminiControl::Disconnect, this)) ("disconnect from ethernet"); T::AddEvent("RECONNECT", "O") (bind(&StateMachinePFminiControl::Reconnect, this, placeholders::_1)) ("(Re)connect ethernet connection to PFmini, a new address can be given" "|[host][string]:new ethernet address in the form "); } int EvalOptions(Configuration &conf) { fPFmini.SetVerbose(!conf.Get("quiet")); fPFmini.SetDebugTx(conf.Get("debug-tx")); fPFmini.SetEndpoint(conf.Get("addr")); fPFmini.SetInterval(conf.Get("interval")); fPFmini.StartConnect(); return -1; } }; // ------------------------------------------------------------------------ #include "Main.h" template int RunShell(Configuration &conf) { return Main::execute>(conf); } void SetupConfiguration(Configuration &conf) { po::options_description control("PFmini control"); control.add_options() ("no-dim,d", po_switch(), "Disable dim services") ("addr,a", var("10.0.130.140:80"), "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.") ("interval", var(15), "Interval in seconds at which a report is requested.") ; 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 pfminictrl is an interface to the PFmini arduino.\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: pfminictrlö [-c type] [OPTIONS]\n" " or: pfminictrl [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; }