Quick and Dirty: Control Lionel LionChief BLE via ESPHome BLE Client

Well, it’s December and that means for many people that Christmas trees are going up and getting decorated. Lots of those trees have trains that run around them. And some of those trains are Lionel LionChief trains.

I got to thinking this year about the LionChief BLE and set out to see if I could control the engine from Home Assistant. Turns out, with a little bluetooth sniffing, an ESP32, and Property404’s previous work decoding Lionel’s bluetooth data here, you can create an ESPHome BLE Client for your LionChief engine.

This was a quick and dirty little project – very much a ‘just get it good enough to use’ implementation – and I know there are better ways to implement the controls. I also didn’t implement all of the features.

I believe the service_uuid varies based on the model you have. I have the Pennsylvania Flyer and the ID is e20a39f4-73f5-4bc4-a12f-17d1ad07a961. But this is visible when you first set up the ble_client component and enable VERBOSE logging level.

The characteristic_uuid is 08590f7e-db05-467e-8757-72f6faeb13d4.

Here is my ESPHome yaml:

esphome:
  name: trainble
  friendly_name: TrainBLE

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: redacted

ota:
  - platform: esphome
    password: redacted

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Trainble Fallback Hotspot"
    password: redacted

captive_portal:
    
esp32_ble_tracker:

ble_client:
  - mac_address: XX:XX:XX:XX:XX:XX
    id: christmas_train
    name: Christmas Train Status
    auto_connect: true
    on_connect:
      then:
        - lambda: |-
            id(ble_connected) = true;
            ESP_LOGD("ble_client_lambda", "Connected to BLE device");
    on_disconnect:
      then:
        - lambda: |-
            id(ble_connected) = false;
            ESP_LOGD("ble_client_lambda", "Disconnected from BLE device");

globals:
  - id: ble_connected
    type: bool
    restore_value: no 
    initial_value: 'false'

binary_sensor:
  - platform: template 
    name: Bluetooth
    id: ble_connection
    lambda: |- 
      return id(ble_connected); 
    device_class: connectivity

button:
  - platform: template
    id: train_stop
    name: Stop
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x45, 0x00]

  - platform: template
    id: train_speed_low
    name: Speed Low
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x45, 0x02]

  - platform: template
    id: train_speed_med
    name: Speed Med
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x45, 0x07]

  - platform: template
    id: train_speed_high
    name: Speed High
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x45, 0x10]

  - platform: template
    id: train_forward
    name: Forward
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x46, 0x01]

  - platform: template
    id: train_reverse
    name: Reverse
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x46, 0x02]

  - platform: template
    id: announce_0
    name: Announcement Random
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x00, 0x00]

  - platform: template
    id: announce_1
    name: Announcement Ready to Roll
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x01, 0x00]

  - platform: template
    id: announce_2
    name: Announcement Hey There
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x02, 0x00]

  - platform: template
    id: announce_3
    name: Announcement Squeaky
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x03, 0x00]

  - platform: template
    id: announce_4
    name: Announcement Water and Fire
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x04, 0x00]

  - platform: template
    id: announce_5
    name: Announcement Fastest Freight
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x05, 0x00]

  - platform: template
    id: announce_6
    name: Announcement Pennsylvania Flyer
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4d, 0x06, 0x00]

  - platform: template
    id: disconnect
    name: Disconnect Train
    on_press: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x4b, 0x00, 0x00]

switch:
  - platform: template
    id: train_bell
    name: Bell
    optimistic: True
    turn_on_action: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x47, 0x01]
    turn_off_action: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x47, 0x00]

  - platform: template
    id: train_horn
    name: Horn
    optimistic: True
    turn_on_action: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x48, 0x01]
    turn_off_action: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x48, 0x00]

  - platform: template
    id: train_lights
    name: Lights
    optimistic: True
    turn_on_action: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x51, 0x01]
    turn_off_action: 
      then:
        - ble_client.ble_write: 
            characteristic_uuid: 08590f7e-db05-467e-8757-72f6faeb13d4
            service_uuid: e20a39f4-73f5-4bc4-a12f-17d1ad07a961
            value: [0x00, 0x51, 0x00]



#All Commands
#Horn start: 48 01
#Horn stop : 48 00
#Bell start: 47 01
#Bell stop : 47 00
#Speech : 4d XX 00 (01 to 06)
#Set speed : 45 <00-1f> (hex from 00 to 31) (only implemented slow/med/fast)
#Forward : 46 01
#Reverse : 46 02
#Disconnect: 4b 0 0
#Set master volume: 4b <00-07> (not implemented in current iteration)
#Set steam chuff volume: 4c <00-07> (not implemented in current iteration)
#Set horn volume/pitch: 44 01 <00-0f> <fe-02> (not implemented in current iteration)
#Set bell volume/pitch: 44 02 <00-0f> <fe-02> (not implemented in current iteration)
#Set speech volume/pitch: 44 03 <00-0f> <fe-02> (not implemented in current iteration)
#Set engine volume/pitch: 44 04 <00-0f> <fe-02> (not implemented in current iteration)
#Set lights off: 51 00

When you add this to Home Assistant, this creates a device full of buttons and switches to control the train. There are obviously more elegant ways to implement this, but again, I just wanted to see if it was possible and get it done (relatively) quickly.

Then I set up a dashboard just for the train controls.

And now I can control the train from Home Assistant.

6 Likes

We just got a new train, and I’m looking to do the same. Any chance you can elaborate a bit more on how you get the ESP32 set up and the steps to get it connected to the Train?

I have more information on here:

I’ve made some changes to the ESPHome yaml since this post to consolidate the speed and announcements. It’s updated on Github.

I used ESPHome Web to flash the ESP32 with a basic configuration and add my wifi. Then I used the ESPHome addon and updated the yaml before adding the device under the ESPHome integration.

Once the ESP32 is connected to ESPHome and you can update the yaml, this is the part that does all the BLE connection magic with the train. You’ll need to get your train’s MAC address.

esp32_ble_tracker:

ble_client:
  - mac_address: XX:XX:XX:XX:XX:XX
    id: christmas_train
    name: Christmas Train Status
    auto_connect: true
    on_connect:
      then:
        - lambda: |-
            id(ble_connected) = true;
            ESP_LOGD("ble_client_lambda", "Connected to BLE device");
    on_disconnect:
      then:
        - lambda: |-
            id(ble_connected) = false;
            ESP_LOGD("ble_client_lambda", "Disconnected from BLE device");

Edit: You can use the ESPHome Bluetooth Low Energy Scanner component to help identify the train’s MAC address if you need to. The name of the train will start with LC in the scan results.

1 Like

This was my first time using ESPHome, but with the extra instructions you gave, and some YouTube videos I got it working. We have the Polar Express model and it worked perfect. The announcements are different, but that was super easy to fix in your .yaml. Also, the UUID was the same, so I didn’t have to change that!. I’m using an Alexa routine to turn on the train and play music from the movie, so this is awesome. It will let me start the train up too!!! Thanks for the help and the work you put into getting it going!

Any chance you would be willing to share the dashboard too?

1 Like

I’m glad you were able to get it set up. It didn’t occur to me that this was your first time using ESPHome. Great job getting it to work!

I think the Polar Express might have some extra announcements. You could try incrementing the third hex value for announcements (0x06 to 0x07) sent to the train by one to check: value: [0x00, 0x4d, 0x06, 0x00]

My original dashboard was literally a sections dashboard with two badges, one to show if track power was one and the other to show if the train was connected to bluetooth, and then just a standard button card for each entity since they were all buttons (separated by heading cards using subtitles).

1 Like

Just got this working! My first ESP32 project too :slight_smile: Thanks for posting this, I’ve wanted to this for years; I knew about Property404’s project, but never had time to do anything with it. This was super easy!

In case it’s helpful for anyone else, I have the Hogwarts Express and its service UUID and characteristic UUID match what @iamjosh posted for the Pennsylvania Flyer.

2 Likes

Awesome! I am so glad this is helpful for other people!

1 Like