# Shining Mask device spec
# Copyright 2026 Pigs Can Fly Labs LLC
# SPDX-License-Identifier: Apache-2.0

device:
  name: "Shining Mask"
  manufacturer: "Shenzhen Shining Bright Technology"
  manufacturer_status: "unsupported"
  protocol: "ble"
  notes: >
    LED face mask with 2074 RGB LEDs, 16-pixel-tall display.
    All commands to the Command characteristic are AES-128 ECB encrypted with a
    fixed key (published in open-source implementations). No pairing/authentication.
    Image data to the upload characteristic is sent unencrypted.
    FCC ID: 2AOLN-16.
    Color order for FC/BC (foreground/background color) commands is R,B,G (not R,G,B).
    CONFIDENCE: command structure and R,B,G color order HIGH (well-documented in
    open-source shining-mask implementations, package cn.com.heaton.shiningmask).
    The AES key is UNRECOVERABLE here (the well-known key ships in native code /
    third-party repos, not as a constant in this evidence set) — static_key OMITTED.

    Enrich-branch evidence: the official Shining Mask Android package
    cn.com.heaton.shiningmask was inspected from APKPure XAPK v1.2.6/versionCode
    126, SHA256 29cc2c088634b28b06b67cab7b17f15bb6551bdb3d422b4e188274c825fbcb94.
    Static analysis and public RE agree on service 0000fff0-0000-1000-8000-00805f9b34fb,
    command characteristic d44bc439-abfd-45a2-b575-925416129600, notification
    characteristic d44bc439-abfd-45a2-b575-925416129601, image/text upload
    characteristic d44bc439-abfd-45a2-b575-92541612960a, audio visualizer
    characteristic d44bc439-abfd-45a2-b575-92541612960b, and CCCD
    00002902-0000-1000-8000-00805f9b34fb. Public sources include GoneUp/mask-go,
    BrickCraftDream/Shining-Mask-stuff, BishopFox/shining-mask, the Bishop Fox
    Bluetooth LED mask write-up, and Staars' Shining Mask gist. Public RE documents
    the fixed AES key as 32672f7974ad43451d9c6c894a0e8764; schema static_key is
    omitted because the field requires hex key material verified in this evidence
    set. Text/bitmap upload uses DATS, REOK acknowledgements, 98-byte chunks when
    MTU is set or 18-byte chunks otherwise, and DATCP/DATCPOK completion.
  identification:
    service_uuids:
      - "0000fff0-0000-1000-8000-00805f9b34fb"

  discovery:
    methods:
      - type: "ble_scan"
        ble:
          service_uuids:
            - "0000fff0-0000-1000-8000-00805f9b34fb"
    identity:
      stable_keys: ["address"]
      display: "service_uuid"
    static_ip_required: false

  setup:
    required: false
    confidence: "medium"
    notes: >
      No provisioning. Commands are AES-encrypted with a fixed key, so any
      client that implements the framing can connect straight away.
    methods:
      - type: "ble_direct"
        verified: false
        description: >
          No provisioning step: the device advertises as soon as it is powered
          on and accepts a connection from any central. There is no account, no
          network credential exchange and no pairing PIN, so a replacement
          client only has to scan, connect and write.
        ble:
          pairing_required: false
        steps:
          - action: "Power the device and scan for its advertisement."
            actor: "client"
            request:
              protocol: "ble_gatt"
          - action: "Connect and run the documented initialization sequence, if the spec defines one."
            actor: "client"
    factory_reset:
      confidence: "low"
      effect: >
        No credential state to clear. Power-cycling drops the current
        connection, which is the actual remedy for the common failure mode:
        the device already being connected to another central.
      procedures:
        - name: "Power cycle"
          steps:
            - action: "Remove power (or battery) for a few seconds and reapply."
              actor: "user"
              expect: "Device advertises again and accepts a new connection."
    rejoin:
      in_place_supported: true
      requires_factory_reset: false
      notes: >
        Nothing binds this device to a network or an owner, so switching
        controllers is just connecting from the new one. If the old client is
        an Android or iOS phone, remove the device from the OS Bluetooth list
        as well — a cached bond can keep the phone reconnecting automatically
        and holding the single available link.
    credentials:
      wifi_passphrase_protection: "not_applicable"
      stored_on_device: []
      issued_to_client: []
      notes: >
        Anything within radio range can connect and issue commands. Physical
        proximity is the only access control.
# Custom consumer-side handler for the shining-mask AES-128-ECB command framing
# and R,B,G color encoding. Declarative.
protocol_handler: "shiningmask_ecb"

services:
  - uuid: "d44bc439-abfd-45a2-b575-925416129600"
    name: "QPP Command Service (encrypted)"
    notes: >
      Quintic Private Profile (QPP): the service UUID intentionally equals its
      first
      characteristic UUID (d44bc439-...-925416129600). This is the Quintic/NXP
      QN-series
      convention, NOT a copy-paste error. The device advertises the TI-family
      0000fff0 UUID (see identification.service_uuids), but the app communicates
      over
      this QPP service; no separate 0000fff0 service block is defined because
      the
      shining-mask codebase (cn.com.heaton.shiningmask) was not available to
      confirm one.
    characteristics:
      - uuid: "d44bc439-abfd-45a2-b575-925416129600"
        name: "Command"
        properties: ["write"]
        encryption:
          algorithm: "aes-128-ecb"
          key_derivation: "static"
        notes: >
          onEncrypt() AES-128-ECB with a fixed key is applied before every write to
          this characteristic. static_key OMITTED (must be hex; the well-known key is
          not carried as a recoverable constant in this evidence set — note it rather
          than invent it). Confidence: HIGH structure, key not included here.
        commands:
          set_brightness:
            description: "Set brightness (AES encrypted). Plaintext: [0x06, 'LIGHT', brightness]"
            template: [0x06, 0x4C, 0x49, 0x47, 0x48, 0x54, "{brightness}"]
            parameters:
              brightness:
                type: "uint8"
                min: 0
                max: 255
          set_mode:
            description: "Set display mode. 0=off, 1=steady, 2=blink, 3=scroll R-to-L, 4=scroll L-to-R, 5=steady custom"
            template: [0x05, 0x4D, 0x4F, 0x44, 0x45, "{mode}"]
            parameters:
              mode:
                type: "uint8"
                min: 0
                max: 5
          set_speed:
            description: "Set animation speed"
            template: [0x06, 0x53, 0x50, 0x45, 0x45, 0x44, "{speed}"]
            parameters:
              speed:
                type: "uint8"
                min: 0
                max: 255
          show_builtin_image:
            description: "Display built-in static image (0x00-0x69, 106 images)"
            template: [0x05, 0x49, 0x4D, 0x41, 0x47, "{image_id}"]
            parameters:
              image_id:
                type: "uint8"
                min: 0
                max: 105
          play_builtin_animation:
            description: "Play built-in animation (0x00-0x45, 70 animations)"
            template: [0x05, 0x41, 0x4E, 0x49, 0x4D, "{anim_id}"]
            parameters:
              anim_id:
                type: "uint8"
                min: 0
                max: 69
          set_foreground_color:
            description: "Set text foreground color. Channels are emitted in R,B,G order (color_order: rbg)."
            template: [0x06, 0x46, 0x43, 0x01, "{red}", "{blue}", "{green}"]
            parameters:
              color_order: "rbg"
              red:
                type: "uint8"
                min: 0
                max: 255
              blue:
                type: "uint8"
                min: 0
                max: 255
              green:
                type: "uint8"
                min: 0
                max: 255

      - uuid: "d44bc439-abfd-45a2-b575-925416129601"
        name: "Notification"
        properties: ["notify"]

      - uuid: "d44bc439-abfd-45a2-b575-92541612960a"
        name: "Image Upload"
        properties: ["write"]

      - uuid: "d44bc439-abfd-45a2-b575-92541612960b"
        name: "Audio Visualizer"
        properties: ["write"]

entities:
  - platform: "light"
    name: "Mask Display"
    features: ["brightness", "color"]
    commands:
      set_brightness: "set_brightness"
