diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 3959178b94..c9b339d81b 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -7,6 +7,58 @@ namespace radon_eye_rd200 { static const char *const TAG = "radon_eye_rd200"; +void RadonEyeRD200::handle_status_response_(const uint8_t *response, uint16_t length) { + if (response[0] != 0x40) { + // This isn't a sensor reading. + return; + } + + radoneye_value_response_t *data = (radoneye_value_response_t *) response; + + ESP_LOGD(TAG, " Measurements (Bq/m³) now: %u", data->latest_bq_m3); + ESP_LOGD(TAG, " Measurements Day (Bq/m³) avg: %u", data->day_avg_bq_m3); + ESP_LOGD(TAG, " Measurements Month (Bq/m³) avg: %u", data->month_avg_bq_m3); + ESP_LESP_LOGDOGW(TAG, " Measurements Peak (Bq/m³): %u", data->peak_bq_m3); + + float flatest_bq_m3 = (float) data->latest_bq_m3; + float fday_avg_bq_m3 = (float) data->day_avg_bq_m3; + float fmonth_avg_bq_m3 = (float) data->month_avg_bq_m3; + float fpeak_bq_m3 = (float) data->peak_bq_m3; + + if (radon_sensor_ != nullptr) { + ESP_LOGD(TAG, " Sensor radon send!"); + radon_sensor_->publish_state(flatest_bq_m3); + delay(25); + } else { + ESP_LOGI(TAG, " Sensor radon not exists!"); + } + if (radon_day_avg_ != nullptr) { + ESP_LOGD(TAG, " Sensor radon_day_avg send!"); + radon_day_avg_->publish_state(fday_avg_bq_m3); + delay(25); + } else { + ESP_LOGI(TAG, " Sensor radon_day_avg not exists!"); + } + if (radon_long_term_sensor_ != nullptr) { + ESP_LOGD(TAG, " Sensor radon_long_term send!"); + radon_long_term_sensor_->publish_state(fmonth_avg_bq_m3); + delay(25); + } else { + ESP_LOGI(TAG, " Sensor radon_long_term not exists!"); + } + if (radon_peak_ != nullptr) { + ESP_LOGD(TAG, " Sensor radon_peak send!"); + radon_peak_->publish_state(fpeak_bq_m3); + delay(25); + } else { + ESP_LOGI(TAG, " Sensor radon_peak not exists!"); + } + + return; +} + +void RadonEyeRD200::handle_history_response_(const uint8_t *response, uint16_t length) { return; } + 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,29 +75,117 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } 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()); + if (this->radon_version_ == 2 || this->radon_version_ == 3) { + ESP_LOGW(TAG, "Use Version 2 or 3"); + this->read_handle_ = 0; + auto *command_chr = + this->parent()->get_characteristic(service_uuid_v2_, sensors_command_characteristic_uuid_v2_); + if (command_chr == nullptr) { + ESP_LOGW(TAG, "No sensor command characteristic found at service %s char %s", + service_uuid_v2_.to_string().c_str(), sensors_command_characteristic_uuid_v2_.to_string().c_str()); + break; + } else { + ESP_LOGD(TAG, "Connect Command_Handler"); + } + this->command_handle_ = command_chr->handle; + + auto *status_chr = this->parent()->get_characteristic(service_uuid_v2_, sensors_status_characteristic_uuid_v2_); + if (status_chr == nullptr) { + ESP_LOGW(TAG, "No sensor status characteristic found at service %s char %s", + service_uuid_v2_.to_string().c_str(), sensors_status_characteristic_uuid_v2_.to_string().c_str()); + break; + } else { + ESP_LOGD(TAG, "Connect Status_Handler"); + } + this->status_handle_ = status_chr->handle; + + auto *history_chr = + this->parent()->get_characteristic(service_uuid_v2_, sensors_history_characteristic_uuid_v2_); + if (history_chr == nullptr) { + ESP_LOGW(TAG, "No sensor history characteristic found at service %s char %s", + service_uuid_v2_.to_string().c_str(), sensors_history_characteristic_uuid_v2_.to_string().c_str()); + break; + } else { + ESP_LOGD(TAG, "Connect History_Handler"); + } + this->history_handle_ = status_chr->handle; + + auto sensor_status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), this->status_handle_); + if (sensor_status) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify sensor status failed, status=%d", sensor_status); + } else { + ESP_LOGD(TAG, "Register for Notify Status_Handler"); + } + + auto sensor_history = esp_ble_gattc_register_for_notify( + this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), this->history_handle_); + if (sensor_history) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify sensor history failed, status=%d", sensor_history); + } else { + ESP_LOGD(TAG, "Register for Notify History_Handler"); + } + + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + + write_status_query_message_(); + + break; + + } else { + 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; } - 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; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (param->notify.handle == this->status_handle_) { + this->status_handle_state_ = esp32_ble_tracker::ClientState::ESTABLISHED; + } + if (param->notify.handle == this->history_handle_) { + this->history_handle_state_ = esp32_ble_tracker::ClientState::ESTABLISHED; } - this->write_handle_ = write_chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + if (this->status_handle_state_ == esp32_ble_tracker::ClientState::ESTABLISHED && + this->history_handle_state_ == esp32_ble_tracker::ClientState::ESTABLISHED) { + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + } + break; + } - write_query_message_(); + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle == this->status_handle_) { + ESP_LOGD(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", param->notify.handle, param->notify.value[0]); - request_read_values_(); + this->handle_status_response_(param->notify.value, param->notify.value_len); + } + if (param->notify.handle == this->history_handle_) { + ESP_LOGD(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", param->notify.handle, param->notify.value[0]); + + this->handle_history_response_(param->notify.value, param->notify.value_len); + } break; } @@ -140,6 +280,11 @@ void RadonEyeRD200::update() { } else { ESP_LOGW(TAG, "Connection in progress"); } + } else { + if (this->radon_version_ == 2 || this->radon_version_ == 3) { + ESP_LOGV(TAG, "Update Version 2 or 3"); + write_status_query_message_(); + } } } @@ -154,6 +299,28 @@ void RadonEyeRD200::write_query_message_() { } } +void RadonEyeRD200::write_status_query_message_() { + ESP_LOGD(TAG, "writing 0x40 to command service"); + int request = 0x40; + auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->command_handle_, sizeof(request), (uint8_t *) &request, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending command request for sensor, status=%d", status); + } +} + +void RadonEyeRD200::write_history_query_message_() { + ESP_LOGD(TAG, "writing 0x41 to command service"); + int request = 0x41; + auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->command_handle_, sizeof(request), (uint8_t *) &request, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending command 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); @@ -163,15 +330,22 @@ void RadonEyeRD200::request_read_values_() { } void RadonEyeRD200::dump_config() { + ESP_LOGCONFIG(TAG, "Radon Eye RD200:"); LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); + LOG_SENSOR(" ", "Radon Day Avg", this->radon_day_avg_); + LOG_SENSOR(" ", "Radon Peak", this->radon_peak_); } RadonEyeRD200::RadonEyeRD200() : PollingComponent(10000), service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + service_uuid_v2_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID_V2)), 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)) {} + sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)), + sensors_command_characteristic_uuid_v2_(esp32_ble_tracker::ESPBTUUID::from_raw(COMMAND_CHARACTERISTIC_UUID_V2)), + sensors_status_characteristic_uuid_v2_(esp32_ble_tracker::ESPBTUUID::from_raw(STATUS_CHARACTERISTIC_UUID_V2)), + sensors_history_characteristic_uuid_v2_(esp32_ble_tracker::ESPBTUUID::from_raw(HISTROY_CHARACTERISTIC_UUID_V2)) {} } // 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..cf4887bdae 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.h +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -18,6 +18,40 @@ 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"; +static const char *const SERVICE_UUID_V2 = "00001523-0000-1000-8000-00805f9b34fb"; +static const char *const COMMAND_CHARACTERISTIC_UUID_V2 = "00001524-0000-1000-8000-00805f9b34fb"; +static const char *const STATUS_CHARACTERISTIC_UUID_V2 = "00001525-0000-1000-8000-00805f9b34fb"; +static const char *const HISTROY_CHARACTERISTIC_UUID_V2 = "00001526-0000-1000-8000-00805f9b34fb"; + +/* Thanks to sormy https://github.com/esphome/issues/issues/3371#issuecomment-1851004514 */ +typedef struct { + /* 00 */ uint8_t command; // supposed to be 0x40 + /* 01 */ uint8_t size; // supposed to be 0x42 + /* 02 */ char serial_part2[6]; + /* 08 */ char serial_part1[3]; + /* 11 */ char serial_part3[4]; + /* 15 */ uint8_t __unk1[1]; + /* 16 */ char model[6]; + /* 22 */ char version[6]; + /* 28 */ uint8_t __unk2[5]; + /* 33 */ uint16_t latest_bq_m3; + /* 35 */ uint16_t day_avg_bq_m3; + /* 37 */ uint16_t month_avg_bq_m3; + /* 39 */ uint8_t __unk3[12]; + /* 51 */ uint16_t peak_bq_m3; + /* 53 */ uint8_t __unk4[16]; // Length 15 or 16? Maybe because of version 3 +} __attribute__((packed)) radoneye_value_response_t; + +typedef struct { + /* 00 */ uint8_t command; // supposed to be 0x41 + /* 01 */ uint8_t response_count; // total number of responses (each response is separate event) + /* 02 */ uint8_t response_no; // number of response + /* 03 */ uint8_t value_count; // within response + /* 04 */ uint16_t values_bq_m3[250]; +} __attribute__((packed)) radoneye_history_response_t; +/* Thanks to sormy https://github.com/esphome/issues/issues/3371#issuecomment-1851004514 */ + + class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode { public: RadonEyeRD200(); @@ -28,24 +62,45 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode 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_version(int version) { radon_version_ = version; } void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } + void set_radon_day_avg(sensor::Sensor *radon_day_avg) { radon_day_avg_ = radon_day_avg; } void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } + void set_radon_peak(sensor::Sensor *radon_peak) { radon_peak_ = radon_peak; } protected: + void handle_status_response_(const uint8_t *response, uint16_t length); + + void handle_history_response_(const uint8_t *response, uint16_t length); + bool is_valid_radon_value_(float radon); void read_sensors_(uint8_t *value, uint16_t value_len); void write_query_message_(); + void write_status_query_message_(); + void write_history_query_message_(); void request_read_values_(); + int radon_version_{1}; sensor::Sensor *radon_sensor_{nullptr}; + sensor::Sensor *radon_day_avg_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; + sensor::Sensor *radon_peak_{nullptr}; uint16_t read_handle_; uint16_t write_handle_; + uint16_t command_handle_; + uint16_t status_handle_; + esp32_ble_tracker::ClientState status_handle_state_; + uint16_t history_handle_; + esp32_ble_tracker::ClientState history_handle_state_; esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID service_uuid_v2_; esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_; esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_command_characteristic_uuid_v2_; + esp32_ble_tracker::ESPBTUUID sensors_status_characteristic_uuid_v2_; + esp32_ble_tracker::ESPBTUUID sensors_history_characteristic_uuid_v2_; union RadonValue { char chars[4]; diff --git a/esphome/components/radon_eye_rd200/sensor.py b/esphome/components/radon_eye_rd200/sensor.py index a9667869b8..34e40e9eae 100644 --- a/esphome/components/radon_eye_rd200/sensor.py +++ b/esphome/components/radon_eye_rd200/sensor.py @@ -6,8 +6,11 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_BECQUEREL_PER_CUBIC_METER, CONF_ID, + CONF_VERSION, CONF_RADON, + CONF_RADON_DAY_AVG, CONF_RADON_LONG_TERM, + CONF_RADON_PEAK, ICON_RADIOACTIVE, ) @@ -22,6 +25,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(RadonEyeRD200), + cv.Optional(CONF_VERSION, default=1): cv.int_range(1, 3), cv.Optional(CONF_RADON): sensor.sensor_schema( unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, icon=ICON_RADIOACTIVE, @@ -34,6 +38,18 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_RADON_PEAK): 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_DAY_AVG): 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")) @@ -47,9 +63,17 @@ async def to_code(config): await ble_client.register_ble_node(var, config) + cg.add(var.set_version(config[CONF_VERSION])) + 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)) + if CONF_RADON_PEAK in config: + sens = await sensor.new_sensor(config[CONF_RADON_PEAK]) + cg.add(var.set_radon_peak(sens)) + if CONF_RADON_DAY_AVG in config: + sens = await sensor.new_sensor(config[CONF_RADON_DAY_AVG]) + cg.add(var.set_radon_day_avg(sens)) diff --git a/esphome/const.py b/esphome/const.py index 16f30c179d..7877a18aec 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -692,6 +692,8 @@ CONF_QOS = "qos" CONF_QUANTILE = "quantile" CONF_RADON = "radon" CONF_RADON_LONG_TERM = "radon_long_term" +CONF_RADON_PEAK = "radon_peak" +CONF_RADON_DAY_AVG = "radon_day_avg" CONF_RANDOM = "random" CONF_RANGE = "range" CONF_RANGE_FROM = "range_from"