mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Added RadonEye RD200 Component (#3119)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
1e5004f495
commit
ad43d6a5bc
9 changed files with 374 additions and 0 deletions
|
@ -139,6 +139,8 @@ esphome/components/psram/* @esphome/core
|
|||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
|
|
23
esphome/components/radon_eye_ble/__init__.py
Normal file
23
esphome/components/radon_eye_ble/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jeffeb3"]
|
||||
|
||||
radon_eye_ble_ns = cg.esphome_ns.namespace("radon_eye_ble")
|
||||
RadonEyeListener = radon_eye_ble_ns.class_(
|
||||
"RadonEyeListener", esp32_ble_tracker.ESPBTDeviceListener
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RadonEyeListener),
|
||||
}
|
||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
25
esphome/components/radon_eye_ble/radon_eye_listener.cpp
Normal file
25
esphome/components/radon_eye_ble/radon_eye_listener.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "radon_eye_listener.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_ble {
|
||||
|
||||
static const char *const TAG = "radon_eye_ble";
|
||||
|
||||
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (not device.get_name().empty()) {
|
||||
if (device.get_name().rfind("FR:R20:SN", 0) == 0) {
|
||||
// This is an RD200, I think
|
||||
ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||
device.address_str().c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace radon_eye_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
19
esphome/components/radon_eye_ble/radon_eye_listener.h
Normal file
19
esphome/components/radon_eye_ble/radon_eye_listener.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_ble {
|
||||
|
||||
class RadonEyeListener : public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
};
|
||||
|
||||
} // namespace radon_eye_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
1
esphome/components/radon_eye_rd200/__init__.py
Normal file
1
esphome/components/radon_eye_rd200/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@jeffeb3"]
|
179
esphome/components/radon_eye_rd200/radon_eye_rd200.cpp
Normal file
179
esphome/components/radon_eye_rd200/radon_eye_rd200.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
#include "radon_eye_rd200.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const TAG = "radon_eye_rd200";
|
||||
|
||||
void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "Connected successfully!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "Disconnected!");
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->read_handle_ = 0;
|
||||
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_read_characteristic_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->read_handle_ = chr->handle;
|
||||
|
||||
// Write a 0x50 to the write characteristic.
|
||||
auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_);
|
||||
if (write_chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_read_characteristic_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->write_handle_ = write_chr->handle;
|
||||
|
||||
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
|
||||
|
||||
write_query_message_();
|
||||
|
||||
request_read_values_();
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->read_handle_) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) {
|
||||
if (value_len < 20) {
|
||||
ESP_LOGD(TAG, "Invalid read");
|
||||
return;
|
||||
}
|
||||
|
||||
// Example data
|
||||
// [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000
|
||||
ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X",
|
||||
value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9],
|
||||
value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18],
|
||||
value[19]);
|
||||
|
||||
if (value[0] != 0x50) {
|
||||
// This isn't a sensor reading.
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert from pCi/L to Bq/m³
|
||||
constexpr float convert_to_bwpm3 = 37.0;
|
||||
|
||||
RadonValue radon_value;
|
||||
radon_value.chars[0] = value[2];
|
||||
radon_value.chars[1] = value[3];
|
||||
radon_value.chars[2] = value[4];
|
||||
radon_value.chars[3] = value[5];
|
||||
float radon_now = radon_value.number * convert_to_bwpm3;
|
||||
if (is_valid_radon_value_(radon_now)) {
|
||||
radon_sensor_->publish_state(radon_now);
|
||||
}
|
||||
|
||||
radon_value.chars[0] = value[6];
|
||||
radon_value.chars[1] = value[7];
|
||||
radon_value.chars[2] = value[8];
|
||||
radon_value.chars[3] = value[9];
|
||||
float radon_day = radon_value.number * convert_to_bwpm3;
|
||||
|
||||
radon_value.chars[0] = value[10];
|
||||
radon_value.chars[1] = value[11];
|
||||
radon_value.chars[2] = value[12];
|
||||
radon_value.chars[3] = value[13];
|
||||
float radon_month = radon_value.number * convert_to_bwpm3;
|
||||
|
||||
if (is_valid_radon_value_(radon_month)) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on month");
|
||||
radon_long_term_sensor_->publish_state(radon_month);
|
||||
} else if (is_valid_radon_value_(radon_day)) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on day");
|
||||
radon_long_term_sensor_->publish_state(radon_day);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month);
|
||||
|
||||
ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3,
|
||||
radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3);
|
||||
|
||||
// This instance must not stay connected
|
||||
// so other clients can connect to it (e.g. the
|
||||
// mobile app).
|
||||
parent()->set_enabled(false);
|
||||
}
|
||||
|
||||
bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; }
|
||||
|
||||
void RadonEyeRD200::update() {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
parent()->connect();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection in progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::write_query_message_() {
|
||||
ESP_LOGV(TAG, "writing 0x50 to write service");
|
||||
int request = 0x50;
|
||||
auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_,
|
||||
sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::request_read_values_() {
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::dump_config() {
|
||||
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
|
||||
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
|
||||
}
|
||||
|
||||
RadonEyeRD200::RadonEyeRD200()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)),
|
||||
sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {}
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
59
esphome/components/radon_eye_rd200/radon_eye_rd200.h
Normal file
59
esphome/components/radon_eye_rd200/radon_eye_rd200.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
|
||||
static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123";
|
||||
static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123";
|
||||
|
||||
class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode {
|
||||
public:
|
||||
RadonEyeRD200();
|
||||
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
|
||||
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
|
||||
|
||||
protected:
|
||||
bool is_valid_radon_value_(float radon);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void write_query_message_();
|
||||
void request_read_values_();
|
||||
|
||||
sensor::Sensor *radon_sensor_{nullptr};
|
||||
sensor::Sensor *radon_long_term_sensor_{nullptr};
|
||||
|
||||
uint16_t read_handle_;
|
||||
uint16_t write_handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_;
|
||||
|
||||
union RadonValue {
|
||||
char chars[4];
|
||||
float number;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
55
esphome/components/radon_eye_rd200/sensor.py
Normal file
55
esphome/components/radon_eye_rd200/sensor.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client
|
||||
|
||||
from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
CONF_ID,
|
||||
CONF_RADON,
|
||||
CONF_RADON_LONG_TERM,
|
||||
ICON_RADIOACTIVE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
radon_eye_rd200_ns = cg.esphome_ns.namespace("radon_eye_rd200")
|
||||
RadonEyeRD200 = radon_eye_rd200_ns.class_(
|
||||
"RadonEyeRD200", cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RadonEyeRD200),
|
||||
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
icon=ICON_RADIOACTIVE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
icon=ICON_RADIOACTIVE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5min"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_RADON in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON])
|
||||
cg.add(var.set_radon(sens))
|
||||
if CONF_RADON_LONG_TERM in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
|
||||
cg.add(var.set_radon_long_term(sens))
|
|
@ -324,6 +324,13 @@ sensor:
|
|||
bus_voltage:
|
||||
name: "INA260 Voltage"
|
||||
update_interval: 60s
|
||||
- platform: radon_eye_rd200
|
||||
ble_client_id: radon_eye_ble_id
|
||||
update_interval: 10min
|
||||
radon:
|
||||
name: "RD200 Radon"
|
||||
radon_long_term:
|
||||
name: "RD200 Radon Long Term"
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
|
@ -423,10 +430,14 @@ ble_client:
|
|||
id: airthings01
|
||||
- mac_address: 01:02:03:04:05:06
|
||||
id: airthingsmini01
|
||||
- mac_address: 01:02:03:04:05:06
|
||||
id: radon_eye_ble_id
|
||||
|
||||
|
||||
airthings_ble:
|
||||
|
||||
radon_eye_ble:
|
||||
|
||||
ruuvi_ble:
|
||||
|
||||
xiaomi_ble:
|
||||
|
|
Loading…
Reference in a new issue