Goby v2
goby-acomms: modemdriver (Driver to interact with modem firmware)

Table of contents for modemdriver:

Return to goby-acomms: An overview of Acoustic Communications Library.

Abstract class: ModemDriverBase

goby::acomms::ModemDriverBase defines the core functionality for an acoustic modem. It provides

Interacting with the goby::acomms::ModemDriverBase

To use the goby::acomms::ModemDriverBase, you need to create one of its implementations such as WHOI Micro-Modem Driver: MMDriver.

You will also need to configure the driver. At the very least this involves a serial port, baud, and modem ID (integer MAC address for the modem).

cfg.set_serial_port("/dev/ttyS0");
cfg.set_modem_id(3);

Most modems will have specific other configuration that is required. For example the WHOI Micro-Modem NVRAM is set using three character strings followed by a number. This modem-specific configuration is stored as Protobuf extensions to goby::acomms::protobuf::DriverConfig, such as micromodem::protobuf::Config. If we were using the WHOI Micro-Modem and wanted to add an NVRAM configuration value we could write

cfg.AddExtension(micromodem::protobuf::Config::nvram_cfg, "DQF,1");

We need to connect any signals we are interested in. At a minimum this is goby::acomms::ModemDriverBase::signal_receive:

goby::acomms::connect(&driver->signal_receive, &handle_data_receive);

where handle_data_receive has the signature:

void handle_data_receive(const goby::acomms::protobuf::ModemTransmission& data_msg);

Next, we start up the driver with our configuration:

driver->startup(cfg);

We need to call goby::acomms::ModemDriverBase::do_work() on some reasonable frequency (greater than 5 Hz; 10 Hz is probably good). Whenever we need to transmit something, we can either directly call goby::acomms::ModemDriverBase::handle_initiate_transmission or connect goby::acomms::MACManager to do so for us on some TDMA cycle.

Protobuf Message goby::acomms::protobuf::ModemTransmission

The goby::acomms::protobuf::ModemTransmission message is used for all outgoing (sending) and incoming (receiving) messages. The message itself only contains the subset of modem functionality that every modem is expected to support (point-to-point transmission of datagrams).

All other functionality is provided by extensions to ModemTransmission such as those in mm_driver.proto for the WHOI Micro-Modem. These extensions provide access to additional features of the WHOI Micro-Modem (such as LBL ranging, two-way pings, and comprehensive receive statistics).

By making use of the Protobuf extensions in this way, Goby can both support unique features of a given modem while at that same time remaining general and agnostic to which modem is used when the features are shared (primarily data transfer).

Writing a new driver

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. You should check that a driver for your modem does not yet exist before attempting to create your own.

These are the requirements of the acoustic modem:

Optionally, it can also support

The steps to writing a new driver include:

The full ABC Modem example driver exists in acomms/modemdriver/abc_driver.h and acomms/modemdriver/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:

WHOI Micro-Modem Driver: MMDriver

Supported Functionality

The goby::acomms::MMDriver extends the goby::acomms::ModemDriverBase for the WHOI Micro-Modem acoustic modem. It is tested to work with revision 0.94.0.00 of the Micro-Modem 1 and revision 2.0.16421 of the Micro-Modem 2 firmware, but is known to work with older firmware (at least 0.92.0.85). It is likely to work properly with newer firmware, and any problems while using newer Micro-Modem firmware should be filed as a bug in Goby. The following features of the WHOI Micro-Modem are implemented, which comprise the majority of the Micro-Modem functionality:

See the UML diagrams for a graphical diagram of using Goby for each of these features.

Micro-Modem NMEA to Goby ModemTransmission mapping

Mapping between modem_message.proto and mm_driver.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):

NMEA talker Mapping
$CACYC If we did not send $CCCYC, buffer data for $CADRQ by augmenting the provided ModemTransmission and calling signal_data_request:
goby::acomms::protobuf::ModemTransmission.time() = goby::common::goby_time<uint64>()
goby::acomms::protobuf::ModemTransmission.src() = ADR1
goby::acomms::protobuf::ModemTransmission.dest() = ADR2
goby::acomms::protobuf::ModemTransmission.rate() = Packet Type
goby::acomms::protobuf::ModemTransmission.max_frame_bytes() = 32 for Packet Type == 0, 64 for Packet Type == 2, 256 for Packet Type == 3 or 5
goby::acomms::protobuf::ModemTransmission.max_num_frames() = 1 for Packet Type == 0, 3 for Packet Type == 2, 2 for Packet Type == 3 or 8 for Packet Type == 5
$CARXD only for the first $CARXD for a given packet (should match with the rest though):
goby::acomms::protobuf::ModemTransmission.time() = goby::common::goby_time<uint64>()
goby::acomms::protobuf::ModemTransmission.type() = goby::acomms::protobuf::ModemTransmission::DATA
goby::acomms::protobuf::ModemTransmission.src() = SRC
goby::acomms::protobuf::ModemTransmission.dest() = DEST
goby::acomms::protobuf::ModemTransmission.ack_requested() = ACK
for each $CARXD:
goby::acomms::protobuf::ModemTransmission.frame(F#-1) = goby::util::hex_decode(HH...HH)
$CAMSG Used only to detect BAD_CRC frames ($CAMSG,BAD_CRC...). (in extension micromodem::protobuf::frame_with_bad_crc)
micromodem::protobuf::frame_with_bad_crc(n) = Frame with BAD CRC (assumed next frame after last good frame). n is an integer 0,1,2,... indicating the nth reported BAD_CRC frame for this packet. (not the frame number)
$CAACK goby::acomms::protobuf::ModemTransmission.time() = goby::common::goby_time<uint64>()
goby::acomms::protobuf::ModemTransmission.src() = SRC
goby::acomms::protobuf::ModemTransmission.dest() = DEST
(first CAACK for a given packet) goby::acomms::protobuf::ModemTransmission.acked_frame(0) = Frame#-1 (Goby starts counting at frame 0, WHOI starts with frame 1)
(second CAACK for a given packet) goby::acomms::protobuf::ModemTransmission.acked_frame(1) = Frame#-1
(third CAACK for a given packet) goby::acomms::protobuf::ModemTransmission.acked_frame(2) = Frame#-1
...
$CAMUA goby::acomms::protobuf::ModemTransmission.type() = goby::acomms::protobuf::ModemTransmission::DRIVER_SPECIFIC
extension micromodem::protobuf::type = micromodem::protobuf::MICROMODEM_MINI_DATA
goby::acomms::protobuf::ModemTransmission.time() = goby::common::goby_time<uint64>()
goby::acomms::protobuf::ModemTransmission.src() = SRC
goby::acomms::protobuf::ModemTransmission.dest() = DEST
goby::acomms::protobuf::ModemTransmission.frame(0) = goby::util::hex_decode(HHHH)
$CAMPR goby::acomms::protobuf::ModemTransmission.time() = goby::common::goby_time<uint64>()
goby::acomms::protobuf::ModemTransmission.dest() = SRC (SRC and DEST flipped to be SRC and DEST of $CCMPC)
goby::acomms::protobuf::ModemTransmission.src() = DEST
goby::acomms::protobuf::ModemTransmission.type() = goby::acomms::protobuf::ModemTransmission::DRIVER_SPECIFIC
extension micromodem::protobuf::type = micromodem::protobuf::MICROMODEM_TWO_WAY_PING
(in extension micromodem::protobuf::ranging_reply)
micromodem::protobuf::RangingReply.one_way_travel_time(0) = Travel Time
$CAMPA goby::acomms::protobuf::ModemTransmission.time() = goby::common::goby_time<uint64>()
goby::acomms::protobuf::ModemTransmission.src() = SRC
goby::acomms::protobuf::ModemTransmission.dest() = DEST
goby::acomms::protobuf::ModemTransmission.type() = goby::acomms::protobuf::ModemTransmission::DRIVER_SPECIFIC
extension micromodem::protobuf::type = micromodem::protobuf::MICROMODEM_TWO_WAY_PING
$SNTTA goby::acomms::protobuf::ModemTransmission.time() = hhmmsss.ss (converted to microseconds since 1970-01-01 00:00:00 UTC)
goby::acomms::protobuf::ModemTransmission.time_source() = goby::acomms::protobuf::MODEM_TIME
goby::acomms::protobuf::ModemTransmission.type() = goby::acomms::protobuf::ModemTransmission::DRIVER_SPECIFIC
extension micromodem::protobuf::type = micromodem::protobuf::MICROMODEM_REMUS_LBL_RANGING or micromodem::protobuf::MICROMODEM_NARROWBAND_LBL_RANGING (depending on which LBL type was last initiated)
goby::acomms::protobuf::ModemTransmission.src() = modem ID
(in extension micromodem::protobuf::ranging_reply)
micromodem::protobuf::RangingReply.one_way_travel_time(0) = TA
micromodem::protobuf::RangingReply.one_way_travel_time(1) = TB
micromodem::protobuf::RangingReply.one_way_travel_time(2) = TC
micromodem::protobuf::RangingReply.one_way_travel_time(3) = TD
$CAXST maps onto extension micromodem::protobuf::transmit_stat of type micromodem::protobuf::TransmitStatistics. The two $CAXST messages (CYC and data) for a rate 0 FH-FSK transmission are grouped and reported at once.
$CACST maps onto extension micromodem::protobuf::receive_stat of type micromodem::protobuf::ReceiveStatistics. The two $CACST messages for a rate 0 FH-FSK transmission are grouped and reported at once. Note that this message contains the one way time of flight for synchronous ranging (used instead of $CATOA).
Also sets (which will overwrite goby_time() set previously):
goby::acomms::protobuf::ModemTransmission.time() = TOA time (converted to microseconds since 1970-01-01 00:00:00 UTC)
goby::acomms::protobuf::ModemTransmission.time_source() = goby::acomms::protobuf::MODEM_TIME
$CAREV Not translated into any of the modem_message.proto messages. Monitored to detect excessive clock skew (between Micro-Modem clock and system clock) or reboot (INIT)
$CAERR Not translated into any of the modem_message.proto messages. Reported to goby::glog.
$CACFG NVRAM setting stored internally.
$CACLK Checked against system clock and if skew is unacceptable another $CCCLK will be sent.
$CADRQ Data request is anticipated from the $CCCYC or $CACYC and buffered. Thus it is not translated into any of the Protobuf messages.
$CARDP goby::acomms::protobuf::ModemTransmission.type() = goby::acomms::protobuf::ModemTransmission::DRIVER_SPECIFIC
extension micromodem::protobuf::type = micromodem::protobuf::MICROMODEM_FLEXIBLE_DATA
goby::acomms::protobuf::ModemTransmission.src() = src
goby::acomms::protobuf::ModemTransmission.dest() = dest
goby::acomms::protobuf::ModemTransmission.rate() = rate
goby::acomms::protobuf::ModemTransmission.frame(0) = goby::util::hex_decode(df1+df2+df3...dfN) where "+" means concatenate, unless any frame fails the CRC check, in which case this field is set to the empty string.
micromodem::protobuf::frame_with_bad_crc(0) = 0 indicated that Goby frame 0 is bad, if any sub-frame in the FDP has a bad CRC

Control Computer to Modem ($CC):

$CCTXD SRC = goby::acomms::protobuf::ModemTransmission.src()
DEST = goby::acomms::protobuf::ModemTransmission.dest()
A = goby::acomms::protobuf::ModemTransmission.ack_requested()
HH...HH = goby::acomms::hex_encode(goby::acomms::protobuf::ModemTransmission.frame(n)), which n is an integer 0,1,2,... corresponding to the Goby frame that this $CCTXD belongs to.
$CCCYC Augment the ModemTransmission:
goby::acomms::protobuf::ModemTransmission.max_frame_bytes() = 32 for Packet Type == 0, 64 for Packet Type == 2, 256 for Packet Type == 3 or 5
goby::acomms::protobuf::ModemTransmission.max_num_frames() = 1 for Packet Type == 0, 3 for Packet Type == 2, 2 for Packet Type == 3 or 8 for Packet Type == 5
If ADR1 == modem ID and frame_size() < max_frame_size(), buffer data for later $CADRQ by passing the ModemTransmission to signal_data_request
CMD = 0 (deprecated field)
ADR1 = goby::acomms::protobuf::ModemTransmission.src()
ADR2 = goby::acomms::protobuf::ModemTransmission.dest()
Packet Type = goby::acomms::protobuf::ModemTransmission.rate()
ACK = if ADR1 == modem ID then goby::acomms::protobuf::ModemTransmission.ack_requested() else 1
Nframes = goby::acomms::protobuf::ModemTransmission.max_num_frames()

$CCCLK Not translated from any of the modem_message.proto messages. (taken from the system time using the boost::date_time library)
$CCCFG Not translated from any of the modem_message.proto messages. (taken from values passed to the extension micromodem::protobuf::Config::nvram_cfg of goby::acomms::protobuf::DriverConfig). If the extension micromodem::protobuf::Config::reset_nvram is set to true, $CCCFG,ALL,0 will be sent before any other $CCCFG values.)
$CCCFQ Not translated from any of the modem_message.proto messages. $CCCFQ,ALL sent at startup.
$CCMPC micromodem::protobuf::MICROMODEM_TWO_WAY_PING == extension micromodem::protobuf::type
SRC = goby::acomms::protobuf::ModemTransmission.src()
DEST = goby::acomms::protobuf::ModemTransmission.dest()
$CCPDT micromodem::protobuf::protobuf::MICROMODEM_REMUS_LBL_RANGING == extension micromodem::protobuf::type
micromodem::protobuf::REMUSLBLParams type used to determine the parameters of the LBL ping. The object provided with configuration (micromodem::protobuf::Config::remus_lbl) is merged with the object provided with the ModemTransmission (micromodem::protobuf::remus_lbl) with the latter taking priority on fields that a set in both objects:
GRP = 1
CHANNEL = modem ID % 4 + 1 (use four consecutive modem IDs if you need multiple vehicles pinging)
SF = 0
STO = 0
Timeout = micromodem::protobuf::REMUSLBLParams::lbl_max_range() m *2/ 1500 m/s * 1000 ms/s + micromodem::protobuf::REMUSLBLParams::turnaround_ms()
goby::acomms::protobuf::ModemRangingRequest.enable_beacons() is a set of four bit flags where the least significant bit is AF enable, most significant bit is DF enable. Thus b1111 == 0x0F enables all beacons
AF = micromodem::protobuf::REMUSLBLParams::enable_beacons() >> 0 & 1
BF = micromodem::protobuf::REMUSLBLParams::enable_beacons() >> 1 & 1
CF = micromodem::protobuf::REMUSLBLParams::enable_beacons() >> 2 & 1
DF = micromodem::protobuf::REMUSLBLParams::enable_beacons() >> 3 & 1
$CCPNT

micromodem::protobuf::protobuf::MICROMODEM_NARROWBAND_LBL_RANGING == extension micromodem::protobuf::type
micromodem::protobuf::NarrowBandLBLParams type used to determine the parameters of the LBL ping. The object provided with configuration (micromodem::protobuf::Config::narrowband_lbl) is merged with the object provided with the ModemTransmission (micromodem::protobuf::narrowband_lbl) with the latter taking priority on fields that a set in both objects:

Ftx = micromodem::protobuf::NarrowBandLBLParams::transmit_freq()
Ttx = micromodem::protobuf::NarrowBandLBLParams::transmit_ping_ms()
Trx = micromodem::protobuf::NarrowBandLBLParams::receive_ping_ms()
Timeout = micromodem::protobuf::NarrowBandLBLParams::lbl_max_range() m *2/ 1500 m/s * 1000 ms/s + micromodem::protobuf::NarrowBandLBLParams::turnaround_ms()
FA = micromodem::protobuf::NarrowBandLBLParams::receive_freq(0) or 0 if receive_freq_size() < 1
FB = micromodem::protobuf::NarrowBandLBLParams::receive_freq(1) or 0 if receive_freq_size() < 2
FC = micromodem::protobuf::NarrowBandLBLParams::receive_freq(2) or 0 if receive_freq_size() < 3
FD = micromodem::protobuf::NarrowBandLBLParams::receive_freq(3) or 0 if receive_freq_size() < 4
Tflag = micromodem::protobuf::NarrowBandLBLParams::transmit_flag()

$CCMUC SRC = goby::acomms::protobuf::ModemTransmission.src()
DEST = goby::acomms::protobuf::ModemTransmission.dest()
HHHH = goby::acomms::hex_encode(goby::acomms::protobuf::ModemTransmission.frame(0)) & 0x1F
$CCTDP dest = goby::acomms::protobuf::ModemTransmission.dest()
rate = goby::acomms::protobuf::ModemTransmission.rate()
ack = 0 (not yet supported by the Micro-Modem 2)
reserved = 0
hexdata = goby::acomms::hex_encode(goby::acomms::protobuf::ModemTransmission.frame(0))

Sequence diagrams for various Micro-Modem features using Goby

FSK (rate 0) data transmission

goby-acomms-mmdriver-rate0.png
PSK (rate 2 shown, others are similar) data transmission

goby-acomms-mmdriver-rate2.png
Narrowband transponder LBL ping

goby-acomms-mmdriver-pnt.png
REMUS transponder LBL ping

goby-acomms-mmdriver-pdt.png
User mini-packet 13 bit data transmission

goby-acomms-mmdriver-muc.png
Two way ping

goby-acomms-mmdriver-mpc.png
Flexible Data Protocol (Micro-Modem 2)

goby-acomms-mmdriver-tdp.png