diff --git a/CODEOWNERS b/CODEOWNERS index 1455643550..384db8098f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -246,6 +246,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core +esphome/components/sen0321/* @notjj esphome/components/sen21231/* @shreyaskarnik esphome/components/sen5x/* @martgras esphome/components/sensirion_common/* @martgras diff --git a/esphome/components/sen0321/__init__.py b/esphome/components/sen0321/__init__.py new file mode 100644 index 0000000000..458ffa67f9 --- /dev/null +++ b/esphome/components/sen0321/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@notjj"] diff --git a/esphome/components/sen0321/sen0321.cpp b/esphome/components/sen0321/sen0321.cpp new file mode 100644 index 0000000000..7801c8c389 --- /dev/null +++ b/esphome/components/sen0321/sen0321.cpp @@ -0,0 +1,36 @@ +#include "sen0321.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace sen0321_sensor { + +static const char *const TAG = "sen0321_sensor.sensor"; + +void Sen0321Sensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up sen0321..."); + if (!this->write_byte(SENSOR_MODE_REGISTER, SENSOR_MODE_AUTO)) { + ESP_LOGW(TAG, "Error setting measurement mode."); + this->mark_failed(); + }; +} + +void Sen0321Sensor::update() { this->read_data_(); } + +void Sen0321Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "DF Robot Ozone Sensor sen0321:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with sen0321 failed!"); + } + LOG_UPDATE_INTERVAL(this); +} + +void Sen0321Sensor::read_data_() { + uint8_t result[2]; + this->read_bytes(SENSOR_AUTO_READ_REG, result, (uint8_t) 2); + this->publish_state(((uint16_t) (result[0] << 8) + result[1])); +} + +} // namespace sen0321_sensor +} // namespace esphome diff --git a/esphome/components/sen0321/sen0321.h b/esphome/components/sen0321/sen0321.h new file mode 100644 index 0000000000..3bb3d5b015 --- /dev/null +++ b/esphome/components/sen0321/sen0321.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +// ref: +// https://github.com/DFRobot/DFRobot_OzoneSensor + +namespace esphome { +namespace sen0321_sensor { +// Sensor Mode +// While passive is supposedly supported, it does not appear to work reliably. +static const uint8_t SENSOR_MODE_REGISTER = 0x03; +static const uint8_t SENSOR_MODE_AUTO = 0x00; +static const uint8_t SENSOR_MODE_PASSIVE = 0x01; +static const uint8_t SET_REGISTER = 0x04; + +// Each register is 2 wide, so 0x07-0x08 for passive, or 0x09-0x0A for auto +// First register is high bits, next low. +static const uint8_t SENSOR_PASS_READ_REG = 0x07; +static const uint8_t SENSOR_AUTO_READ_REG = 0x09; + +class Sen0321Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void update() override; + void dump_config() override; + void setup() override; + + protected: + void read_data_(); +}; + +} // namespace sen0321_sensor +} // namespace esphome diff --git a/esphome/components/sen0321/sensor.py b/esphome/components/sen0321/sensor.py new file mode 100644 index 0000000000..ee613dc440 --- /dev/null +++ b/esphome/components/sen0321/sensor.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + ICON_CHEMICAL_WEAPON, + UNIT_PARTS_PER_BILLION, + STATE_CLASS_MEASUREMENT, +) + +CODEOWNERS = ["@notjj"] +DEPENDENCIES = ["i2c"] + +sen0321_sensor_ns = cg.esphome_ns.namespace("sen0321_sensor") +Sen0321Sensor = sen0321_sensor_ns.class_( + "Sen0321Sensor", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + Sen0321Sensor, + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x73)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/tests/test1.yaml b/tests/test1.yaml index b78407069d..33782dbf53 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1081,6 +1081,11 @@ sensor: ambient_pressure_compensation: 961mBar temperature_offset: 4.2C i2c_id: i2c_bus + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s + i2c_id: i2c_bus - platform: sgp30 eco2: name: Workshop eCO2