Skip to content

Latest commit

 

History

History
206 lines (158 loc) · 6.8 KB

File metadata and controls

206 lines (158 loc) · 6.8 KB

Device Protocol

Neon Meter accepts the same compact provider payload over USB serial and BLE. USB serial is preferred by the host app when a CoreS3 is connected by cable; BLE remains the wireless fallback.

USB Serial Protocol

USB uses the CoreS3 CDC serial interface at 115200 baud. Hosts must set DTR high so ESP32-S3 USB CDC delivers serial traffic, and should keep RTS low to avoid toggling the boot/reset line. Frames are UTF-8 JSON objects delimited by \n. The firmware may also print diagnostic text on the same serial stream, so hosts must ignore non-JSON lines and unknown JSON frames.

Handshake

The host probes a serial port by writing:

{"type":"hello","protocol":"neon-meter-usb","version":1}

The firmware responds:

{"type":"hello","protocol":"neon-meter-usb","version":1,"device":"Neon Meter","firmwareVersion":"1.0.8","chipFamily":"ESP32-S3","capabilities":["ble-repair"]}

After a successful handshake, the host sends a heartbeat every 5 seconds:

{"type":"ping","protocol":"neon-meter-usb","version":1}

The firmware does not answer ping. Any valid inbound USB protocol frame keeps the USB app connection active; if no such frame arrives for more than 15 seconds, the device clears the USB connected state and returns to its normal BLE or waiting status.

Payload

The host writes the provider bundle inside a typed payload frame:

{"type":"payload","payload":{"rotationSeconds":30,"providers":[]}}

For compatibility, the firmware also accepts a raw provider bundle or raw single-provider object as one newline-delimited JSON line.

USB Control Frames

The firmware sends these control frames:

Frame Meaning
{"type":"ack","ack":true} Payload accepted.
{"type":"err","err":true} Payload rejected or failed to parse.
{"type":"refresh-requested"} Host should send a fresh provider bundle.

BLE Pairing Repair

Firmware that lists ble-repair in the hello capabilities array accepts this request over USB:

{"type":"ble-repair"}

Before making any Bluetooth changes, the firmware acknowledges the request:

{"type":"ble-repair-accepted","ok":true}

It then clears stored peer bonds, generates and persists a new random-static BLE identity, and restarts. The new identity makes the meter discoverable as a fresh peripheral when the computer still retains an obsolete pairing record. Hosts must treat this command as optional for compatibility with older firmware.

Service

The firmware advertises as Neon Meter and exposes a custom BLE GATT service:

Item UUID
Data Service 41494d45-7465-7220-0000-000000000001
RX write characteristic 41494d45-7465-7220-0000-000000000002
TX notify/read characteristic 41494d45-7465-7220-0000-000000000003
Refresh notify characteristic 41494d45-7465-7220-0000-000000000004
Firmware metadata read characteristic 41494d45-7465-7220-0000-000000000005

The UUIDs are stable for host compatibility.

The host writes a compact UTF-8 JSON payload to RX. The device notifies TX with {"ack":true} or {"err":true}. When the host subscribes to the refresh characteristic and the device has no data yet, the device sends a one-byte notification to request a fresh payload.

Newer firmware also exposes a read-only metadata characteristic:

{"firmwareVersion":"1.0.8","chipFamily":"ESP32-S3"}

Hosts should treat this metadata as optional so older firmware remains connectable.

Provider Bundle

Current Neon Meter hosts send one bundle with every detected provider. If the bundle contains one provider, the device keeps showing that provider. If it contains two, the device rotates between cached provider screens using rotationSeconds, which defaults to 30.

{
  "rotationSeconds": 30,
  "providers": [
    {
      "p": "claude",
      "title": "Claude Code",
      "se": true,
      "s": 29,
      "sl": "Session",
      "sr": 142,
      "w": 4,
      "wl": "Weekly",
      "wr": 9730,
      "st": "allowed",
      "detail": "5h 29% / 7d 4%",
      "ok": true
    },
    {
      "p": "chatgpt",
      "title": "ChatGPT",
      "se": true,
      "s": 55,
      "sl": "Session",
      "sr": 180,
      "w": 40,
      "wl": "Weekly",
      "wr": 10080,
      "st": "ok",
      "detail": "5h 55% / 7d 40%",
      "ok": true
    }
  ]
}

Single compact provider objects remain supported for compatibility.

Payload Fields

Field Type Meaning
p string Provider key, such as claude, chatgpt, or host. Defaults to claude for Clawdmeter compatibility.
title string Optional UI title. Defaults from provider.
se boolean Whether a real Session window is available. Missing means true; false hides Session everywhere.
s number Primary/current usage percent, clamped to 0-100.
sl string Primary/current panel label. Defaults to Current.
sr number Minutes until primary/current window reset. 0 means the reset is due, and -1 means unknown.
w number Secondary/weekly/monthly usage percent, clamped to 0-100.
wl string Secondary panel label. Defaults to Weekly.
wr number Minutes until secondary window reset. 0 means the reset is due, and -1 means unknown.
st string Provider status, such as ok, allowed, limited, or error.
detail string Optional short status/spend note.
ok boolean Whether the host-side fetch succeeded.

The s and w fields remain consumed usage percentages for host compatibility. The firmware displays them as remaining gauge capacity: 100 - s and 100 - w.

When Codex has switched to Luna Reserve, the host sends the active reserve pool as a single-window payload. It uses title: "Luna-Reserve", se: false, and the reserve pool's consumed percentage and reset time in w, wl, and wr. The firmware hides the ordinary Session panel and displays the reserve pool as the remaining-capacity gauge. This does not treat the reserve pool as an ordinary weekly limit.

Examples

Claude-compatible:

{"s":29,"sr":142,"w":4,"wr":9730,"st":"allowed","ok":true}

ChatGPT/Codex:

{"p":"chatgpt","title":"ChatGPT","se":true,"s":55,"sl":"Session","sr":180,"w":40,"wl":"Weekly","wr":10080,"st":"ok","detail":"5h 55% / 7d 40%","ok":true}

ChatGPT/Codex with no Session window:

{"p":"chatgpt","title":"ChatGPT","se":false,"s":0,"sl":"Session","sr":-1,"w":52,"wl":"Weekly","wr":7942,"st":"ok","detail":"7d 52%","ok":true}

The host classifies exact 18000-second Session and 604800-second Weekly windows by duration rather than primary_window or secondary_window position. Updated firmware hides the complete Session panel while se is false, moves Weekly into the upper panel slot, and restores both panels when a Session window returns.