Skip to content

martinecker/SkellyUltra

Repository files navigation

💀 Skelly Ultra Integration

Home Assistant integration for the Home Depot 6.5 ft Ultra Skelly Halloween animatronic BLE device.

Example Skelly Dashboard

See the Quick Start Guide or keep reading for detailed instructions.

There is a companion project that provides a fully web browser-based controller here https://github.com/martinecker/SkellyUltraWebController

📑 Table of Contents

✨ Features

  • 📊 Sensor entities: Volume, live name, storage capacity, file count, file order
  • 💡 Light entities: RGB lighting control for Torso and Head channels
  • 🔌 Switch entities:
    • Live Mode (enables classic Bluetooth speaker)
    • Color Cycle (rainbow effect for Torso and Head lights)
    • Movement controls (Head, Arm, Torso, and All body parts)
  • 🎚️ Number entities: Volume control, effect speed (for Torso and Head)
  • 🎨 Select entities: Eye icon selection, effect mode (Static/Strobe/Pulse for Torso and Head)
  • 🖼️ Image entities: Eye icon preview
  • 🎵 Media Player entities:
    • Live Mode Speaker: Play audio to the device's Bluetooth speaker (when Live Mode is enabled)
      • Supports TTS (Text-to-Speech) services
      • Supports multiple audio formats (WAV, MP3, FLAC, OGG, etc.)
    • Internal Files: Control playback of audio files stored on the device
      • Browse and play files from device storage
      • Next/previous track controls
      • File metadata available as entity attributes
      • Select files by name
  • ⚙️ Services: Play/stop individual files stored on the device, enable classic Bluetooth

📋 Prerequisites

🖥️ Skelly Ultra REST Server

If you want to use Skelly's live mode and the live mode media player provided by this integration you have to set up and run the Skelly Ultra REST server.

The Skelly Ultra REST server must be running on a Linux host (e.g., Raspberry Pi, Ubuntu server, etc.) that can connect to both:

  1. Your Home Assistant instance (via network)
  2. The Skelly Ultra device via Bluetooth

The REST server handles:

  • Classic Bluetooth pairing and connections for Live Mode audio playback
  • Audio processing and streaming to the Skelly Ultra speaker

Important: The REST server requires pipewire, bluetoothctl, and related audio tools on the Linux host where it runs.

Important: When using a Raspberry Pi to run the server it is highly recommended to use a dedicated Bluetooth USB dongle. The built-in Bluetooth controller usually has problems to stream audio to a classic Bluetooth speaker device like the Ultra Skelly, resulting in very choppy playback. I've successfully used the TP-Link UB500 Plus.

For installation and setup of the REST server, see the REST Server Documentation.

📡 Live Mode Bluetooth Pairing

Live Mode uses Classic Bluetooth, which can be paired either automatically (if REST server runs as root) or manually:

Option 1: Automatic Pairing (Recommended)

The REST server can automatically pair devices when you enable Live Mode, if it's able to acquire root privileges during the pairing: See the REST Server Documentation for details.

How it works:

  1. Add the integration in Home Assistant (see Installation section below)
  2. Turn on the "Live Mode" switch in Home Assistant
    • The integration will automatically discover, pair, and connect to the device
    • Pairing happens seamlessly in the background using the configured PIN
    • The switch should stay on once pairing completes

That's it! The pairing persists and you won't need to pair again.

Option 2: Manual Pairing

If you prefer not to run the REST server allowing it elevated root privileges for pairing, or automatic pairing fails, you can pair manually:

Step 1: Make the Bluetooth Speaker Discoverable

  1. First, add the integration in Home Assistant (see Installation section below)
  2. Turn on the "Live Mode" switch in Home Assistant
    • This tells the Skelly device to enable its Bluetooth speaker
    • The switch will likely turn off again because pairing hasn't been completed yet - this is expected
    • Alternatively, you can call the skelly_ultra.enable_classic_bt service

The Skelly device will now be discoverable via Bluetooth as <Device Name>(Live). For example, if your device is named "Animated Skelly" (the default), it will appear as "Animated Skelly(Live)".

Step 2: Pair Using bluetoothctl

On the Linux host running the REST server, use bluetoothctl to pair with the device:

bluetoothctl
> scan on
# Wait for your device to appear as "Animated Skelly(Live)" or "<Your Device Name>(Live)"
> pair <MAC_ADDRESS>
# Enter PIN when prompted (default: 1234)
> trust <MAC_ADDRESS>
> exit

Important notes:

  • Pairing only needs to be done once per device. The pairing will persist.
  • You must enable Live Mode in HA first (even if it doesn't stay on) to make the speaker discoverable
  • Look for the device name with (Live) suffix - this is the Classic Bluetooth speaker

🔄 BLE Proxy Mode (Alternative Connection Method)

By default, the integration connects directly from your Home Assistant host to the Skelly Ultra device via BLE (Bluetooth Low Energy). However, you can alternatively configure the integration to use the Skelly Ultra REST server as a BLE proxy.

What is BLE Proxy Mode?

In BLE proxy mode:

  • The REST server handles the BLE connection to your Skelly Ultra device
  • Home Assistant communicates with the REST server via HTTP
  • The REST server forwards BLE commands to the device and relays responses back to Home Assistant

When to Use BLE Proxy Mode

BLE proxy mode is useful when:

  • Your Home Assistant host doesn't have Bluetooth hardware or doesn't support BLE
  • Your Skelly device is out of range from your Home Assistant host but within range of your REST server
  • You want to centralize Bluetooth management on a dedicated Linux host
  • You're already running the REST server for Live Mode audio and want to simplify your setup

How to Enable BLE Proxy Mode

When adding the integration in Home Assistant:

  1. Go to SettingsDevices & ServicesAdd Integration"Skelly Ultra"
  2. In the configuration form:
    • Check "Use BLE Proxy" (enable the checkbox)
    • Enter REST server URL: http://<rest-server-ip>:8765 (e.g., http://192.168.1.100:8765)
  3. Choose your preferred discovery mode:
    • Scan: The REST server will scan for nearby devices
    • Manual: Enter the device's MAC address manually

Important Differences in BLE Proxy Mode

Device Identification

  • In direct BLE mode, devices are identified by MAC address only
  • In BLE proxy mode, devices are identified by both MAC address and REST server URL
  • Example device title in proxy mode: Animated Skelly (AA:BB:CC:DD:EE:FF via http://192.168.1.100:8765)

Scanning Behavior

  • In direct BLE mode, scanning uses Home Assistant's Bluetooth integration
  • In BLE proxy mode, scanning is performed by the REST server using its BLE adapter
  • The REST server maintains a background scan cache and returns results quickly
  • Scan timeout is properly utilized to wait for devices to appear

Prerequisites for BLE Proxy Mode

  • Skelly Ultra REST server must be installed and running (see REST Server Documentation)
  • REST server must be accessible from your Home Assistant host via HTTP
  • Bluetooth hardware must be available on the REST server host
  • No Bluetooth integration required in Home Assistant (BLE proxy mode bypasses it)

Switching Between Direct and Proxy Mode

If you want to change connection modes:

  1. Remove the existing integration from Home Assistant
  2. Re-add the integration with the new settings
  3. Choose "Use BLE Proxy" checkbox accordingly

Note: You cannot switch modes for an existing config entry - you must remove and re-add the integration.

🚀 Installation

Step 1: Install the Integration Files

Copy the integration files to your Home Assistant configuration directory:

# Copy to custom_components folder
cp -r skelly_ultra <config_directory>/custom_components/

Or use HACS and add https://github.com/martinecker/SkellyUltra as a custom repository, or manually download and extract to <config>/custom_components/skelly_ultra/.

Step 2: Set Up the REST Server

This step is optional but highly recommended and required if you want to use the live mode media player provided by this integration.

If you want to use the live mode audio feature, set up and start the Skelly Ultra REST server on your Linux host. See skelly_ultra_srv/README.md for detailed instructions.

Step 3: Add the Integration in Home Assistant

  1. Restart Home Assistant to load the custom component

  2. Add the integration:

    • Go to SettingsDevices & Services
    • Click "+ Add Integration"
    • Search for "Skelly Ultra"
    • Choose connection mode:
      • Use BLE Proxy: Check this to use the REST server as a BLE proxy (see BLE Proxy Mode section)
      • Leave unchecked for direct BLE connection from Home Assistant host
    • Choose configuration mode:
      • Manual: Enter the Bluetooth MAC address
      • Scan: Discover nearby Skelly devices automatically
  3. Configure connection settings:

    • REST server URL: URL of the Skelly Ultra REST server (default: http://localhost:8765)
      • If the server is on a different host, use http://<server-ip>:8765
      • Required for Live Mode audio
      • Required for BLE proxy mode where the host running the REST server establishes the connection to the BLE device (and is also used for Live Mode audio)
  4. Verify setup:

    • The integration should show as "Connected"
    • Entities will be created for sensors, lights, switches, etc.

Step 4: Enable Live Mode for Audio Playback

  1. Ensure Bluetooth pairing is complete (see Prerequisites above)

  2. Turn on the "Live Mode" switch:

    • This tells the Skelly device to enable its Classic Bluetooth speaker
    • The REST server will attempt to connect to the speaker
  3. Wait for connection (may take 10-30 seconds):

    • The Media Player entity will become available once connected
    • Check the Live Mode switch - it should show as "On"
  4. Troubleshoot if needed:

    • Check Home Assistant logs for error messages
    • Check REST server logs for connection details
    • Verify the device is paired and trusted in bluetoothctl

🎮 Usage

📦 Available Entities

The integration creates the following entities:

  • Media Player - Live Mode Speaker (media_player.skelly_ultra_live_mode_speaker):

    • Control audio playback when Live Mode is enabled
    • Supports volume control, play/pause, stop
    • Works with TTS (Text-to-Speech) services
    • Supports multiple audio formats (MP3, WAV, FLAC, OGG, M4A, etc.)
  • Media Player - Internal Files (media_player.skelly_ultra_internal_files):

    • Play audio files stored on the device's internal storage
    • Browse files via media browser UI
    • Next/previous track navigation
    • Select files by name
    • File metadata exposed as entity attributes (file_index, file_name, file_length, file_action, file_eye_icon)
    • Shared volume control with device
  • Sensors: Monitor device status (mode, volume, battery, connection)

  • Switches: Toggle Live Mode and other features

  • Lights: Control LED patterns (if supported)

Note about entity IDs: The actual entity IDs in your Home Assistant instance will differ from the examples shown in this README. Entity IDs are generated based on:

  1. The device name set in the Skelly Ultra mobile app (defaults to "Animated Skelly")
  2. The device's Bluetooth MAC address (to support multiple Skelly devices)

For example, if your device is named "Animated Skelly" with MAC address AA:BB:CC:DD:EE:FF, the media player entity might be:

  • media_player.animated_skelly_aa_bb_cc_dd_ee_ff_live_mode_speaker

You can find your actual entity IDs in SettingsDevices & ServicesSkelly Ultra → click on your device.

🔊 Playing Audio

Note: In all examples below, replace media_player.skelly_ultra_live_mode_speaker with your actual media player entity ID (see note above about entity naming).

Using Text-to-Speech (TTS)

Make your Skelly speak using any TTS service:

service: tts.google_translate_say
target:
  entity_id: media_player.skelly_ultra_live_mode_speaker  # Replace with your actual entity ID
data:
  message: "Happy Halloween! Trick or treat!"
  language: "en"

Other TTS services also work:

service: tts.cloud_say
target:
  entity_id: media_player.skelly_ultra_live_mode_speaker  # Replace with your actual entity ID
data:
  message: "The front door is open"

Playing Media from URL

service: media_player.play_media
target:
  entity_id: media_player.skelly_ultra_live_mode_speaker
data:
  media_content_type: "music"
  media_content_id: "http://example.com/spooky-sound.mp3"

Playing Local Files

service: media_player.play_media
target:
  entity_id: media_player.skelly_ultra_live_mode_speaker
data:
  media_content_type: "music"
  media_content_id: "media-source://media_source/local/halloween_sounds.mp3"

Or using a file path accessible from the Home Assistant container:

service: media_player.play_media
target:
  entity_id: media_player.skelly_ultra_live_mode_speaker
data:
  media_content_type: "music"
  media_content_id: "/config/www/sounds/my_audio.wav"

🤖 Automation Examples

Announce When Someone Arrives

automation:
  - alias: "Skelly Greets Visitors"
    trigger:
      - platform: state
        entity_id: binary_sensor.front_door
        to: "on"
    action:
      - service: tts.google_translate_say
        target:
          entity_id: media_player.skelly_ultra_live_mode_speaker
        data:
          message: "Welcome! Enter if you dare!"

Play Spooky Sounds at Sunset

automation:
  - alias: "Skelly Sunset Sounds"
    trigger:
      - platform: sun
        event: sunset
    action:
      - service: media_player.play_media
        target:
          entity_id: media_player.skelly_ultra_live_mode_speaker
        data:
          media_content_type: "music"
          media_content_id: "media-source://media_source/local/spooky_ambience.mp3"

Notes:

  • Supported audio formats: MP3, WAV, FLAC, OGG, M4A, and more
  • The integration automatically handles connecting the Bluetooth speaker
  • Pairing can be done automatically if the REST server can get elevated root privileges and otherwise must be done manually via bluetoothctl (only needed once)
  • First connection after enabling Live Mode may take 10-30 seconds

📁 Using the Internal Files Media Player

The Internal Files media player entity provides a full playlist interface for audio files stored on the device's internal storage.

Features

  • Media Browser: Browse and select files through Home Assistant's media browser UI
  • Playback Controls: Play, stop, next track, previous track
  • Source Selection: Select files by name from a dropdown list
  • Volume Control: Shared volume control with the device (same as other media players and volume number entity)
  • File Metadata: Access detailed file information via entity attributes
  • Real-time State Sync: Automatically tracks device play/pause state from device events

Controlling Playback

# Play a specific file by selecting it as a source
service: media_player.select_source
target:
  entity_id: media_player.skelly_ultra_internal_files
data:
  source: "Spooky Laugh.mp3"

# Play the selected file
service: media_player.media_play
target:
  entity_id: media_player.skelly_ultra_internal_files

# Skip to next file
service: media_player.media_next_track
target:
  entity_id: media_player.skelly_ultra_internal_files

# Stop playback
service: media_player.media_stop
target:
  entity_id: media_player.skelly_ultra_internal_files

Using File Metadata in Automations

File metadata is available as entity attributes and can be used in automation conditions and templates:

automation:
  - alias: "React to specific file playing"
    trigger:
      - platform: state
        entity_id: media_player.skelly_ultra_internal_files
        to: "playing"
    condition:
      - condition: template
        value_template: "{{ state_attr('media_player.skelly_ultra_internal_files', 'file_name') == 'Spooky Laugh.mp3' }}"
    action:
      - service: light.turn_on
        target:
          entity_id: light.front_porch
        data:
          effect: "flash"

  - alias: "Log file metadata when playing"
    trigger:
      - platform: state
        entity_id: media_player.skelly_ultra_internal_files
        to: "playing"
    action:
      - service: system_log.write
        data:
          message: >
            Playing file {{ state_attr('media_player.skelly_ultra_internal_files', 'file_name') }}
            (index: {{ state_attr('media_player.skelly_ultra_internal_files', 'file_index') }},
            length: {{ state_attr('media_player.skelly_ultra_internal_files', 'file_length') }}ms)

Available Metadata Attributes

The media player exposes the following attributes when a file is selected/playing:

  • file_index: The 1-based index of the file
  • file_name: The filename
  • file_length: Duration in milliseconds
  • file_action: Associated action/movement setting, which is a bitfield where bit 0 = head, bit 1 = arm, bit 2 = torso
  • file_eye_icon: Associated eye icon
  • file_cluster: File cluster information
  • total_files: Total number of files on device
  • file_order: Playback order list (same as File Order sensor, something like [3, 1, 5, 4, 2])

🎵 Playing Files Stored on Device (via Services)

The Skelly Ultra can store audio files on its internal storage. You can play or stop these files using the integration's services.

File Order Sensor

The File Order sensor shows the current playback order of files stored on the device as a list, for example: [1, 2, 3, 4]. This represents the order in which files will play.

Play File Service

Play a specific file from the device's internal storage:

service: skelly_ultra.play_file
data:
  device_id: <device_id>  # Optional if you have only one device
  file_index: 1  # File index (1-based, must be ≥ 1)

Or using an entity ID instead of device ID:

service: skelly_ultra.play_file
data:
  entity_id: sensor.skelly_ultra_volume  # Any entity from the device
  file_index: 2

Stop File Service

Stop a specific file that's currently playing:

service: skelly_ultra.stop_file
data:
  device_id: <device_id>  # Optional if you have only one device
  file_index: 1  # File index (1-based, must be ≥ 1)

📤 Uploading Audio Files to Device

The integration provides file transfer services to upload audio files to the device's internal storage. Audio files are automatically converted to 8kHz mono MP3 format for compatibility with the device.

Send File Service

Upload an audio file to the device. The file will be automatically converted to the required format if needed.

Supported file sources:

  • Local file paths: Full paths to files accessible from the Home Assistant container
  • HTTP/HTTPS URLs: Download and upload files from the web
  • Home Assistant media files: Use /media/... paths to reference files in your media folder
# Upload a local file
service: skelly_ultra.send_file
data:
  device_id: <device_id>  # Optional if you have only one device
  file_path: "/config/www/sounds/spooky_laugh.mp3"
  target_filename: "laugh"  # Extension (.mp3) added automatically

# Upload from URL
service: skelly_ultra.send_file
data:
  file_path: "https://example.com/halloween-sound.wav"
  target_filename: "halloween_sound"

# Upload from Home Assistant media folder
service: skelly_ultra.send_file
data:
  file_path: "/media/local/my_audio.ogg"
  target_filename: "my_audio"

Notes:

  • The .mp3 extension is added automatically to target_filename
  • Audio is automatically converted to the preferred device format, which is 8kHz mono MP3 format
  • Supported input formats: MP3, WAV, FLAC, OGG, M4A, and many others
  • Upload progress is displayed in Home Assistant logs
  • Large files may take several minutes to upload over BLE

Cancel File Transfer Service

Cancel an ongoing file upload if it's taking too long or you need to stop it:

service: skelly_ultra.cancel_file_transfer
data:
  device_id: <device_id>  # Optional if you have only one device

Or using an entity ID:

service: skelly_ultra.cancel_file_transfer
data:
  entity_id: sensor.skelly_ultra_volume  # Any entity from the device

Notes:

  • This service only works while a file transfer is in progress
  • The device will receive a cancellation command and stop accepting chunks
  • Partial uploads are discarded
  • After cancellation, you can start a new upload

Automation Example: Play Different Files Based on Time

automation:
  - alias: "Skelly Morning Greeting"
    trigger:
      - platform: time
        at: "09:00:00"
    action:
      - service: skelly_ultra.play_file
        data:
          file_index: 1  # Play the first stored file

  - alias: "Skelly Evening Sounds"
    trigger:
      - platform: time
        at: "18:00:00"
    action:
      - service: skelly_ultra.play_file
        data:
          file_index: 3  # Play the third stored file

Notes:

  • File indices are 1-based (first file is index 1, not 0)
  • The valid range is [1, N] where N is the number of files on the device
  • Check the File Count sensor to see how many files are stored
  • Check the File Order sensor to see the playback order
  • If you have multiple Skelly devices, specify device_id or entity_id
  • If you have only one device, these parameters can be omitted

🛠️ Troubleshooting

❌ Media Player Shows "Unavailable"

Cause: Live Mode is not enabled or the Bluetooth speaker is not connected.

Solution:

  1. Turn on the Live Mode switch in Home Assistant
  2. Wait 10-30 seconds for the connection to establish
  3. Check Home Assistant logs for error messages
  4. Verify the device is paired and trusted:
    bluetoothctl info <MAC_ADDRESS>
    • Look for Paired: yes and Trusted: yes

⚠️ "Device is NOT Paired" Error

Cause: The Skelly device has not been manually paired via Bluetooth.

Solution:

  1. Pair the device manually (this only needs to be done once):
    bluetoothctl
    scan on
    # Wait for your device to appear
    pair <MAC_ADDRESS>
    # Enter PIN: 1234
    trust <MAC_ADDRESS>
    exit
  2. Try enabling Live Mode again in Home Assistant

🔇 Audio Playback Fails

Symptom: Media player accepts commands but no audio plays.

Solution:

  1. Check REST server logs for PipeWire/Bluetooth errors
  2. Verify PipeWire is running on the REST server host:
    systemctl --user status pipewire pipewire-pulse
  3. Test audio manually on the REST server host:
    pw-play /path/to/test.wav
  4. Check Bluetooth connection on the REST server host:
    bluetoothctl info <MAC_ADDRESS>
    # Look for "Connected: yes"

🌐 REST Server Connection Fails

Symptom: Integration shows "Cannot connect to REST server" or similar error.

Solution:

  1. Verify the REST server is running:
    curl http://localhost:8765/health
    • Should return {"status": "ok"}
  2. Check the REST server URL in the integration configuration:
    • If on same host: http://localhost:8765
    • If on different host: http://<server-ip>:8765
  3. Check firewall rules if the REST server is on a different host

⏱️ Bluetooth Connection Takes Too Long

Symptom: Live Mode switch takes 30+ seconds to turn on.

Explanation: Classic Bluetooth connections can take time, especially on first connect. This is normal behavior.

Solutions:

  • Be patient and wait for the connection to establish
  • Check REST server logs for progress
  • If it fails, try disabling and re-enabling Live Mode

📡 Troubleshooting BLE Proxy Mode

REST Server Unreachable

If you see "Cannot connect to REST server" error:

  • Verify the REST server is running: curl http://<server-ip>:8765/health
  • Check firewall rules on the REST server host
  • Ensure the URL is correct (include http:// and port :8765)

Device Not Found During Scan

If scanning doesn't find your device:

  • Ensure the device is powered on and within range of the REST server's Bluetooth adapter
  • Check REST server logs for BLE scanning activity
  • Try increasing the scan timeout in the config flow
  • Verify the device is advertising (try scanning with bluetoothctl on the server)

Connection Drops Frequently

If the BLE connection is unstable:

  • Check for Bluetooth interference (Wi-Fi, other devices)
  • Move the REST server closer to the Skelly device
  • Check REST server logs for BLE errors
  • Consider using a dedicated USB Bluetooth adapter

🆘 Still Having Issues?

  1. Check Home Assistant logs: Settings → System → Logs
  2. Check REST server logs: See output from the REST server terminal
  3. Verify prerequisites: Ensure PipeWire, bluetoothctl, and all dependencies are installed on the REST server host
  4. Test manually: Try connecting and playing audio directly on the REST server host before using Home Assistant

📄 License

This integration is provided as-is for use with Ultra Skelly devices. Use it at your own risk. It might brick your Skelly.