OCCode CLI — Communications Guide

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
  }
}
Tip: You only need to include the channels you use. Omitted channels default to disabled.

4. Bluetooth

Wireless

Requirements

Hardware: Bluetooth adapter (built-in or USB dongle)
Package: npm install bluetooth-serial-port
Target: 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

CommandDescription
/comms bt scanScan for nearby Bluetooth devices
/comms bt send <message>Send message to the configured device
/comms bt statusShow Bluetooth connection status

Testing Steps

  1. Pair your Bluetooth device at the OS level.
  2. Find the MAC address: /comms bt scan
  3. Set deviceAddress in comms.json and set "enabled": true.
  4. Restart OCCode or run /comms status — Bluetooth should show connected.
  5. Send a test message: /comms bt send Hello from OCCode!
  6. Any inbound messages appear in the REPL automatically.
Encryption: Set sharedSecret on both ends to enable AES-256-GCM encryption with HMAC-SHA256 challenge-response authentication.

5. WiFi LAN

Wireless

Requirements

Hardware: WiFi or Ethernet connection to a local network
Package: npm install bonjour-service
Peers: 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

CommandDescription
/comms wifi peersList discovered LAN peers
/comms wifi send <peer-name> <message>Send to a specific peer
/comms wifi statusShow WiFi LAN status and listening port

Testing Steps

  1. Install bonjour-service on two machines on the same network.
  2. Set different peerName values and matching serviceName on each.
  3. Enable and restart OCCode on both machines.
  4. Run /comms wifi peers — you should see the other machine.
  5. Send a message: /comms wifi send other-laptop Hello!
  6. The receiving machine sees the message in its REPL.
Encryption: Set sharedSecret on both peers for AES-256-GCM encryption.

6. Modem SMS

Serial

Requirements

Hardware: USB GSM modem (Huawei E303, SIM800H, Quectel EC25, etc.)
Packages: npm install serialport @serialport/parser-readline
SIM: 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

CommandDescription
/comms modem portsList available serial ports
/comms modem send <phone> <message>Send SMS to phone number
/comms modem signalCheck GSM signal quality
/comms modem statusShow modem connection status

Testing Steps

  1. Plug in your USB modem and identify the serial port: /comms modem ports
  2. Set serialPort in comms.json (e.g. /dev/ttyUSB0 on Linux, COM3 on Windows).
  3. If your SIM has a PIN, set simPin.
  4. Enable and restart. Check: /comms modem status
  5. Verify signal: /comms modem signal (0–31 scale, higher is better).
  6. Send a test SMS: /comms modem send +15551234567 Test from OCCode
  7. Reply to the modem's number — inbound SMS appear in the REPL within 10 seconds.
Recipient format: International phone number with country code, e.g. +15551234567

7. Telegram

Internet

Requirements

Account: Telegram account
Package: npm install node-telegram-bot-api
Setup: Create a bot via @BotFather on Telegram

Configuration

"telegram": {
  "enabled": true,
  "botToken": "123456789:ABCDEfghijklmnopqrstuvwxyz1234567890",
  "allowedChatIds": [123456789]   // optional whitelist (empty = allow all)
}

CLI Commands

CommandDescription
/comms tg send <chatId> <message>Send message to a Telegram chat
/comms tg statusShow Telegram bot status and username

Testing Steps

  1. Open Telegram and message @BotFather. Send /newbot and follow the prompts to get your bot token.
  2. Set the botToken in comms.json.
  3. Start OCCode. The bot connects via long polling — check: /comms tg status
  4. Find your chat ID: send any message to your bot on Telegram, then check the REPL — inbound messages show the chatId.
  5. Send a reply: /comms tg send 123456789 Hello from OCCode!
  6. Optionally add your chat ID to allowedChatIds to restrict who can message the bot.
Recipient format: Numeric chat ID as a string, e.g. 123456789

8. Cloud SMS (Twilio)

Internet

Requirements

Account: Twilio account (paid)
Package: npm install twilio
Setup: 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

CommandDescription
/comms sms send <phone> <message>Send SMS via Twilio
/comms sms statusShow Cloud SMS status and webhook URL

Testing Steps

  1. Sign up at twilio.com and get your Account SID, Auth Token, and a phone number.
  2. Set credentials in comms.json.
  3. Enable and restart. Check: /comms sms status
  4. Send a test: /comms sms send +15559876543 Hello from OCCode via Twilio!
  5. For inbound SMS: Expose webhookPort (default 3100) to the internet (e.g. via ngrok) and set the webhook URL in your Twilio console pointing to https://your-domain/sms.
Recipient format: International phone number with country code, e.g. +15559876543

9. LoRa Radio

Radio

Requirements

Hardware: LoRa radio module — RYLR896, RYLR998, RAK4200, or RN2483 connected via USB/serial
Packages: npm install serialport @serialport/parser-readline
Range: 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

CommandDescription
/comms lora send <address> <message>Send to a LoRa node by address
/comms lora infoGet module firmware and configuration
/comms lora versionGet firmware version
/comms lora statusShow LoRa connection status

Testing Steps

  1. Connect your LoRa module via USB. Identify the port: ls /dev/ttyUSB*
  2. Set serialPort, address, networkId, and band in comms.json.
  3. On the second LoRa node, set a different address but the same networkId and band.
  4. Enable and restart on both machines. Check: /comms lora status
  5. Verify the module: /comms lora info
  6. Send a message: /comms lora send 2 Hello from node 1!
  7. Incoming messages include rssi and snr metadata for signal quality diagnostics.
Recipient format: Node address as a number, e.g. 2 (range 0–65535)

10. Meshtastic Mesh

Radio

Requirements

Hardware: Meshtastic-compatible device — T-Beam, Heltec LoRa 32, RAK WisBlock, etc.
Package: npm install @meshtastic/js
Network: 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

CommandDescription
/comms mesh send <dest> <message>Send to node number, name, or broadcast
/comms mesh nodesList discovered mesh nodes with battery, GPS, signal
/comms mesh infoShow your device info and node number
/comms mesh statusShow Meshtastic connection status

Testing Steps

  1. Flash your devices with Meshtastic firmware (see meshtastic.org).
  2. Connect one device via USB and set serialPort in comms.json.
  3. Enable and restart. Check: /comms mesh status
  4. Power on a second Meshtastic device nearby — it joins the mesh automatically.
  5. Discover nodes: /comms mesh nodes (shows name, battery, GPS coordinates, last heard).
  6. Send to a specific node: /comms mesh send MyDevice Hello mesh!
  7. Broadcast to all: /comms mesh send broadcast Anyone there?
Built-in encryption: Meshtastic uses AES-256 encryption on all traffic by default. No extra configuration needed.
Recipient formats:
  • Node number: 1234567890
  • Node name: MyDevice
  • Broadcast: broadcast or 0xffffffff

11. Ham Radio

Radio
License required: Transmitting on amateur radio frequencies requires a valid amateur radio license issued by your country's regulatory authority (e.g. FCC in the US, OFCOM in the UK). Receiving is generally unrestricted.

Requirements

Hardware: Amateur radio transceiver + TNC (hardware or software like Direwolf)
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:

CommandDescription
/comms ham freqGet current frequency
/comms ham freq 145500000Set frequency (Hz)
/comms ham modeGet current mode (FM, USB, LSB, etc.)
/comms ham mode USBSet mode
/comms ham infoGet radio info and signal strength

CLI Commands

CommandDescription
/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 infoGet radio info
/comms ham statusShow ham radio connection status

Testing Steps (KISS TNC)

  1. Start Direwolf (or your TNC software) on localhost:8001.
  2. Set callsign to your licensed callsign and mode to "kiss".
  3. Enable and restart. Check: /comms ham status
  4. Send a packet: /comms ham send W1AW Hello from packet radio!
  5. Inbound AX.25 frames are decoded and displayed in the REPL.

Testing Steps (JS8Call)

  1. Start JS8Call and enable the TCP API (Settings → Reporting → API, default port 2442).
  2. Set callsign and mode to "js8call".
  3. Enable and restart. Check: /comms ham status
  4. Send a directed message: /comms ham send W1AW Hello from JS8!
  5. Received messages include SNR and frequency metadata.
Recipient format: Callsign, e.g. W1AW or N0CALL-5 (with SSID)

12. Encryption

Two channels support optional end-to-end encryption via a pre-shared key (PSK):

ChannelEncryptionHow to Enable
BluetoothAES-256-GCMSet "sharedSecret" in config
WiFi LANAES-256-GCMSet "sharedSecret" in config
MeshtasticAES-256 (built-in)Always on — managed by device firmware
OthersTransport onlyUse TLS/HTTPS at the transport layer

How PSK Encryption Works

  1. Key derivation: SHA-256(sharedSecret) produces a 256-bit key.
  2. Authentication: On connect, a HMAC-SHA256 challenge-response verifies both sides share the same secret.
  3. Message encryption: Each message is encrypted with AES-256-GCM using a random 12-byte IV. The encrypted envelope contains ciphertext, iv, and tag (all base64-encoded).
// Encrypted message envelope
{
  "encrypted": true,
  "ciphertext": "base64...",
  "iv": "base64...",
  "tag": "base64..."
}
Important: Both endpoints must use the exact same sharedSecret value. Mismatched keys will cause authentication failure on connect.

13. Troubleshooting

General

SymptomCauseFix
Channel shows "unavailable"Required npm package not installedInstall the package listed in the channel's Requirements section
Channel shows "disconnected"Not enabled in configSet "enabled": true in ~/.occode/comms.json
Channel shows "error"Configuration issue or hardware not connectedCheck logs and verify config values
/comms not recognizedCLI not rebuilt after updateRun npm run build in the occode directory

Bluetooth

IssueSolution
Cannot find deviceEnsure the device is paired at the OS level and discoverable. Verify the MAC address with /comms bt scan.
"Permission denied" on LinuxAdd your user to the bluetooth group: sudo usermod -aG bluetooth $USER
Auto-reconnect not workingVerify "autoReconnect": true and that the remote device is powered on and in range.

WiFi LAN

IssueSolution
Peers not discoveredEnsure both machines are on the same subnet. Some networks block mDNS (port 5353/UDP). Try a direct IP test.
Firewall blockingAllow inbound TCP on the assigned port (check with /comms wifi status).

Modem SMS

IssueSolution
Serial port not foundRun /comms modem ports to list available ports. Check USB connection.
SIM PIN errorVerify the PIN in config or remove the PIN lock from the SIM using a phone.
Signal quality 0Try repositioning the modem or connecting an external antenna.
SMS not receivedIncrease pollIntervalMs or check that the SIM has an active plan.

Telegram

IssueSolution
"401 Unauthorized"Invalid bot token. Regenerate via @BotFather.
Messages not receivedIf allowedChatIds is set, make sure your chat ID is in the list.

LoRa / Meshtastic

IssueSolution
No communication between nodesVerify matching networkId and band on both LoRa modules. For Meshtastic, ensure both are on the same channel.
Very weak RSSIIncrease spreadingFactor (up to 12) for longer range at the cost of speed. Improve antenna placement.
Serial timeoutCheck baud rate matches the module's setting (usually 115200 for LoRa, 115200 for Meshtastic).

Ham Radio

IssueSolution
Cannot connect to KISS TNCVerify Direwolf (or TNC) is running on the configured host:port. Check kissHost and kissPort.
Cannot connect to JS8CallEnable the TCP API in JS8Call settings (Reporting → API). Default port is 2442.
rigctld unavailableStart rigctld: rigctld -m <model> -r <device>. The ham channel works without it — radio control features are optional.