- JavaScript 91.4%
- CSS 6.6%
- HTML 2%
Ground-up rewrite applying lessons from the sibling MeshGarmin port, replacing the previous multi-file app. - BLE (bluetooth.js): mozBluetooth scan + PIN pairing/bonding + GATT over the bonded (encrypted) link, so the app works with STOCK MeshCore firmware. This is the structural advantage over Garmin, whose Connect IQ apps can only open unencrypted BLE and thus cannot talk to a stock node. - Protocol (protocol.js): APP_START/SELF_INFO, DEVICE_QUERY/DEVICE_INFO, GET_CONTACTS/CONTACT, GET_CHANNEL/CHANNEL_INFO, SEND_TXT_MSG (direct), SEND_CHANNEL_TXT_MSG, SYNC_NEXT_MESSAGE, msg recv v0/v3, battery. - Paced retrying write queue + quiet-gap frame reassembly (carried from Garmin). - Receive via SYNC polling, with BLE notifications as an accelerator. - UI (app.js): 240x320 D-pad; connect/PIN/menu/channels/contacts/conversation/ compose/info screens; localStorage persistence (store.js). - mock.js fake node for full browser testing (no hardware). - Verified in browser mock mode; ESLint clean; no post-FF48 APIs. - Docs refreshed (README, CLAUDE, research note); stale old-app docs/logs removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| css | ||
| icons | ||
| js | ||
| screenshots | ||
| .gitignore | ||
| CLAUDE.md | ||
| eslint.config.mjs | ||
| index.html | ||
| manifest.webapp | ||
| MESHCORE_KAIOS_RESEARCH.md | ||
| README.md | ||
MeshCore for KaiOS
A vanilla-JS KaiOS app (Nokia 2720 Flip / 8110) that connects to a MeshCore companion radio (e.g. SenseCAP T1000-E) over Bluetooth LE and lets you chat on an off-grid LoRa mesh. Built fresh, applying the hard-won lessons from the sibling MeshGarmin project.
Nokia (KaiOS) ──BLE / Nordic UART (bonded, PIN)── T1000-E ──LoRa── MeshCore mesh
(BLE central) (companion fw)
Why KaiOS succeeds where Garmin couldn't
The MeshGarmin port hit a hard wall: MeshCore companion firmware demands an
MITM-encrypted (bonded) BLE link (SerialBLEInterface.cpp:
setMITM(true) + SECMODE_ENC_WITH_MITM). Garmin Connect IQ apps can only open
an Open / unencrypted connection (encrypted links are reserved for Garmin's
own accessories), so the watch's notify-enable write was rejected (d18) and no
messages flowed. The only Garmin fix was custom node firmware with open BLE.
KaiOS does not have that limit. navigator.mozBluetooth performs
system-level PIN pairing/bonding, and this privileged app then runs GATT over
that bonded (encrypted) link. So Nokia talks to stock MeshCore firmware — no
firmware changes needed. That is exactly goal #1: connect to a node using PIN
verification.
Features
- Connect with PIN — scan for MeshCore nodes, pair with the node's BLE PIN
(default
123456), bond, and connect over GATT. - Channels — lists channel slots (
GET_CHANNEL0–7); send/receive group messages. - Contacts — syncs the node's contact list (
GET_CONTACTS); send/receive direct messages. - Conversations — per-channel / per-contact history with unread badges,
persisted in
localStorage. - Device info — model, firmware, radio (freq/SF/CR), battery, storage, BLE
PIN (
DEVICE_QUERY+GET_BATT_AND_STORAGE).
Screen real estate is tight (240×320), so the UI is list-first: a single content pane, 3 softkeys, D-pad navigation, and a native input for keypad/T9 text entry.
File structure
KaiOS/
├── manifest.webapp privileged app, "bluetooth" permission
├── index.html header + content pane + softkey bar
├── css/app.css 240x320, D-pad, compact lists & bubbles
└── js/
├── utils.js bytes/UTF-8/hex/LE helpers
├── protocol.js MeshCore frame build + parse (commands/responses/pushes)
├── bluetooth.js mozBluetooth: scan, PIN pair, GATT, notify+poll,
│ paced write queue, frame reassembly. Mock-aware.
├── mock.js in-browser fake node (auto-used when no mozBluetooth)
├── store.js localStorage cache: contacts, channels, messages
└── app.js orchestrator: session flow, screens, keys, rendering
Carry-overs from the working Garmin code
- Paced, retrying write queue (
WRITE_PACE_MS, retries) — avoids the "not enough resources" failure seen on fast/early writes. - Frame reassembly via quiet-gap timer (
FRAME_GAP_MS = 40) — BLE delivers frames in MTU-sized notification chunks; the firmware spaces whole frames ≥60 ms apart, so a <60 ms gap marks a frame boundary. (148-byte contact frames need this on small-MTU links.) - Command-response sequencing —
APP_STARTis sent first; the rest of the session (time, device query, contacts, channels) starts only afterSELF_INFOarrives. - Polling for receive — KaiOS BLE notifications are unreliable on some
builds, so the app polls
SYNC_NEXT_MESSAGEevery 2.5 s; aMSG_WAITING(0x83) push just triggers an immediate sync when notifications do work.
Run in a browser (mock mode)
No hardware needed — when navigator.mozBluetooth is absent the app uses a fake
node (mock.js).
cd KaiOS
python3 -m http.server 8000
# open http://localhost:8000
# F1 = left softkey, Enter = center, F3 = right, arrows = D-pad
Mock mode exercises the full flow: scan → pair → session sync (2 contacts + Public channel) → open a conversation → write → send. An incoming message is injected ~8 s after connect.
Captured mock screens are in screenshots/ — connect, menu,
contacts, conversation (send + receive), compose, channels, and device info.
Deploy to a Nokia device
Uses the kdeploy tool (Firefox Remote Debugging Protocol over ADB):
# 1. Enable debugging on the phone: dial *#*#33284#*#*
# 2. Connect USB, then:
adb forward tcp:6000 localfilesystem:/data/local/debugger-socket
# 3. from a kdeploy checkout:
node main.js install "/path/to/this/repo" # manifest.webapp is at the repo root
node main.js list
node main.js launch "app://<app-id-from-list>/manifest.webapp"
(install may log a callback error but still install — verify with list.)
Alternatively use Firefox WebIDE → Open Packaged App → select this folder.
Pairing note: if the GATT connect is rejected for security, pair the node once via the phone's Settings → Bluetooth (enter the BLE PIN) so the bond exists, then connect from the app.
Protocol reference
- MeshCore firmware (authoritative): https://github.com/meshcore-dev/MeshCore
—
docs/companion_protocol.mdand command codes / contact frame inexamples/companion_radio/MyMesh.cpp. - Frame layouts cross-checked against the MeshGarmin
Protocol.mc. - Full protocol notes also in
CLAUDE.mdandMESHCORE_KAIOS_RESEARCH.md.
Status
Builds and runs fully in browser mock mode (verified: protocol round-trips,
frame reassembly, all screens render and navigate, send/receive stored). On-device
BLE (scan / PIN pair / GATT / notify) is written against the mozBluetooth API
but not yet tested on hardware — that is the next step.