mirror of
https://github.com/esphome/esphome.git
synced 2024-11-28 12:46:22 +01:00
New PM sensor Panasonic SN-GCJA5 (#4988)
This commit is contained in:
parent
a8fa4b56f9
commit
0ed0bdc655
@ -102,6 +102,7 @@ esphome/components/fastled_base/* @OttoWinter
|
|||||||
esphome/components/feedback/* @ianchi
|
esphome/components/feedback/* @ianchi
|
||||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||||
esphome/components/fs3000/* @kahrendt
|
esphome/components/fs3000/* @kahrendt
|
||||||
|
esphome/components/gcja5/* @gcormier
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gp8403/* @jesserockz
|
esphome/components/gp8403/* @jesserockz
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
|
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))
|
@ -228,6 +228,10 @@ uart:
|
|||||||
baud_rate: 256000
|
baud_rate: 256000
|
||||||
parity: NONE
|
parity: NONE
|
||||||
stop_bits: 1
|
stop_bits: 1
|
||||||
|
- id: gcja5_uart
|
||||||
|
rx_pin: GPIO10
|
||||||
|
parity: EVEN
|
||||||
|
baud_rate: 9600
|
||||||
|
|
||||||
ota:
|
ota:
|
||||||
safe_mode: true
|
safe_mode: true
|
||||||
@ -341,6 +345,24 @@ mcp23s17:
|
|||||||
deviceaddress: 1
|
deviceaddress: 1
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
|
- platform: gcja5
|
||||||
|
pm_1_0:
|
||||||
|
name: "Particulate Matter <1.0µm Concentration"
|
||||||
|
pm_2_5:
|
||||||
|
name: "Particulate Matter <2.5µm Concentration"
|
||||||
|
pm_10_0:
|
||||||
|
name: "Particulate Matter <10.0µm Concentration"
|
||||||
|
pmc_0_5:
|
||||||
|
name: "PMC 0.5"
|
||||||
|
pmc_1_0:
|
||||||
|
name: "PMC 1.0"
|
||||||
|
pmc_2_5:
|
||||||
|
name: "PMC 2.5"
|
||||||
|
pmc_5_0:
|
||||||
|
name: "PMC 5.0"
|
||||||
|
pmc_10_0:
|
||||||
|
name: "PMC 10.0"
|
||||||
|
uart_id: gcja5_uart
|
||||||
- platform: internal_temperature
|
- platform: internal_temperature
|
||||||
name: Internal Temperature
|
name: Internal Temperature
|
||||||
- platform: ble_client
|
- platform: ble_client
|
||||||
|
Loading…
Reference in New Issue
Block a user