BinktermPHP MeshCore Bridge

May 21, 2026 · View on GitHub

Serial companion bridge between a MeshCore node and the BinktermPHP packet BBS gateway API.

The bridge connects to a MeshCore device over USB serial, receives direct messages from radio users, sends each message as a command to the BBS API, and relays the plain-text BBS response back to the originating MeshCore node.

MeshCore requires a contact record for a remote node before messages from that node will pass through to the bridge. As of BinktermPHP 1.9.6, contacts can be managed directly through the BBS — by the sysop via Admin → BBS Settings → Packet BBS Nodes, and by users through their account settings under MeshCore Radio. The device can also be configured to auto-add users based on adverts; when another node advertises itself, the local MeshCore node adds the contact and will then accept messages from that remote user. Auto-add via adverts is a reasonable choice on small or lightly-used networks such as rural deployments, where the contact list is unlikely to grow large enough to stress device memory. On large or busy mesh networks it is not recommended, as frequent adverts from many nodes can fill device contact storage quickly.

Features

  • Uses the MeshCore binary companion protocol over USB/UART.
  • Forwards MeshCore direct messages to POST /api/packetbbs/command.
  • Verifies BBS API connectivity with GET /api/verify during startup.
  • Polls GET /api/packetbbs/pending for queued outbound BBS messages.
  • Sends long BBS responses as numbered text chunks.
  • Reconnects automatically if the serial port is disconnected.
  • Supports manual and scheduled MeshCore adverts.
  • Provides optional debug and packet trace logging.
  • Runs on PHP 8.1 or newer.

Requirements

  • PHP 8.1 or newer.
  • Composer.
  • A MeshCore companion radio flashed with USB firmware and connected using a USB cable.
  • A reachable BinktermPHP instance with the packet BBS API enabled.
  • An API key accepted by the BBS packet API.

Platform notes:

  • On Linux and other Unix-like systems, the bridge configures the serial port with stty.
  • On Windows, PHP's FFI extension must be enabled for non-blocking COM port I/O.

Installation

Install dependencies:

composer install

Create a local configuration file:

cp bridge.json.example bridge.json

Edit bridge.json for your BBS URL, API key, and serial port.

Configuration

Example:

{
    "bbs_url": "https://yourbbs.example.com",
    "api_key": "replace-with-your-bbs-api-key",
    "serial_port": "/dev/ttyUSB0",
    "baud_rate": 115200,
    "interface": "meshcore",
    "max_send_line_length": 200,
    "inter_chunk_delay_ms": 500,
    "min_command_interval_seconds": 2,
    "post_open_delay_seconds": 0,
    "handshake_timeout_seconds": 30,
    "reconnect_delay_seconds": 5,
    "poll_interval_seconds": 30,
    "poll_node_ids": [],
    "advert_zero_hop_interval_seconds": 0,
    "advert_flood_interval_seconds": 0,
    "http_timeout": 15
}

Common keys:

KeyRequiredDescription
bbs_urlYesBase URL for the BinktermPHP site, without a trailing API path.
api_keyYesShared bearer token accepted by the BBS packet API.
serial_portYesSerial device path, such as /dev/ttyUSB0, /dev/ttyACM0, or COM3.
baud_rateNoSerial speed. Defaults to 115200.
interfaceNoInterface name sent to the BBS API. Defaults to meshcore.
max_send_line_lengthNoMaximum response chunk size before splitting long BBS replies. Defaults to 200.
inter_chunk_delay_msNoDelay between multi-part response chunks. Defaults to 500.
min_command_interval_secondsNoPer-node command rate limit. Defaults to 2.
post_open_delay_secondsNoDelay after opening the serial port before handshaking. Useful for devices that reboot on open.
handshake_timeout_secondsNoTime to wait for MeshCore device and self info during startup. Defaults to 30.
reconnect_delay_secondsNoDelay before reopening the serial port after an error. Defaults to 5.
poll_interval_secondsNoHow often to poll the BBS for pending outbound messages. Defaults to 30.
poll_node_idsNoNode IDs to poll for pending outbound messages even before those users contact this bridge process. Defaults to an empty list.
advert_zero_hop_interval_secondsNoAutomatic local advert interval. 0 disables scheduled zero-hop adverts.
advert_flood_interval_secondsNoAutomatic flooded advert interval. 0 disables scheduled flood adverts.
http_timeoutNoHTTP request timeout in seconds. Defaults to 15.

Additional optional keys supported by the bridge:

KeyDescription
message_probe_interval_secondsHow often to proactively ask the radio for queued messages. Defaults to 2.0; set to 0 to disable.
message_sync_timeout_secondsHow long to wait for a sync response before allowing another sync request. Defaults to 1.0; set to 0 to disable the timeout.

Running

Start with the default bridge.json:

php bridge.php

Or through Composer:

composer start

Use a custom config path:

php bridge.php /path/to/bridge.json

Enable bridge-level debug logging:

php bridge.php --debug

Enable decoded packet trace logging:

php bridge.php --trace

Run as a background daemon (Linux/Unix only; requires the pcntl and posix PHP extensions):

php bridge.php --daemon --pid-file=/var/run/bridge.pid --log-file=/var/log/bridge.log

--pid-file and --log-file are optional. Output is discarded when --log-file is omitted. The PID file is removed automatically when the bridge stops.

Runtime console commands are available only when the bridge can poll STDIN in non-blocking mode. They are disabled for Windows TTY sessions and when running as a daemon.

CommandDescription
advert or aSend a zero-hop advert immediately.
flood or afSend a flooded advert immediately.
help or ?Show available console commands.
quit or exitStop the bridge.

Message Flow

Startup:

  1. Open the serial port.
  2. Send MeshCore DeviceQuery.
  3. Wait for DeviceInfo.
  4. Send AppStart.
  5. Wait for SelfInfo and register the bridge node ID with the BBS API client.
  6. Send zero-hop and flooded MeshCore adverts.
  7. Call GET /api/verify and display the returned BBS information.

Inbound radio-to-BBS flow:

  1. MeshCore reports MsgWaiting, or the bridge probes for queued messages.

  2. The bridge sends SyncNextMessage until MeshCore reports NoMoreMessages.

  3. Each direct contact message is checked against the local command table (see below).

  4. Unrecognised commands are forwarded to the BBS:

    POST /api/packetbbs/command
    Authorization: Bearer <api_key>
    
  5. The plain-text API response is sent back as a MeshCore direct message.

Local bridge commands

The following commands are handled directly by the bridge without a BBS round-trip:

CommandAliasResponse
testtPath (relay count or direct) and SNR of the received message.
pingPong! with the same path and SNR information.

Example responses:

Test OK | path: direct | SNR: +7.25 dB
Pong! | path: 2 hops | SNR: +4.50 dB

SNR is only available when the MeshCore node uses the V3 companion protocol; older firmware reports n/a.

Outbound BBS-to-radio flow:

  1. The bridge remembers nodes that have recently contacted it and also uses any configured poll_node_ids.

  2. Every poll_interval_seconds, it calls:

    GET /api/packetbbs/pending?node_id=<node>&bridge_node_id=<bridge>
    Authorization: Bearer <api_key>
    
  3. Any returned messages are sent to that MeshCore node.

Node IDs

The BBS-facing node ID is the lowercase 12-character hex encoding of the 6-byte MeshCore public-key prefix from a received direct message.

Example:

8f12ab34cd56

The bridge also sends its own full public key as bridge_node_id after MeshCore returns SelfInfo.

Serial Port Examples

Linux:

"serial_port": "/dev/ttyUSB0"

Windows:

"serial_port": "COM3"

If the serial device cannot be opened on Linux, confirm the user running the bridge has permission to access the port. This often means adding the user to the appropriate serial group, such as dialout, then logging out and back in.

Troubleshooting

Config file not found

Copy bridge.json.example to bridge.json or pass the config path as the first argument.

Missing required config key

Set bbs_url, api_key, and serial_port in the config file.

Cannot open serial device

Check the serial port name, cable, device power, and OS-level permissions.

PHP FFI extension is required

On Windows, enable ffi in php.ini.

ERROR: Could not reach BBS

Check bbs_url, api_key, TLS certificate validity, network connectivity, and the BBS packet API routes.

BBS verify failed

Check that <bbs_url>/api/verify is reachable from the bridge host and accepts the configured API key.

No replies on the radio:

  • Run with --debug to see commands and BBS responses.
  • Run with --trace to see decoded MeshCore packets.
  • Lower max_send_line_length if long messages are not transmitting reliably.
  • Increase inter_chunk_delay_ms if multi-part responses are sent too quickly.

Project Layout

bridge.php              CLI entry point
bridge.json.example     Example runtime configuration
src/BbsApiClient.php    HTTP client for the packet BBS API
src/MeshCoreBridge.php  Main bridge loop and message handling
src/Packet.php          MeshCore companion packet encoder/decoder
src/SerialPort.php      Cross-platform serial port wrapper

License

BSD-3-Clause. See LICENSE.md.