ESPHome  2024.4.1
airthings_wave_base.cpp
Go to the documentation of this file.
1 #include "airthings_wave_base.h"
2 
3 // All information related to reading battery information came from the sensors.airthings_wave
4 // project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
5 
6 #ifdef USE_ESP32
7 
8 namespace esphome {
9 namespace airthings_wave_base {
10 
11 static const char *const TAG = "airthings_wave_base";
12 
13 void 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
42  this->set_response_timeout_();
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 
79 bool 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  ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
98  return false;
99  }
100 
101  this->handle_ = chr->handle;
102 
103  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
104  ESP_GATT_AUTH_REQ_NONE);
105  if (status) {
106  ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
107  return false;
108  }
109 
110  this->response_pending_();
111  return true;
112 }
113 
115  uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
116  uint8_t cccd_value[2] = {1, 0};
117 
119  if (chr == nullptr) {
120  ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
121  this->service_uuid_.to_string().c_str(),
123  return false;
124  }
125 
127  CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
128  if (descr == nullptr) {
129  ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
131  return false;
132  }
133 
134  auto reg_status =
135  esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
136  if (reg_status) {
137  ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
138  return false;
139  }
140 
141  this->acp_handle_ = chr->handle;
142  this->cccd_handle_ = descr->handle;
143 
144  auto descr_status =
145  esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
146  2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
147  if (descr_status) {
148  ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
149  return false;
150  }
151 
152  auto chr_status =
153  esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
154  &battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
155  if (chr_status) {
156  ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
157  return false;
158  }
159 
160  this->response_pending_();
161  return true;
162 }
163 
164 void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
165  auto *value = (AccessControlPointResponse *) (&raw_value[2]);
166 
167  if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
168  ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
169 
170  if (this->battery_voltage_ != nullptr) {
171  float voltage = value->battery / 1000.0f;
172 
173  this->battery_voltage_->publish_state(voltage);
174  }
175 
176  // read the battery again at the configured update interval
177  if (this->battery_update_interval_ != this->update_interval_) {
178  this->read_battery_next_update_ = false;
179  this->set_timeout("battery", this->battery_update_interval_,
180  [this]() { this->read_battery_next_update_ = true; });
181  }
182  }
183 
184  this->response_received_();
185 }
186 
188  this->responses_pending_++;
189  this->set_response_timeout_();
190 }
191 
193  if (--this->responses_pending_ == 0) {
194  // This instance must not stay connected
195  // so other clients can connect to it (e.g. the
196  // mobile app).
197  this->parent()->set_enabled(false);
198  }
199 }
200 
202  this->set_timeout("response_timeout", 30 * 1000, [this]() {
203  this->responses_pending_ = 1;
204  this->response_received_();
205  });
206 }
207 
208 } // namespace airthings_wave_base
209 } // namespace esphome
210 
211 #endif // USE_ESP32
BLEDescriptor * get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr)
virtual void read_sensors(uint8_t *raw_value, uint16_t value_len)=0
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition: component.cpp:69
void set_enabled(bool enabled)
Definition: ble_client.cpp:38
void read_battery_(uint8_t *raw_value, uint16_t value_len)
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
std::string to_string() const
Definition: ble_uuid.cpp:165
uint8_t status
Definition: bl0942.h:23
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
espbt::ClientState node_state
Definition: ble_client.h:38