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