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