# AUTOBABA LED Backpack device spec (LOY SPACE protocol)
# Copyright 2026 Pigs Can Fly Labs LLC
# SPDX-License-Identifier: Apache-2.0

device:
  name: "AUTOBABA LED Backpack"
  manufacturer: "LOY SPACE / popled.cn"
  manufacturer_status: "abandoned"
  protocol: "ble"
  notes: >
    Programmable full-color LED backpack screen. Uses the LOY SPACE / popled.cn protocol
    (identical to NYAN GEAR). Supports BLE and Wi-Fi (AP mode, SSID starts with "YS",
    default password 12345678, UDP port 9090).
    BLE device name starts with "YS" or "TL".
    Packet format: [0xAA, 0x55, addr_hi, addr_lo, len_hi, len_lo, sno_hi, sno_lo, flags, type, ...payload].
    Commands are JSON-based, serialized to binary TLV packets.
    Images sent as 24-bit uncompressed BMP.
    Supports multiple resolutions: 16x16, 32x16, 32x32, 48x28, 64x20, 64x64, 96x128, 160x32, 192x40.
    Backend API: store-cdn.popled.cn.
    Sliding window ACK protocol with max window size 3-10.
    BLE transport: commands are JSON objects serialized into framed binary packets
    with a 0xAA55 magic header (bytes AA 55) + 2 reserved + little-endian uint16
    payload length, then the JSON bytes; chunked at ~180 bytes/write. Regular JSON
    commands carry NO checksum (firmware images use MD5 separately).
    CONFIDENCE: HIGH — service/char UUIDs, name prefixes and command JSON verified in
    the uni-app app-service.js bundle (serviceId 0000FFF0, WriteId 0000FFF2,
    NotifyId 0000FFF1; header magic constant present).
  identification:
    local_name_prefix: "YS"
    service_uuids:
      - "0000fff0-0000-1000-8000-00805f9b34fb"

  discovery:
    methods:
      - type: "ble_scan"
        ble:
          local_name:
            match: "prefix"
            value: "YS"
          service_uuids:
            - "0000fff0-0000-1000-8000-00805f9b34fb"
    identity:
      stable_keys: ["address"]
      display: "local_name"
    static_ip_required: false

  setup:
    required: false
    confidence: "medium"
    notes: >
      The vendor app advertises WiFi support for this product family, but the
      documented control path is BLE only and needs no provisioning. If a WiFi
      mode exists on a given unit it has not been captured here.
    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:
          advertised_name: "YS"
          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: JSON command serialization into the popled 0xAA55
# framed transport (shared with the NYAN BT Image Controller). Declarative.
protocol_handler: "popled_json"

# Full-color image / animation upload (device-reported resolution may differ;
# app converts GIF/PNG/video to the device's native frame format).
features:
  - type: "image_upload"
    format: "gif"
    max_width: 32
    max_height: 16

services:
  - uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
    name: "LOY SPACE BLE Service"
    characteristics:
      - uuid: "0000fff1-0000-1000-8000-00805f9b34fb"
        name: "Notify / Write"
        properties: ["read", "write", "notify"]
        framing:
          length_prefix: true
          max_chunk_size: 180
        notes: >
          NotifyId in app-service.js. 0xAA55 header + 2 reserved + LE uint16 length
          frame; no checksum on JSON commands. Evidence: common module b8e6 get_pkt_data.

      - uuid: "0000fff2-0000-1000-8000-00805f9b34fb"
        name: "Write"
        properties: ["write"]
        framing:
          length_prefix: true
          max_chunk_size: 180
        notes: >
          WriteId in app-service.js. JSON commands are wrapped as
          {"cmd": <command>, "sno": <sequence>} and framed with the 0xAA55 header.
          checksum OMITTED: none on regular JSON commands (firmware uses MD5).
        commands:
          power_toggle:
            description: 'Toggle screen power. JSON: {"cmd":{"power":{"type":0}},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.power.type"
              value_type: "int"
          set_brightness:
            description: 'Set brightness 0-15. JSON: {"cmd":{"light":{"type":0,"value_fix":N}},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.light.value_fix"
              value_type: "int"
          get_device_info:
            description: 'Query device info. JSON: {"cmd":{"get":"dev_info"},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.get"
              value_type: "string"
          get_power_state:
            description: 'Query power state. JSON: {"cmd":{"get":"power"},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.get"
              value_type: "string"
          get_brightness:
            description: 'Query brightness. JSON: {"cmd":{"get":"light"},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.get"
              value_type: "string"
          delete_all_programs:
            description: 'Delete all programs. JSON: {"cmd":{"delete":{"del_all":1}},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.delete.del_all"
              value_type: "int"
          set_rotation:
            description: 'Set screen rotation. JSON: {"cmd":{"rotate":N},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.rotate"
              value_type: "int"
          play_program:
            description: 'Play a program by index. JSON: {"cmd":{"pgm_play":{"model":0,"index":N}},"sno":N}'
            encoding: "json"
            payload:
              key: "cmd.pgm_play.index"
              value_type: "int"

entities:
  - platform: "light"
    name: "LED Screen"
    features: ["brightness"]
    commands:
      set_brightness: "set_brightness"
