Skip to content

Instantly share code, notes, and snippets.

@LeoDJ
Last active February 19, 2025 20:28
Show Gist options
  • Save LeoDJ/217d431addee2ccf0288c32256e1e6d5 to your computer and use it in GitHub Desktop.
Save LeoDJ/217d431addee2ccf0288c32256e1e6d5 to your computer and use it in GitHub Desktop.
ESPHome Custom Sensor CM1106 CO2
esphome:
name: cm1106_sensor
platform: ESP32
board: wemos_d1_mini32
includes:
- "cm1106.h"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_pass
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "CM1106 Fallback Hotspot"
password: "1234567890"
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
uart:
rx_pin: GPIO16
tx_pin: GPIO17
baud_rate: 9600
id: cm1106_uart
sensor:
- platform: custom
lambda: |-
auto cm1106Sensor = new CM1106Sensor(id(cm1106_uart), 10000);
App.register_component(cm1106Sensor);
return {cm1106Sensor};
sensors:
- name: "CO₂ Sensor"
accuracy_decimals: 0
unit_of_measurement: "ppm"
icon: "mdi:molecule-co2"
switch:
- platform: custom
lambda: |-
auto cm1106Calib = new CM1106CalibrateSwitch(id(cm1106_uart));
App.register_component(cm1106Calib);
return {cm1106Calib};
switches:
- name: "CM1106 Zero Calibration"
// 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(CM1106_CMD_SET_CO2_CALIB, sizeof(CM1106_CMD_SET_CO2_CALIB), 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);
}
}
};
@schellekensonline
Copy link

Are you planning to make an external component for this sensor? Support for Custom components unfortunately stopped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment