Chat

Welcome to Flux

Treat Flux like your intern. Tell it what you'd like to build and it'll get to work. You can also ask it questions, brainstorm ideas, and teach it your preferences. Learn More

Explain project
Write a project description
Find schematic issues
Create a test plan
Simulate a circuit
Prepare for Manufacturing
Component Selection Workflow
New Subsystem Wizard
Write I2C initialization code
Generate firmware skeleton

Refine this doc
Ask about this doc
Web Configurator Architecture
Product: Black Magick Heavy Industries KYB1
Model: BLKMGK-KYB1-BASIC
Target hardware: 69 keys, XIAO ESP32-C5, RAK3112, addressable RGB, microSD, dual USB-C, and three SMA connectors.
Purpose and scope
A browser-based, local-first configurator for the 69-key XIAO ESP32-C5 keyboard. The first transport is WebSerial over the XIAO USB connection. Web Bluetooth may be added later without changing the application data model or command semantics.
The app must work without a cloud account: static HTML/TypeScript assets may be hosted locally or as a PWA; configuration remains on the user's computer and device unless explicitly exported.
System architecture
  1. UI/PWA — TypeScript, Vite, React or Lit; keyboard layout editor, layers/macros, RGB, storage/update, USB power status, and RAK radio pages.
  2. Domain model — JSON Schema validation (Ajv), migrations, defaults, diff generation, and safety policy.
  3. Transport adapterWebSerialTransport initially; later WebBluetoothTransport with the same request/response/event API.
  4. Protocol codec — incremental byte parser, framing, CRC, request IDs, timeouts, and chunking.
  5. Firmware service — validates, stages, commits, and reports effective (clamped) settings. It never trusts browser-side validation alone.
  6. Persistence — NVS for active metadata/safety defaults; microSD for keymaps, macros, effects, backups, and update images.
Versioned configuration schema
Canonical media type: application/vnd.blkmgk.kyb1.config+json; current schemaVersion is 1. Unknown required fields reject; unknown optional fields are preserved when possible for forward compatibility.

JSON


{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://kyb1.local/schema/config-v1.json",
  "type": "object",
  "required": ["schemaVersion", "device", "layers", "rgb", "power", "storage", "rak3112"],
  "properties": {
    "schemaVersion": {"const": 1},
    "revision": {"type": "integer", "minimum": 0},
    "device": {
      "type": "object",
      "required": ["keyCount", "matrix"],
      "properties": {
        "keyCount": {"const": 69},
        "matrix": {
          "type": "object",
          "required": ["rows", "columns", "positions"],
          "properties": {
            "rows": {"const": 6}, "columns": {"const": 13},
            "positions": {
              "type": "array", "minItems": 78, "maxItems": 78,
              "items": {"anyOf": [{"type": "integer", "minimum": 0, "maximum": 68}, {"type": "null"}]}
            }
          }
        }
      }
    },
    "layers": {
      "type": "array", "minItems": 1, "maxItems": 16,
      "items": {
        "type": "object", "required": ["id", "name", "keys"],
        "properties": {
          "id": {"type": "integer", "minimum": 0, "maximum": 15},
          "name": {"type": "string", "minLength": 1, "maxLength": 32},
          "keys": {
            "type": "array", "minItems": 69, "maxItems": 69,
            "items": {
              "type": "object", "required": ["type"],
              "properties": {
                "type": {"enum": ["hid", "consumer", "layerMomentary", "layerToggle", "macro", "disabled"]},
                "usage": {"type": "integer", "minimum": 0, "maximum": 65535},
                "modifiers": {"type": "integer", "minimum": 0, "maximum": 255},
                "targetLayer": {"type": "integer", "minimum": 0, "maximum": 15},
                "macroId": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,32}$"}
              }
            }
          }
        }
      }
    },
    "macros": {
      "type": "array", "maxItems": 128,
      "items": {
        "type": "object", "required": ["id", "steps"],
        "properties": {
          "id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,32}$"},
          "steps": {"type": "array", "maxItems": 256,
            "items": {"type": "object", "required": ["op"],
              "properties": {
                "op": {"enum": ["press", "release", "tap", "delayMs", "text"]},
                "usage": {"type": "integer", "minimum": 0, "maximum": 65535},
                "value": {"type": "integer", "minimum": 0, "maximum": 5000},
                "text": {"type": "string", "maxLength": 256}
              }
            }
          }
        }
      }
    },
    "rgb": {
      "type": "object", "required": ["enabled", "brightness", "currentLimitmA", "effect", "colors"],
      "properties": {
        "enabled": {"type": "boolean"},
        "brightness": {"type": "integer", "minimum": 0, "maximum": 255},
        "currentLimitmA": {"type": "integer", "minimum": 50, "maximum": 1200},
        "effect": {"enum": ["off", "solid", "perKey", "breathing", "rainbow", "reactive"]},
        "speed": {"type": "integer", "minimum": 0, "maximum": 255},
        "colors": {"type": "array", "minItems": 69, "maxItems": 69,
          "items": {"type": "string", "pattern": "^#[0-9A-Fa-f]{6}$"}}
      }
    },
    "power": {
      "type": "object", "required": ["safeStartup", "defaultSourceRgbLimitmA", "source1_5ALimitmA", "source3ALimitmA"],
      "properties": {
        "safeStartup": {"const": true},
        "defaultSourceRgbLimitmA": {"type": "integer", "minimum": 0, "maximum": 250},
        "source1_5ALimitmA": {"type": "integer", "minimum": 0, "maximum": 700},
        "source3ALimitmA": {"type": "integer", "minimum": 0, "maximum": 1200}
      }
    },
    "storage": {
      "type": "object", "properties": {
        "configPath": {"const": "/kyb1/config.json"},
        "backupCount": {"type": "integer", "minimum": 1, "maximum": 8},
        "allowWebFileWrite": {"type": "boolean"}
      }
    },
    "rak3112": {
      "type": "object", "required": ["uartBaud", "wifi", "lora"],
      "properties": {
        "uartBaud": {"enum": [9600, 57600, 115200, 230400, 460800, 921600]},
        "wifi": {"type": "object", "properties": {
          "enabled": {"type": "boolean"}, "ssid": {"type": "string", "maxLength": 32},
          "credentialRef": {"type": ["string", "null"], "maxLength": 32}
        }},
        "lora": {"type": "object", "properties": {
          "enabled": {"type": "boolean"}, "region": {"enum": ["EU868", "US915", "AU915", "AS923", "IN865", "KR920", "RU864"]},
          "frequencyHz": {"type": "integer", "minimum": 150000000, "maximum": 960000000},
          "txPowerDbm": {"type": "integer", "minimum": 0, "maximum": 22},
          "bandwidthHz": {"enum": [7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000]},
          "spreadingFactor": {"type": "integer", "minimum": 5, "maximum": 12},
          "codingRate": {"type": "integer", "minimum": 5, "maximum": 8}
        }}
      }
    }
  }
}
The app also stores UI-only metadata (key labels, coordinates, theme) outside the device schema. Secrets are never exported in clear text: credentialRef points to firmware-secured storage and reads return only configured: true/false.
WebSerial binary framing
All multibyte integers are little-endian.

Table


FieldBytesMeaning
SOF20x4B 0x44 (KD)
version1protocol version, initially 1
flags1bit0 response, bit1 event, bit2 error, bit3 more chunks
requestId2browser-selected nonzero ID; 0 for unsolicited event
command1command ID
payloadLength20..4096
payloadNCBOR preferred; UTF-8 JSON allowed during bring-up
CRC32C4version through payload
Parser requirements: resynchronize on SOF, reject oversize frames before allocation, 2 s request timeout (long operations emit progress), maximum four outstanding requests, and chunked transfer with monotonically increasing chunk index and final SHA-256.
Command set
  • 0x01 HELLO — protocol/device/firmware/schema versions, capabilities, serial number hash.
  • 0x02 GET_STATUS — uptime, reset reason, power mode, effective RGB clamp, SD state, RAK-ready state, active revision.
  • 0x10 GET_CONFIG / 0x11 VALIDATE_CONFIG / 0x12 STAGE_CONFIG / 0x13 COMMIT_CONFIG / 0x14 ROLLBACK_CONFIG.
  • 0x20 MATRIX_LIVE — start/stop debounced 69-key event stream; never inject HID from browser traffic.
  • 0x21 SET_PREVIEW_RGB / 0x22 STOP_PREVIEW_RGB — volatile, timeout-limited preview.
  • 0x30 SD_LIST / 0x31 SD_READ / 0x32 SD_WRITE_BEGIN / 0x33 SD_WRITE_CHUNK / 0x34 SD_WRITE_COMMIT / 0x35 SD_DELETE.
  • 0x40 RAK_GET / 0x41 RAK_SET / 0x42 RAK_TEST / 0x43 RAK_RESET; firmware serializes UART operations and returns sanitized results.
  • 0x50 FW_INFO / 0x51 FW_UPDATE_BEGIN / 0x52 FW_UPDATE_CHUNK / 0x53 FW_UPDATE_VERIFY / 0x54 FW_UPDATE_APPLY.
  • Events: POWER_MODE_CHANGED, KEY_EVENT, SD_CHANGED, RAK_EVENT, PROGRESS, FAULT.
Every response echoes requestId and command. Error payload: {code, message, fieldPath?, retryable}. Stable codes include BAD_FRAME, UNSUPPORTED_VERSION, VALIDATION_FAILED, POWER_POLICY, BUSY, NO_SD, AUTH_REQUIRED, and INTERNAL.
Validation and safety policy
Validation occurs in both Ajv/browser and firmware; firmware is authoritative.
  • Exactly 69 unique key indices; layer key arrays are exactly 69. Matrix map is exactly 78 entries with 69 unique integers and nine null holes.
  • HID usages and modifiers must be within descriptor-supported ranges. Layer references and macro IDs must resolve. Reject recursive layer/macro cycles and macros over 5 s total delay or 256 steps.
  • RGB: 69 colors exactly; stored limit ≤1200 mA. Effective limit is clamped at runtime: unknown/default source ≤250 mA, 1.5 A source ≤700 mA, 3 A source ≤1200 mA. Brightness is also clamped; preview expires after 30 s and restores persisted settings.
  • Safe startup is immutable: RGB_PWR_EN remains low until U6 is initialized and USB-C OUT1/OUT2 are sampled. Power-mode downgrade immediately blanks/clamps LEDs before other work.
  • Sustained SD writes and RAK radio tests cannot run concurrently with high-brightness preview except in allowed 3 A policy; firmware may return POWER_POLICY.
  • LoRa region/frequency/power combinations are checked against a firmware regional table. The UI displays regulatory warnings but cannot override limits.
  • Wi-Fi credentials are write-only, length-limited, never logged, never returned, and excluded from exports/backups.
  • File paths are canonicalized under /kyb1; reject .., absolute host paths, control characters, and files over configured quotas.
  • Configuration commit is transactional: validate → write temp → fsync/close → hash → rotate backup → atomic rename → update NVS revision/hash. Boot falls back to last-known-good on parse/hash failure.
Security model
  • WebSerial requires an explicit browser permission gesture and physical USB access; no automatic reconnection to an unknown device.
  • HELLO shows device identity before enabling writes. Destructive actions require an in-app confirmation displaying target device and revision.
  • Optional pairing PIN/challenge can gate writes; read-only status may remain open. Use random nonces and HMAC once a per-device key exists; never invent encryption without authenticated key provisioning.
  • Firmware protocol parser is length-bounded, allocation-bounded, fuzz-tested, and watchdog-safe. Logs redact secrets and macro text by default.
  • Signed firmware images are mandatory for production update. Reject downgrade unless a physical recovery action is present.
microSD management
The storage page exposes only the /kyb1 namespace: configuration, timestamped backups, user effects, logs, and staged update images. Upload is begin/chunk/commit with size, CRC32C per frame, and final SHA-256. Interrupted uploads remain .partial and are removed at boot. Config writes use the transactional commit flow. SD removal aborts outstanding operations without changing the active NVS-backed configuration.
Firmware update strategy
  1. Development: PlatformIO/USB flashing using the XIAO bootloader.
  2. Configurator-assisted: download a release manifest over normal browser HTTPS, verify model/schema/version, stream a signed image to firmware, verify SHA-256 and signature on device, then write the inactive OTA partition.
  3. Recovery: documented physical bootloader entry and wired upload; configurator cannot bypass secure-boot/rollback rules.
  4. RAK3112 firmware is a separate explicitly selected operation. Stage its image on SD, verify vendor signature/hash, force RGB off, enter RAK boot mode using U6 GPA3/GPA2, stream over the real U1 HardwareSerial, verify, reset, and report version. Never treat a XIAO image as a RAK image.
UI pages
  • Device/status: firmware versions, live USB-C source mode (unknown/default/1.5 A/3 A), effective current clamp, SD and RAK status.
  • Keymap: visual 69-key editor, 1–16 layers, HID/consumer/layer/macro actions, live key highlighting.
  • Macros: bounded step editor, duration estimator, import/export, recursion checks.
  • RGB: global/per-key colors, supported effects, brightness, current-limit estimator, temporary preview.
  • Storage: list/read/write/backup/restore under /kyb1.
  • RAK3112: sanitized LoRa region/radio parameters, Wi-Fi SSID/credential-set state, test controls with power warnings.
  • Updates: signed manifest, progress, rollback/recovery guidance.
Implementation milestones
  1. Protocol codec + simulator; framing/CRC/resync unit tests and parser fuzz corpus.
  2. Firmware HELLO, GET_STATUS, power events, and read-only WebSerial device page.
  3. JSON Schema v1, migrations, 69-key/6×13 model, config validate/stage/commit/rollback.
  4. Keymap/layer/macro editor and USB HID integration after the ESP32-C5 Arduino HID API is confirmed.
  5. RGB preview/control with runtime current clamps and power-mode downgrade tests.
  6. SD namespace, chunked file transfer, atomic config/backups, card-removal tests.
  7. RAK3112 UART service and settings UI; select/document the actual RAK firmware command protocol before implementing command semantics.
  8. Signed XIAO OTA, recovery flow, then separately signed RAK update flow.
  9. Optional BLE transport, reusing commands/schema and adding authenticated pairing.
  10. Production hardening: threat review, secret redaction, malformed-frame soak, brownout/power-source transition tests, and browser compatibility matrix.
  • Purpose and scope

  • System architecture

  • Versioned configuration schema

  • WebSerial binary framing

  • Command set

  • Validation and safety policy

  • Security model

  • microSD management

  • Firmware update strategy

  • UI pages

  • Implementation milestones

Black Magick Heavy Industries - KYB1 thumbnail
Black Magick Heavy Industries KYB1 programmable wireless keyboard, model BLKMGK-KYB1-BASIC, featuring a 69-key layout, XIAO ESP32-C5 controller, Seeed Studio Wio-SX1262 LoRa/Wi-Fi module, configurable RGB backlighting, microSD storage, dual USB-C interface

Properties

Pricing & Availability

Distributor

Qty 1

Arrow

$9.54–$14.41

Digi-Key

$5.21–$5.25

HQonline

$9.94–$10.02

LCSC

$19.51–$19.67

Mouser

$27.72–$27.93

TME

$1.23–$1.35

Verical

$8.39–$37.36

Controls