mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	New PM sensor Panasonic SN-GCJA5 (#4988)
This commit is contained in:
		
							
								
								
									
										1
									
								
								esphome/components/gcja5/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/gcja5/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@gcormier"] | ||||
							
								
								
									
										119
									
								
								esphome/components/gcja5/gcja5.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								esphome/components/gcja5/gcja5.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| /* From snooping with a logic analyzer, the I2C on this sensor is broken. I was only able | ||||
|  * to receive 1's as a response from the sensor. I was able to get the UART working. | ||||
|  * | ||||
|  * The datasheet says the values should be divided by 1000, but this must only be for the I2C | ||||
|  * implementation. Comparing UART values with another sensor, there is no need to divide by 1000. | ||||
|  */ | ||||
| #include "gcja5.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cstring> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gcja5 { | ||||
|  | ||||
| static const char *const TAG = "gcja5"; | ||||
|  | ||||
| void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); } | ||||
|  | ||||
| void GCJA5Component::loop() { | ||||
|   const uint32_t now = millis(); | ||||
|   if (now - this->last_transmission_ >= 500) { | ||||
|     // last transmission too long ago. Reset RX index. | ||||
|     this->rx_message_.clear(); | ||||
|   } | ||||
|  | ||||
|   if (this->available() == 0) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // There must now be data waiting | ||||
|   this->last_transmission_ = now; | ||||
|   uint8_t val; | ||||
|   while (this->available() != 0) { | ||||
|     this->read_byte(&val); | ||||
|     this->rx_message_.push_back(val); | ||||
|  | ||||
|     // check if rx_message_ has 32 bytes of data | ||||
|     if (this->rx_message_.size() == 32) { | ||||
|       this->parse_data_(); | ||||
|  | ||||
|       if (this->have_good_data_) { | ||||
|         if (this->pm_1_0_sensor_ != nullptr) | ||||
|           this->pm_1_0_sensor_->publish_state(get_32_bit_uint_(1)); | ||||
|         if (this->pm_2_5_sensor_ != nullptr) | ||||
|           this->pm_2_5_sensor_->publish_state(get_32_bit_uint_(5)); | ||||
|         if (this->pm_10_0_sensor_ != nullptr) | ||||
|           this->pm_10_0_sensor_->publish_state(get_32_bit_uint_(9)); | ||||
|         if (this->pmc_0_3_sensor_ != nullptr) | ||||
|           this->pmc_0_3_sensor_->publish_state(get_16_bit_uint_(13)); | ||||
|         if (this->pmc_0_5_sensor_ != nullptr) | ||||
|           this->pmc_0_5_sensor_->publish_state(get_16_bit_uint_(15)); | ||||
|         if (this->pmc_1_0_sensor_ != nullptr) | ||||
|           this->pmc_1_0_sensor_->publish_state(get_16_bit_uint_(17)); | ||||
|         if (this->pmc_2_5_sensor_ != nullptr) | ||||
|           this->pmc_2_5_sensor_->publish_state(get_16_bit_uint_(21)); | ||||
|         if (this->pmc_5_0_sensor_ != nullptr) | ||||
|           this->pmc_5_0_sensor_->publish_state(get_16_bit_uint_(23)); | ||||
|         if (this->pmc_10_0_sensor_ != nullptr) | ||||
|           this->pmc_10_0_sensor_->publish_state(get_16_bit_uint_(25)); | ||||
|       } else { | ||||
|         this->status_set_warning(); | ||||
|         ESP_LOGV(TAG, "Have 32 bytes but not good data. Skipping."); | ||||
|       } | ||||
|  | ||||
|       this->rx_message_.clear(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool GCJA5Component::calculate_checksum_() { | ||||
|   uint8_t crc = 0; | ||||
|  | ||||
|   for (uint8_t i = 1; i < 30; i++) | ||||
|     crc = crc ^ this->rx_message_[i]; | ||||
|  | ||||
|   ESP_LOGVV(TAG, "Checksum packet was (0x%02X), calculated checksum was (0x%02X)", this->rx_message_[30], crc); | ||||
|  | ||||
|   return (crc == this->rx_message_[30]); | ||||
| } | ||||
|  | ||||
| uint32_t GCJA5Component::get_32_bit_uint_(uint8_t start_index) { | ||||
|   return (((uint32_t) this->rx_message_[start_index + 3]) << 24) | | ||||
|          (((uint32_t) this->rx_message_[start_index + 2]) << 16) | | ||||
|          (((uint32_t) this->rx_message_[start_index + 1]) << 8) | ((uint32_t) this->rx_message_[start_index]); | ||||
| } | ||||
|  | ||||
| uint16_t GCJA5Component::get_16_bit_uint_(uint8_t start_index) { | ||||
|   return (((uint32_t) this->rx_message_[start_index + 1]) << 8) | ((uint32_t) this->rx_message_[start_index]); | ||||
| } | ||||
|  | ||||
| void GCJA5Component::parse_data_() { | ||||
|   ESP_LOGVV(TAG, "GCJA5 Data: "); | ||||
|   for (uint8_t i = 0; i < 32; i++) { | ||||
|     ESP_LOGVV(TAG, "  %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->rx_message_[i]), | ||||
|               this->rx_message_[i]); | ||||
|   } | ||||
|  | ||||
|   if (this->rx_message_[0] != 0x02 || this->rx_message_[31] != 0x03 || !this->calculate_checksum_()) { | ||||
|     ESP_LOGVV(TAG, "Discarding bad packet - failed checks."); | ||||
|     return; | ||||
|   } else | ||||
|     ESP_LOGVV(TAG, "Good packet found."); | ||||
|  | ||||
|   this->have_good_data_ = true; | ||||
|   uint8_t status = this->rx_message_[29]; | ||||
|   if (!this->first_status_log_) { | ||||
|     this->first_status_log_ = true; | ||||
|  | ||||
|     ESP_LOGI(TAG, "GCJA5 Status"); | ||||
|     ESP_LOGI(TAG, "Overall Status : %i", (status >> 6) & 0x03); | ||||
|     ESP_LOGI(TAG, "PD Status      : %i", (status >> 4) & 0x03); | ||||
|     ESP_LOGI(TAG, "LD Status      : %i", (status >> 2) & 0x03); | ||||
|     ESP_LOGI(TAG, "Fan Status     : %i", (status >> 0) & 0x03); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GCJA5Component::dump_config() { ; } | ||||
|  | ||||
| }  // namespace gcja5 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										52
									
								
								esphome/components/gcja5/gcja5.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/gcja5/gcja5.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gcja5 { | ||||
|  | ||||
| class GCJA5Component : public Component, public uart::UARTDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } | ||||
|   void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } | ||||
|   void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; } | ||||
|  | ||||
|   void set_pmc_0_3_sensor(sensor::Sensor *pmc_0_3) { pmc_0_3_sensor_ = pmc_0_3; } | ||||
|   void set_pmc_0_5_sensor(sensor::Sensor *pmc_0_5) { pmc_0_5_sensor_ = pmc_0_5; } | ||||
|   void set_pmc_1_0_sensor(sensor::Sensor *pmc_1_0) { pmc_1_0_sensor_ = pmc_1_0; } | ||||
|   void set_pmc_2_5_sensor(sensor::Sensor *pmc_2_5) { pmc_2_5_sensor_ = pmc_2_5; } | ||||
|   void set_pmc_5_0_sensor(sensor::Sensor *pmc_5_0) { pmc_5_0_sensor_ = pmc_5_0; } | ||||
|   void set_pmc_10_0_sensor(sensor::Sensor *pmc_10_0) { pmc_10_0_sensor_ = pmc_10_0; } | ||||
|  | ||||
|  protected: | ||||
|   void parse_data_(); | ||||
|   bool calculate_checksum_(); | ||||
|  | ||||
|   uint32_t get_32_bit_uint_(uint8_t start_index); | ||||
|   uint16_t get_16_bit_uint_(uint8_t start_index); | ||||
|   uint32_t last_transmission_{0}; | ||||
|   std::vector<uint8_t> rx_message_; | ||||
|  | ||||
|   bool have_good_data_{false}; | ||||
|   bool first_status_log_{false}; | ||||
|   sensor::Sensor *pm_1_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pm_2_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pm_10_0_sensor_{nullptr}; | ||||
|  | ||||
|   sensor::Sensor *pmc_0_3_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_0_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_1_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_2_5_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_5_0_sensor_{nullptr}; | ||||
|   sensor::Sensor *pmc_10_0_sensor_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace gcja5 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										118
									
								
								esphome/components/gcja5/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								esphome/components/gcja5/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import uart, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_PM_1_0, | ||||
|     CONF_PM_2_5, | ||||
|     CONF_PM_10_0, | ||||
|     CONF_PMC_0_5, | ||||
|     CONF_PMC_1_0, | ||||
|     CONF_PMC_2_5, | ||||
|     CONF_PMC_10_0, | ||||
|     UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|     ICON_CHEMICAL_WEAPON, | ||||
|     ICON_COUNTER, | ||||
|     DEVICE_CLASS_PM1, | ||||
|     DEVICE_CLASS_PM10, | ||||
|     DEVICE_CLASS_PM25, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@gcormier"] | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| gcja5_ns = cg.esphome_ns.namespace("gcja5") | ||||
|  | ||||
| GCJA5Component = gcja5_ns.class_("GCJA5Component", cg.PollingComponent, uart.UARTDevice) | ||||
|  | ||||
| CONF_PMC_0_3 = "pmc_0_3" | ||||
| CONF_PMC_5_0 = "pmc_5_0" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(GCJA5Component), | ||||
|         cv.Optional(CONF_PM_1_0): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_CHEMICAL_WEAPON, | ||||
|             accuracy_decimals=2, | ||||
|             device_class=DEVICE_CLASS_PM1, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PM_2_5): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_CHEMICAL_WEAPON, | ||||
|             accuracy_decimals=2, | ||||
|             device_class=DEVICE_CLASS_PM25, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PM_10_0): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_CHEMICAL_WEAPON, | ||||
|             accuracy_decimals=2, | ||||
|             device_class=DEVICE_CLASS_PM10, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_COUNTER, | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_COUNTER, | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_COUNTER, | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_COUNTER, | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_COUNTER, | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|         cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, | ||||
|             icon=ICON_COUNTER, | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|     } | ||||
| ).extend(uart.UART_DEVICE_SCHEMA) | ||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||
|     "gcja5", baud_rate=9600, require_rx=True, parity="EVEN" | ||||
| ) | ||||
| TYPES = { | ||||
|     CONF_PM_1_0: "set_pm_1_0_sensor", | ||||
|     CONF_PM_2_5: "set_pm_2_5_sensor", | ||||
|     CONF_PM_10_0: "set_pm_10_0_sensor", | ||||
|     CONF_PMC_0_3: "set_pmc_0_3_sensor", | ||||
|     CONF_PMC_0_5: "set_pmc_0_5_sensor", | ||||
|     CONF_PMC_1_0: "set_pmc_1_0_sensor", | ||||
|     CONF_PMC_2_5: "set_pmc_2_5_sensor", | ||||
|     CONF_PMC_5_0: "set_pmc_5_0_sensor", | ||||
|     CONF_PMC_10_0: "set_pmc_10_0_sensor", | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await uart.register_uart_device(var, config) | ||||
|  | ||||
|     for key, funcName in TYPES.items(): | ||||
|         if key in config: | ||||
|             sens = await sensor.new_sensor(config[key]) | ||||
|             cg.add(getattr(var, funcName)(sens)) | ||||
		Reference in New Issue
	
	Block a user