1. Overview
OCCode's communications module lets you send and receive messages across 8 independent channels — from Bluetooth and WiFi on your local desk to LoRa radios, Meshtastic mesh networks, and amateur radio spanning continents. Every channel is optional: it only activates when configured and its required npm package is installed.
Bluetooth
Serial Port Profile (SPP) over paired devices
WiFi LAN
mDNS discovery + TCP messaging on your local network
Modem SMS
GSM modem with AT commands over USB serial
Telegram
Bot API with long polling and chat-ID whitelist
Cloud SMS
Twilio REST API + inbound webhook
LoRa Radio
Long-range radio (RYLR896/998, RAK, RN2483)
Meshtastic
LoRa mesh with multi-hop routing and built-in AES
Ham Radio
AX.25 packet + JS8Call weak-signal HF
Unified Message Format
All channels use a common CommMessage envelope:
{
"id": "uuid-v4",
"channel": "bluetooth | wifi_lan | modem_sms | telegram | cloud_sms | lora | meshtastic | ham_radio",
"sender": "identifier (varies by channel)",
"recipient": "identifier (varies by channel)",
"content": "message text",
"timestamp": "ISO-8601",
"direction": "inbound | outbound",
"metadata": { }
}
CLI Command
All operations go through the /comms slash command inside the OCCode REPL:
/comms status — show all channel statuses
/comms <channel> <sub> — channel-specific operations
/comms history [n] — recent messages (default 20)
/comms config — configuration help
/comms help — full command reference
2. Quick Start
Step 1 — Install Dependencies
Install only the packages for the channels you plan to use:
# Bluetooth
npm install bluetooth-serial-port
# WiFi LAN
npm install bonjour-service
# Modem SMS
npm install serialport @serialport/parser-readline
# Telegram
npm install node-telegram-bot-api
# Cloud SMS (Twilio)
npm install twilio
# LoRa Radio (reuses serialport)
npm install serialport @serialport/parser-readline
# Meshtastic
npm install @meshtastic/js
# Ham Radio — no extra packages (uses built-in net module)
Step 2 — Create Configuration
Create or edit ~/.occode/comms.json and enable the channels you want. See the Configuration section for the full schema.
Step 3 — Verify
Inside the OCCode REPL, run:
/comms status
You should see each enabled channel with its connection state (connected, connecting, error, or disconnected).
3. Configuration
All channel settings live in a single JSON file at ~/.occode/comms.json. Each top-level key corresponds to a channel. Set "enabled": true to activate a channel. Here is the complete schema with every channel:
{
"bluetooth": {
"enabled": false,
"deviceAddress": "00:11:22:33:44:55",
"channel": 1,
"autoReconnect": true,
"reconnectIntervalMs": 5000,
"sharedSecret": ""
},
"wifiLan": {
"enabled": false,
"serviceName": "occode-comms",
"port": 0,
"peerName": "my-machine",
"sharedSecret": ""
},
"modemSms": {
"enabled": false,
"serialPort": "/dev/ttyUSB0",
"baudRate": 9600,
"simPin": "",
"pollIntervalMs": 10000
},
"telegram": {
"enabled": false,
"botToken": "",
"allowedChatIds": []
},
"cloudSms": {
"enabled": false,
"accountSid": "",
"authToken": "",
"fromNumber": "+15551234567",
"webhookPort": 3100,
"webhookUrl": ""
},
"lora": {
"enabled": false,
"serialPort": "/dev/ttyUSB0",
"baudRate": 115200,
"address": 1,
"networkId": 6,
"band": 915000000,
"spreadingFactor": 12,
"bandwidth": 7,
"codingRate": 1,
"preamble": 4
},
"meshtastic": {
"enabled": false,
"serialPort": "/dev/ttyUSB0",
"baudRate": 115200,
"meshChannel": 0
},
"hamRadio": {
"enabled": false,
"callsign": "N0CALL",
"mode": "both",
"kissHost": "127.0.0.1",
"kissPort": 8001,
"js8Host": "127.0.0.1",
"js8Port": 2442,
"rigctlEnabled": false,
"rigctlHost": "127.0.0.1",
"rigctlPort": 4532
}
}
4. Bluetooth
WirelessRequirements
Package:
npm install bluetooth-serial-portTarget: Any device exposing a Bluetooth Serial Port Profile (SPP)
Configuration
"bluetooth": {
"enabled": true,
"deviceAddress": "00:11:22:33:44:55", // target device MAC
"channel": 1, // SPP channel number
"autoReconnect": true, // reconnect on drop
"reconnectIntervalMs": 5000, // retry delay (ms)
"sharedSecret": "my-secret-key" // optional PSK for encryption
}
CLI Commands
| Command | Description |
|---|---|
/comms bt scan | Scan for nearby Bluetooth devices |
/comms bt send <message> | Send message to the configured device |
/comms bt status | Show Bluetooth connection status |
Testing Steps
- Pair your Bluetooth device at the OS level.
- Find the MAC address:
/comms bt scan - Set
deviceAddressincomms.jsonand set"enabled": true. - Restart OCCode or run
/comms status— Bluetooth should show connected. - Send a test message:
/comms bt send Hello from OCCode! - Any inbound messages appear in the REPL automatically.
sharedSecret on both ends to enable AES-256-GCM encryption with HMAC-SHA256 challenge-response authentication.
5. WiFi LAN
WirelessRequirements
Package:
npm install bonjour-servicePeers: Other OCCode instances on the same LAN
Configuration
"wifiLan": {
"enabled": true,
"serviceName": "occode-comms", // mDNS service type (must match on all peers)
"port": 0, // 0 = auto-assign
"peerName": "my-laptop", // your display name on the LAN
"sharedSecret": "lan-secret" // optional PSK for encryption
}
CLI Commands
| Command | Description |
|---|---|
/comms wifi peers | List discovered LAN peers |
/comms wifi send <peer-name> <message> | Send to a specific peer |
/comms wifi status | Show WiFi LAN status and listening port |
Testing Steps
- Install
bonjour-serviceon two machines on the same network. - Set different
peerNamevalues and matchingserviceNameon each. - Enable and restart OCCode on both machines.
- Run
/comms wifi peers— you should see the other machine. - Send a message:
/comms wifi send other-laptop Hello! - The receiving machine sees the message in its REPL.
sharedSecret on both peers for AES-256-GCM encryption.
6. Modem SMS
SerialRequirements
Packages:
npm install serialport @serialport/parser-readlineSIM: Active SIM card with SMS plan
Configuration
"modemSms": {
"enabled": true,
"serialPort": "/dev/ttyUSB0", // serial port path (Linux/Mac)
"baudRate": 9600, // typical for GSM modems
"simPin": "1234", // SIM PIN (leave empty if none)
"pollIntervalMs": 10000 // poll for incoming SMS every 10s
}
CLI Commands
| Command | Description |
|---|---|
/comms modem ports | List available serial ports |
/comms modem send <phone> <message> | Send SMS to phone number |
/comms modem signal | Check GSM signal quality |
/comms modem status | Show modem connection status |
Testing Steps
- Plug in your USB modem and identify the serial port:
/comms modem ports - Set
serialPortincomms.json(e.g./dev/ttyUSB0on Linux,COM3on Windows). - If your SIM has a PIN, set
simPin. - Enable and restart. Check:
/comms modem status - Verify signal:
/comms modem signal(0–31 scale, higher is better). - Send a test SMS:
/comms modem send +15551234567 Test from OCCode - Reply to the modem's number — inbound SMS appear in the REPL within 10 seconds.
+15551234567
7. Telegram
InternetRequirements
Package:
npm install node-telegram-bot-apiSetup: Create a bot via @BotFather on Telegram
Configuration
"telegram": {
"enabled": true,
"botToken": "123456789:ABCDEfghijklmnopqrstuvwxyz1234567890",
"allowedChatIds": [123456789] // optional whitelist (empty = allow all)
}
CLI Commands
| Command | Description |
|---|---|
/comms tg send <chatId> <message> | Send message to a Telegram chat |
/comms tg status | Show Telegram bot status and username |
Testing Steps
- Open Telegram and message @BotFather. Send
/newbotand follow the prompts to get your bot token. - Set the
botTokenincomms.json. - Start OCCode. The bot connects via long polling — check:
/comms tg status - Find your chat ID: send any message to your bot on Telegram, then check the REPL — inbound messages show the
chatId. - Send a reply:
/comms tg send 123456789 Hello from OCCode! - Optionally add your chat ID to
allowedChatIdsto restrict who can message the bot.
123456789
8. Cloud SMS (Twilio)
InternetRequirements
Package:
npm install twilioSetup: Account SID, Auth Token, and a Twilio phone number
Configuration
"cloudSms": {
"enabled": true,
"accountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"authToken": "your_auth_token",
"fromNumber": "+15551234567", // your Twilio phone number
"webhookPort": 3100, // local webhook server port
"webhookUrl": "" // public URL for inbound SMS (optional)
}
CLI Commands
| Command | Description |
|---|---|
/comms sms send <phone> <message> | Send SMS via Twilio |
/comms sms status | Show Cloud SMS status and webhook URL |
Testing Steps
- Sign up at twilio.com and get your Account SID, Auth Token, and a phone number.
- Set credentials in
comms.json. - Enable and restart. Check:
/comms sms status - Send a test:
/comms sms send +15559876543 Hello from OCCode via Twilio! - For inbound SMS: Expose
webhookPort(default 3100) to the internet (e.g. via ngrok) and set the webhook URL in your Twilio console pointing tohttps://your-domain/sms.
+15559876543
9. LoRa Radio
RadioRequirements
Packages:
npm install serialport @serialport/parser-readlineRange: 2–15 km line-of-sight depending on power and antenna
Configuration
"lora": {
"enabled": true,
"serialPort": "/dev/ttyUSB0",
"baudRate": 115200,
"address": 1, // local node address (0-65535)
"networkId": 6, // network group (0-16, must match on all nodes)
"band": 915000000, // frequency in Hz (915 MHz for US, 868 MHz for EU)
"spreadingFactor": 12, // 7-12 (higher = longer range, slower speed)
"bandwidth": 7, // 0-9 (7 = 125 kHz, typical)
"codingRate": 1, // 1-4
"preamble": 4 // preamble length
}
CLI Commands
| Command | Description |
|---|---|
/comms lora send <address> <message> | Send to a LoRa node by address |
/comms lora info | Get module firmware and configuration |
/comms lora version | Get firmware version |
/comms lora status | Show LoRa connection status |
Testing Steps
- Connect your LoRa module via USB. Identify the port:
ls /dev/ttyUSB* - Set
serialPort,address,networkId, andbandincomms.json. - On the second LoRa node, set a different
addressbut the samenetworkIdandband. - Enable and restart on both machines. Check:
/comms lora status - Verify the module:
/comms lora info - Send a message:
/comms lora send 2 Hello from node 1! - Incoming messages include
rssiandsnrmetadata for signal quality diagnostics.
2 (range 0–65535)
10. Meshtastic Mesh
RadioRequirements
Package:
npm install @meshtastic/jsNetwork: Two or more Meshtastic devices for mesh communication
Configuration
"meshtastic": {
"enabled": true,
"serialPort": "/dev/ttyUSB0",
"baudRate": 115200,
"meshChannel": 0 // channel index (0 = default/primary)
}
CLI Commands
| Command | Description |
|---|---|
/comms mesh send <dest> <message> | Send to node number, name, or broadcast |
/comms mesh nodes | List discovered mesh nodes with battery, GPS, signal |
/comms mesh info | Show your device info and node number |
/comms mesh status | Show Meshtastic connection status |
Testing Steps
- Flash your devices with Meshtastic firmware (see meshtastic.org).
- Connect one device via USB and set
serialPortincomms.json. - Enable and restart. Check:
/comms mesh status - Power on a second Meshtastic device nearby — it joins the mesh automatically.
- Discover nodes:
/comms mesh nodes(shows name, battery, GPS coordinates, last heard). - Send to a specific node:
/comms mesh send MyDevice Hello mesh! - Broadcast to all:
/comms mesh send broadcast Anyone there?
- Node number:
1234567890 - Node name:
MyDevice - Broadcast:
broadcastor0xffffffff
11. Ham Radio
RadioRequirements
Software (optional): JS8Call for HF weak-signal mode, Hamlib/rigctld for radio control
Packages: None — uses Node.js built-in
net module
Configuration
"hamRadio": {
"enabled": true,
"callsign": "N0CALL", // your amateur radio callsign
"mode": "both", // "kiss", "js8call", or "both"
"kissHost": "127.0.0.1", // KISS TNC host (e.g. Direwolf)
"kissPort": 8001, // KISS TNC TCP port
"js8Host": "127.0.0.1", // JS8Call API host
"js8Port": 2442, // JS8Call API port
"rigctlEnabled": false, // enable Hamlib radio control
"rigctlHost": "127.0.0.1",
"rigctlPort": 4532
}
Two Operating Modes
A — KISS TNC / AX.25 Packet Radio
For VHF/UHF local-area packet radio. Connect to a software TNC like Direwolf or a hardware TNC. Messages are sent as AX.25 UI frames using standard KISS framing.
# Start Direwolf (example)
direwolf -t 0 -p # listens on TCP port 8001 by default
B — JS8Call / HF Weak-Signal
For long-distance HF messaging. Connect to the JS8Call application's TCP API. Messages use the TX.SEND_MESSAGE command and are received via RX.DIRECTED events. Metadata includes SNR, frequency, and offset.
# Start JS8Call, enable API in settings (default port 2442)
C — Radio Control (Hamlib / rigctld)
Optionally connect to rigctld to query and control the radio transceiver:
| Command | Description |
|---|---|
/comms ham freq | Get current frequency |
/comms ham freq 145500000 | Set frequency (Hz) |
/comms ham mode | Get current mode (FM, USB, LSB, etc.) |
/comms ham mode USB | Set mode |
/comms ham info | Get radio info and signal strength |
CLI Commands
| Command | Description |
|---|---|
/comms ham send <callsign> <message> | Send to a callsign via KISS or JS8Call |
/comms ham freq [frequency] | Get or set radio frequency |
/comms ham mode [mode] | Get or set radio mode |
/comms ham info | Get radio info |
/comms ham status | Show ham radio connection status |
Testing Steps (KISS TNC)
- Start Direwolf (or your TNC software) on
localhost:8001. - Set
callsignto your licensed callsign andmodeto"kiss". - Enable and restart. Check:
/comms ham status - Send a packet:
/comms ham send W1AW Hello from packet radio! - Inbound AX.25 frames are decoded and displayed in the REPL.
Testing Steps (JS8Call)
- Start JS8Call and enable the TCP API (Settings → Reporting → API, default port 2442).
- Set
callsignandmodeto"js8call". - Enable and restart. Check:
/comms ham status - Send a directed message:
/comms ham send W1AW Hello from JS8! - Received messages include SNR and frequency metadata.
W1AW or N0CALL-5 (with SSID)
12. Encryption
Two channels support optional end-to-end encryption via a pre-shared key (PSK):
| Channel | Encryption | How to Enable |
|---|---|---|
| Bluetooth | AES-256-GCM | Set "sharedSecret" in config |
| WiFi LAN | AES-256-GCM | Set "sharedSecret" in config |
| Meshtastic | AES-256 (built-in) | Always on — managed by device firmware |
| Others | Transport only | Use TLS/HTTPS at the transport layer |
How PSK Encryption Works
- Key derivation:
SHA-256(sharedSecret)produces a 256-bit key. - Authentication: On connect, a HMAC-SHA256 challenge-response verifies both sides share the same secret.
- Message encryption: Each message is encrypted with AES-256-GCM using a random 12-byte IV. The encrypted envelope contains
ciphertext,iv, andtag(all base64-encoded).
// Encrypted message envelope
{
"encrypted": true,
"ciphertext": "base64...",
"iv": "base64...",
"tag": "base64..."
}
sharedSecret value. Mismatched keys will cause authentication failure on connect.
13. Troubleshooting
General
| Symptom | Cause | Fix |
|---|---|---|
| Channel shows "unavailable" | Required npm package not installed | Install the package listed in the channel's Requirements section |
| Channel shows "disconnected" | Not enabled in config | Set "enabled": true in ~/.occode/comms.json |
| Channel shows "error" | Configuration issue or hardware not connected | Check logs and verify config values |
/comms not recognized | CLI not rebuilt after update | Run npm run build in the occode directory |
Bluetooth
| Issue | Solution |
|---|---|
| Cannot find device | Ensure the device is paired at the OS level and discoverable. Verify the MAC address with /comms bt scan. |
| "Permission denied" on Linux | Add your user to the bluetooth group: sudo usermod -aG bluetooth $USER |
| Auto-reconnect not working | Verify "autoReconnect": true and that the remote device is powered on and in range. |
WiFi LAN
| Issue | Solution |
|---|---|
| Peers not discovered | Ensure both machines are on the same subnet. Some networks block mDNS (port 5353/UDP). Try a direct IP test. |
| Firewall blocking | Allow inbound TCP on the assigned port (check with /comms wifi status). |
Modem SMS
| Issue | Solution |
|---|---|
| Serial port not found | Run /comms modem ports to list available ports. Check USB connection. |
| SIM PIN error | Verify the PIN in config or remove the PIN lock from the SIM using a phone. |
| Signal quality 0 | Try repositioning the modem or connecting an external antenna. |
| SMS not received | Increase pollIntervalMs or check that the SIM has an active plan. |
Telegram
| Issue | Solution |
|---|---|
| "401 Unauthorized" | Invalid bot token. Regenerate via @BotFather. |
| Messages not received | If allowedChatIds is set, make sure your chat ID is in the list. |
LoRa / Meshtastic
| Issue | Solution |
|---|---|
| No communication between nodes | Verify matching networkId and band on both LoRa modules. For Meshtastic, ensure both are on the same channel. |
| Very weak RSSI | Increase spreadingFactor (up to 12) for longer range at the cost of speed. Improve antenna placement. |
| Serial timeout | Check baud rate matches the module's setting (usually 115200 for LoRa, 115200 for Meshtastic). |
Ham Radio
| Issue | Solution |
|---|---|
| Cannot connect to KISS TNC | Verify Direwolf (or TNC) is running on the configured host:port. Check kissHost and kissPort. |
| Cannot connect to JS8Call | Enable the TCP API in JS8Call settings (Reporting → API). Default port is 2442. |
| rigctld unavailable | Start rigctld: rigctld -m <model> -r <device>. The ham channel works without it — radio control features are optional. |