mirror of
https://github.com/esphome/esphome.git
synced 2026-01-10 04:00:51 -07:00
[radon_eye_rd200] update Radon Eye RD200 with v2/v3 support (#7962)
Co-authored-by: Artem Butusov <art.sormy@gmail.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
#include "radon_eye_listener.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -11,17 +10,11 @@ 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()) {
|
||||
// Vector containing the prefixes to search for
|
||||
std::vector<std::string> prefixes = {"FR:R", "FR:I", "FR:H"};
|
||||
|
||||
// Check if the device name starts with any of the prefixes
|
||||
if (std::any_of(prefixes.begin(), prefixes.end(),
|
||||
[&](const std::string &prefix) { return device.get_name().starts_with(prefix); })) {
|
||||
// Device found
|
||||
ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||
device.address_str().c_str());
|
||||
}
|
||||
// Radon Eye devices have names starting with "FR:"
|
||||
if (device.get_name().starts_with("FR:")) {
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||
device.address_str_to(addr_buf));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "radon_eye_rd200.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
@@ -7,6 +9,22 @@ namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const TAG = "radon_eye_rd200";
|
||||
|
||||
static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V1 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001523-1212-efde-1523-785feabcd123");
|
||||
static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V1 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001524-1212-efde-1523-785feabcd123");
|
||||
static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V1 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001525-1212-efde-1523-785feabcd123");
|
||||
static const uint8_t WRITE_COMMAND_V1 = 0x50;
|
||||
|
||||
static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V2 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001523-0000-1000-8000-00805f9b34fb");
|
||||
static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V2 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001524-0000-1000-8000-00805f9b34fb");
|
||||
static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V2 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001525-0000-1000-8000-00805f9b34fb");
|
||||
static const uint8_t WRITE_COMMAND_V2 = 0x40;
|
||||
|
||||
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) {
|
||||
@@ -23,6 +41,22 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (this->parent()->get_service(SERVICE_UUID_V1) != nullptr) {
|
||||
service_uuid_ = SERVICE_UUID_V1;
|
||||
sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V1;
|
||||
sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V1;
|
||||
write_command_ = WRITE_COMMAND_V1;
|
||||
} else if (this->parent()->get_service(SERVICE_UUID_V2) != nullptr) {
|
||||
service_uuid_ = SERVICE_UUID_V2;
|
||||
sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V2;
|
||||
sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V2;
|
||||
write_command_ = WRITE_COMMAND_V2;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "No supported device has been found, disconnecting");
|
||||
parent()->set_enabled(false);
|
||||
break;
|
||||
}
|
||||
|
||||
this->read_handle_ = 0;
|
||||
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
@@ -32,90 +66,114 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
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());
|
||||
sensors_write_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_();
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_register_for_notify(gattc_if, this->parent()->get_remote_bda(), this->read_handle_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error registering for sensor notify, status=%d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->get_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);
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "write descr failed, error status = %x", param->write.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->read_handle_) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
ESP_LOGV(TAG, "Write descr success, writing 0x%02X at write_handle=%d", this->write_command_,
|
||||
this->write_handle_);
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_write_char(gattc_if, this->parent()->get_conn_id(), this->write_handle_, sizeof(write_command_),
|
||||
(uint8_t *) &write_command_, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error writing 0x%02x command, status=%d", write_command_, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.is_notify) {
|
||||
ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value, %d bytes", param->notify.value_len);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value, %d bytes", param->notify.value_len);
|
||||
}
|
||||
read_sensors_(param->notify.value, param->notify.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");
|
||||
if (value_len < 1) {
|
||||
ESP_LOGW(TAG, "Unexpected empty message");
|
||||
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]);
|
||||
uint8_t command = value[0];
|
||||
|
||||
if (value[0] != 0x50) {
|
||||
// This isn't a sensor reading.
|
||||
if ((command == WRITE_COMMAND_V1 && value_len < 20) || (command == WRITE_COMMAND_V2 && value_len < 68)) {
|
||||
ESP_LOGW(TAG, "Unexpected command 0x%02X message length %d", command, value_len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Example data V1:
|
||||
// 501085EBB9400000000000000000220025000000
|
||||
// Example data V2:
|
||||
// 4042323230313033525532303338330652443230304e56322e302e3200014a00060a00080000000300010079300000e01108001c00020000003822005c8f423fa4709d3f
|
||||
ESP_LOGV(TAG, "radon sensors raw bytes");
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, value, value_len, ESP_LOG_VERBOSE);
|
||||
|
||||
// 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);
|
||||
float radon_now; // in Bq/m³
|
||||
float radon_day; // in Bq/m³
|
||||
float radon_month; // in Bq/m³
|
||||
if (command == WRITE_COMMAND_V1) {
|
||||
// Use memcpy to avoid unaligned memory access
|
||||
float temp;
|
||||
memcpy(&temp, value + 2, sizeof(float));
|
||||
radon_now = temp * convert_to_bwpm3;
|
||||
memcpy(&temp, value + 6, sizeof(float));
|
||||
radon_day = temp * convert_to_bwpm3;
|
||||
memcpy(&temp, value + 10, sizeof(float));
|
||||
radon_month = temp * convert_to_bwpm3;
|
||||
} else if (command == WRITE_COMMAND_V2) {
|
||||
// Use memcpy to avoid unaligned memory access
|
||||
uint16_t temp;
|
||||
memcpy(&temp, value + 33, sizeof(uint16_t));
|
||||
radon_now = temp;
|
||||
memcpy(&temp, value + 35, sizeof(uint16_t));
|
||||
radon_day = temp;
|
||||
memcpy(&temp, value + 37, sizeof(uint16_t));
|
||||
radon_month = temp;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unexpected command value: 0x%02X", command);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
if (this->radon_sensor_ != nullptr) {
|
||||
this->radon_sensor_->publish_state(radon_now);
|
||||
}
|
||||
|
||||
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);
|
||||
if (this->radon_long_term_sensor_ != nullptr) {
|
||||
if (radon_month > 0) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on month");
|
||||
this->radon_long_term_sensor_->publish_state(radon_month);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on day");
|
||||
this->radon_long_term_sensor_->publish_state(radon_day);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG,
|
||||
@@ -130,49 +188,23 @@ void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) {
|
||||
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()->get_gattc_if(), this->parent()->get_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()->get_gattc_if(), this->parent()->get_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)) {}
|
||||
RadonEyeRD200::RadonEyeRD200() : PollingComponent(10000) {}
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
} // namespace esphome
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
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();
|
||||
@@ -32,25 +28,17 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode
|
||||
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};
|
||||
|
||||
uint8_t write_command_;
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user