# NIIMBOT D110 / B21 Thermal Label Printer
# Copyright 2026 Pigs Can Fly Labs LLC
# SPDX-License-Identifier: Apache-2.0

device:
  name: "NIIMBOT D110 / B21 Thermal Label Printer"
  manufacturer: "NIIMBOT (Wuhan Jincheng Intelligent Technology Co., Ltd.)"
  manufacturer_status: "unsupported"
  protocol: "ble"
  notes: >
    Popular cheap thermal label printer with a closed ecosystem. $15-30.
    D110 is a compact single-label printer; B21 adds larger print width
    and more features. The community open-source client "NiimBlue"
    (Web Bluetooth) has reverse-engineered the full protocol through
    MultiMote/niimbluelib.

    PROTOCOL: packet-based binary protocol over BLE GATT. Each packet uses
    0x55 0x55 as header and 0xAA 0xAA as tail. Packet structure:
    [prefix(optional)] [0x55 0x55] [command] [data_length] [data...]
    [checksum] [0xAA 0xAA].

    CHECKSUM: XOR of all bytes from command through the last data byte
    (inclusive). For simple request packets (data_length=1, data=1),
    checksum equals the command byte.

    PACKET TYPES:
    - 0xC1 Connect / 0xC2 response (connection handshake, only packet
      with 0x03 prefix)
    - 0x01 PrintStart (1-9 byte variants depending on model)
    - 0x13 SetPageSize (2-13 byte variants)
    - 0x03 PageStart / 0xE3 PageEnd
    - 0xF3 PrintEnd
    - 0x21 SetDensity / 0x31 response
    - 0x23 SetLabelType / 0x33 response
    - 0x84 PrintEmptyRow (fill row with white pixels)
    - 0x85 PrintBitmapRow (send row with black/white pixel data)
    - 0x83 PrintBitmapRowIndexed (row with < 7 black pixels, uses pixel
      indexes)
    - 0xDC Heartbeat / 0xDD-0xDF responses
    - 0x40 GetDeviceInfo — multiple response IDs for different info
      (serial, hardware, firmware, etc.)
    - 0x28 PrinterReset / 0x38 response
    - 0xF5 StartFirmwareUpgrade (uses CRC32 variant of packet format)

    D110 SPECIFIC: uses 1-byte PrintStart variant. SetPageSize typically
    2 bytes (row count only, column count fixed to printhead width 384px).
    Printhead 384px wide (48 bytes/row for 1-bit data).

    BITMAP ROW ENCODING: PrintBitmapRow (0x85) sends row data with a
    black-pixel-count segment. For rows with fewer than 7 black pixels,
    PrintBitmapRowIndexed (0x83) sends pixel x-coordinates as uint16 LE
    values instead of a full bitmap.

    HEARTBEAT: send 0xDC with data [0x01] or [0x04] (protocol version
    dependent). Response includes lid closed status, charge level, paper
    inserted, RFID status, and (on newer models) ribbon state.

    DISCOVERY: advertised names include "D110", "B21", "NIIMBOT". Service
    UUID is manufacturer-specific; the niimbluelib handles service/char
    discovery dynamically.

    CONFIDENCE: HIGH for packet format, checksum algorithm, command IDs,
    PrintStart/SetPageSize variants, bitmap row encoding, and heartbeat
    format — sourced from MultiMote/niimbluelib, the production-quality
    TypeScript library behind niim.blue, tested against D110, B21, B1,
    D11, and other NIIMBOT models.

  identification:
    local_name_prefix: "NIIMBOT"

  discovery:
    methods:
      - type: "ble_scan"
        ble:
          local_name:
            match: "prefix"
            value: "NIIMBOT"
    identity:
      stable_keys: ["address"]
      display: "local_name"
    static_ip_required: false

  setup:
    required: false
    confidence: "medium"
    notes: >
      Connect, send Connect packet (0xC1), then begin print sequence.
      No pairing or bonding required.
    methods:
      - type: "ble_direct"
        verified: true
        description: >
          Scan for NIIMBOT advertisement, connect, send connect handshake,
          then start print sequence.
        ble:
          pairing_required: false
        steps:
          - action: "Power on the printer."
            actor: "user"
            request:
              protocol: "ble_gatt"
          - action: >
              Scan for "NIIMBOT"/"D110"/"B21" name and connect.
            actor: "client"
          - action: >
              Send Connect packet (0xC1) and receive response (0xC2).
            actor: "client"
          - action: >
              Begin print sequence: PrintStart → SetPageSize → SetDensity →
              PageStart → print rows → PageEnd → PrintEnd.
            actor: "client"
    factory_reset:
      confidence: "medium"
      effect: >
        Send PrinterReset packet (0x28). Returns response 0x38.
      procedures:
        - name: "Command reset"
          steps:
            - action: "Send 0x28 packet to printer."
              actor: "client"
              expect: "Device responds with 0x38 and resets."
    rejoin:
      in_place_supported: true
      requires_factory_reset: false
      notes: "No bonding. Any central can connect."
    credentials:
      wifi_passphrase_protection: "not_applicable"
      stored_on_device: []
      issued_to_client: []
      notes: "BLE-only. No auth."

  variants:
    - model: "D110"
      identification:
        local_name_prefix: "D110"
      notes: >
        384px printhead. 1-byte PrintStart. 2-byte SetPageSize (row count
        only).
    - model: "B21"
      identification:
        local_name_prefix: "B21"
      notes: >
        Larger print width. May use extended PrintStart/SetPageSize variants.
    - model: "D11"
      identification:
        local_name_prefix: "D11"
      notes: "Similar to D110. 1-byte PrintStart."

  features:
    - type: "image_upload"
      max_width: 384
      max_height: 65535
      format: "1bit-bitmap"

services:
  - uuid: "manufacturer-specific-by-model"
    name: "NIIMBOT Print Service"
    notes: >
      The service UUID varies by model. The niimbluelib discovers the
      service and characteristics dynamically. The packet-based binary
      protocol is consistent across all NIIMBOT models.

      PACKET FORMAT: `[0x03?] 0x55 0x55 <cmd> <len> <data...> <xor_checksum> 0xAA 0xAA`
      The 0x03 prefix is used only on the Connect packet (0xC1).
      Checksum = XOR of cmd through last data byte.

      PRINT SEQUENCE:
      1. Connect (0xC1) — handshake
      2. PrintStart (0x01) — announce total pages
      3. SetPageSize (0x13) — row count, column count, copies
      4. SetDensity (0x21) — print density level
      5. SetLabelType (0x23) — label/gap/continuous
      6. PageStart (0x03) — begin page
      7. PrintBitmapRow (0x85) / PrintBitmapRowIndexed (0x83) / PrintEmptyRow (0x84)
      8. PageEnd (0xE3) — end page
      9. Repeat 6-8 for multiple copies
      10. PrintEnd (0xF3) — finish job

      D110 MODEL: PrintStart uses 1-byte variant `55 55 01 01 01 <cs> aa aa`.
      SetPageSize uses 2-byte variant `55 55 13 02 <row_lo> <row_hi> <cs> aa aa`.
      Column count is implicit (384px for D110).
    characteristics:
      - uuid: "model-specific-write"
        name: "Write"
        properties: ["write", "write_without_response"]
        notes: "Write channel for command packets and bitmap row data."

      - uuid: "model-specific-notify"
        name: "Notify"
        properties: ["notify"]
        notes: "Notification channel for responses and heartbeat data."

entities:
  - platform: "cover"
    name: "Print Feed"
    state_characteristic: "model-specific-notify"
  - platform: "sensor"
    name: "Battery Level"
    state_characteristic: "model-specific-notify"
  - platform: "binary_sensor"
    name: "Lid"
    state_characteristic: "model-specific-notify"
  - platform: "binary_sensor"
    name: "Paper"
    state_characteristic: "model-specific-notify"
