# Anki Vector Robot device spec
# Copyright 2026 Pigs Can Fly Labs LLC
# SPDX-License-Identifier: Apache-2.0

device:
  name: "Vector Robot"
  manufacturer: "Anki / Digital Dream Labs"
  manufacturer_status: "active"
  protocol: "wifi"
  notes: >
    Desktop companion robot with primary local control over WiFi using gRPC and
    protobuf 3 over TLS on port 443. Discovery uses mDNS service
    _ankivector._tcp.local. Connections require robot certificate pinning and a
    GUID bearer token obtained during onboarding. BLE onboarding uses Anki RTS
    protocol with CLAD serialization and NaCl/libsodium encryption. The
    anki_vector Python SDK is available under Apache 2.0 and documents the
    ExternalInterface service.
  identification:
    mdns_service_type: "_ankivector._tcp.local."
    default_port: 443

  discovery:
    methods:
      - type: "mdns"
        mdns:
          service_type: "_ankivector._tcp.local."
          port: 443
          txt_record_keys: ["serial"]
          identity_mapping:
            stable_keys:
              - source: "txt:serial"
              - source: "hostname"
            display:
              source: "name"
    identity:
      stable_keys: ["serial", "hostname"]
      display: "name"
    static_ip_required: false

  setup:
    required: true
    confidence: "high"
    notes: >
      Vector is the best-documented onboarding flow in this repository because
      the BLE provisioning protocol (RTS) is published under Apache 2.0. WiFi
      credentials never touch a vendor server: they travel over an encrypted
      BLE channel established by a Curve25519 exchange, with a PIN shown on the
      robot's own screen as the proof of physical possession. Only the last
      step — trading a session token for the control GUID — has a cloud
      dependency, and wire-pod replaces it.
    methods:
      - type: "ble_provisioning"
        verified: false
        description: >
          Anki RTS over BLE: handshake, PIN confirmation, WiFi scan, credential
          push, then certificate/GUID retrieval. Versions 2 through 5 of the
          protocol all carry the same WiFi messages.
        ble:
          advertised_name: "Vector-XXXX"
          protocol: "Anki RTS (CLAD over BLE, NaCl secretbox after handshake)"
          write_characteristic: "30619f2d-0f54-41bd-a65a-7588d8c85b45"
          read_characteristic: "7d2a4bda-d29b-4152-b725-2491478c5cd7"
          pairing_required: true
          pin_source: "Six-digit PIN rendered on the robot's face display during pairing mode."
        steps:
          - action: "Place the robot on its charger and double-click the backpack button to enter pairing mode."
            actor: "user"
            expect: "Robot advertises as Vector-XXXX and shows a PIN on its screen."
          - action: "Connect and complete the RTS handshake (RtsConnRequest / RtsConnResponse / RtsNonceMessage / RtsChallengeMessage)."
            actor: "client"
            request:
              protocol: "ble_gatt"
              service: "Anki RTS"
              action: "RtsConnRequest"
            expect: "RtsChallengeSuccessMessage; all later traffic is NaCl-encrypted."
          - action: "Request a WiFi scan and present the results to the user."
            actor: "client"
            request:
              protocol: "ble_gatt"
              service: "Anki RTS"
              action: "RtsWifiScanRequest"
            expect: "RtsWifiScanResponse listing SSID, signal, auth type, hidden and provisioned flags."
            timeout_seconds: 30
          - action: "Send the selected network's credentials."
            actor: "client"
            request:
              protocol: "ble_gatt"
              service: "Anki RTS"
              action: "RtsWifiConnectRequest"
              arguments:
                - name: "wifi_ssid_hex"
                  type: "string"
                  required: true
                  description: "SSID hex-encoded, not raw text — the field carries the hex string itself."
                - name: "password"
                  type: "string"
                  required: true
                  description: "Passphrase in the clear inside the already-encrypted BLE channel."
                - name: "auth_type"
                  type: "uint8"
                  required: true
                - name: "timeout"
                  type: "uint8"
                  required: true
                  description: "Seconds the robot should spend attempting the join before reporting failure."
            expect: "RtsWifiConnectResponse carrying the resulting WiFi state and connect result."
            timeout_seconds: 60
          - action: "Read back the assigned address to confirm the join."
            actor: "client"
            request:
              protocol: "ble_gatt"
              service: "Anki RTS"
              action: "RtsWifiIpRequest"
            expect: "RtsWifiIpResponse with an IPv4 address; the robot now answers _ankivector._tcp.local."
          - action: "Exchange a session token for the long-lived control GUID."
            actor: "client"
            request:
              protocol: "ble_gatt"
              service: "Anki RTS"
              action: "RtsCloudSessionRequest"
            expect: "client_token_guid returned; store it with the robot's certificate for gRPC sessions."
            notes: "Only cloud-dependent step. wire-pod issues the token locally instead."
    factory_reset:
      confidence: "medium"
      effect: >
        Clears WiFi credentials, the owner/account association, enrolled faces
        and the issued client GUID. The ESN (serial) is hardware identity and
        survives, so the mDNS name and BLE advertisement stay the same.
      procedures:
        - name: "Backpack button held on the charger"
          hold_seconds: 15
          indicator: "The screen shows the clear-user-data confirmation and the robot reboots into onboarding."
          steps:
            - action: "Place the robot on the charger."
              actor: "user"
            - action: "Press and hold the backpack button for about 15 seconds until the clear-user-data screen appears."
              actor: "user"
            - action: "Wait for the reboot; the robot returns to BLE pairing mode."
              actor: "device"
              timeout_seconds: 180
    rejoin:
      in_place_supported: true
      requires_factory_reset: false
      notes: >
        Moving Vector to a new network never needs a factory reset. Re-enter
        BLE pairing mode and send a fresh RtsWifiConnectRequest;
        RtsWifiForgetRequest drops one stored network or all of them. Because
        BLE works regardless of WiFi state, the robot is recoverable even after
        the old network is gone — the failure mode that strands most WiFi-only
        devices.
      steps:
        - action: "Enter BLE pairing mode and complete the RTS handshake."
          actor: "user"
        - action: "Optionally clear stale networks with RtsWifiForgetRequest."
          actor: "client"
          request:
            protocol: "ble_gatt"
            service: "Anki RTS"
            action: "RtsWifiForgetRequest"
        - action: "Send RtsWifiConnectRequest for the new network."
          actor: "client"
    credentials:
      wifi_passphrase_protection: "device_encrypted"
      stored_on_device:
        - "WiFi credentials for each provisioned network"
        - "Owner/account association"
      issued_to_client:
        - "Robot self-signed TLS certificate (pin it)"
        - "client_token_guid bearer token for gRPC"
      notes: >
        The passphrase is sent as plaintext inside the NaCl-encrypted BLE
        session, so confidentiality depends on the handshake having been
        completed with the on-screen PIN rather than on the field itself.

protocols:
  wifi_grpc:
    service: "ExternalInterface"
    port: 443
    transport: "HTTP/2 over TLS"
    serialization: "protobuf 3"
    authentication:
      tls: "robot self-signed certificate pinning"
      bearer: "client_token_guid"
    rpc_count: 48
    key_state_rpcs:
      - "BatteryState"
      - "VersionState"
      - "RobotState"
  rest_gateway:
    base_path: "/v1"
    description: "gRPC-gateway JSON endpoints for many unary RPCs."
  ble_onboarding:
    name: "RTS"
    serialization: "CLAD"
    encryption: "NaCl/libsodium Curve25519 key exchange + secretbox"
    read_characteristic: "7d2a4bda-d29b-4152-b725-2491478c5cd7"
    write_characteristic: "30619f2d-0f54-41bd-a65a-7588d8c85b45"
  sdk:
    name: "anki_vector Python"
    license: "Apache-2.0"
    url: "https://github.com/anki/vector-python-sdk"

http_endpoints:
  - method: "POST"
    path: "/v1/protocol_version"
    name: "ProtocolVersion"
    description: "Negotiate SDK/robot protocol version."
    request_body:
      content_type: "application/json"
      fields:
        - name: "client_version"
          type: "integer"
          description: "Client protocol version."
    response_body:
      content_type: "application/json"
      fields:
        - name: "version"
          type: "integer"
          description: "Robot protocol version; current documented version is 5."
  - method: "POST"
    path: "/v1/sdk_initialization"
    name: "SDKInitialization"
    description: "Initialize SDK session metadata."
  - method: "POST"
    path: "/v1/user_authentication"
    name: "UserAuthentication"
    description: "Authenticate a cloud or wire-pod session token and obtain a GUID."
  - method: "POST"
    path: "/v1/event_stream"
    name: "EventStream"
    description: "Server stream carrying robot state, world, and status events."
  - method: "POST"
    path: "/v1/battery_state"
    name: "BatteryState"
    description: "Return battery voltage, charge level, and charging status."
    response_body:
      content_type: "application/json"
      fields:
        - name: "battery_volts"
          type: "number"
          description: "Battery voltage."
        - name: "battery_level"
          type: "integer"
          description: "Battery level enum."
        - name: "is_charging"
          type: "boolean"
          description: "Whether Vector is on charger and charging."
  - method: "POST"
    path: "/v1/version_state"
    name: "VersionState"
    description: "Return firmware, OS, engine, and serial/version identifiers."
  - method: "POST"
    path: "/v1/list_animations"
    name: "ListAnimations"
    description: "List installed animation names."
  - method: "POST"
    path: "/v1/list_animation_triggers"
    name: "ListAnimationTriggers"
    description: "List available animation triggers."
  - method: "POST"
    path: "/v1/display_face_image_rgb"
    name: "DisplayFaceImageRGB"
    description: "Display an RGB image on Vector's face screen."
  - method: "POST"
    path: "/v1/set_eye_color"
    name: "SetEyeColor"
    description: "Set Vector eye color."
  - method: "POST"
    path: "/v1/go_to_pose"
    name: "GoToPose"
    description: "Drive to a pose."
  - method: "POST"
    path: "/v1/dock_with_cube"
    name: "DockWithCube"
    description: "Approach and dock with the cube."
  - method: "POST"
    path: "/v1/drive_off_charger"
    name: "DriveOffCharger"
    description: "Drive off the charger."
  - method: "POST"
    path: "/v1/drive_on_charger"
    name: "DriveOnCharger"
    description: "Return to charger."
  - method: "POST"
    path: "/v1/find_faces"
    name: "FindFaces"
    description: "Look for faces."
  - method: "POST"
    path: "/v1/look_around_in_place"
    name: "LookAroundInPlace"
    description: "Scan the environment in place."
  - method: "POST"
    path: "/v1/enable_face_detection"
    name: "EnableFaceDetection"
    description: "Enable or disable face detection."
  - method: "POST"
    path: "/v1/enable_marker_detection"
    name: "EnableMarkerDetection"
    description: "Enable or disable marker detection."
  - method: "POST"
    path: "/v1/enable_motion_detection"
    name: "EnableMotionDetection"
    description: "Enable or disable motion detection."
  - method: "POST"
    path: "/v1/cancel_face_enrollment"
    name: "CancelFaceEnrollment"
    description: "Cancel face enrollment."
  - method: "POST"
    path: "/v1/request_enrolled_names"
    name: "RequestEnrolledNames"
    description: "List enrolled face names."
  - method: "POST"
    path: "/v1/update_enrolled_face_by_id"
    name: "UpdateEnrolledFaceByID"
    description: "Rename an enrolled face."
  - method: "POST"
    path: "/v1/erase_enrolled_face_by_id"
    name: "EraseEnrolledFaceByID"
    description: "Erase one enrolled face."
  - method: "POST"
    path: "/v1/erase_all_enrolled_faces"
    name: "EraseAllEnrolledFaces"
    description: "Erase all enrolled faces."
  - method: "POST"
    path: "/v1/set_face_to_enroll"
    name: "SetFaceToEnroll"
    description: "Begin face enrollment."
  - method: "POST"
    path: "/v1/connect_cube"
    name: "ConnectCube"
    description: "Connect to Vector's cube."
  - method: "POST"
    path: "/v1/disconnect_cube"
    name: "DisconnectCube"
    description: "Disconnect from cube."
  - method: "POST"
    path: "/v1/cubes_available"
    name: "CubesAvailable"
    description: "Check whether cubes are available."
  - method: "POST"
    path: "/v1/flash_cube_lights"
    name: "FlashCubeLights"
    description: "Flash cube lights."
  - method: "POST"
    path: "/v1/set_cube_lights"
    name: "SetCubeLights"
    description: "Set cube light pattern."
  - method: "POST"
    path: "/v1/forget_preferred_cube"
    name: "ForgetPreferredCube"
    description: "Forget preferred cube."
  - method: "POST"
    path: "/v1/set_preferred_cube"
    name: "SetPreferredCube"
    description: "Set preferred cube."
  - method: "POST"
    path: "/v1/delete_custom_objects"
    name: "DeleteCustomObjects"
    description: "Delete custom objects."
  - method: "POST"
    path: "/v1/create_fixed_custom_object"
    name: "CreateFixedCustomObject"
    description: "Create a fixed custom object."
  - method: "POST"
    path: "/v1/define_custom_object"
    name: "DefineCustomObject"
    description: "Define a custom object marker."
  - method: "POST"
    path: "/v1/photos_info"
    name: "PhotosInfo"
    description: "List photo metadata."
  - method: "POST"
    path: "/v1/photo"
    name: "Photo"
    description: "Fetch one photo."
  - method: "POST"
    path: "/v1/thumbnail"
    name: "Thumbnail"
    description: "Fetch one thumbnail."
  - method: "POST"
    path: "/v1/delete_photo"
    name: "DeletePhoto"
    description: "Delete one photo."
  - method: "POST"
    path: "/v1/say_text"
    name: "SayText"
    description: "Make Vector speak text."
  - method: "POST"
    path: "/v1/set_master_volume"
    name: "SetMasterVolume"
    description: "Set speaker master volume."
  - method: "POST"
    path: "/v1/enable_mirror_mode"
    name: "EnableMirrorMode"
    description: "Enable or disable mirror mode."
  - method: "POST"
    path: "/v1/enable_image_streaming"
    name: "EnableImageStreaming"
    description: "Enable or disable image streaming."
  - method: "POST"
    path: "/v1/is_image_streaming_enabled"
    name: "IsImageStreamingEnabled"
    description: "Return image streaming enabled state."
  - method: "POST"
    path: "/v1/cancel_action_by_id_tag"
    name: "CancelActionByIdTag"
    description: "Cancel a running action by id tag."
  - method: "POST"
    path: "/v1/roll_block"
    name: "RollBlock"
    description: "Roll cube/block."
  - method: "POST"
    path: "/v1/capture_single_image"
    name: "CaptureSingleImage"
    description: "Capture one camera image."

grpc_rpcs:
  - "ProtocolVersion"
  - "SDKInitialization"
  - "UserAuthentication"
  - "DriveWheels"
  - "MoveHead"
  - "MoveLift"
  - "StopAllMotors"
  - "DriveStraight"
  - "TurnInPlace"
  - "SetHeadAngle"
  - "SetLiftHeight"
  - "PlayAnimation"
  - "PlayAnimationTrigger"
  - "ListAnimations"
  - "ListAnimationTriggers"
  - "DisplayFaceImageRGB"
  - "SetEyeColor"
  - "GoToPose"
  - "DockWithCube"
  - "DriveOffCharger"
  - "DriveOnCharger"
  - "FindFaces"
  - "LookAroundInPlace"
  - "EnableFaceDetection"
  - "EnableMarkerDetection"
  - "EnableMotionDetection"
  - "CancelFaceEnrollment"
  - "RequestEnrolledNames"
  - "UpdateEnrolledFaceByID"
  - "EraseEnrolledFaceByID"
  - "EraseAllEnrolledFaces"
  - "SetFaceToEnroll"
  - "TurnTowardsFace"
  - "GoToObject"
  - "RollObject"
  - "PopAWheelie"
  - "PickupObject"
  - "PlaceObjectOnGroundHere"
  - "ConnectCube"
  - "DisconnectCube"
  - "CubesAvailable"
  - "FlashCubeLights"
  - "SetCubeLights"
  - "ForgetPreferredCube"
  - "SetPreferredCube"
  - "DeleteCustomObjects"
  - "CreateFixedCustomObject"
  - "DefineCustomObject"
  - "PhotosInfo"
  - "Photo"
  - "Thumbnail"
  - "DeletePhoto"
  - "SayText"
  - "SetMasterVolume"
  - "ExternalAudioStreamPlayback"
  - "EventStream"
  - "BehaviorControl"
  - "CameraFeed"
  - "AudioFeed"
  - "NavMapFeed"
  - "BatteryState"
  - "VersionState"
  - "EnableMirrorMode"
  - "EnableImageStreaming"
  - "IsImageStreamingEnabled"
  - "CancelActionByIdTag"
  - "RollBlock"
  - "CaptureSingleImage"

state_models:
  - name: "BatteryState"
    description: "Battery voltage, level, and charging state."
  - name: "VersionState"
    description: "Robot firmware, OS, engine, and serial identifiers."
  - name: "RobotState"
    description: "Position, pose, lift/head angles, motion, carrying, and status fields emitted on EventStream."

entities:
  - platform: "sensor"
    name: "Battery"
    device_class: "battery"
    unit: "%"
    state_topic: "/v1/battery_state"
    state_mapping:
      value: "battery_level"
  - platform: "sensor"
    name: "Version"
    state_topic: "/v1/version_state"
    state_mapping:
      value: "os_version"
