Add support for Qwiic PIR binary sensor (#5194)

This commit is contained in:
kahrendt 2023-10-25 15:29:21 -04:00 committed by GitHub
parent 258b0fbff3
commit 841b24f744
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 278 additions and 0 deletions

View file

@ -234,6 +234,7 @@ esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/qmp6988/* @andrewpc esphome/components/qmp6988/* @andrewpc
esphome/components/qr_code/* @wjtje esphome/components/qr_code/* @wjtje
esphome/components/qwiic_pir/* @kahrendt
esphome/components/radon_eye_ble/* @jeffeb3 esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet esphome/components/rc522/* @glmnet

View file

View file

@ -0,0 +1,67 @@
from esphome import core
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, binary_sensor
from esphome.const import (
CONF_DEBOUNCE,
DEVICE_CLASS_MOTION,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@kahrendt"]
qwiic_pir_ns = cg.esphome_ns.namespace("qwiic_pir")
DebounceMode = qwiic_pir_ns.enum("DebounceMode")
DEBOUNCE_MODE_OPTIONS = {
"RAW": DebounceMode.RAW_DEBOUNCE_MODE,
"NATIVE": DebounceMode.NATIVE_DEBOUNCE_MODE,
"HYBRID": DebounceMode.HYBRID_DEBOUNCE_MODE,
}
CONF_DEBOUNCE_MODE = "debounce_mode"
QwiicPIRComponent = qwiic_pir_ns.class_(
"QwiicPIRComponent", cg.Component, i2c.I2CDevice, binary_sensor.BinarySensor
)
def validate_no_debounce_unless_native(config):
if CONF_DEBOUNCE in config:
if config[CONF_DEBOUNCE_MODE] != "NATIVE":
raise cv.Invalid("debounce can only be set if debounce_mode is NATIVE")
return config
CONFIG_SCHEMA = cv.All(
binary_sensor.binary_sensor_schema(
QwiicPIRComponent,
device_class=DEVICE_CLASS_MOTION,
)
.extend(
{
cv.Optional(CONF_DEBOUNCE): cv.All(
cv.time_period,
cv.Range(max=core.TimePeriod(milliseconds=65535)),
),
cv.Optional(CONF_DEBOUNCE_MODE, default="HYBRID"): cv.enum(
DEBOUNCE_MODE_OPTIONS, upper=True
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x12)),
validate_no_debounce_unless_native,
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if debounce_time_setting := config.get(CONF_DEBOUNCE):
cg.add(var.set_debounce_time(debounce_time_setting.total_milliseconds))
else:
cg.add(var.set_debounce_time(1)) # default to 1 ms if not configured
cg.add(var.set_debounce_mode(config[CONF_DEBOUNCE_MODE]))

View file

@ -0,0 +1,137 @@
#include "qwiic_pir.h"
#include "esphome/core/log.h"
namespace esphome {
namespace qwiic_pir {
static const char *const TAG = "qwiic_pir";
void QwiicPIRComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Qwiic PIR...");
// Verify I2C communcation by reading and verifying the chip ID
uint8_t chip_id;
if (!this->read_byte(QWIIC_PIR_CHIP_ID, &chip_id)) {
ESP_LOGE(TAG, "Failed to read the chip's ID");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (chip_id != QWIIC_PIR_DEVICE_ID) {
ESP_LOGE(TAG, "Unknown chip ID, is this a Qwiic PIR?");
this->error_code_ = ERROR_WRONG_CHIP_ID;
this->mark_failed();
return;
}
if (!this->write_byte_16(QWIIC_PIR_DEBOUNCE_TIME, this->debounce_time_)) {
ESP_LOGE(TAG, "Failed to configure debounce time.");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (this->debounce_mode_ == NATIVE_DEBOUNCE_MODE) {
// Publish the starting raw state of the PIR sensor
// If NATIVE mode, the binary_sensor state would be unknown until a motion event
if (!this->read_byte(QWIIC_PIR_EVENT_STATUS, &this->event_register_.reg)) {
ESP_LOGE(TAG, "Failed to read initial sensor state.");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
this->publish_state(this->event_register_.raw_reading);
}
}
void QwiicPIRComponent::loop() {
// Read Event Register
if (!this->read_byte(QWIIC_PIR_EVENT_STATUS, &this->event_register_.reg)) {
ESP_LOGW(TAG, "Failed to communicate with sensor");
return;
}
if (this->debounce_mode_ == HYBRID_DEBOUNCE_MODE) {
// Use a combination of the raw sensor reading and the device's event detection to determine state
// - The device is hardcoded to use a debounce time of 1 ms in this mode
// - Any event, even if it is object_removed, implies motion was active since the last loop, so publish true
// - Use ESPHome's built-in filters for debouncing
this->publish_state(this->event_register_.raw_reading || this->event_register_.event_available);
if (this->event_register_.event_available) {
this->clear_events_();
}
} else if (this->debounce_mode_ == NATIVE_DEBOUNCE_MODE) {
// Uses the device's firmware to debounce the signal
// - Follows the logic of SparkFun's example implementation:
// https://github.com/sparkfun/SparkFun_Qwiic_PIR_Arduino_Library/blob/master/examples/Example2_PrintPIRStatus/Example2_PrintPIRStatus.ino
// (accessed July 2023)
// - Is unreliable at detecting an object being removed, especially at debounce rates even slightly large
if (this->event_register_.event_available) {
// If an object is detected, publish true
if (this->event_register_.object_detected)
this->publish_state(true);
// If an object has been removed, publish false
if (this->event_register_.object_removed)
this->publish_state(false);
this->clear_events_();
}
} else if (this->debounce_mode_ == RAW_DEBOUNCE_MODE) {
// Publishes the raw PIR sensor reading with no further logic
// - May miss a very short motion detection if the ESP's loop time is slow
this->publish_state(this->event_register_.raw_reading);
}
}
void QwiicPIRComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Qwiic PIR:");
if (this->debounce_mode_ == RAW_DEBOUNCE_MODE) {
ESP_LOGCONFIG(TAG, " Debounce Mode: RAW");
} else if (this->debounce_mode_ == NATIVE_DEBOUNCE_MODE) {
ESP_LOGCONFIG(TAG, " Debounce Mode: NATIVE");
ESP_LOGCONFIG(TAG, " Debounce Time: %ums", this->debounce_time_);
} else if (this->debounce_mode_ == HYBRID_DEBOUNCE_MODE) {
ESP_LOGCONFIG(TAG, " Debounce Mode: HYBRID");
}
switch (this->error_code_) {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, " Communication with Qwiic PIR failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(TAG, " Qwiic PIR has wrong chip ID - please verify you are using a Qwiic PIR");
break;
default:
ESP_LOGE(TAG, " Qwiic PIR error code %d", (int) this->error_code_);
break;
}
LOG_I2C_DEVICE(this);
LOG_BINARY_SENSOR(" ", "Qwiic PIR Binary Sensor", this);
}
void QwiicPIRComponent::clear_events_() {
// Clear event status register
if (!this->write_byte(QWIIC_PIR_EVENT_STATUS, 0x00))
ESP_LOGW(TAG, "Failed to clear events on sensor");
}
} // namespace qwiic_pir
} // namespace esphome

View file

@ -0,0 +1,70 @@
/*
* Adds support for Qwiic PIR motion sensors that communicate over an I2C bus.
* These sensors use Sharp PIR motion sensors to detect motion. A firmware running on an ATTiny84 translates the digital
* output to I2C communications.
* ATTiny84 firmware: https://github.com/sparkfun/Qwiic_PIR (acccessed July 2023)
* SparkFun's Arduino library: https://github.com/sparkfun/SparkFun_Qwiic_PIR_Arduino_Library (accessed July 2023)
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace qwiic_pir {
// Qwiic PIR I2C Register Addresses
enum {
QWIIC_PIR_CHIP_ID = 0x00,
QWIIC_PIR_EVENT_STATUS = 0x03,
QWIIC_PIR_DEBOUNCE_TIME = 0x05, // uint16_t debounce time in milliseconds
};
enum DebounceMode {
RAW_DEBOUNCE_MODE,
NATIVE_DEBOUNCE_MODE,
HYBRID_DEBOUNCE_MODE,
};
static const uint8_t QWIIC_PIR_DEVICE_ID = 0x72;
class QwiicPIRComponent : public Component, public i2c::I2CDevice, public binary_sensor::BinarySensor {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_debounce_time(uint16_t debounce_time) { this->debounce_time_ = debounce_time; }
void set_debounce_mode(DebounceMode mode) { this->debounce_mode_ = mode; }
protected:
uint16_t debounce_time_{};
DebounceMode debounce_mode_{};
enum ErrorCode {
NONE = 0,
ERROR_COMMUNICATION_FAILED,
ERROR_WRONG_CHIP_ID,
} error_code_{NONE};
union {
struct {
bool raw_reading : 1; // raw state of PIR sensor
bool event_available : 1; // a debounced object has been detected or removed
bool object_removed : 1; // a debounced object is no longer detected
bool object_detected : 1; // a debounced object has been detected
bool : 4;
};
uint8_t reg;
} event_register_ = {.reg = 0};
void clear_events_();
};
} // namespace qwiic_pir
} // namespace esphome

View file

@ -1813,6 +1813,9 @@ binary_sensor:
name: still name: still
out_pin_presence_status: out_pin_presence_status:
name: out pin presence status name: out pin presence status
- platform: qwiic_pir
i2c_id: i2c_bus
name: "Qwiic PIR Motion Sensor"
pca9685: pca9685:
frequency: 500 frequency: 500