mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add support for Qwiic PIR binary sensor (#5194)
This commit is contained in:
		| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/qwiic_pir/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/qwiic_pir/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										67
									
								
								esphome/components/qwiic_pir/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/qwiic_pir/binary_sensor.py
									
									
									
									
									
										Normal 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])) | ||||||
							
								
								
									
										137
									
								
								esphome/components/qwiic_pir/qwiic_pir.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/qwiic_pir/qwiic_pir.cpp
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										70
									
								
								esphome/components/qwiic_pir/qwiic_pir.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/qwiic_pir/qwiic_pir.h
									
									
									
									
									
										Normal 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 | ||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user