Add Radon Eye RD200 V2/3 read status

This commit is contained in:
Dotsch2005 2024-10-09 20:19:56 +02:00
parent 4bac9707fe
commit 20910cc45e
4 changed files with 272 additions and 17 deletions

View file

@ -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

View file

@ -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];

View file

@ -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))

View file

@ -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"