#include #include // std::string #include // std::transform #include // std::tolower #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 "json.hpp" using json = nlohmann::json; #include "HeadersLid.h" namespace ba = boost::asio; namespace bs = boost::system; namespace dummy = ba::placeholders; using namespace std; class ConnectionLid : public Connection { private: uint16_t fInterval; bool fIsVerbose; boost::asio::streambuf fBuffer; Time fLastReport; Lid::State::states_t fArduinoState; virtual void UpdateControllerState(){} virtual void UpdateMotorReport(Lid::MotorReport& report){} 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."); PostClose(true); return; } // 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; } std::string fRdfData((std::istreambuf_iterator(&fBuffer)), std::istreambuf_iterator()); json j; try { j = json::parse(fRdfData); if (fIsVerbose) Out() << j << endl; } catch(std::invalid_argument) { if (fIsVerbose) Out() << "invalid JSON found" << endl; if (fIsVerbose) Out() << fRdfData << endl; j = json::parse("{}"); } try { fArduinoState = static_cast( j["state"]["state_id"].get() ); UpdateControllerState(); } catch(std::domain_error){ if (fIsVerbose) Out() << "j is not ControllerState:" << j << endl; } try { Lid::MotorReport motor_report = j; UpdateMotorReport(motor_report); } catch(std::domain_error){ if (fIsVerbose) Out() << "j is not MotorReport:" << j << endl; } //Update(); fLastReport = Time(); StartReadReport(); } void StartReadReport() { ba::async_read_until( *this, fBuffer, '\n', boost::bind( &ConnectionLid::HandleRead, this, dummy::error, dummy::bytes_transferred ) ); } // This is called when a connection was established void ConnectionEstablished() { StartReadReport(); PostMessage("s", 1); } public: ConnectionLid(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), fIsVerbose(true), fLastReport(Time::none), fArduinoState(Lid::State::states_t::kUnknown) { SetLogStream(&imp); } void SetVerbose(bool b) { fIsVerbose = b; Connection::SetVerbose(b); } void SetInterval(uint16_t i) { fInterval = i; } int GetInterval() const { return fInterval; } Lid::State::states_t GetState() { return fArduinoState; } }; // ------------------------------------------------------------------------ #include "DimDescriptionService.h" class ConnectionDimLid : public ConnectionLid { private: DimDescribedService fDimControllerState; DimDescribedService fDimMotorReport; public: ConnectionDimLid(ba::io_service& ioservice, MessageImp &imp) : ConnectionLid(ioservice, imp), fDimControllerState( "LID_CONTROL/CONTROLLER_STATE", "S", "|state[int]: arduino state id" ), fDimMotorReport( "LID_CONTROL/MOTOR", "I:1;I:1;I:1;I:100;I:100", "|id[byte]: motor id" "|duration[int32]: movement time in [ms]" "|stop_reason[byte]: Lid::motor_stop_reason_t" "|current[int32]: power drawn by motor [3.4mA]" "|position[int32]: position measured by hall sensor" ) { } void UpdateControllerState() { struct DimControllerState { int16_t state; } __attribute__((__packed__)); DimControllerState s; s.state = GetState(); fDimControllerState.setQuality(GetState()); fDimControllerState.Update(s); } void UpdateMotorReport(Lid::MotorReport& report) { Lid::DimMotorReport dim_report(report); fDimMotorReport.setQuality(GetState()); fDimMotorReport.Update(dim_report); } }; // ------------------------------------------------------------------------ template class StateMachineLidControl : public StateMachineAsio { private: S fLid; 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; fLid.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int Open() { fLid.PostMessage("o", 1); return T::GetCurrentState(); } int RequestStatus() { fLid.PostMessage("s", 1); return T::GetCurrentState(); } int Close() { fLid.PostMessage("c", 1); return T::GetCurrentState(); } int Execute() { return fLid.GetState(); } public: StateMachineLidControl(ostream &out=cout) : StateMachineAsio(out, "LID_CONTROL"), fLid(*this, *this) { // State names T::AddStateName(Lid::State::kDisconnected, "NoConnection", "No connection to web-server could be established recently"); T::AddStateName(Lid::State::kConnected, "Connected", "Connection established, but status still not known"); T::AddStateName(Lid::State::kUnknown, "Unknown", "Arduino just restarted"); T::AddStateName(Lid::State::kClosed, "Closed", "Both lids are closed"); T::AddStateName(Lid::State::kOpen, "Open", "Both lids are open"); T::AddStateName(Lid::State::kClosing, "Closing", "Lids are Closing"); T::AddStateName(Lid::State::kFailClose, "FailClose", "Closing failed: timeout?|user_interrupt?"); T::AddStateName(Lid::State::kOpening, "Opening", "Lids are Opening"); T::AddStateName(Lid::State::kFailOpen, "FailOpen", "Opening failed: timeout?|user_interrupt?|overcurrent?"); // Verbosity commands T::AddEvent("SET_VERBOSE", "B") (bind(&StateMachineLidControl::SetVerbosity, this, placeholders::_1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); T::AddEvent("OPEN") (bind(&StateMachineLidControl::Open, this)) ("Open the lids"); T::AddEvent("CLOSE") (bind(&StateMachineLidControl::Close, this)) ("Close the lids"); T::AddEvent("STATUS") (bind(&StateMachineLidControl::RequestStatus, this)) ("Request Arduino Status"); } int EvalOptions(Configuration &conf) { fLid.SetVerbose(!conf.Get("quiet")); fLid.SetInterval(conf.Get("interval")); fLid.SetDebugTx(conf.Get("debug-tx")); fLid.SetEndpoint(conf.Get("addr")); fLid.StartConnect(); return -1; } }; // ------------------------------------------------------------------------ #include "Main.h" template int RunShell(Configuration &conf) { return Main::execute>(conf); } void SetupConfiguration(Configuration &conf) { po::options_description control("Lid control"); control.add_options() ("no-dim,d", po_switch(), "Disable dim services") ("addr,a", var(""), "Network address of the lid controling Arduino including port") ("url,u", var(""), "File name and path to load") ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.") ("interval,i", var(5), "Interval between two updates on the server in seconds") ("time-to-move", var(20), "Expected minimum time the lid taks to open/close") ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.") ; 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 lidctrl is an interface to the LID control hardware.\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: lidctrl [-c type] [OPTIONS]\n" " or: lidctrl [OPTIONS]\n"; cout << endl; } void 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; // 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; }