Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for the AirThings Wave Plus (#1656)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
- Loading branch information
1 parent
58350b6
commit 140ef79
Showing
10 changed files
with
445 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome.components import esp32_ble_tracker | ||
from esphome.const import CONF_ID | ||
|
||
DEPENDENCIES = ["esp32_ble_tracker"] | ||
CODEOWNERS = ["@jeromelaban"] | ||
|
||
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble") | ||
AirthingsListener = airthings_ble_ns.class_( | ||
"AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener | ||
) | ||
|
||
CONFIG_SCHEMA = cv.Schema( | ||
{ | ||
cv.GenerateID(): cv.declare_id(AirthingsListener), | ||
} | ||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||
|
||
|
||
def to_code(config): | ||
var = cg.new_Pvariable(config[CONF_ID]) | ||
yield esp32_ble_tracker.register_ble_device(var, config) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#include "airthings_listener.h" | ||
#include "esphome/core/log.h" | ||
|
||
#ifdef ARDUINO_ARCH_ESP32 | ||
|
||
namespace esphome { | ||
namespace airthings_ble { | ||
|
||
static const char *TAG = "airthings_ble"; | ||
|
||
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||
for (auto &it : device.get_manufacturer_datas()) { | ||
if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) { | ||
if (it.data.size() < 4) | ||
continue; | ||
|
||
uint32_t sn = it.data[0]; | ||
sn |= ((uint32_t) it.data[1] << 8); | ||
sn |= ((uint32_t) it.data[2] << 16); | ||
sn |= ((uint32_t) it.data[3] << 24); | ||
|
||
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str()); | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
} // namespace airthings_ble | ||
} // namespace esphome | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#pragma once | ||
|
||
#ifdef ARDUINO_ARCH_ESP32 | ||
|
||
#include "esphome/core/component.h" | ||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||
#include <BLEDevice.h> | ||
|
||
namespace esphome { | ||
namespace airthings_ble { | ||
|
||
class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener { | ||
public: | ||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||
}; | ||
|
||
} // namespace airthings_ble | ||
} // namespace esphome | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
CODEOWNERS = ["@jeromelaban"] |
142 changes: 142 additions & 0 deletions
142
esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
#include "airthings_wave_plus.h" | ||
|
||
#ifdef ARDUINO_ARCH_ESP32 | ||
|
||
namespace esphome { | ||
namespace airthings_wave_plus { | ||
|
||
void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||
esp_ble_gattc_cb_param_t *param) { | ||
switch (event) { | ||
case ESP_GATTC_OPEN_EVT: { | ||
if (param->open.status == ESP_GATT_OK) { | ||
ESP_LOGI(TAG, "Connected successfully!"); | ||
} | ||
break; | ||
} | ||
|
||
case ESP_GATTC_DISCONNECT_EVT: { | ||
ESP_LOGW(TAG, "Disconnected!"); | ||
break; | ||
} | ||
|
||
case ESP_GATTC_SEARCH_CMPL_EVT: { | ||
this->handle = 0; | ||
auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid); | ||
if (chr == nullptr) { | ||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(), | ||
sensors_data_characteristic_uuid.to_string().c_str()); | ||
break; | ||
} | ||
this->handle = chr->handle; | ||
this->node_state = espbt::ClientState::Established; | ||
|
||
request_read_values_(); | ||
break; | ||
} | ||
|
||
case ESP_GATTC_READ_CHAR_EVT: { | ||
if (param->read.conn_id != this->parent()->conn_id) | ||
break; | ||
if (param->read.status != ESP_GATT_OK) { | ||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); | ||
break; | ||
} | ||
if (param->read.handle == this->handle) { | ||
read_sensors_(param->read.value, param->read.value_len); | ||
} | ||
break; | ||
} | ||
|
||
default: | ||
break; | ||
} | ||
} | ||
|
||
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { | ||
auto value = (WavePlusReadings *) raw_value; | ||
|
||
if (sizeof(WavePlusReadings) <= value_len) { | ||
ESP_LOGD(TAG, "version = %d", value->version); | ||
|
||
if (value->version == 1) { | ||
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); | ||
|
||
this->humidity_sensor_->publish_state(value->humidity / 2.0f); | ||
if (is_valid_radon_value_(value->radon)) { | ||
this->radon_sensor_->publish_state(value->radon); | ||
} | ||
if (is_valid_radon_value_(value->radon_lt)) { | ||
this->radon_long_term_sensor_->publish_state(value->radon_lt); | ||
} | ||
this->temperature_sensor_->publish_state(value->temperature / 100.0f); | ||
this->pressure_sensor_->publish_state(value->pressure / 50.0f); | ||
if (is_valid_co2_value_(value->co2)) { | ||
this->co2_sensor_->publish_state(value->co2); | ||
} | ||
if (is_valid_voc_value_(value->voc)) { | ||
this->tvoc_sensor_->publish_state(value->voc); | ||
} | ||
|
||
// This instance must not stay connected | ||
// so other clients can connect to it (e.g. the | ||
// mobile app). | ||
parent()->set_enabled(false); | ||
} else { | ||
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); | ||
} | ||
} | ||
} | ||
|
||
bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; } | ||
|
||
bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; } | ||
|
||
bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; } | ||
|
||
void AirthingsWavePlus::loop() {} | ||
|
||
void AirthingsWavePlus::update() { | ||
if (this->node_state != espbt::ClientState::Established) { | ||
if (!parent()->enabled) { | ||
ESP_LOGW(TAG, "Reconnecting to device"); | ||
parent()->set_enabled(true); | ||
parent()->connect(); | ||
} else { | ||
ESP_LOGW(TAG, "Connection in progress"); | ||
} | ||
} | ||
} | ||
|
||
void AirthingsWavePlus::request_read_values_() { | ||
auto status = | ||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); | ||
if (status) { | ||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); | ||
} | ||
} | ||
|
||
void AirthingsWavePlus::dump_config() { | ||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); | ||
LOG_SENSOR(" ", "Radon", this->radon_sensor_); | ||
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); | ||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); | ||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); | ||
LOG_SENSOR(" ", "CO2", this->co2_sensor_); | ||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); | ||
} | ||
|
||
AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { | ||
auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); | ||
auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); | ||
|
||
service_uuid = espbt::ESPBTUUID::from_uuid(service_bt); | ||
sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt); | ||
} | ||
|
||
void AirthingsWavePlus::setup() {} | ||
|
||
} // namespace airthings_wave_plus | ||
} // namespace esphome | ||
|
||
#endif // ARDUINO_ARCH_ESP32 |
79 changes: 79 additions & 0 deletions
79
esphome/components/airthings_wave_plus/airthings_wave_plus.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#pragma once | ||
|
||
#include "esphome/core/component.h" | ||
#include "esphome/components/ble_client/ble_client.h" | ||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||
#include "esphome/components/sensor/sensor.h" | ||
#include "esphome/core/log.h" | ||
#include <algorithm> | ||
#include <iterator> | ||
|
||
#ifdef ARDUINO_ARCH_ESP32 | ||
#include <esp_gattc_api.h> | ||
#include <BLEDevice.h> | ||
|
||
using namespace esphome::ble_client; | ||
|
||
namespace esphome { | ||
namespace airthings_wave_plus { | ||
|
||
static const char *TAG = "airthings_wave_plus"; | ||
|
||
class AirthingsWavePlus : public PollingComponent, public BLEClientNode { | ||
public: | ||
AirthingsWavePlus(); | ||
|
||
void setup() override; | ||
void dump_config() override; | ||
void update() override; | ||
void loop() override; | ||
|
||
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_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } | ||
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } | ||
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } | ||
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } | ||
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } | ||
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } | ||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } | ||
|
||
protected: | ||
bool is_valid_radon_value_(short radon); | ||
bool is_valid_voc_value_(short voc); | ||
bool is_valid_co2_value_(short co2); | ||
|
||
void read_sensors_(uint8_t *value, uint16_t value_len); | ||
void request_read_values_(); | ||
|
||
sensor::Sensor *temperature_sensor_{nullptr}; | ||
sensor::Sensor *radon_sensor_{nullptr}; | ||
sensor::Sensor *radon_long_term_sensor_{nullptr}; | ||
sensor::Sensor *humidity_sensor_{nullptr}; | ||
sensor::Sensor *pressure_sensor_{nullptr}; | ||
sensor::Sensor *co2_sensor_{nullptr}; | ||
sensor::Sensor *tvoc_sensor_{nullptr}; | ||
|
||
uint16_t handle; | ||
espbt::ESPBTUUID service_uuid; | ||
espbt::ESPBTUUID sensors_data_characteristic_uuid; | ||
|
||
struct WavePlusReadings { | ||
uint8_t version; | ||
uint8_t humidity; | ||
uint8_t ambientLight; | ||
uint8_t unused01; | ||
uint16_t radon; | ||
uint16_t radon_lt; | ||
uint16_t temperature; | ||
uint16_t pressure; | ||
uint16_t co2; | ||
uint16_t voc; | ||
}; | ||
}; | ||
|
||
} // namespace airthings_wave_plus | ||
} // namespace esphome | ||
|
||
#endif // ARDUINO_ARCH_ESP32 |
Oops, something went wrong.