Goby Underwater Autonomy Project
Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
|
Table of contents for libmodemdriver:
Return to goby-acomms: Overview of Acoustic Communications Libraries.
goby::acomms::ModemDriverBase defines the core functionality for an acoustic modem. It provides
The goby::acomms::MMDriver extends the goby::acomms::ModemDriverBase for the WHOI Micro-Modem acoustic modem. It is tested to work with revision 0.93.0.30 of the Micro-Modem firmware, but is known to work with older firmware (at least 0.92.0.85), as well as newer (WHOI firmware 0.93.0.35 to 0.93.0.51 has a critical bug, however, where XST should be set to 0). The following commands of the WHOI Micro-Modem are implemented:
Modem to Control Computer ($CA / $SN):
Control Computer to Modem ($CC). Also implemented is the NMEA acknowledge (e.g. $CACLK for $CCCLK):
goby::acomms::protobuf::DriverConfig cfg;
cfg.AddExtension(MicroModemConfig::nvram_cfg, "SNR,1");
mm_driver.startup(cfg);
Mapping between modem_message.proto messages and NMEA fields (see http://acomms.whoi.edu/documents/uModem%20Software%20Interface%20Guide.pdf for NMEA fields of the WHOI Micro-Modem):
Modem to Control Computer ($CA / $SN):
Control Computer to Modem ($CC):
All of goby-acomms is designed to be agnostic of which physical modem is used. Different modems can be supported by subclassing goby::acomms::ModemDriverBase.
These are the requirements of the acoustic modem:
Optionally, it can also support
The steps to writing a new driver include:
Figure out what type of configuration the modem will need. For example, the WHOI Micro-Modem is configured using string values (e.g. "SNV,1"). Extend goby::acomms::protobuf::DriverConfig to accomodate these configuration options. You will need to claim a group of extension field numbers that do not overlap with any of the drivers. The WHOI Micro-Modem driver goby::acomms::MMDriver uses extension field numbers 1000-1100 (see mm_driver.proto). You can read more about extensions in the official Google Protobuf documentation here: <http://code.google.com/apis/protocolbuffers/docs/proto.html#extensions>.
For example, if I was writing a new driver for the ABC Modem that needs to be configured using a few boolean flags, I might create a new message abc_driver.proto:
import "goby/protobuf/driver_base.proto"; // load up message DriverBaseConfig message ABCDriverConfig { extend goby.acomms.protobuf.DriverConfig { optional bool enable_foo = 1201 [ default = true ]; optional bool enable_bar = 1202 [ default = false ]; } }
make a note in driver_base.proto claiming extension numbers 1201 and 1202 (and others you may expect to need in the future). Extension field numbers can go up to 536,870,911 so don't worry about running out.
Subclass goby::acomms::ModemDriverBase and overload the pure virtual methods (goby::acomms::ModemDriverBase::handle_initiate_ranging is optional). Your interface should look like this:
namespace goby { namespace acomms { class ABCDriver : public ModemDriverBase { public: ABCDriver(std::ostream* log = 0); void startup(const protobuf::DriverConfig& cfg); void shutdown(); void do_work(); void handle_initiate_transmission(protobuf::ModemMsgBase* m); private: protobuf::DriverConfig driver_cfg_; // configuration given to you at launch std::ostream* log_; // place to log all human readable debugging messages // rest is up to you! }; } }
goby::acomms::ABCDriver::ABCDriver() : ModemDriverBase(log), log_(log) { // other initialization you can do before you have your goby::acomms::DriverConfig configuration object }
At startup() you get your configuration from the application (pAcommsHandler or other)
void goby::acomms::ABCDriver::startup(const protobuf::DriverConfig& cfg) { driver_cfg_ = cfg; // check `driver_cfg_` to your satisfaction and then start the modem physical interface if(!driver_cfg_.has_serial_baud()) driver_cfg_.set_serial_baud(DEFAULT_BAUD); // log_ is allowed to be 0 (NULL), so always check it first if(log_) *log_ << group("modem_out") << "ABCDriver configuration good. Starting modem..." << std::endl; ModemDriverBase::modem_start(driver_cfg_); // set your local modem id (MAC address) driver_cfg_.modem_id(); { std::stringstream raw; raw << "CONF,MAC:" << driver_cfg_.modem_id() << "\r\n"; signal_and_write(raw.str()); } // now set our special configuration values { std::stringstream raw; raw << "CONF,FOO:" << driver_cfg_.GetExtension(ABCDriverConfig::enable_foo) << "\r\n"; signal_and_write(raw.str()); } { std::stringstream raw; raw << "CONF,BAR:" << driver_cfg_.GetExtension(ABCDriverConfig::enable_bar) << "\r\n"; signal_and_write(raw.str()); } } // startup
void goby::acomms::ABCDriver::shutdown() { // put the modem in a low power state? // ... ModemDriverBase::modem_close(); } // shutdown
void goby::acomms::ABCDriver::handle_initiate_transmission(protobuf::ModemMsgBase* base_msg) { if(log_) { // base_msg->rate() can be 0 (lowest), 1, 2, 3, 4, or 5 (lowest). Map these integers onto real bit-rates // in a meaningful way (on the WHOI Micro-Modem 0 ~= 80 bps, 5 ~= 5000 bps). *log_ << group("modem_out") << "We were asked to transmit from " << base_msg->src() << " to " << base_msg->dest() << " at bitrate code " << base_msg->rate() << std::endl; } protobuf::ModemDataRequest request_msg; // used to request data from libqueue protobuf::ModemDataTransmission data_msg; // used to store the requested data // set up request_msg request_msg.mutable_base()->set_src(base_msg->src()); request_msg.mutable_base()->set_dest(base_msg->dest()); // let's say ABC modem uses 500 byte packet request_msg.set_max_bytes(500); ModemDriverBase::signal_data_request(request_msg, &data_msg); // do nothing with an empty message if(data_msg.data().empty()) return; if(log_) { *log_ << group("modem_out") << "Sending these data now: " << data_msg << std::endl; } // let's say we can send at three bitrates with ABC modem: map these onto 0-5 const unsigned BITRATE [] = { 100, 1000, 10000, 10000, 10000, 10000}; // I'm making up a syntax for the wire protocol... std::stringstream raw; raw << "SEND,TO:" << data_msg.base().dest() << ",FROM:" << data_msg.base().src() << ",HEX:" << hex_encode(data_msg.data()) << ",BITRATE:" << BITRATE[base_msg->rate()] << ",ACK:TRUE" << "\r\n"; // let anyone who is interested know signal_and_write(raw.str(), base_msg); } // handle_initiate_transmission
void goby::acomms::ABCDriver::do_work() { std::string in; while(modem_read(&in)) { std::map<std::string, std::string> parsed; // breaks `in`: "RECV,TO:3,FROM:6,HEX:ABCD015910" // into `parsed`: "KEY"=>"RECV", "TO"=>"3", "FROM"=>"6", "HEX"=>"ABCD015910" try { boost::trim(in); // get whitespace off from either end parse_in(in, &parsed); protobuf::ModemMsgBase base_msg; base_msg.set_raw(in); using google::protobuf::int32; base_msg.set_src(goby::util::as<int32>(parsed["FROM"])); base_msg.set_dest(goby::util::as<int32>(parsed["TO"])); base_msg.set_time(goby::util::as<std::string>( goby::util::goby_time())); if(log_) *log_ << group("modem_in") << in << std::endl; ModemDriverBase::signal_all_incoming(base_msg); if(parsed["KEY"] == "RECV") { protobuf::ModemDataTransmission data_msg; data_msg.mutable_base()->CopyFrom(base_msg); data_msg.set_data(hex_decode(parsed["HEX"])); if(log_) *log_ << group("modem_in") << "received: " << data_msg << std::endl; ModemDriverBase::signal_receive(data_msg); } else if(parsed["KEY"] == "ACKN") { protobuf::ModemDataAck ack_msg; ack_msg.mutable_base()->CopyFrom(base_msg); ModemDriverBase::signal_ack(ack_msg); } } catch(std::exception& e) { if(log_) *log_ << warn << "Bad line: " << in << std::endl; if(log_) *log_ << warn << "Exception: " << e.what() << std::endl; } } } // do_work
The full ABC Modem example driver exists in acomms/libmodemdriver/abc_driver.h and acomms/libmodemdriver/abc_driver.cpp. A simulator for the ABC Modem exists that uses TCP to mimic a very basic set of modem commands (send data and acknowledgment). To use the ABC Modem using the driver_simple example, run this set of commands (`socat` is available in most package managers or at <http://www.dest-unreach.org/socat/>):
1. run abc_modem_simulator running on same port (as TCP server) > abc_modem_simulator 54321 2. create fake tty terminals connected to TCP as client to port 54321 > socat -d -d -v pty,raw,echo=0,link=/tmp/ttyFAKE1 TCP:localhost:54321 > socat -d -d -v pty,raw,echo=0,link=/tmp/ttyFAKE2 TCP:localhost:54321 3. start up driver_simple > driver_simple /tmp/ttyFAKE1 1 ABCDriver // wait a few seconds to avoid collisions > driver_simple /tmp/ttyFAKE2 2 ABCDriver
Notes: