Hello all,
I’ve been facing an issue with an ESP32 (ESP32-WROOM-32) that has the following sensors connected:
- CM1106 Single Beam NDIR CO2 Sensor Module (CO2 Sensor)
- CUBIC PM2105 Laser Particle Sensor Module (PM1/PM2.5/PM10 Sensor)
- TEXAS HDC1080 (Temperature and Humidity sensor)
- SENSIRION SGP30 (TVOC/eCO2 sensor)
Everything works fine however the ESP reboots every ~5000 seconds
And after adding a memory sensor I’ve confirmed that is the memory is not released and cause the ESP to reboot
This is the ESPHome yaml:
substitutions:
devicename: daikin-air-sensor-floor-1
friendly_name: Daikin Air Sensor Floor 1
device_description: DAIKIN Air Sensor BRY88AB151K
esphome:
name: $devicename
comment: ${device_description}
project:
name: "DAIKIN.BRY88AB151K"
version: "BRY88AB151K"
# name_add_mac_suffix: true
includes:
- cm1106.h
- pm2105.h # For PM2105 module
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
baud_rate: 0
level: INFO
# Enable Home Assistant API
api:
encryption:
key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ota:
password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
wifi:
ssid: !secret ls_ac1750_wifi_ssid
password: !secret ls_ac1750_wifi_password
manual_ip:
static_ip: 192.168.1.123
gateway: 192.168.1.254
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "$devicename"
password: "xxxxxxxxx"
captive_portal:
web_server:
port: 80
version: 2
uart:
- id: cm1106_uart
rx_pin: GPIO16
tx_pin: GPIO17
baud_rate: 9600
sensor:
- platform: wifi_signal
name: "${friendly_name} Wifi Signal"
update_interval: 30s
- platform: uptime
name: "${friendly_name} Uptime"
- platform: template
id: esp_memory
icon: mdi:memory
name: "${friendly_name} Free Memory"
lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024;
unit_of_measurement: 'kB'
state_class: measurement
entity_category: "diagnostic"
- platform: custom
lambda: |-
auto cm1106Sensor = new CM1106Sensor(id(cm1106_uart), 10000);
App.register_component(cm1106Sensor);
return {cm1106Sensor};
sensors:
- name: "${friendly_name} CO2"
id: daikinco2
accuracy_decimals: 0
unit_of_measurement: "ppm"
device_class: carbon_dioxide
on_value_range: #CO2 LED always on when CO2 is below 1000
- below: 1000
then:
- output.turn_on: ledco2
- above: 1000 #CO2 LED blink slowly when CO2 is above 1000 but below 2000
below: 2000
then:
- while:
condition:
sensor.in_range:
id: daikinco2
above: 1000
below: 2000
then:
- output.turn_on: ledco2
- delay: 500ms
- output.turn_off: ledco2
- delay: 500ms
- above: 2000 #CO2 LED blink quickly when CO2 is above 2000
then:
- while:
condition:
sensor.in_range:
id: daikinco2
above: 2000
then:
- output.turn_on: ledco2
- delay: 200ms
- output.turn_off: ledco2
- delay: 200ms
- platform: custom
lambda: |-
auto pmsensor = new pm2005();
App.register_component(pmsensor);
return {pmsensor->vpm1, pmsensor->vpm25, pmsensor->vpm10};
sensors:
- name: "${friendly_name} PM1"
id: daikinpm1
unit_of_measurement: "µg/m³"
accuracy_decimals: 0
device_class: pm1
- name: "${friendly_name} PM2.5"
id: daikinpm25
unit_of_measurement: "µg/m³"
accuracy_decimals: 0
device_class: pm25
on_value_range: #PM2.5 LED always on when PM2.5 is below 35
- below: 35
then:
- output.turn_on: ledpm25
- above: 35 #PM2.5 LED blink slowly when PM2.5 is above 35 but below 75
below: 75
then:
- while:
condition:
sensor.in_range:
id: daikinpm25
above: 35
below: 75
then:
- output.turn_on: ledpm25
- delay: 500ms
- output.turn_off: ledpm25
- delay: 500ms
- above: 75 #PM2.5 LED blink quickly when PM2.5 is above 75
then:
- while:
condition:
sensor.in_range:
id: daikinpm25
above: 75
then:
- output.turn_on: ledpm25
- delay: 200ms
- output.turn_off: ledpm25
- delay: 200ms
- name: "${friendly_name} PM10"
id: daikinpm10
unit_of_measurement: "µg/m³"
accuracy_decimals: 0
device_class: pm10
- platform: hdc1080
i2c_id: sgp30_bus
temperature:
name: "${friendly_name} Temperature"
filters:
- offset: -5.0 # original was -4.0
id: temperature
humidity:
name: "${friendly_name} Humidity"
filters:
- offset: 6.0
id: humidity
update_interval: 30s
- platform: sgp30
i2c_id: sgp30_bus
eco2:
name: "${friendly_name} eCO2"
accuracy_decimals: 1
# internal: true
tvoc:
name: "${friendly_name} TVOC"
id: daikintvocppb
filters:
# - lambda: return x * 0.0023; #ppd -> mg/m³
- lambda: return x * 2.3; #ppd -> µg/m³
- exponential_moving_average:
alpha: 0.1
send_every: 10
send_first_at: 1
# unit_of_measurement: "mg/m³"
unit_of_measurement: "µg/m³"
device_class: volatile_organic_compounds
# accuracy_decimals: 2
accuracy_decimals: 0
on_value_range: #TVOC LED always on when TVOC is below 35
- below: 500
then:
- output.turn_on: ledtvoc
- above: 500 #TVOC LED blink slowly when TVOC is above 35 but below 75
below: 700
then:
- while:
condition:
sensor.in_range:
id: daikinpm25
above: 500
below: 700
then:
- output.turn_on: ledtvoc
- delay: 500ms
- output.turn_off: ledtvoc
- delay: 500ms
- above: 700 #TVOC LED blink quickly when TVOC is above 75
then:
- while:
condition:
sensor.in_range:
id: daikinpm25
above: 700
then:
- output.turn_on: ledtvoc
- delay: 200ms
- output.turn_off: ledtvoc
- delay: 200ms
compensation:
temperature_source: temperature
humidity_source: humidity
store_baseline: yes
address: 0x58
update_interval: 1s
switch:
- platform: custom
lambda: |-
auto cm1106Calib = new CM1106CalibrateSwitch(id(cm1106_uart));
App.register_component(cm1106Calib);
return {cm1106Calib};
switches:
- id: calibration
internal: true
status_led:
pin:
number: 25
inverted: True
button:
- platform: restart
name: "${friendly_name} Restart"
- platform: template
name: "${friendly_name} CO2 Calibration"
on_press:
then:
- switch.turn_on: calibration
binary_sensor:
- platform: gpio
pin: GPIO34
id: RESET
internal: true
on_press: # After pressing the RESET button on the back, the CO2 indicator light is on, and after ten minutes, set the ambient CO2 concentration as the reference 400ppm and turn off the CO2 indicator light
then:
- output.turn_on: ledco2
- delay: 10min
- switch.turn_on: calibration
- delay: 10s
- output.turn_on: ledco2
i2c:
- id: PM25_bus
sda: 23
scl: 22
scan: true
- id: sgp30_bus
sda: 19
scl: 21
scan: true
output:
- platform: gpio #DAIKIN Air Sensor CO2 LED
pin: GPIO4
id: ledco2
- platform: gpio #DAIKIN Air Sensor TVOC LED
pin: GPIO32
id: ledtvoc
- platform: gpio #DAIKIN Air Sensor PM2.5 LED
pin: GPIO33
id: ledpm25
text_sensor:
- platform: wifi_info
ip_address:
name: "${friendly_name} IP Address"
icon: "mdi:ip-outline"
ssid:
name: "${friendly_name} Connected SSID"
icon: "mdi:router-wireless"
mac_address:
name: "${friendly_name} MAC Address"
icon: "mdi:lan"
- platform: version
name: "${friendly_name} FW Version"
An the code of the two libraries used:
- cm1106.h
// put this file in your esphome folder
// protocol implemented as described in https://en.gassensor.com.cn/Product_files/Specifications/CM1106-C%20Single%20Beam%20NDIR%20CO2%20Sensor%20Module%20Specification.pdf
#include "esphome.h"
class CM1106 : public UARTDevice {
public:
CM1106(UARTComponent *parent) : UARTDevice(parent) {}
void setCo2CalibValue(uint16_t ppm = 400) {
uint8_t cmd[6];
memcpy(cmd, CM1106_CMD_SET_CO2_CALIB, sizeof(cmd));
cmd[3] = ppm >> 8;
cmd[4] = ppm & 0xFF;
uint8_t response[4] = {0};
bool success = sendUartCommand(cmd, sizeof(cmd), response, sizeof(response));
if(!success) {
ESP_LOGW(TAG, "Reading data from CM1106 failed!");
return;
}
// check if correct response received
if(memcmp(response, CM1106_CMD_SET_CO2_CALIB_RESPONSE, sizeof(response)) != 0) {
ESP_LOGW(TAG, "Got wrong UART response: %02X %02X %02X %02X", response[0], response[1], response[2], response[3]);
return;
}
ESP_LOGD(TAG, "CM1106 Successfully calibrated sensor to %uppm", ppm);
}
int16_t getCo2PPM() {
uint8_t response[8] = {0}; // response: 0x16, 0x05, 0x01, DF1, DF2, DF3, DF4, CRC. PPM: DF1*256+DF2
bool success = sendUartCommand(CM1106_CMD_GET_CO2, sizeof(CM1106_CMD_GET_CO2), response, sizeof(response));
if(!success) {
ESP_LOGW(TAG, "Reading data from CM1106 failed!");
return -1;
}
if(!(response[0] == 0x16 && response[1] == 0x05 && response[2] == 0x01)) {
ESP_LOGW(TAG, "Got wrong UART response: %02X %02X %02X %02X...", response[0], response[1], response[2], response[3]);
return -1;
}
uint8_t checksum = calcCRC(response, sizeof(response));
if(response[7] != checksum) {
ESP_LOGW(TAG, "Got wrong UART checksum: 0x%02X - Calculated: 0x%02X", response[7], checksum);
return -1;
}
int16_t ppm = response[3] << 8 | response[4];
ESP_LOGD(TAG, "CM1106 Received COâ‚‚=%uppm DF3=%02X DF4=%02X", ppm, response[5], response[6]);
return ppm;
}
private:
const char *TAG = "cm1106";
uint8_t CM1106_CMD_GET_CO2[4] = {0x11, 0x01, 0x01, 0xED}; // head, len, cmd, [data], crc
uint8_t CM1106_CMD_SET_CO2_CALIB[6] = {0x11, 0x03, 0x03, 0x00, 0x00, 0x00};
uint8_t CM1106_CMD_SET_CO2_CALIB_RESPONSE[4] = {0x16, 0x01, 0x03, 0xE6};
// Checksum: 256-(HEAD+LEN+CMD+DATA)%256
uint8_t calcCRC(uint8_t* response, size_t len) {
uint8_t crc = 0;
// last byte of response is checksum, don't calculate it
for(int i = 0; i < len - 1; i++) {
crc -= response[i];
}
return crc;
}
bool sendUartCommand(uint8_t *command, size_t commandLen, uint8_t *response = nullptr, size_t responseLen = 0) {
// Empty RX Buffer
while (available()) {
read();
}
// calculate CRC
command[commandLen - 1] = calcCRC(command, commandLen);
write_array(command, commandLen);
flush();
if(response == nullptr) {
return true;
}
return read_array(response, responseLen);
}
};
class CM1106Sensor : public PollingComponent, public Sensor {
private:
CM1106 *cm1106;
public:
CM1106Sensor(UARTComponent *parent, uint32_t update_interval) : PollingComponent(update_interval) {
cm1106 = new CM1106(parent);
}
float get_setup_priority() const { return setup_priority::DATA; }
void setup() override {
}
void update() override {
int16_t ppm = cm1106->getCo2PPM();
if(ppm > -1) {
publish_state(ppm);
}
}
};
class CM1106CalibrateSwitch : public Component, public Switch {
private:
CM1106 *cm1106;
public:
CM1106CalibrateSwitch(UARTComponent *parent) {
cm1106 = new CM1106(parent);
}
void write_state(bool state) override {
if(state) {
publish_state(state);
cm1106->setCo2CalibValue();
turn_off();
}
else {
publish_state(state);
}
}
};
- pm2015.h
#include "esphome.h"
uint16_t pm1; //PM1.0
uint16_t pm25; //PM2.5
uint16_t pm10; //PM10
uint8_t Sensor_Situation = 0; // Close: 1 Malfunction : 2 Under detecting : 3 Detecting completed: 0x80; other data is invalid.
uint16_t Sensor_MeasuringMode = 0; // Single measuring mode: 2 Continuous measuring mode: 3 Dynamic measuring mode: 5 Timing measuring mode: >= 300 (means measuring time)
bool Updated = 0; //
class pm2005 : public PollingComponent {//, public Sensor {
public:
Sensor *vpm1 = new Sensor();
Sensor *vpm25 = new Sensor();
Sensor *vpm10 = new Sensor();
pm2005() : PollingComponent(1000) {}
void setup() override {
// This will be called by App.setup()
//Wire.begin(23, 22);
//delay(100);
//Wire.beginTransmission(0x28);
//Wire.write(0x16); //Frame header
//Wire.write(0x01); //Number of byte, not including length of device address (From P1 to P7, 7 bytes in total)
//Wire.write(0x05); //Data 1
//Wire.write(0x00); //Data 2, high byte
//Wire.write(0x24); //Data 2 , low byte
//Wire.write(0x00); //Reserved
//Wire.write(0x16^0x01^0x03^0x24); //Data check code
//Wire.endTransmission(true);
}
void update() override {
// This will be called every "update_interval" milliseconds.
byte* buf = new byte[12];
Wire.requestFrom(0x28, 12);
Wire.readBytes(buf, 12);
/*
ESP_LOGD("custom", "P1 0x16 : %02X", buf[1]);
ESP_LOGD("custom", "P3 Sensor situation : %02X", buf[2]);
ESP_LOGD("custom", "P4 high byte PM1.0 : %02X", buf[3]);
ESP_LOGD("custom", "P5 low byte PM1.0 : %02X", buf[4]);
ESP_LOGD("custom", "P6 high byte PM2.5 : %02X", buf[5]);
ESP_LOGD("custom", "P7 low byte PM2.5 : %02X", buf[6]);
ESP_LOGD("custom", "P8 high byte PM10 : %02X", buf[7]);
ESP_LOGD("custom", "P9 low byte PM10 : %02X", buf[8]);
ESP_LOGD("custom", "P10 high byte M mode : %02X", buf[9]);
ESP_LOGD("custom", "P11 low byte M mode : %02X", buf[10]);
*/
if(Sensor_Situation != buf[2])
{
Sensor_Situation = buf[2];
if(Sensor_Situation==1) ESP_LOGD("custom", "Sensor situation: Close.");
else if(Sensor_Situation==2) ESP_LOGD("custom", "Sensor situation: Malfunction.");
else if(Sensor_Situation==3) ESP_LOGD("custom", "Sensor situation: Under detecting.");
else if(Sensor_Situation==0x80)
{
Updated = 1;
ESP_LOGD("custom", "Sensor situation: Detecting completed.");
}
}
pm1 = buf[3] * 0x100 + buf[4];
pm25 = buf[5] * 0x100 + buf[6];
pm10 = buf[7] * 0x100 + buf[8];
Sensor_MeasuringMode = buf[9] * 0x100 + buf[10];
if(Updated)
{
ESP_LOGD("custom", "PM1.0: %d", pm1);
ESP_LOGD("custom", "PM2.5: %d", pm25);
ESP_LOGD("custom", "PM10 : %d", pm10);
vpm1 -> publish_state(pm1); //PM1.0
vpm25 -> publish_state(pm25); //PM2.5
vpm10 -> publish_state(pm10); //PM10
if(Sensor_MeasuringMode==2) ESP_LOGD("custom", "The measuring mode of sensor: Single measuring mode.");
else if(Sensor_MeasuringMode==3) ESP_LOGD("custom", "The measuring mode of sensor: Continuous measuring mode.");
else if(Sensor_MeasuringMode==5) ESP_LOGD("custom", "The measuring mode of sensor: Dynamic measuring mode.");
Updated = 0;
}
}
};
I’m currently using ESPHome 2023.6.3 (but noticed the issue with prior versions).
Any help or comment is more than welcome.