Hi. I did use the HA integration for some time, reporting the same name, connected via a bluetooth proxy. It didnāt need pairing, it just listens to broadcasts. If there is no water flowing, there is no power to the device, so indeed no data. The data should have come in within a few seconds after the water runs and the display starts showing data. It might be your bluetooth isnāt getting though correctly.
I do not know if it was autodetected or that you had to enter a BLE MAC address though. If it is autodetected I would expect it to work when the shower runs, and that bluetooth is able to see the device. If does not, they might have changes the manufacturing code somewhat, or your range is too limited. A bluetooth proxy might help then.
At some point however I moved away from the HA integration, and moved over to a version that runs entirely on the ESP and links to HA. It might have been because there was a bug at the time, I think that might have got fixed later on but I did not wait for it because the other solution worked fine for me.
I do not know where I got the code from, it was here somewhere on the forum I think. Creating an ESP firmware is not as hard as it sounds. The is no soldering involved. If you create a BLE proxy for it and adopt it to have the yaml in EspHome, you can simply adjust the yaml to add this in. Or you can create a default empty ESP with esp-idf framework, and add the below to that.
If you go the same route, this is what you need. Create a folder āpackagesā besides the yaml of your esp. Two files will go there: First there needs to be a file present names oras_dsh.h with this in it:
#include "esphome.h"
using namespace esphome;
class DSH {
public:
unsigned long tStartTime;
unsigned long tLastUpdate;
char szLastShowerEnded[32]; // Human readable of tLastUpdate
unsigned long lCurrentShowerNo; //
unsigned long lLastShowerNo; // No of previous shower - used to find out if previous shower is being continued
float curWaterLiters;
float curEnergy;
float curBathTemp;
uint16_t curShowerFlowDuration;
float lastWaterLiters=0;
float lastEnergy=0;
float lastBathTemp=0;
uint16_t lastShowerFlowDuration;
unsigned long lastShowerDuration=0;
bool occupancy=0;
uint8_t bleData[20];
} dsh;
Then I broke out the shower device in a separate file shower.yaml with this in it. You also put it in the packages folder. At some places you need to replace the MAC address with the one of your showerhead:
esphome:
devices:
- id: showerhead
name: Showerhead
includes:
- packages/oras_dsh.h # File is included in order to manage "global" data between the different sections of lambda code
# Time component is needed for time calculations
time:
- platform: homeassistant
id: homeassistant_time
text_sensor:
- platform: template
device_id: showerhead
name: "dsh_lastShowerFinished"
id: dsh_lastShowerFinished
icon: mdi:calendar-clock
update_interval: never
esp32_ble_tracker:
scan_parameters:
active: true
on_ble_advertise:
- then:
- lambda: |-
char stringbuf[256]; // Buffer for outputting raw data in a readable format
char* buf2 = stringbuf; // Pointer for buffer
char* endofbuf = stringbuf + sizeof(stringbuf); // Pointer to end of buffer
int i; // integer to be sued for counter
if(strcmp(x.get_name().c_str(),"DHS") == 0) { // Check manufacturer name
ESP_LOGI("DSH_tracker", "New BLE device");
ESP_LOGI("DSH_tracker", " address: %s", x.address_str().c_str());
ESP_LOGI("DSH_tracker", "(%s) name: %s", x.address_str().c_str(), x.get_name().c_str());
ESP_LOGI("DSH_tracker", "(%s) Advertised service UUIDs:",x.address_str().c_str());
for (auto uuid : x.get_service_uuids()) {
ESP_LOGI("DSH_tracker", "(%s) - %s", x.address_str().c_str(), uuid.to_string().c_str());
}
ESP_LOGI("DSH", "(%s) Advertised service data:",x.address_str().c_str());
for (auto data : x.get_service_datas()) {
ESP_LOGI("DSH_tracker", "(%s) - %s: (length %i)", x.address_str().c_str(), data.uuid.to_string().c_str(), data.data.size());
}
ESP_LOGI("DSH_tracker", "(%s) Advertised manufacturer data:",x.address_str().c_str());
for (auto data : x.get_manufacturer_datas()) {
ESP_LOGI("DSH_tracker", "(%s) - %s: (length %i)", x.address_str().c_str(), data.uuid.to_string().c_str(), data.data.size());
for(i=0;i<18;i++) {
if (buf2 + 5 < endofbuf) {
if (i > 0) {
buf2 += sprintf(buf2, ":");
}
buf2 += sprintf(buf2, "%02X", data.data[i]);
}
}
ESP_LOGI("DSH_tracker", "(%s) Data block: %s", x.address_str().c_str(), (char*)stringbuf);
// Detailed analysis of data - check if shower head is active (only works before connection is established with ble_client)
if ( dsh.occupancy == 0 && data.data[14]==0x32 && data.data[15]==0x4F) { // Enhance filtering on data - to ensure correct match
auto time = id(homeassistant_time).now(); // Get current time
time_t currTime = time.timestamp;
unsigned long int totShowers = data.data[2]; // Total Showers consist of index 1 and 2 in the array
unsigned long int bathTemp = data.data[11]; // Temperature consist of index 11 in the array
unsigned long int bathKwhDec= data.data[13]; // kWh consumptionm consist of index 12 and 13 in the array (multiplied by 100)
unsigned long int bathLiter = data.data[9]; // L consumptionm consist of index 8 and 9 in the array (multiplied by 100)
unsigned int iShowerDuration = data.data[7]; // idx 6+7 = shower time
dsh.tStartTime = time.timestamp; // Set start time of shower
ESP_LOGW("DSH_tracker", "Initializing start time to: %ld and setting occupancy on",time.timestamp);
dsh.occupancy =1 ;
totShowers += ( data.data[1]<<8); // Calculate shower number
dsh.lCurrentShowerNo = totShowers;
dsh.curBathTemp = bathTemp;
bathKwhDec += ( data.data[12]<<8); // Get data for energy (16 bit number) - number must be divided by 100
dsh.curEnergy = (float)bathKwhDec/100;
bathLiter += ( data.data[8]<<8); // Get amount of water (16 bit number) - number must be divided by 10
dsh.curWaterLiters = float(bathLiter)/10;
iShowerDuration += ( data.data[6]<<8); // Get data for shower time (16 bit number)
dsh.curShowerFlowDuration = iShowerDuration;
if(memcmp(&dsh.bleData[0],&data.data[0],18)!=0) { // If BLE data is different than what we have in the "cache", copy the new data
memcpy(&dsh.bleData[0],&data.data[0],18);
}
ESP_LOGI("DSH_tracker", "Data block changes since last: %s", (char*)stringbuf);
dsh.tLastUpdate = time.timestamp; // Set information about when data was last updated
ESP_LOGI("DSH_tracker", "Timestamp for last update set to: %ld",time.timestamp);
strftime(dsh.szLastShowerEnded, sizeof(dsh.szLastShowerEnded), "%Y-%m-%d %H:%M:%S", localtime(&currTime));
}
}
}
# Timer running on 5 sec interval - to ensure fairly quickly updates once the shower is active
interval:
- interval: 5sec
then:
- lambda: |-
// Get current time
auto time = id(homeassistant_time).now();
// ESP_LOGW("DSH", "Interval timer spawned");
if ( (time.timestamp - dsh.tLastUpdate) > 180 && dsh.tLastUpdate != 0 ) { // More than 180 seconds has passed since last update, assume finished
unsigned long int showerDurationSecs = dsh.tLastUpdate - dsh.tStartTime; // Calculate shower duration
dsh.occupancy = 0; // Set variable for shower as inactive
ESP_LOGI("DSH", "3 minutes has passed since last updated - shower duration in seconds has been calculated to: %ld seconds",showerDurationSecs);
// Publish LastShower sensors to HA
id(dsh_lastShowerkWh).publish_state(dsh.curEnergy);
id(dsh_lastShowerTemp).publish_state(dsh.curBathTemp);
id(dsh_lastShowerLiter).publish_state(dsh.curWaterLiters);
id(dsh_lastShowerM3).publish_state((float)dsh.curWaterLiters*0.001);
id(dsh_lastDurationSeconds).publish_state(showerDurationSecs);
id(dsh_showerOccupied).publish_state(dsh.occupancy);
id(dsh_lastShowerFinished).publish_state(dsh.szLastShowerEnded);
id(dsh_lastShowerWaterFlowTime).publish_state(dsh.curShowerFlowDuration);
// Update internal variables for historical lookup
dsh.lastWaterLiters=dsh.curWaterLiters;
dsh.lastEnergy=dsh.curEnergy;
dsh.lastBathTemp=dsh.curBathTemp;
dsh.lastShowerDuration=showerDurationSecs;
dsh.lastShowerFlowDuration = dsh.curShowerFlowDuration;
// Set CurrentShower to NaN - as shower is inactive
id(dsh_curShowerkWh).publish_state(NAN);
id(dsh_curShowerTemp).publish_state(NAN);
id(dsh_curShowerLiter).publish_state(NAN);
id(dsh_curShowerM3).publish_state(NAN);
id(dsh_curShowerTimeSeconds).publish_state(NAN);
id(dsh_curShowerWaterFlowTime).publish_state(NAN);
// Reset internal counters and data
ESP_LOGI("DSH", "Consider shower to be finished and reset Occupancy");
dsh.tLastUpdate = 0;
dsh.lLastShowerNo = dsh.lCurrentShowerNo; // Set counter shower number - to be able to identify if current shower continues
} else if ( dsh.tStartTime != 0 && dsh.occupancy==1 && dsh.tLastUpdate != 0) {
// Regular updates every 5 seconds when shower is on
// Only send if more than 10 seconds since last update from BLE device
unsigned long int showerDurationSecs = dsh.tLastUpdate - dsh.tStartTime; // Calculate shower duration
ESP_LOGW("DSH", "Updating HA sensors ... ");
id(dsh_curShowerkWh).publish_state(dsh.curEnergy);
id(dsh_curShowerTemp).publish_state(dsh.curBathTemp);
id(dsh_curShowerLiter).publish_state(dsh.curWaterLiters);
id(dsh_curShowerM3).publish_state((float)dsh.curWaterLiters*0.001);
id(dsh_curShowerTimeSeconds).publish_state(showerDurationSecs);
id(dsh_showerOccupied).publish_state(dsh.occupancy);
id(dsh_totShowers).publish_state(dsh.lCurrentShowerNo);
id(dsh_lastShowerWaterFlowTime).publish_state(dsh.curShowerFlowDuration);
}
ble_client:
- mac_address: D8:71:4D:C3:FB:50 # Address needs to match shower head - 60:77:71:3A:D6:BB
id: DSH
on_connect:
then:
lambda: |-
auto time = id(homeassistant_time).now();
ESP_LOGI("DSH", "Shower head connected ...");
on_disconnect:
then:
lambda: |-
// In principle a disconnect only means that the shower head disabled BLE - it does not mean, that the shower is finished,
// as there is a 2 minute timeout in the shower head
auto time = id(homeassistant_time).now();
ESP_LOGI("DSH", "Shower head disconnected - if no new data has been received after approx 3 minutes, the shower is considered finished");
# Sensor to indicate whether the shower is busy/occupied or not.
binary_sensor:
- platform: template
device_id: showerhead
name: "dsh_showerOccupied"
id: dsh_showerOccupied
icon: 'mdi:shower-head'
device_class: occupancy
sensor:
# Total number of showers registered by the shower-head
- platform: template
device_id: showerhead
name: "dsh_totShowers"
id: dsh_totShowers
icon: 'mdi:speedometer-medium'
unit_of_measurement: "x"
update_interval: never
# Current temperature
- platform: template
device_id: showerhead
name: "dsh_curShowerTemp"
id: dsh_curShowerTemp
icon: 'mdi:thermometer'
device_class: temperature
update_interval: never
unit_of_measurement: "°C"
# Previous temperature
- platform: template
device_id: showerhead
name: "dsh_lastShowerTemp"
id: dsh_lastShowerTemp
icon: 'mdi:thermometer'
device_class: temperature
update_interval: never
unit_of_measurement: "°C"
# Current energy usage in kwH
- platform: template
device_id: showerhead
name: "dsh_curShowerkWh"
id: dsh_curShowerkWh
device_class: energy
icon: 'mdi:water-thermometer'
accuracy_decimals: 2
unit_of_measurement: "kWh"
update_interval: never
state_class: total_increasing
# Previous energy usage in kwH
- platform: template
device_id: showerhead
name: "dsh_lastShowerkWh"
id: dsh_lastShowerkWh
device_class: energy
icon: 'mdi:water-thermometer'
accuracy_decimals: 2
unit_of_measurement: "kWh"
update_interval: never
state_class: total_increasing
# Current duration
- platform: template
device_id: showerhead
name: "dsh_curShowerTimeSeconds"
id: dsh_curShowerTimeSeconds
icon: 'mdi:timer'
unit_of_measurement: "s"
update_interval: never
state_class: total_increasing
# Last duration
- platform: template
device_id: showerhead
name: "dsh_lastDurationSeconds"
id: dsh_lastDurationSeconds
icon: 'mdi:timer'
unit_of_measurement: "s"
update_interval: never
state_class: total_increasing
# Duration timer from shower head
- platform: template
device_id: showerhead
name: "dsh_curShowerWaterFlowTime"
id: dsh_curShowerWaterFlowTime
icon: 'mdi:timer'
unit_of_measurement: "s"
update_interval: never
state_class: total_increasing
# Last duration timer from shower head
- platform: template
device_id: showerhead
name: "dsh_lastShowerWaterFlowTime"
id: dsh_lastShowerWaterFlowTime
icon: 'mdi:timer'
unit_of_measurement: "s"
update_interval: never
state_class: total_increasing
# Current water consumption in Liter
- platform: template
device_id: showerhead
name: "dsh_curShowerLiter"
id: dsh_curShowerLiter
device_class: water
icon: 'mdi:water'
unit_of_measurement: "L"
accuracy_decimals: 1
update_interval: never
state_class: total_increasing
# Last water consumption in Liter
- platform: template
device_id: showerhead
name: "dsh_lastShowerLiter"
id: dsh_lastShowerLiter
device_class: water
icon: 'mdi:water'
unit_of_measurement: "L"
accuracy_decimals: 1
update_interval: never
state_class: total_increasing
# Current water consumption in m³
- platform: template
device_id: showerhead
name: "dsh_curShowerM3"
id: dsh_curShowerM3
device_class: water
icon: 'mdi:cup-water'
unit_of_measurement: "m³"
accuracy_decimals: 5
update_interval: never
state_class: total_increasing
# Previous water consumption in m³
- platform: template
device_id: showerhead
name: "dsh_lastShowerM3"
id: dsh_lastShowerM3
device_class: water
icon: 'mdi:cup-water'
unit_of_measurement: "m³"
accuracy_decimals: 5
update_interval: never
state_class: total_increasing
# Total number of showers - this is where the magic happens
- platform: ble_client
device_id: showerhead
name: "DSH Total Showers"
ble_client_id: DSH
id: dsh_totalShowers
update_interval: never
internal: true
type: characteristic
service_uuid: '7f402200-504f-4c41-5261-6d706869726f' # Not sure if these are the same on all shower heads - as I only have a single showerhead
characteristic_uuid: '7f402203-504f-4c41-5261-6d706869726f' #
notify: true # Enable notifications on the service/charateristic
lambda: |-
auto time = id(homeassistant_time).now(); // Get current time
time_t currTime = time.timestamp; // Struct to generate human readable date/timestamp
char stringbuf[256]; // Buffer for outputting raw data in a readable format
char* buf2 = stringbuf; // Pointer for buffer
char* endofbuf = stringbuf + sizeof(stringbuf); // Pointer to end of buffer
int i; // integer to be sued for counter
uint8_t* pdata = (uint8_t*) x.data(); // The BLE data array
unsigned long int totShowers = pdata[2]; // Total Showers consist of index 1 and 2 in the array
unsigned long int bathTemp = pdata[11]; // Temperature consist of index 11 in the array
unsigned long int bathKwhDec= pdata[13]; // kWh consumptionm consist of index 12 and 13 in the array (multiplied by 100)
unsigned long int bathLiter = pdata[9]; // L consumptionm consist of index 8 and 9 in the array (multiplied by 10)
unsigned int iShowerFlowDuration = pdata[7]; // idx 6+7 = shower time
totShowers += ( pdata[1]<<8);
// ESP_LOGD("ble_adv", "Total showers %ld", totShowers);
dsh.lCurrentShowerNo = totShowers;
// Only reset counters when a new shower being started - as it can have paused. This can be identified by the shower number
// retrieved from the BLE data
if(dsh.occupancy == 0 && dsh.lLastShowerNo != dsh.lCurrentShowerNo) {
dsh.tStartTime = time.timestamp; // Set start time of shower
ESP_LOGW("DSH", "Initializing start time to: %ld and setting occupancy on",time.timestamp);
id(dsh_curShowerkWh).publish_state(0);
id(dsh_curShowerTemp).publish_state(0);
id(dsh_curShowerLiter).publish_state(0);
id(dsh_curShowerM3).publish_state(0);
id(dsh_curShowerTimeSeconds).publish_state(0);
id(dsh_curShowerWaterFlowTime).publish_state(0);
memset(&dsh.bleData[0],0,sizeof(dsh.bleData)); // Zeroize data block buffer
dsh.occupancy = 1; // Set shower as active/occupied
id(dsh_showerOccupied).publish_state(dsh.occupancy); // Publish sensor to HA
}
dsh.curBathTemp = bathTemp;
// ESP_LOGD("ble_adv", "Bath temperature %ld", dsh.curBathTemp);
bathKwhDec += ( pdata[12]<<8);
dsh.curEnergy = (float)bathKwhDec/100;
// ESP_LOGD("ble_adv", "Bath Energy %f kWh", dsh.curEnergy);
bathLiter += ( pdata[8]<<8);
dsh.curWaterLiters = float(bathLiter)/10;
// ESP_LOGD("ble_adv", "Bath water consumption %f L", dsh.curWaterLiters);
iShowerFlowDuration += ( pdata[6]<<8); // Get data for shower time (16 bit number)
dsh.curShowerFlowDuration = iShowerFlowDuration;
// Find out if data has changed compared to previous notification
if(memcmp(&dsh.bleData[0],pdata,18)!=0) {
// Data is different, copy new data
memcpy(&dsh.bleData[0],pdata,18);
// Dump hex data to the log
for(i=0;i<18;i++) {
if (buf2 + 5 < endofbuf) {
if (i > 0) {
buf2 += sprintf(buf2, ":");
}
buf2 += sprintf(buf2, "%02X", pdata[i]);
}
}
ESP_LOGI("DSH", "Data block changes since last: %s", (char*)stringbuf);
// Set information about when data was last updated
dsh.tLastUpdate = time.timestamp;
ESP_LOGI("DSH", "Timestamp for last update set to: %ld",time.timestamp);
strftime(dsh.szLastShowerEnded, sizeof(dsh.szLastShowerEnded), "%Y-%m-%d %H:%M:%S", localtime(&currTime));
// sprintf((char *)dsh.szLastShowerEnded, "%04d-%02d-%02d %02d:%02d:%02d", time.year,time.month,time.day_of_month,time.hour,time.minute,time.second);
}
return(totShowers);
And finally, you need to add this to the yaml of your esp:
packages:
showerhead: !include packages/shower.yaml
This has been working for me ever since, no problems. The advantage is the BLE device can be placed near the shower.
If the above also does not work, you can very likely use the ESP logging in this code to see why it isnāt recognised and maybe adjust the manufacturer code if that is the problem.