From 0ccfea1379ce5c12350937797fe707a866a72601 Mon Sep 17 00:00:00 2001 From: Artem Butusov Date: Sat, 9 Nov 2024 18:24:34 -0500 Subject: [PATCH] add rd200 v2/v3 support --- .../radon_eye_ble/radon_eye_listener.cpp | 2 +- .../radon_eye_rd200/radon_eye_rd200.cpp | 119 ++++++++++-------- .../radon_eye_rd200/radon_eye_rd200.h | 10 +- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index 340322c188..334f887e30 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -10,7 +10,7 @@ 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:R", 0) == 0) { + if (device.get_name().rfind("FR:", 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()); diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 3959178b94..e7cebac6af 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -7,6 +7,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 +39,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,7 +64,6 @@ 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(), @@ -43,19 +74,14 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + request_read_values_(); + write_query_message_(); - request_read_values_(); 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); - break; - } + case ESP_GATTC_NOTIFY_EVT: { if (param->read.handle == this->read_handle_) { read_sensors_(param->read.value, param->read.value_len); } @@ -68,48 +94,48 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } 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; + float radon_now; // in Bq/m³ + float radon_day; // in Bq/m³ + float radon_month; // in Bq/m³ + if (command == WRITE_COMMAND_V1) { + radon_now = *(float *) (value + 2) * convert_to_bwpm3; + radon_day = *(float *) (value + 6) * convert_to_bwpm3; + radon_month = *(float *) (value + 10) * convert_to_bwpm3; + } else if (command == WRITE_COMMAND_V2) { + radon_now = *(uint16_t *) (value + 33); + radon_day = *(uint16_t *) (value + 35); + radon_month = *(uint16_t *) (value + 37); + } else { + ESP_LOGW(TAG, "Unexpected command value: 0x%02X", command); + return; + } + 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); @@ -129,7 +155,7 @@ 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; } +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) { @@ -144,10 +170,9 @@ void RadonEyeRD200::update() { } void RadonEyeRD200::write_query_message_() { - ESP_LOGV(TAG, "writing 0x50 to write service"); - int request = 0x50; + ESP_LOGV(TAG, "writing 0x%02x to write service", write_command_); 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, + 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 sending write request for sensor, status=%d", status); @@ -155,10 +180,10 @@ void RadonEyeRD200::write_query_message_() { } 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); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), + this->read_handle_); if (status) { - ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); + ESP_LOGW(TAG, "Error registering for sensor notify, status=%d", status); } } @@ -167,11 +192,7 @@ void RadonEyeRD200::dump_config() { 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 diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.h b/esphome/components/radon_eye_rd200/radon_eye_rd200.h index 7b29be7bd8..d44a22a515 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.h +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -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(); @@ -41,16 +37,12 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode 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