I made a voice assistant that only listens when you roll higher than a certain number. You may be wondering why? Well, I don’t have a good answer for you. I did not ask myself if I should do this, I only asked if I could do it. Such is the folly of creativity.
The components I used
- ATOM Echo Smart Speaker Development Kit.
- Generic ESP32 to act as a BLE client for the GoDice.
- GoDice.
You could combine the first two into one device. But I prefer to have separate devices so I can use the Dice BLE client for other purposes (perhaps you can force your guests to roll a six in order to turn on the lights).
Preparing the ESP32 BLE client.
First, I should mention that I have become so spoiled by Home Assistant that I immediately expected the dice to be auto-discovered by home assistant through my BLE proxies. Sadly, it was not to be. I then considered writing a custom integration but in the end decided to make an ESPHOME BLE client for the GoDice.
Luckily, the creators of the GoDice (Particula) provide some nice examples using the GoDice here. I simply took their python example and adapted to ESPHOME. Here is the yaml for the ESPHOME dice client:
esphome:
name: dice-bleclient
friendly_name: Dice bluetooth client
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
captive_portal:
esp32_ble_tracker:
ble_client:
- mac_address: DC:53:1D:57:85:BE
id: dice1
on_connect:
then:
- lambda: |-
ESP_LOGD("ble_client_lambda", "Connected to Dice 1");
- delay: 1s
- ble_client.ble_write:
id: dice1
service_uuid: 6e400001-b5a3-f393-e0a9-e50e24dcca9e
characteristic_uuid: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
# List of bytes to write.
value: [0x10, 0x03, 0x28, 0x32, 0x00, 0x00, 0xff, 0x01, 0x00]
button:
- platform: template
name: "pulse blue"
on_press:
- ble_client.ble_write:
id: dice1
service_uuid: 6e400001-b5a3-f393-e0a9-e50e24dcca9e
characteristic_uuid: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
# List of bytes to write.
value: [0x10, 0x03, 0x28, 0x32, 0x00, 0x00, 0xff, 0x01, 0x00]
sensor:
- platform: ble_client
type: characteristic
ble_client_id: dice1
name: "Dice state"
id: dice_state
service_uuid: '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
characteristic_uuid: '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
notify: True
lambda: |-
int16_t first_byte = x[0];
// Rolling if birst byte is 82
if (first_byte == 82) {
return 0.0;
}
// Get second and third byte
int16_t second_byte = x[1];
int16_t third_byte = x[2];
// It's a message with a battery update, skip parsing it and return -1
if (first_byte == 66 && second_byte == 97 && third_byte == 116) {
return -1.0;
}
// It is a message with a color update, skip parsing and return -2
if (first_byte == 67 && second_byte == 111 && third_byte == 108) {
return -2.0;
}
// Define NUM_VECTORS inside the loop
const int NUM_VECTORS = 6;
// define d_6 vectors of dice
int d6Vectors[NUM_VECTORS][3] = {
{-64, 0, 0},
{0, 0, 64},
{0, 64, 0},
{0, -64, 0},
{0, 0, -64},
{64, 0, 0}
};
// This is a roll, so parse the bytes
if (first_byte == 83) {
// Get 2nd, 3rd, and fourth bytes and unpack
int8_t x_v, y_v, z_v;
// Unpack bytes into unsigned 8-bit integers
x_v = static_cast<int8_t>(x[1]);
y_v = static_cast<int8_t>(x[2]);
z_v = static_cast<int8_t>(x[3]);
// Define xyz array outside the loop
int xyz[3] = {x_v, y_v, z_v};
float distances[NUM_VECTORS];
for (int i = 0; i < NUM_VECTORS; i++) {
distances[i] = 0;
for (int j = 0; j < 3; j++) {
distances[i] += pow(static_cast<float>(xyz[j]) - d6Vectors[i][j], 2);
}
distances[i] = sqrt(distances[i]);
}
int idxMinDistance = 0;
for (int i = 1; i < NUM_VECTORS; i++) {
if (distances[i] < distances[idxMinDistance]) {
idxMinDistance = i;
}
}
int rolledValue = idxMinDistance + 1;
ESP_LOGD("ble_client_lambda", "The rolled value is :%d", rolledValue);
return float(rolledValue);
}
return -10.0;
You will need to find the mac address of your GoDice using any BLE scanner app on your smart phone and replace it. The most important part is the dice state sensor. I have configured it so that when the dice is being rolled a state of “0” is shown. When the dice is rolled, the state should match the dice. For any other movements, or during charging, the dice state should be a negative integer. So for our voice assistant we only care when the state is between 1-6. Here is how it looks in my home assistant dashboard. Note that I added a pulse blue button so I can check if the dice is connected.
For the next part, we are going to need the sensor entity name of our ESPHOME BLE client. For me that was sensor.dice_bleclient_dice_state
.
Preparing the voice assistant
This part was a bit simpler to figure out. I used the original logic of the M5 for the year of the voice, when we had to press the button to activate the voice assistant.
To get the M5 to communicate with the dice we need to add a couple things to the ESPHOME ready made projects yaml file.
First I added a text sensor from home assistant that brings in the BLE client state:
text_sensor:
- platform: homeassistant
internal: True
id: dice_state
entity_id: sensor.dice_bleclient_dice_state
on_value:
then:
- if:
condition:
lambda: |-
return id(dice_state).state >= id(dice_cutoff).state;
then:
- if:
condition:
not: voice_assistant.is_running
then:
- voice_assistant.start:
Notice that I have a dice_cutoff
variable in the lambda. This is so I can configure the lowest number needed for activation directly in Home Assistant. This is done like this.
# dice selection
select:
- platform: template
entity_category: config
name: Dice cutoff
id: dice_cutoff
options:
- "1"
- "2"
- "3"
- "4"
- "5"
- "6"
initial_option: "3"
optimistic: true
set_action:
- logger.log:
format: "Chosen cutoff: %s"
args: ["x.c_str()"]
The final piece of the puzzle was to add a toggle for enabling the dice throw. Here I added a new switch that turns off the wake word.
- platform: template
name: Use dice throw
id: use_dice
optimistic: true
entity_category: config
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- switch.turn_off: use_wake_word
Small note: I also added a switch.turn_off: use_dice
to the use wake word switch so that they become mutually exclusive. The modified device looks like this in Home Assistant:
For ease of use I also added the complete yaml for the BLE client and the voice assistant.
And finally, here is a video of me using it! I was too eager on the second throw. Enjoy!