|
Goby3 3.4.0
2026.04.13
|
goby::acomms::ModemDriverBase defines the core functionality for an acoustic modem. It provides
To use the goby::acomms::ModemDriverBase, you need to create one of its implementations such as goby::acomms::MMDriver (WHOI Micro-Modem).
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).
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 goby::acomms::micromodem::protobuf::config. If we were using the WHOI Micro-Modem and wanted to add an NVRAM configuration value we could write
We need to connect any signals we are interested in. At a minimum this is goby::acomms::ModemDriverBase::signal_receive:
where handle_data_receive has the signature:
Next, we start up the driver with our configuration:
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.
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).
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:
src/acomms/modemdriver/abc_driver.h):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 goby3_example_driver_simple example, run this set of commands (socat is available in most package managers or at http://www.dest-unreach.org/socat/):
Notes:
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:
Mapping between modem_message.proto and mm_driver.proto messages and NMEA fields (see the MicroModem users guide at https://acomms.whoi.edu/micro-modem/software-interface/ 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:ModemTransmission.time() = goby_time<uint64>()ModemTransmission.src() = ADR1<br>ModemTransmission.dest() = ADR2<br>ModemTransmission.rate() = Packet Type<br>ModemTransmission.max_frame_bytes() = 32 for Packet Type == 0, 64 for Packet Type == 2, 256 for Packet Type == 3 or 5ModemTransmission.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):ModemTransmission.time() = goby_time<uint64>()ModemTransmission.type() = ModemTransmission::DATAModemTransmission.src() = SRC<br>ModemTransmission.dest() = DEST<br>ModemTransmission.ack_requested() = ACKFor each $CARXD: ModemTransmission.frame(F#-1) = hex_decode(HH...HH) |
| $CAMSG | Used only to detect BAD_CRC frames ($CAMSG,BAD_CRC...). In extension micromodem::protobuf::Transmission::frame_with_bad_crc: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 | ModemTransmission.time() = goby_time<uint64>()ModemTransmission.src() = SRC<br>ModemTransmission.dest() = DEST<br>(first CAACK) ModemTransmission.acked_frame(0) = Frame#-1 (Goby starts at frame 0, WHOI starts at frame 1)(second CAACK) ModemTransmission.acked_frame(1) = Frame#-1(third CAACK) ModemTransmission.acked_frame(2) = Frame#-1... |
| $CAMUA | ModemTransmission.type() = ModemTransmission::DRIVER_SPECIFICextension micromodem::protobuf::Transmission::type = MICROMODEM_MINI_DATAModemTransmission.time() = goby_time<uint64>()ModemTransmission.src() = SRC<br>ModemTransmission.dest() = DEST<br>ModemTransmission.frame(0) = hex_decode(HHHH) |
| $CAMPR | ModemTransmission.time() = goby_time<uint64>()ModemTransmission.dest() = SRC (SRC and DEST flipped to be SRC and DEST of $CCMPC)ModemTransmission.src() = DEST<br>ModemTransmission.type() = ModemTransmission::DRIVER_SPECIFICextension micromodem::protobuf::Transmission::type = MICROMODEM_TWO_WAY_PINGIn extension micromodem::Transmission::protobuf::ranging_reply:RangingReply::one_way_travel_time(0) = Travel Time |
| $CAMPA | ModemTransmission.time() = goby_time<uint64>()ModemTransmission.src() = SRC<br>ModemTransmission.dest() = DEST<br>ModemTransmission.type() = ModemTransmission::DRIVER_SPECIFICextension micromodem::protobuf::Transmission::type = MICROMODEM_TWO_WAY_PING |
| $SNTTA | ModemTransmission.time() = hhmmsss.ss (converted to microseconds since 1970-01-01 00:00:00 UTC)ModemTransmission.time_source() = MODEM_TIMEModemTransmission.type() = ModemTransmission::DRIVER_SPECIFICextension micromodem::protobuf::Transmission::type = MICROMODEM_REMUS_LBL_RANGING or MICROMODEM_NARROWBAND_LBL_RANGING (depending on which LBL type was last initiated)ModemTransmission.src() = modem IDIn extension micromodem::protobuf::Transmission::ranging_reply:RangingReply.one_way_travel_time(0) = TA<br>RangingReply.one_way_travel_time(1) = TB<br>RangingReply.one_way_travel_time(2) = TC<br>RangingReply.one_way_travel_time(3) = TD |
| $CAXST | Maps onto extension micromodem::protobuf::Transmission::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::Transmission::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): ModemTransmission.time() = TOA time (converted to microseconds since 1970-01-01 00:00:00 UTC)ModemTransmission.time_source() = 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 | ModemTransmission.type() = ModemTransmission::DRIVER_SPECIFICextension micromodem::protobuf::Transmission::type = MICROMODEM_FLEXIBLE_DATAModemTransmission.src() = src<br>ModemTransmission.dest() = dest<br>ModemTransmission.rate() = rate<br>ModemTransmission::frame(0) = 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 indicates that Goby frame 0 is bad, if any sub-frame in the FDP has a bad CRC. |
Control Computer to Modem ($CC):
| NMEA talker | Mapping |
|---|---|
| $CCTXD | SRC = ModemTransmission.src()DEST = ModemTransmission.dest()A = ModemTransmission.ack_requested()HH...HH = hex_encode(ModemTransmission::frame(n)), where n is an integer 0,1,2,... corresponding to the Goby frame that this $CCTXD belongs to. |
| $CCCYC | Augment the ModemTransmission:ModemTransmission.max_frame_bytes() = 32 for Packet Type == 0, 64 for Packet Type == 2, 256 for Packet Type == 3 or 5ModemTransmission.max_num_frames() = 1 for Packet Type == 0, 3 for Packet Type == 2, 2 for Packet Type == 3 or 8 for Packet Type == 5If 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 = ModemTransmission.src()ADR2 = ModemTransmission.dest()Packet Type = ModemTransmission.rate()ACK = if ADR1 == modem ID then ModemTransmission.ack_requested() else 1Nframes = ModemTransmission.max_num_frames() |
| $CCCLK | Not translated from any of the modem_message.proto messages. (taken from the system time) |
| $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::Transmission::typeSRC = ModemTransmission.src()DEST = ModemTransmission.dest() |
| $CCPDT | micromodem::protobuf::MICROMODEM_REMUS_LBL_RANGING == extension micromodem::protobuf::Transmission::typemicromodem::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 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 = REMUSLBLParams::lbl_max_range() m * 2 / 1500 m/s * 1000 ms/s + REMUSLBLParams::turnaround_ms()REMUSLBLParams::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 beaconsAF = enable_beacons() >> 0 & 1BF = enable_beacons() >> 1 & 1CF = enable_beacons() >> 2 & 1DF = enable_beacons() >> 3 & 1 |
| $CCPNT | micromodem::protobuf::MICROMODEM_NARROWBAND_LBL_RANGING == extension micromodem::protobuf::Transmission::typemicromodem::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 set in both objects:Ftx = NarrowBandLBLParams::transmit_freq()Ttx = NarrowBandLBLParams::transmit_ping_ms()Trx = NarrowBandLBLParams::receive_ping_ms()Timeout = NarrowBandLBLParams::lbl_max_range() m * 2 / 1500 m/s * 1000 ms/s + NarrowBandLBLParams::turnaround_ms()FA = NarrowBandLBLParams::receive_freq(0) or 0 if receive_freq_size() < 1FB = NarrowBandLBLParams::receive_freq(1) or 0 if receive_freq_size() < 2FC = NarrowBandLBLParams::receive_freq(2) or 0 if receive_freq_size() < 3FD = NarrowBandLBLParams::receive_freq(3) or 0 if receive_freq_size() < 4Tflag = NarrowBandLBLParams::transmit_flag() |
| $CCMUC | SRC = ModemTransmission.src()DEST = ModemTransmission.dest()HHHH = hex_encode(ModemTransmission::frame(0)) & 0x1F |
| $CCTDP | dest = ModemTransmission.dest()rate = ModemTransmission.rate()ack = 0 (not yet supported by the Micro-Modem 2) reserved = 0 hexdata = hex_encode(ModemTransmission::frame(0)) |
FSK (rate 0) data transmission
PSK (rate 2 shown, others are similar) data transmission
Narrowband transponder LBL ping
REMUS transponder LBL ping
User mini-packet 13 bit data transmission
Two way ping
Flexible Data Protocol (Micro-Modem 2)
The goby::acomms::StoreServerDriver implements a store-and-forward modem emulator that talks to the goby_store_server application. The server maintains an SQLite database so nodes can exchange messages asynchronously even when they are not simultaneously online. This driver is useful any time that nodes need to communicate over (potentially low throughput) TCP links but may not be present at the same time.
| Type | Details |
|---|---|
| TCP (client) | Connects to goby_store_server at the configured TCP address/port (default port 11244). Reconnects automatically on timeout. |
The connection_type field is ignored; the driver always opens a TCP client connection.
Each poll cycle the driver serializes a goby.acomms.protobuf.StoreServerRequest and sends it to the server; the server replies with a goby.acomms.protobuf.StoreServerResponse. Both messages are Protobuf-serialized binary, framed with a encoding also used by the Iridium RUDICS driver and a \r line delimiter.
| StoreServerRequest field | Source |
|---|---|
modem_id | DriverConfig.modem_id |
outbox (repeated ModemTransmission) | DATA messages queued since the last poll, one entry per handle_initiate_transmission call where src == local modem ID |
| StoreServerResponse field | Action |
|---|---|
inbox (repeated ModemTransmission) | Each message is delivered via signal_receive. ACKs are auto-generated for messages with ack_requested == true. |
| Extension type | Description |
|---|---|
STORE_SERVER_DRIVER_POLL | When the MAC schedules a transmission from a remote modem (i.e. src ≠ local modem ID), the driver sends a poll request through the server asking the remote node to forward data to the local modem. The remote driver sees this in its inbox and calls handle_initiate_transmission to enqueue the reply. |
goby::acomms::UDPDriver and goby::acomms::UDPMulticastDriver implement ModemDriverBase over IP/UDP. They are useful for simulation, testing, or real Ethernet/Wi-Fi links where no acoustic hardware is involved.
| Driver | Transport | Details |
|---|---|---|
DRIVER_UDP | IPv4 or IPv6 UDP unicast | Binds a local UDP port; sends to one or more explicitly configured remote endpoints (IP + port + modem ID). Supports IPv6 via ipv6: true. |
DRIVER_UDP_MULTICAST | IPv4 UDP multicast | Joins a multicast group; all nodes on the same multicast address/port receive every packet. Simpler configuration for single-subnet deployments. |
Serial connections are not used. The connection_type field in DriverConfig is ignored.
The entire ModemTransmission Protobuf message is serialized as a binary Protobuf byte string and sent as a single UDP datagram (up to max_frame_size bytes, default 1400). The receiver calls ParseFromArray to reconstruct the message. No additional header or framing is added.
| ModemTransmission field | Wire behavior |
|---|---|
src, dest, type, ack_requested, frame(0) | Serialized as-is in the binary Protobuf datagram |
dest == BROADCAST_ID | Sent to all configured remote endpoints (UDP) or to the multicast group (UDP Multicast) |
ack_requested == true | Receiving driver sends an ACK ModemTransmission back immediately in software |
Neither driver defines any DRIVER_SPECIFIC transmission types.
All drivers use the same configuration except for modem_id, but this typically only works on localhost or a single switch (most routers do not forward multicast data):
Modem 1:
Modem 2:
Add more remotes to expand beyond two modems.
goby::acomms::IridiumDriver (vehicle side) and goby::acomms::IridiumShoreDriver (shore side) together provide support for Iridium satellite communications. The vehicle driver has been tested on the Iridium 9523 (voice-enabled ISU) for RUDICS (call-based stream protocol), and on the Iridium 9602/9603 (e.g. RockBLOCK) for SBD (message-based protocol). Making mobile-terminated (MT, shore-to-vehicle) RUDICS calls is not supported; all calls are mobile-originated (MO, vehicle-to-shore). SBD works for both MT and MO.
| Driver | Connection type | Details |
|---|---|---|
DRIVER_IRIDIUM | Serial | RS-232 serial connection to the Iridium ISU; AT (Hayes) command set used to control the modem |
DRIVER_IRIDIUM_SHORE (RUDICS) | TCP server | Listens for inbound RUDICS TCP connections from the ISU on rudics_server_port |
DRIVER_IRIDIUM_SHORE (SBD DirectIP) | TCP server | Listens for MO SBD messages from the Iridium gateway on mo_sbd_server_port (default 40001); sends MT SBD to Iridium gateway via TCP |
DRIVER_IRIDIUM_SHORE (SBD RockBLOCK) | HTTPS | Receives MO messages via RockBLOCK webhook; sends MT messages via RockBLOCK HTTP API |
A compact DCCL-encoded IridiumHeader (7 bytes max) is prepended to the raw payload bytes. The header carries src, dest, rate, type, ack_requested, frame_start, and acked_frame. The remaining bytes after the header are the contents of frame(0).
ModemTransmission.rate | Mode | Max payload (MO/MT) |
|---|---|---|
| 0 (SBD) | Iridium Short Burst Data — stores data in ISU buffer and initiates mailbox check | ~1953 / ~1883 bytes (9523); ~333 / ~263 bytes (9602/9603). See iridium_driver_common.h for precise details. |
| 1 (RUDICS) | RUDICS call — opens a dial-up data call to the shore station; binary stream | ~1500 bytes per message (configurable). Messages are sent regularly while the call is in progress. |
| Extension field | Description |
|---|---|
iridium::protobuf::Transmission::rockblock_rx | Populated on receive when using RockBLOCK SBD mode; contains IMEI, MOMSN, GPS fix, CEP (Circular Error Probable) radius, and JWT verification flag from the RockBLOCK webhook payload |
iridium::protobuf::Transmission::rockblock_tx | Populated after an MT SBD send via RockBLOCK; indicates success/failure and the Iridium MT ID or error code |
iridium::protobuf::Transmission::if_no_data_do_mailbox_check | When true (default), an SBD mailbox check is initiated even if there is no outgoing data, allowing the driver to retrieve pending MT messages |
goby::acomms::BenthosATM900Driver supports the Benthos ATM900 series of acoustic modems using the Benthos CLAM (Command Language for Acoustic Modems) shell and AT command set.
| Type | Details |
|---|---|
| Serial | RS-232 serial connection (default 9600 baud) to the modem |
A DCCL-encoded 5-byte BenthosHeader is prepended to the payload. The header contains type, ack_requested, and acked_frame list. Each data frame is then RUDICS-encoded (byte-stuffing for binary safety) and delimited by \r.
ModemTransmission field | Wire element |
|---|---|
type, ack_requested, acked_frame(n) | DCCL-encoded into BenthosHeader (5 bytes) |
frame(n) | RUDICS-encoded and appended after the header |
src, dest | Carried by the Benthos AT layer (not in the Goby header) |
| Extension type | Description |
|---|---|
BENTHOS_TWO_WAY_PING | Initiates a two-way ranging ping. Modem 1 interrogates modem 2; modem 2 replies and modem 1 computes the one-way travel time. The result is returned in benthos::protobuf::Transmission::ranging_reply.one_way_travel_time. |
goby::acomms::PopotoDriver supports the Popoto acoustic modem using its JSON command API.
| Type | Details |
|---|---|
| Serial | RS-232 serial connection (default 115200 baud) |
| TCP | TCP client connection to the modem's network interface |
Commands are sent as JSON objects with Command and Arguments fields, newline-delimited. Incoming messages are also received as JSON. Data payloads are sent using the TransmitJSON command.
ModemTransmission field | JSON mapping |
|---|---|
src | SetValue LocalID <src> before each transmission |
dest | SetValue RemoteID <dest> (255 for BROADCAST) |
rate (0–5) | Rate command sent before transmission: 0=80 bps, 1=640 bps, 2=1280 bps, 3=2560 bps, 4=5120 bps, 5=10240 bps |
ack_requested | TransmitJSON.AckRequest field (boolean: 1 = ACK requested, 0 = no ACK) |
frame(0) | 1-byte Goby header (encodes type, ack_requested, frame number) prepended to raw payload bytes; sent as TransmitJSON.Payload.Data byte array |
| Extension type | Description |
|---|---|
POPOTO_TWO_WAY_RANGE_REQUEST | Send a ranging request to dest; result arrives as a received POPOTO_TWO_WAY_RANGE_RESPONSE with ranging_reply.one_way_travel_time, two_way_travel_time, and modem_range |
POPOTO_TWO_WAY_RANGE_RESPONSE | Received only; contains ranging results in popoto::protobuf::Transmission::ranging_reply |
POPOTO_PLAY_FILE | Play an audio file stored on the modem; set popoto::protobuf::Transmission::file_location and optionally transmit_power |
POPOTO_DEEP_SLEEP | Put the local modem into low-power sleep mode |
POPOTO_WAKE | Wake a sleeping remote modem (sends a ping) |
POPOTO_SET_TX | Update the transmit power; set popoto::protobuf::Transmission::transmit_power (watts) |
These drivers were contributed by Mission Systems Pty Ltd (https://github.com/mission-systems-pty-ltd). For questions about these drivers please contact Mission Systems via their Github page.
These drivers currently require that Goby be built from source with additional dependencies.
goby::acomms::JanusDriver implements the JANUS NATO STANAG 4748 underwater acoustic communication standard, allowing any ALSA-compatible sound device to act as an acoustic modem.
Requires libplugin libraries which can be built following instructions from https://github.com/mission-systems-pty-ltd/janus-c
Compile in Goby using
| Type | Details |
|---|---|
| ALSA | Audio input/output device (e.g. default, hw:0,0); configured via stream_driver and stream_driver_args. No serial or TCP connection is used. Separate TX and RX configurations are given in rx_config and tx_config extensions. |
A 1-byte Goby header (encodes type, ack_requested, and frame number) is prepended to the raw payload bytes. The combined byte string is placed into the JANUS cargo field and transmitted as a standard JANUS packet with the configured class_id and application_type. On receive, the JANUS packet is decoded and the Goby header is stripped to reconstruct the ModemTransmission.
ModemTransmission field | JANUS mapping |
|---|---|
src | JANUS station_id |
dest | JANUS destination_id |
ack_requested | Carried in Goby 1-byte header and AckRequest JANUS field |
frame(0) | JANUS cargo (after stripping 1-byte Goby header) |
The Janus driver does not define any DRIVER_SPECIFIC transmission types. Only DATA and ACK transmissions are supported.
The following drivers are only available when Goby is compiled with MOOS support (libgoby_moos) and are used via pAcommsHandler. See the MOOS documentation for setup details.
DRIVER_UFIELD_SIM_DRIVER is a simulation driver that uses the MOOS-IvP uField toolbox as its transport layer. It is intended for multi-vehicle simulation within a MOOS-IvP environment and does not communicate with any real acoustic hardware.
DRIVER_BLUEFIN_MOOS is a driver for Bluefin Robotics autonomous underwater vehicles that communicates via the MOOS middleware. It allows pAcommsHandler (using iFrontSeat_bluefin to exchange acoustic messages through the Bluefin vehicle's Standard Payload Interface using the $BPCPD, $BFCPS, $BFCMA, and $BFCPR messages.