I’ve been having issues with API connections dropping on an ESP32 with BLE sensors and BLE tracker enabled. I typically see EOF errors in the HomeAssistant Core logs, though not always.
The ESP32 is reading heart-rate from a bluetooth strap, and then sets the speed of a fan based on a couple of thresholds. The bluetooth and fan control works just fine.
The ESP exposes a few elements for control: a mode select, and the threshold values for low, medium, and high speeds. As long as HomeAssistant doesn’t attempt to change any of these controls the device works find. However, when I try to change the mode or the thresholds, sometimes the change is effected, sometimes not. When it is not, typically there is an EOF error in the Core log.
If I disable BLE scan then I don’t get any errors, and changes are effective. With the scan enabled, a change via HomeAssistant will fail within ~4 attempts. At all times I can be connected to the ESP log output via wifi…even when I am getting the EOF errors the log output continues uninterrupted.
I’ve tried changing the BLE scan window to everything from 30ms to 300ms. Smaller window values seem to work “better”, but I can’t really quantify that…its not significant enough to do so. Note that the smaller scan window values don’t work very well for detecting my BLE device.
I’m not super familiar with this forum, so please excuse any formatting issues or other etiquette missteps. Happy to fill in any additional details.
ETA: The ESP has ~70kb of available heap space, and never resets during any of the above issues. You can see that enabled below, as I was initially concerned that perhaps the BLE component was using up RAM and randomly causing resets. But, I have no evidence that is the case. I’ve never seen heap drop below ~65k, and uptime never resets.
This device has been working for over a year. I’ve only recently noticed this issue. Mostly, “it just works” via automations, and I rarely fiddle with it from the frontend. So, I don’t know exactly when this may have changed since I first deployed it.
esphome:
name: hrm-fan
project:
name: "tom.hrmfan"
version: "0.9.0"
esp32:
board: esp32doit-devkit-v1
framework:
type: arduino
debug:
update_interval: 5s
# Enable logging
logger:
level: DEBUG
# Enable Home Assistant API
api:
on_client_connected:
- logger.log:
format: "Client %s connected to API with IP %s"
args: ["client_info.c_str()", "client_address.c_str()"]
on_client_disconnected:
- logger.log: "API client disconnected!"
ota:
password: "..."
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esp32-Test Fallback Hotspot"
password: "..."
captive_portal:
button:
- platform: restart
name: ${name} "Restart"
# BLE configuration for the ESPHome Heart Rate Display
#
# Copyright (c) 2021 Koen Vervloesem
# SPDX-License-Identifier: MIT
#
esp32_ble_tracker:
id: ble_track
scan_parameters:
window: 300ms
ble_client:
# - mac_address: "F3:74:EF:EE:A8:C1"
- mac_address: "DB:E3:CE:55:CF:20"
id: tom_hrm
on_disconnect:
then:
lambda: |-
id(tom_hrm_bpm).publish_state(0.0); //# send a bunch of 0's to flush the averaging on the receive side.
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
id(tom_hrm_bpm).publish_state(0.0);
#
# BLE HRM controlled Fan
# Operating modes:
# Disabled = FAN is OFF
# Low/Med/High = manual mode fan setting as requested
# HRM = HRM Controlled
#
select:
- platform: template
name: HRM Fan Mode
id: hrm_fan_mode
optimistic: true
update_interval: 1s
options:
- disabled
- low
- med
- high
- hrm
initial_option: disabled
restore_value: true
set_action:
- logger.log:
format: "Chosen option: %s"
args: ["x.c_str()"]
binary_sensor:
- platform: gpio
id: hrm_switch_low
pin:
number: GPIO17
mode:
input: true
pullup: true
- platform: gpio
id: hrm_switch_med
pin:
number: GPIO18
mode:
input: true
pullup: true
- platform: gpio
id: hrm_switch_high
pin:
number: GPIO19
mode:
input: true
pullup: true
# HRM Thresholds:
# OFF = fan is off when HRM is below this value, Low if above
# MED = Fan is medium if above this value
# HIGH = Fan is high above this value
#
number:
- platform: template
name: low
id: hrm_low_thresh
min_value: 75
max_value: 200
step: 1
optimistic: true
restore_value: true
- platform: template
name: med
id: hrm_med_thresh
min_value: 75
max_value: 200
step: 1
optimistic: true
restore_value: true
- platform: template
name: high
id: hrm_high_thresh
min_value: 75
max_value: 200
step: 1
optimistic: true
restore_value: true
sensor:
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Max Block"
loop_time:
name: "Loop Time"
- platform: uptime
name: HRM Uptime
- platform: ble_client
type: characteristic
ble_client_id: tom_hrm
id: tom_hrm_bpm
name: "test Heart rate measurement"
service_uuid: '180d' # Heart Rate Service
characteristic_uuid: '2a37' # Heart Rate Measurement
notify: true
lambda: |-
if (x.empty()) {
ESP_LOGD("HRM:", "Empty\n");
return (float)(x.size());
}
uint16_t heart_rate_measurement = x[1];
if (x[0] & 1) {
heart_rate_measurement += (x[2] << 8);
}
return (float)heart_rate_measurement;
icon: 'mdi:heart'
unit_of_measurement: 'bpm'
filters:
- sliding_window_moving_average:
window_size: 6
send_every: 3
update_interval: 1s
- platform: ble_client
type: characteristic
ble_client_id: tom_hrm
id: hrm_battery
name: "HRM batt"
service_uuid: '180F' # Heart Rate Service
characteristic_uuid: '2a19' # Heart Rate Measurement
- platform: ble_client
type: characteristic
ble_client_id: tom_hrm
id: device_name
service_uuid: '1800' # Generic Access Profile
characteristic_uuid: '2a00' # Device Name
lambda: |-
std::string data_string(x.begin(), x.end());
id(tom_hrm_name).publish_state(data_string.c_str());
return (float)x.size();
update_interval: 30s
text_sensor:
- platform: debug
device:
name: "Device Info"
reset_reason:
name: "Reset Reason"
- platform: template
name: "test heart rate sensor name"
id: tom_hrm_name
- platform: template
name: "HRM Fan"
id: tom_hrm_fan
output:
- platform: gpio
pin: GPIO27
id: low_speed_fan
- platform: gpio
pin: GPIO26
id: med_speed_fan
- platform: gpio
pin: GPIO25
id: high_speed_fan
interval:
- interval: 5s
then:
lambda: !lambda |-
int fan_state = 0;
ESP_LOGD("interval:", "fired state = %f", id(tom_hrm_bpm).get_state());
ESP_LOGD("interval:", " mode = %s", id(hrm_fan_mode).state.c_str());
ESP_LOGD("interval:", " low_t = %f", id(hrm_low_thresh).state);
ESP_LOGD("interval:", " med_t = %f", id(hrm_med_thresh).state);
ESP_LOGD("interval:", " high_t = %f", id(hrm_high_thresh).state);
// if fan is in a manual mode then set the fan state to that mode
if (!strcmp(id(hrm_fan_mode).state.c_str(), "disabled")) fan_state = 0;
if (!strcmp(id(hrm_fan_mode).state.c_str(), "low")) fan_state = 1;
if (!strcmp(id(hrm_fan_mode).state.c_str(), "med")) fan_state = 2;
if (!strcmp(id(hrm_fan_mode).state.c_str(), "high")) fan_state = 3;
if (!strcmp(id(hrm_fan_mode).state.c_str(), "hrm")){ // if in HRM mode then set the speed according to the thresholds
//id(ble_track).start_scan();
if (id(tom_hrm_bpm).get_state() < id(hrm_low_thresh).state || isnan(id(tom_hrm_bpm).get_state())) {
fan_state = 0;
}
else if (id(tom_hrm_bpm).get_state() > id(hrm_low_thresh).state && id(tom_hrm_bpm).get_state() < id(hrm_med_thresh).state ) {
fan_state = 1;
}
else if (id(tom_hrm_bpm).get_state() > id(hrm_med_thresh).state && id(tom_hrm_bpm).get_state() < id(hrm_high_thresh).state ) {
fan_state = 2;
}
else {
fan_state = 3;
}
}
else {
//id(ble_track).stop_scan();
}
// set the fan output state to according to the above
switch (fan_state) {
case 0: //off
ESP_LOGD("interval:", " Fan = OFF\n");
id(tom_hrm_fan).publish_state("off");
id(low_speed_fan).turn_off();
id(med_speed_fan).turn_off();
id(high_speed_fan).turn_off();
break;
case 1: //#low
ESP_LOGD("interval:", " Fan = LOW\n");
id(tom_hrm_fan).publish_state("Low");
id(low_speed_fan).turn_on();
id(med_speed_fan).turn_off();
id(high_speed_fan).turn_off();
break;
case 2: //#med
ESP_LOGD("interval:", " Fan = MED\n");
id(tom_hrm_fan).publish_state("Med");
id(low_speed_fan).turn_off();
id(med_speed_fan).turn_on();
id(high_speed_fan).turn_off();
break;
case 3: //#high
ESP_LOGD("interval:", " Fan = HIGH\n");
id(tom_hrm_fan).publish_state("High");
id(low_speed_fan).turn_off();
id(med_speed_fan).turn_off();
id(high_speed_fan).turn_on();
break;
}