ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
airthings_wave_base.cpp
Go to the documentation of this file.
3
4// All information related to reading battery information came from the sensors.airthings_wave
5// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
6
7#ifdef USE_ESP32
8
10
11ESPHOME_LOG_TAG(TAG, "airthings_wave_base");
12
13static constexpr uint32_t BATTERY_READ_TIMEOUT_ID = 0;
14static constexpr uint32_t RESPONSE_TIMEOUT_ID = 1;
15
16void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
17 esp_ble_gattc_cb_param_t *param) {
18 switch (event) {
19 case ESP_GATTC_OPEN_EVT: {
20 if (param->open.status == ESP_GATT_OK) {
21 ESP_LOGI(TAG, "Connected successfully!");
22 }
23 break;
24 }
25
26 case ESP_GATTC_DISCONNECT_EVT: {
27 this->handle_ = 0;
28 this->acp_handle_ = 0;
29 this->cccd_handle_ = 0;
30 ESP_LOGW(TAG, "Disconnected!");
31 break;
32 }
33
34 case ESP_GATTC_SEARCH_CMPL_EVT: {
35 if (this->request_read_values_()) {
36 if (!this->read_battery_next_update_) {
37 this->node_state = espbt::ClientState::ESTABLISHED;
38 } else {
39 // delay setting node_state to ESTABLISHED until confirmation of the notify registration
40 this->request_battery_();
41 }
42 }
43
44 // ensure that the client will be disconnected even if no responses arrive
46
47 break;
48 }
49
50 case ESP_GATTC_READ_CHAR_EVT: {
51 if (param->read.conn_id != this->parent()->get_conn_id())
52 break;
53 if (param->read.status != ESP_GATT_OK) {
54 ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
55 break;
56 }
57 if (param->read.handle == this->handle_) {
58 this->read_sensors(param->read.value, param->read.value_len);
59 }
60 break;
61 }
62
63 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
64 this->node_state = espbt::ClientState::ESTABLISHED;
65 break;
66 }
67
68 case ESP_GATTC_NOTIFY_EVT: {
69 if (param->notify.conn_id != this->parent()->get_conn_id())
70 break;
71 if (param->notify.handle == this->acp_handle_) {
72 this->read_battery_(param->notify.value, param->notify.value_len);
73 }
74 break;
75 }
76
77 default:
78 break;
79 }
80}
81
82bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return voc <= 16383; }
83
85 if (this->node_state != espbt::ClientState::ESTABLISHED) {
86 if (!this->parent()->enabled) {
87 ESP_LOGW(TAG, "Reconnecting to device");
88 this->parent()->set_enabled(true);
89 this->parent()->connect();
90 } else {
91 ESP_LOGW(TAG, "Connection in progress");
92 }
93 }
94}
95
98 if (chr == nullptr) {
99 char service_buf[esp32_ble::UUID_STR_LEN];
100 char char_buf[esp32_ble::UUID_STR_LEN];
101 ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
102 this->sensors_data_characteristic_uuid_.to_str(char_buf));
103 return false;
104 }
105
106 this->handle_ = chr->handle;
107
108 auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
109 ESP_GATT_AUTH_REQ_NONE);
110 if (status) {
111 ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
112 return false;
113 }
114
115 this->response_pending_();
116 return true;
117}
118
120 uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
121 uint8_t cccd_value[2] = {1, 0};
122
124 if (chr == nullptr) {
125 char service_buf[esp32_ble::UUID_STR_LEN];
126 char char_buf[esp32_ble::UUID_STR_LEN];
127 ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
128 this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf));
129 return false;
130 }
131
133 CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
134 if (descr == nullptr) {
135 char service_buf[esp32_ble::UUID_STR_LEN];
136 char char_buf[esp32_ble::UUID_STR_LEN];
137 ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf),
138 this->access_control_point_characteristic_uuid_.to_str(char_buf));
139 return false;
140 }
141
142 auto reg_status =
143 esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
144 if (reg_status) {
145 ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
146 return false;
147 }
148
149 this->acp_handle_ = chr->handle;
150 this->cccd_handle_ = descr->handle;
151
152 auto descr_status =
153 esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
154 2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
155 if (descr_status) {
156 ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
157 return false;
158 }
159
160 auto chr_status =
161 esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
162 &battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
163 if (chr_status) {
164 ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
165 return false;
166 }
167
168 this->response_pending_();
169 return true;
170}
171
172void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
173 auto *value = (AccessControlPointResponse *) (&raw_value[2]);
174
175 if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
176 ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
177
178 if (this->battery_voltage_ != nullptr) {
179 float voltage = value->battery / 1000.0f;
180
181 this->battery_voltage_->publish_state(voltage);
182 }
183
184 // read the battery again at the configured update interval
185 if (this->battery_update_interval_ != this->update_interval_) {
186 this->read_battery_next_update_ = false;
187 this->set_timeout(BATTERY_READ_TIMEOUT_ID, this->battery_update_interval_,
188 [this]() { this->read_battery_next_update_ = true; });
189 }
190 }
191
192 this->response_received_();
193}
194
199
201 if (--this->responses_pending_ == 0) {
202 // This instance must not stay connected
203 // so other clients can connect to it (e.g. the
204 // mobile app).
205 this->parent()->set_enabled(false);
206 }
207}
208
210 this->set_timeout(RESPONSE_TIMEOUT_ID, 30 * 1000, [this]() {
211 this->responses_pending_ = 1;
212 this->response_received_();
213 });
214}
215
216} // namespace esphome::airthings_wave_base
217
218#endif // USE_ESP32
uint8_t status
Definition bl0942.h:8
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void read_battery_(uint8_t *raw_value, uint16_t value_len)
virtual void read_sensors(uint8_t *raw_value, uint16_t value_len)=0
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_enabled(bool enabled)
const char * to_str(char *buf) const
Write "0xABCD" / "0xABCDEF01" / the dashed 128-bit form, or "None" for an unset UUID,...
BLEDescriptor * get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr)
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
ESPHOME_LOG_TAG(TAG, "airthings_wave_base")
static void uint32_t