From 2a4ec6dd6a48668589c7db2206c97e8781b644b3 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli (think)" Date: Thu, 16 Aug 2012 16:29:59 +0200 Subject: crc-dabmod: added RemoteControl code and test program --- src/RemoteControl.cpp | 215 +++++++++++++++++++++++++++++++++++++++++ src/RemoteControl.h | 198 +++++++++++++++++++++++++++++++++++++ src/testremotecontrol/Makefile | 5 + src/testremotecontrol/README | 4 + src/testremotecontrol/test.cpp | 114 ++++++++++++++++++++++ 5 files changed, 536 insertions(+) create mode 100644 src/RemoteControl.cpp create mode 100644 src/RemoteControl.h create mode 100644 src/testremotecontrol/Makefile create mode 100644 src/testremotecontrol/README create mode 100644 src/testremotecontrol/test.cpp (limited to 'src') diff --git a/src/RemoteControl.cpp b/src/RemoteControl.cpp new file mode 100644 index 0000000..b534914 --- /dev/null +++ b/src/RemoteControl.cpp @@ -0,0 +1,215 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Written by Matthias P. Braendli, matthias.braendli@mpb.li, 2012 + */ +/* + This file is part of CRC-DADMOD. + + CRC-DADMOD is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + CRC-DADMOD is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CRC-DADMOD. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RemoteControl.h" + +using boost::asio::ip::tcp; + +void +RemoteControllerTelnet::process(long) +{ + welcome_ = "CRC-DABMOD Remote Control CLI\nWrite 'help' for help.\n**********\n"; + prompt_ = "> "; + + std::string in_message; + size_t length; + + try { + boost::asio::io_service io_service; + tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port_)); + + while (running_) { + in_message = ""; + + tcp::socket socket(io_service); + + acceptor.accept(socket); + + boost::system::error_code ignored_error; + + boost::asio::write(socket, boost::asio::buffer(welcome_), ignored_error); + + while (running_ && in_message != "quit") { + boost::asio::write(socket, boost::asio::buffer(prompt_), ignored_error); + + in_message = ""; + + boost::asio::streambuf buffer; + length = boost::asio::read_until( socket, buffer, "\n", ignored_error); + + std::istream str(&buffer); + std::getline(str, in_message); + + if (length == 0) { + std::cerr << "Connection terminated" << std::endl; + break; + } + + while (in_message.length() > 0 && + (in_message[in_message.length()-1] == '\r' || in_message[in_message.length()-1] == '\n')) { + in_message.erase(in_message.length()-1, 1); + } + + if (in_message.length() == 0) { + continue; + } + + std::cerr << "Got message '" << in_message << "'" << std::endl; + + dispatch_command(socket, in_message); + } + std::cerr << "Closing socket" << std::endl; + socket.close(); + } + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + } +} + +void +RemoteControllerTelnet::dispatch_command(tcp::socket& socket, string command) +{ + vector cmd = tokenise_(command); + + if (cmd[0] == "help") { + reply(socket, + "The following commands are supported:\n" + " list\n" + " * Lists the modules that are loaded\n" + " list MODULE\n" + " * Lists the parameters exported by module MODULE\n" + " show MODULE\n" + " * Lists all parameters and their values from module MODULE\n" + " get MODULE PARAMETER\n" + " * Gets the value for the specified PARAMETER from module MODULE\n" + " set MODULE PARAMETER VALUE\n" + " * Sets the value for the PARAMETER ofr module MODULE\n" + " quit\n" + " * Terminate this session\n" + "\n"); + } + else if (cmd[0] == "list") { + stringstream ss; + + if (cmd.size() == 1) { + for (list::iterator it = cohort_.begin(); it != cohort_.end(); it++) { + ss << (*it)->get_rc_name() << " "; + } + } + else if (cmd.size() == 2) { + try { + stringstream ss; + list params = get_param_list_(cmd[1]); + for (list::iterator it = params.begin(); it != params.end(); it++) { + ss << *it << " "; + } + reply(socket, ss.str()); + } + catch (ParameterError &e) { + reply(socket, e.what()); + } + } + else { + reply(socket, "Too many arguments for command 'list'"); + } + + reply(socket, ss.str()); + } + else if (cmd[0] == "show") { + if (cmd.size() == 2) { + try { + stringstream ss; + list< vector > r = get_param_list_values_(cmd[1]); + for (list< vector >::iterator it = r.begin(); it != r.end(); it++) { + ss << (*it)[0] << ": " << (*it)[1] << endl; + } + reply(socket, ss.str()); + + } + catch (ParameterError &e) { + reply(socket, e.what()); + } + } + else + { + reply(socket, "Incorrect parameters for command 'show'"); + } + } + else if (cmd[0] == "get") { + if (cmd.size() == 3) { + try { + string r = get_param_(cmd[1], cmd[2]); + reply(socket, r); + } + catch (ParameterError &e) { + reply(socket, e.what()); + } + } + else + { + reply(socket, "Incorrect parameters for command 'get'"); + } + } + else if (cmd[0] == "set") { + if (cmd.size() == 4) { + try { + set_param_(cmd[1], cmd[2], cmd[3]); + reply(socket, "ok"); + } + catch (ParameterError &e) { + reply(socket, e.what()); + } + } + else + { + reply(socket, "Incorrect parameters for command 'get'"); + } + } + else if (cmd[0] == "quit") { + reply(socket, "Goodbye"); + } + else { + reply(socket, "Message not understood"); + } +} + + void +RemoteControllerTelnet::reply(tcp::socket& socket, string message) +{ + boost::system::error_code ignored_error; + stringstream ss; + ss << message << "\r\n"; + boost::asio::write(socket, boost::asio::buffer(ss.str()), ignored_error); +} + diff --git a/src/RemoteControl.h b/src/RemoteControl.h new file mode 100644 index 0000000..8b0a30f --- /dev/null +++ b/src/RemoteControl.h @@ -0,0 +1,198 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Written by Matthias P. Braendli, matthias.braendli@mpb.li, 2012 + */ +/* + This file is part of CRC-DADMOD. + + CRC-DADMOD is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + CRC-DADMOD is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CRC-DADMOD. If not, see . + */ + +#ifndef _REMOTECONTROL_H +#define _REMOTECONTROL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using boost::asio::ip::tcp; + +class ParameterError : public std::exception +{ + public: + ParameterError(string message) : message_(message) {} + ~ParameterError() throw() {}; + const char* what() { return message_.c_str(); } + + private: + string message_; +}; + +class RemoteControllable; + +/* Remote controllers (that recieve orders from the user) must implement BaseRemoteController */ +class BaseRemoteController { + public: + /* Add a new controllable under this controller's command */ + virtual void enrol(RemoteControllable* controllable) = 0; +}; + +/* Objects that support remote control must implement the following class */ +class RemoteControllable { + public: + /* return a short name used to identify the controllable. + * It might be used in the commands the user has to type, so keep + * it short + */ + virtual std::string get_rc_name() = 0; + + /* Tell the controllable to enrol at the given controller */ + virtual void enrol_at(BaseRemoteController& controller) { + controller.enrol(this); + } + + /* Return a list of possible parameters that can be set */ + virtual list get_supported_parameters() = 0; + + /* Base function to set parameters. */ + virtual void set_parameter(string parameter, string value) = 0; + + /* Convenience functions for other common types */ + virtual void set_parameter(string parameter, double value) = 0; + virtual void set_parameter(string parameter, long value) = 0; + + /* Getting a parameter always returns a string. */ + virtual string get_parameter(string parameter) = 0; +}; + +/* Implements a Remote controller based on a simple telnet CLI + * that listens on localhost + */ +class RemoteControllerTelnet : public BaseRemoteController { + public: + RemoteControllerTelnet(int port) { + port_ = port; + running_ = false; + }; + + void start() { + running_ = true; + child_thread_ = boost::thread(&RemoteControllerTelnet::process, this, 0); + } + + void stop() { + running_ = false; + child_thread_.interrupt(); + child_thread_.join(); + } + + void process(long); + + void dispatch_command(tcp::socket& socket, string command); + + void reply(tcp::socket& socket, string message); + + void enrol(RemoteControllable* controllable) { + cohort_.push_back(controllable); + } + + + private: + vector tokenise_(string message) { + vector all_tokens; + + boost::char_separator sep(" "); + boost::tokenizer< boost::char_separator > tokens(message, sep); + BOOST_FOREACH (const string& t, tokens) { + all_tokens.push_back(t); + } + return all_tokens; + } + + list get_param_list_(string controllable) { + for (list::iterator it = cohort_.begin(); it != cohort_.end(); it++) { + if ((*it)->get_rc_name() == controllable) + { + return (*it)->get_supported_parameters(); + } + } + throw ParameterError("Module name unknown"); + } + + list< vector > get_param_list_values_(string controllable) { + list< vector > allparams; + for (list::iterator it = cohort_.begin(); it != cohort_.end(); it++) { + if ((*it)->get_rc_name() == controllable) + { + list params = (*it)->get_supported_parameters(); + for (list::iterator it2 = params.begin(); it2 != params.end(); it2++) { + vector item; + item.push_back(*it2); + item.push_back((*it)->get_parameter(*it2)); + + allparams.push_back(item); + } + return allparams; + } + } + throw ParameterError("Module name unknown"); + } + + string get_param_(string controllable, string param) { + for (list::iterator it = cohort_.begin(); it != cohort_.end(); it++) { + if ((*it)->get_rc_name() == controllable) + { + return (*it)->get_parameter(param); + } + } + throw ParameterError("Module name unknown"); + } + + void set_param_(string controllable, string param, string value) { + for (list::iterator it = cohort_.begin(); it != cohort_.end(); it++) { + if ((*it)->get_rc_name() == controllable) + { + (*it)->set_parameter(param, value); + return; + } + } + throw ParameterError("Module name unknown"); + } + + bool running_; + boost::thread child_thread_; + + /* This controller commands the controllables in the cohort */ + list cohort_; + + std::string welcome_; + std::string prompt_; + + int port_; +}; + +#endif diff --git a/src/testremotecontrol/Makefile b/src/testremotecontrol/Makefile new file mode 100644 index 0000000..d5b5d7d --- /dev/null +++ b/src/testremotecontrol/Makefile @@ -0,0 +1,5 @@ +CXXFLAGS=-Wall -g -lboost_system -lboost_thread -I.. + +all: test + +test: test.cpp ../RemoteControl.cpp diff --git a/src/testremotecontrol/README b/src/testremotecontrol/README new file mode 100644 index 0000000..f161c6f --- /dev/null +++ b/src/testremotecontrol/README @@ -0,0 +1,4 @@ +This folder contains a non-essential small +demo + test program for the remote control + +2012, Matthias P. Braendli diff --git a/src/testremotecontrol/test.cpp b/src/testremotecontrol/test.cpp new file mode 100644 index 0000000..7733a96 --- /dev/null +++ b/src/testremotecontrol/test.cpp @@ -0,0 +1,114 @@ +#include +#include +#include "RemoteControl.h" + +using namespace std; + +class TestControllable : public RemoteControllable +{ + public: + TestControllable(string name) + { + name_ = name; + parameterlist_.push_back("foo"); + parameterlist_.push_back("bar"); + parameterlist_.push_back("baz"); + } + + std::string get_rc_name() { return name_; }; + + list get_supported_parameters() { + return parameterlist_; + } + + void set_parameter(string parameter, string value) { + if (parameter == "foo") { + stringstream ss(value); + ss >> foo_; + } + else if (parameter == "bar") { + bar_ = value; + } + else if (parameter == "baz") { + stringstream ss(value); + ss >> baz_; + } + else { + stringstream ss; + ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + } + + void set_parameter(string parameter, double value) { + if (parameter == "baz") { + baz_ = value; + } + else { + stringstream ss; + ss << "Parameter '" << parameter << "' is not a double in controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + } + + void set_parameter(string parameter, long value) { + if (parameter == "foo") { + foo_ = value; + } + else { + stringstream ss; + ss << "Parameter '" << parameter << "' is not a long in controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + } + + string get_parameter(string parameter) { + stringstream ss; + if (parameter == "foo") { + ss << foo_; + } + else if (parameter == "bar") { + ss << bar_; + } + else if (parameter == "baz") { + ss << baz_; + } + else { + stringstream ss; + ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + return ss.str(); + } + + private: + long foo_; + std::string bar_; + std::string name_; + double baz_; + std::list parameterlist_; + +}; + +int main() +{ + RemoteControllerTelnet rc (2121); + TestControllable t("test1"); + TestControllable t2("test2"); + + t.enrol_at(rc); + t2.enrol_at(rc); + + rc.start(); + + std::cerr << "Thread has been launched" << std::endl; + + sleep(100); + + std::cerr << "Stop" << std::endl; + + rc.stop(); + + return 0; +} + -- cgit v1.2.3