diff --git a/CODEOWNERS b/CODEOWNERS index 79c9c4f94b..d24a19adb1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -210,6 +210,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core +esphome/components/sen21231/* @shreyaskarnik esphome/components/sen5x/* @martgras esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core diff --git a/esphome/components/sen21231/__init__.py b/esphome/components/sen21231/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/sen21231/sen21231.cpp b/esphome/components/sen21231/sen21231.cpp new file mode 100644 index 0000000000..aa123dff62 --- /dev/null +++ b/esphome/components/sen21231/sen21231.cpp @@ -0,0 +1,32 @@ +#include "sen21231.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sen21231_sensor { + +static const char *const TAG = "sen21231_sensor.sensor"; + +void Sen21231Sensor::update() { this->read_data_(); } + +void Sen21231Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "SEN21231:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with SEN21231 failed!"); + } + ESP_LOGI(TAG, "SEN21231: %s", this->is_failed() ? "FAILED" : "OK"); + LOG_UPDATE_INTERVAL(this); +} + +void Sen21231Sensor::read_data_() { + person_sensor_results_t results; + this->read_bytes(PERSON_SENSOR_I2C_ADDRESS, (uint8_t *) &results, sizeof(results)); + ESP_LOGD(TAG, "SEN21231: %d faces detected", results.num_faces); + this->publish_state(results.num_faces); + if (results.num_faces == 1) { + ESP_LOGD(TAG, "SEN21231: is facing towards camera: %d", results.faces[0].is_facing); + } +} + +} // namespace sen21231_sensor +} // namespace esphome diff --git a/esphome/components/sen21231/sen21231.h b/esphome/components/sen21231/sen21231.h new file mode 100644 index 0000000000..b4d540df55 --- /dev/null +++ b/esphome/components/sen21231/sen21231.h @@ -0,0 +1,77 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +// ref: +// https://github.com/usefulsensors/person_sensor_pico_c/blob/main/person_sensor.h + +namespace esphome { +namespace sen21231_sensor { +// The I2C address of the person sensor board. +static const uint8_t PERSON_SENSOR_I2C_ADDRESS = 0x62; +static const uint8_t PERSON_SENSOR_REG_MODE = 0x01; +static const uint8_t PERSON_SENSOR_REG_ENABLE_ID = 0x02; +static const uint8_t PERSON_SENSOR_REG_SINGLE_SHOT = 0x03; +static const uint8_t PERSON_SENSOR_REG_CALIBRATE_ID = 0x04; +static const uint8_t PERSON_SENSOR_REG_PERSIST_IDS = 0x05; +static const uint8_t PERSON_SENSOR_REG_ERASE_IDS = 0x06; +static const uint8_t PERSON_SENSOR_REG_DEBUG_MODE = 0x07; + +static const uint8_t PERSON_SENSOR_MAX_FACES_COUNT = 4; +static const uint8_t PERSON_SENSOR_MAX_IDS_COUNT = 7; + +// The results returned from the sensor have a short header providing +// information about the length of the data packet: +// reserved: Currently unused bytes. +// data_size: Length of the entire packet, excluding the header and +// checksum. +// For version 1.0 of the sensor, this should be 40. +using person_sensor_results_header_t = struct { + uint8_t reserved[2]; // Bytes 0-1. + uint16_t data_size; // Bytes 2-3. +}; + +// Each face found has a set of information associated with it: +// box_confidence: How certain we are we have found a face, from 0 to 255. +// box_left: X coordinate of the left side of the box, from 0 to 255. +// box_top: Y coordinate of the top edge of the box, from 0 to 255. +// box_width: Width of the box, where 255 is the full view port size. +// box_height: Height of the box, where 255 is the full view port size. +// id_confidence: How sure the sensor is about the recognition result. +// id: Numerical ID assigned to this face. +// is_looking_at: Whether the person is facing the camera, 0 or 1. +using person_sensor_face_t = struct __attribute__((__packed__)) { + uint8_t box_confidence; // Byte 1. + uint8_t box_left; // Byte 2. + uint8_t box_top; // Byte 3. + uint8_t box_right; // Byte 4. + uint8_t box_bottom; // Byte 5. + int8_t id_confidence; // Byte 6. + int8_t id; // Byte 7 + uint8_t is_facing; // Byte 8. +}; + +// This is the full structure of the packet returned over the wire from the +// sensor when we do an I2C read from the peripheral address. +// The checksum should be the CRC16 of bytes 0 to 38. You shouldn't need to +// verify this in practice, but we found it useful during our own debugging. +using person_sensor_results_t = struct __attribute__((__packed__)) { + person_sensor_results_header_t header; // Bytes 0-4. + int8_t num_faces; // Byte 5. + person_sensor_face_t faces[PERSON_SENSOR_MAX_FACES_COUNT]; // Bytes 6-37. + uint16_t checksum; // Bytes 38-39. +}; + +class Sen21231Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void update() override; + void dump_config() override; + + protected: + void read_data_(); +}; + +} // namespace sen21231_sensor +} // namespace esphome diff --git a/esphome/components/sen21231/sensor.py b/esphome/components/sen21231/sensor.py new file mode 100644 index 0000000000..fb1dc19278 --- /dev/null +++ b/esphome/components/sen21231/sensor.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ICON_MOTION_SENSOR + +CODEOWNERS = ["@shreyaskarnik"] +DEPENDENCIES = ["i2c"] + +sen21231_sensor_ns = cg.esphome_ns.namespace("sen21231_sensor") +Sen21231Sensor = sen21231_sensor_ns.class_( + "Sen21231Sensor", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema(Sen21231Sensor, icon=ICON_MOTION_SENSOR, accuracy_decimals=1) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x62)) +) + + +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 bdc6da3406..d43490c8cc 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1221,7 +1221,9 @@ sensor: name: "Still Energy" detection_distance: name: "Distance Detection" - + - platform: sen21231 + name: "Person Sensor" + i2c_id: i2c_bus esp32_touch: setup_mode: false iir_filter: 10ms