mirror of
https://github.com/esphome/esphome.git
synced 2024-11-23 15:38:11 +01:00
Implement SC01_O3 UART Ozone sensor.
This commit is contained in:
parent
e81191ebd2
commit
c28e761e2a
6 changed files with 214 additions and 0 deletions
|
@ -348,6 +348,7 @@ esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||||
esphome/components/rtl87xx/* @kuba2k2
|
esphome/components/rtl87xx/* @kuba2k2
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||||
|
esphome/components/sc01_o3/* @Tthecreator
|
||||||
esphome/components/scd4x/* @martgras @sjtrny
|
esphome/components/scd4x/* @martgras @sjtrny
|
||||||
esphome/components/script/* @esphome/core
|
esphome/components/script/* @esphome/core
|
||||||
esphome/components/sdl/* @clydebarrow
|
esphome/components/sdl/* @clydebarrow
|
||||||
|
|
0
esphome/components/sc01_o3/__init__.py
Normal file
0
esphome/components/sc01_o3/__init__.py
Normal file
114
esphome/components/sc01_o3/sc01_o3.cpp
Normal file
114
esphome/components/sc01_o3/sc01_o3.cpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#include "sc01_o3.h"
|
||||||
|
|
||||||
|
static const char *const TAG = "SC01O3Sensor";
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace sc01_o3 {
|
||||||
|
|
||||||
|
// The position of various data points in the return packet, excluding the first 0xFF byte.
|
||||||
|
constexpr uint8_t GAS_NAME_POS = 0;
|
||||||
|
constexpr uint8_t UNIT_POS = 1;
|
||||||
|
constexpr uint8_t GAS_CONCENTRATION_HIGH_POS = 3;
|
||||||
|
constexpr uint8_t GAS_CONCENTRATION_LOW_POS = 4;
|
||||||
|
constexpr uint8_t CHECKSUM_POS = 7;
|
||||||
|
|
||||||
|
// Defined constant
|
||||||
|
constexpr uint8_t O3_GAS_TYPE = 0x17;
|
||||||
|
constexpr uint8_t UNIT_PPB = 0x04;
|
||||||
|
constexpr uint8_t START_BYTE_VALUE = 0xFF;
|
||||||
|
|
||||||
|
constexpr std::array<uint8_t, 7> SET_QNA_MODE_COMMAND = {
|
||||||
|
0x01, // Reserved
|
||||||
|
0x78, // Change order
|
||||||
|
0x41, // Q & A mode (This is where we have to ask the device for a
|
||||||
|
// measurement, instead of the measurement happening every second.)
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Reserved
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array<uint8_t, 7> ASK_FOR_DATA_COMMAND = {
|
||||||
|
0x01, // Reserved
|
||||||
|
0x86, // Order
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, // Reserved
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array<uint8_t, 1> DATA_PREPEND_BYTES = {
|
||||||
|
START_BYTE_VALUE, // Start byte
|
||||||
|
};
|
||||||
|
|
||||||
|
void SC01O3Sensor::setup() {
|
||||||
|
if (!this->is_initialized_) {
|
||||||
|
this->set_qna_mode_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SC01O3Sensor::dump_config() { ESP_LOGCONFIG(TAG, "SC01 O3 sensor!"); }
|
||||||
|
|
||||||
|
uint8_t SC01O3Sensor::calculate_checksum(const uint8_t *data, uint8_t len) {
|
||||||
|
// Calculation is to add up all bytes (except first 0xFF) bytes, then negate it, and add one.
|
||||||
|
uint8_t out = 0;
|
||||||
|
for (uint8_t i = 0; i < len; ++i) {
|
||||||
|
out += data[i];
|
||||||
|
}
|
||||||
|
out = (~out) + 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SC01O3Sensor::send_command_(const std::array<uint8_t, 7> data) {
|
||||||
|
this->write_array(DATA_PREPEND_BYTES);
|
||||||
|
this->write_array(data);
|
||||||
|
uint8_t checksum = calculate_checksum(data.data(), data.size());
|
||||||
|
this->write_array(&checksum, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SC01O3Sensor::set_qna_mode_() {
|
||||||
|
// Called once during setup
|
||||||
|
send_command_(SET_QNA_MODE_COMMAND);
|
||||||
|
ESP_LOGI(TAG, "O3 Sensor setup complete.");
|
||||||
|
this->is_initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SC01O3Sensor::update() {
|
||||||
|
if (!this->is_initialized_) {
|
||||||
|
set_qna_mode_();
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Update....");
|
||||||
|
send_command_(SET_QNA_MODE_COMMAND);
|
||||||
|
send_command_(ASK_FOR_DATA_COMMAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SC01O3Sensor::loop() {
|
||||||
|
// ESP_LOGI(TAG, "Loop....");
|
||||||
|
if (!this->is_initialized_) {
|
||||||
|
set_qna_mode_();
|
||||||
|
}
|
||||||
|
// Polling function to read data from UART
|
||||||
|
while (this->available() >= 9) {
|
||||||
|
// Read the first byte and check for the start byte (0xFF)
|
||||||
|
uint8_t first_byte = this->read();
|
||||||
|
if (first_byte != START_BYTE_VALUE) {
|
||||||
|
continue; // Skip and search for the next start byte
|
||||||
|
}
|
||||||
|
// If we find START_BYTE_VALUE, attempt to read the rest of the packet
|
||||||
|
|
||||||
|
uint8_t buffer[8]; // Excluding the start byte
|
||||||
|
this->read_array(buffer, 8);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Received data: %x %x %x %x %x %x %x %x", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4],
|
||||||
|
buffer[5], buffer[6], buffer[7]);
|
||||||
|
|
||||||
|
// Validate packet
|
||||||
|
if (buffer[GAS_NAME_POS] == O3_GAS_TYPE && buffer[UNIT_POS] == UNIT_PPB &&
|
||||||
|
buffer[CHECKSUM_POS] == calculate_checksum(buffer, 7)) { // The gas name and unit should be consistent.
|
||||||
|
uint16_t concentration =
|
||||||
|
(static_cast<uint16_t>(buffer[GAS_CONCENTRATION_HIGH_POS]) << 8) | buffer[GAS_CONCENTRATION_LOW_POS];
|
||||||
|
state = concentration;
|
||||||
|
this->publish_state(concentration);
|
||||||
|
ESP_LOGI(TAG, "Valid data: %u ppb", concentration);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Invalid data received after start byte");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sc01_o3
|
||||||
|
} // namespace esphome
|
29
esphome/components/sc01_o3/sc01_o3.h
Normal file
29
esphome/components/sc01_o3/sc01_o3.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/uart/uart_component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace sc01_o3 {
|
||||||
|
|
||||||
|
class SC01O3Sensor : public sensor::Sensor, public PollingComponent, public uart::UARTDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
void loop() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
uint16_t state = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool is_initialized_ = false;
|
||||||
|
void set_qna_mode_();
|
||||||
|
static uint8_t calculate_checksum(const uint8_t *data, uint8_t len);
|
||||||
|
void send_command_(std::array<uint8_t, 7> data);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sc01_o3
|
||||||
|
} // namespace esphome
|
56
esphome/components/sc01_o3/sensor.py
Normal file
56
esphome/components/sc01_o3/sensor.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import sensor, uart
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
|
DEVICE_CLASS_OZONE,
|
||||||
|
ICON_MOLECULE_CO2,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_PARTS_PER_BILLION,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uart"]
|
||||||
|
CODEOWNERS = ["@Tthecreator"]
|
||||||
|
|
||||||
|
SC01O3_ns = cg.esphome_ns.namespace("sc01_o3")
|
||||||
|
SC01O3Component = SC01O3_ns.class_(
|
||||||
|
"SC01O3Sensor", sensor.Sensor, cg.PollingComponent, uart.UARTDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||||
|
icon=ICON_MOLECULE_CO2,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
device_class=DEVICE_CLASS_OZONE,
|
||||||
|
)
|
||||||
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(SC01O3Component),
|
||||||
|
cv.Optional(
|
||||||
|
CONF_UPDATE_INTERVAL, "1000ms"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||||
|
"sc01_o3",
|
||||||
|
baud_rate=9600,
|
||||||
|
require_tx=True,
|
||||||
|
require_rx=True,
|
||||||
|
data_bits=8,
|
||||||
|
parity=None,
|
||||||
|
stop_bits=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
# var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await uart.register_uart_device(var, config)
|
||||||
|
cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
14
tests/components/sc01_o3/test.esp32-ard.yaml
Normal file
14
tests/components/sc01_o3/test.esp32-ard.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
uart:
|
||||||
|
tx_pin: GPIO021
|
||||||
|
rx_pin: GPIO016
|
||||||
|
baud_rate: 9600
|
||||||
|
stop_bits: 1
|
||||||
|
id: uart_bus
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: sc01_o3
|
||||||
|
id: o3_sensor
|
||||||
|
uart_id: uart_bus
|
||||||
|
name: "Ozone Concentration"
|
||||||
|
icon: "mdi:chemical-weapon"
|
||||||
|
update_interval: 5s
|
Loading…
Reference in a new issue