Skip to content

Instantly share code, notes, and snippets.

@GOROman
Created August 7, 2026 07:15
Show Gist options
  • Select an option

  • Save GOROman/2044c3748f4d0c71fcb11b99666f09d5 to your computer and use it in GitHub Desktop.

Select an option

Save GOROman/2044c3748f4d0c71fcb11b99666f09d5 to your computer and use it in GitHub Desktop.
Codex Micro LED制御プロトコル解析

Codex Micro LED制御プロトコル解析

解析日: 2026-08-07
対象: Codexデスクトップアプリ + Work Louder Codex Micro(BLE HID)
目的: 6個のエージェントキーと、キー照明・外周照明を互換デバイスから制御する

結論

Codex MicroのLED制御は、独自UUIDのBLEサービスではない。標準のBLE HID Service(HOGP)内にある Report Characteristic を使い、Vendor HID Report ID 6 の中へJSON-RPCを分割して送っている。

6個のエージェントキーはスロット 0...5 に対応し、個別の色・明るさ・エフェクトは次のRPCで更新される。

  • v.oai.thstatus: 6個のエージェントキーの個別LED
  • v.oai.rgbcfg: 通常キーのバックライトと外周アンビエントLED

重要なのはBLE Characteristic UUIDだけでなく、0x2908 Report Reference Descriptorで Report ID 6 / Output Report を選ぶことである。

BLE GATT / HID構造

項目
BLE Service HID Service 0x1812
Characteristic Report 0x2A4D
Descriptor Report Reference 0x2908
LED制御のReport Reference値 06 02
Report ID 6
Report Type 2 = Output
Reportデータ長 63 bytes(Report IDを除く)
Usage Page 0xFF00(Vendor Defined)
Output Usage 0x03

0x2A4Dは同じHID Service内に複数存在するため、UUIDだけでは特定できない。各Characteristicの0x2908を読み、次の2バイトで識別する。

0x2908 用途
06 01 Report ID 6 / Input。デバイスからCodexへの通知・RPC応答
06 02 Report ID 6 / Output。CodexからデバイスへのRPC・LED制御
06 03 Report ID 6 / Feature

Output Characteristicは通常、ReadWriteWrite Without Responseを持つ。実際のAttribute Handleは接続・GATT実装ごとに変わるため固定してはいけない。

Vendor部分のHID Report Mapは、概念的には次の構成になる。

Usage Page (Vendor 0xFF00)
Usage 0x01
Collection (Application)
  Report ID 6
  Input   Usage 0x02, 63 bytes
  Output  Usage 0x03, 63 bytes
  Feature Usage 0x04, 63 bytes
End Collection

macOSではHID ServiceがOSに管理されるため、CoreBluetoothから直接アクセスしにくい場合がある。その場合はIOHIDManagerまたはnode-hidでReport ID 6を開く方が確実である。

HID上のパケット形式

1回のHID Reportは64 bytesである。node-hidの書き込みバッファでは先頭にReport IDが付く。

byte 0      Report ID = 0x06
byte 1      Channel   = 0x02 (JSON-RPC)
byte 2      このパケットに入っているJSON断片の長さ = 0...61
byte 3...63 UTF-8 JSON断片(最大61 bytes)

BLE GATTの0x2A4Dへ直接書く場合、Report IDは0x2908で決まるため、Characteristic Valueは通常次の63 bytesになる。

byte 0      Channel = 0x02
byte 1      JSON断片長
byte 2...62 UTF-8 JSON断片

JSONが61 bytesを超える場合は複数Reportへ連続分割する。JSON自体に分割番号や終端記号はなく、受信側は断片を連結し、完全なJSONとしてparseできた時点で1リクエストと判定する。

RPCリクエストの基本形は次の通り。

{
  "method": "v.oai.thstatus",
  "params": [],
  "id": 123
}

6個のエージェントキー

物理キーのイベント名はAG00AG05、LED更新時のスロットIDは05である。

キー LEDスロット
AG00 id: 0
AG01 id: 1
AG02 id: 2
AG03 id: 3
AG04 id: 4
AG05 id: 5

v.oai.thstatus

Codexアプリは6スロットの状態を短縮フィールド名で送る。

フィールド 意味
id スロット番号 0...5
c Packed RGB 0xRRGGBBを整数化
b 明るさ 0.0...1.0
e LEDエフェクト 下表参照
s アニメーション速度 0.0...1.0
sk 通常キー照明と同期 0 / 1
sa アンビエント照明と同期 0 / 1

Codexアプリが使用する状態色は次の通り。

Codex状態 10進数 RGB
working 0x304FFE 3166206 48, 79, 254
unread 0x00FF4C 65356 0, 255, 76
idle 0xFFFFFF 16777215 255, 255, 255
awaiting-approval 0xFF6D00 16739584 255, 109, 0
awaiting-response 0xFF6D00 16739584 255, 109, 0
error 0xFF0033 16711731 255, 0, 51
off 0x000000 0 0, 0, 0

awaiting-approvalawaiting-responseは同じオレンジ色なので、LED表示としては6色(青・緑・白・オレンジ・赤・消灯)になる。

Codexアプリのエフェクト選択

  • off: e=0, b=0, s=0
  • 通常表示: e=1(solid), s=0
  • 選択中またはpulse中: e=4(breath), s=0.4
  • bにはCodex設定画面の明るさが0.0...1.0で入る
  • 現在のCodexアプリはsk=0, sa=0を送る

6スロットを一括更新する例:

{
  "method": "v.oai.thstatus",
  "params": [
    { "id": 0, "c": 3166206,  "b": 1, "e": 4, "s": 0.4, "sk": 0, "sa": 0 },
    { "id": 1, "c": 65356,    "b": 1, "e": 1, "s": 0,   "sk": 0, "sa": 0 },
    { "id": 2, "c": 16777215, "b": 1, "e": 1, "s": 0,   "sk": 0, "sa": 0 },
    { "id": 3, "c": 16739584, "b": 1, "e": 1, "s": 0,   "sk": 0, "sa": 0 },
    { "id": 4, "c": 16711731, "b": 1, "e": 1, "s": 0,   "sk": 0, "sa": 0 },
    { "id": 5, "c": 0,        "b": 0, "e": 0, "s": 0,   "sk": 0, "sa": 0 }
  ],
  "id": 123
}

外周・通常キー照明

v.oai.rgbcfg

このRPCは6個のエージェントキーとは別に、通常キー照明と外周アンビエント照明を設定する。

{
  "method": "v.oai.rgbcfg",
  "params": {
    "ambient": { "e": 2, "b": 1, "s": 0.4, "m": 0, "c": 3166206 },
    "keys":    { "e": 0, "b": 0, "s": 0,   "m": 0, "c": 0 }
  },
  "id": 124
}
フィールド 意味
ambient 外周LED
keys 通常キーのバックライト
e エフェクト番号
b 明るさ 0.0...1.0
s 速度 0.0...1.0
m エフェクト固有パラメータ
c Packed RGB整数

エフェクト番号

名前 動作
0 off 消灯
1 solid 単色点灯
2 snake 色のセグメントが移動
3 rainbow 虹色循環
4 breath 明滅
5 gradient グラデーション
6 shallowBreath 50%〜100%付近で浅く明滅

Codexアプリ固有の表示ルール

  • 選択中スロットがworkingなら、外周はそのスロット色のsnake、速度0.4
  • 選択直後の約4秒間は、通常キーも選択スロットまたは外周と同じ色でsolid
  • 音声録音中は外周が0x2E8B57(SeaGreen)のsnake
  • 音声処理中は外周が白のsnake
  • 音声完了時は外周が白のsolid
  • 非アクティブタイムアウト時はrgbcfgthstatusの両方を消灯設定にする

必須RPC応答

Codexアプリは照明RPCへの応答を待ってから次のコマンドを送る。互換デバイス側が応答しないとタイムアウトし、未検出・再接続・照明停止の原因になる。

応答はReport ID 6のInput Report(0x2908 = 06 01)で返し、JSON末尾にCRLFを付ける。

{"result":{"ok":1},"id":123,"method":"v.oai.thstatus"}\r\n
{"result":{"ok":1},"id":124,"method":"v.oai.rgbcfg"}\r\n

応答側の63-byte payloadも同じ形式である。

byte 0      Channel = 0x02
byte 1      UTF-8断片長(最大61)
byte 2...62 JSON応答断片

複数パケットになる場合は、前のBLE notificationが送信キューを抜ける時間を確保して順番にnotifyする。

実装上の注意

  1. 0x2A4Dは複数ある。0x2908 = 06 02でOutput Reportを選ぶ。
  2. GATT Handleは固定しない。Service Discovery後に毎回解決する。
  3. Report IDをCharacteristic Valueへ含めるかどうかはAPI層で異なる。
    • node-hid.write: 先頭に0x06を含む64 bytes
    • BLE GATT直接write: 0x06を除く63 bytes
  4. 61-byte境界でUTF-8を分割する。現在のフィールド値はASCII中心だが、一般化する場合はバイト列で分割する。
  5. 受信JSONは複数Reportを連結してからparseする。
  6. v.oai.thstatusv.oai.rgbcfgの両方へ必ず同じidを付けて応答する。
  7. HID Report ID 6のInput notificationを有効にしてからRPCを開始する。

解析の確度

以下はCodexデスクトップアプリに同梱されたWork LouderデバイスSDKの通信形式、Codex側の照明状態生成ロジック、実機互換HID Report Map、およびM5Stack Core2互換実装での送受信確認を突き合わせた結果である。

  • Report ID 6、63-byte Vendor Input/Output/Feature: 確認済み
  • JSON-RPCフレーミングと61-byte分割: 確認済み
  • v.oai.thstatus / v.oai.rgbcfgのフィールド: 確認済み
  • 6スロットの状態色とエフェクト選択: Codexアプリ実装から確認済み
  • BLE側のCharacteristic識別: HOGP Report Characteristic + Report Reference仕様、および互換GATT実装から確認
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment