diff --git a/CODEOWNERS b/CODEOWNERS index 0106b87fa8..d5df9e8109 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/homeassistant/* @OttoWinter esphome/components/i2c/* @esphome/core +esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter esphome/components/interval/* @esphome/core diff --git a/esphome/components/inkbird_ibsth1_mini/__init__.py b/esphome/components/inkbird_ibsth1_mini/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp new file mode 100644 index 0000000000..a1c1c5d201 --- /dev/null +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -0,0 +1,86 @@ +#include "inkbird_ibsth1_mini.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkbird_ibsth1_mini { + +static const char *TAG = "inkbird_ibsth1_mini"; + +void InkbirdIBSTH1_MINI::dump_config() { + ESP_LOGCONFIG(TAG, "Inkbird IBS TH1 MINI"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + // The below is based on my research and reverse engineering of a single device + // It is entirely possible that some of that may be inaccurate or incomplete + + // for Inkbird IBS-TH1 Mini device we expect + // 1) expected mac address + // 2) device address type == PUBLIC + // 3) no service datas + // 4) one manufacturer datas + // 5) the manufacturer datas should contain a 16-bit uuid amd a 7-byte data vector + // 6) the 7-byte data component should have data[2] == 0 and data[6] == 8 + + // the address should match the address we declared + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + if (device.get_address_type() != BLE_ADDR_TYPE_PUBLIC) { + ESP_LOGVV(TAG, "parse_device(): address is not public"); + return false; + } + if (device.get_service_datas().size() != 0) { + ESP_LOGVV(TAG, "parse_device(): service_data is expected to be empty"); + return false; + } + auto mnfDatas = device.get_manufacturer_datas(); + if (mnfDatas.size() != 1) { + ESP_LOGVV(TAG, "parse_device(): manufacturer_datas is expected to have a single element"); + return false; + } + auto mnfData = mnfDatas[0]; + if (mnfData.uuid.get_uuid().len != ESP_UUID_LEN_16) { + ESP_LOGVV(TAG, "parse_device(): manufacturer data element is expected to have uuid of length 16"); + return false; + } + if (mnfData.data.size() != 7) { + ESP_LOGVV(TAG, "parse_device(): manufacturer data element length is expected to be of length 7"); + return false; + } + if ((mnfData.data[2] != 0) || (mnfData.data[6] != 8)) { + ESP_LOGVV(TAG, "parse_device(): unexpected data"); + return false; + } + + // sensor output encoding + // data[5] is a battery level + // data[0] and data[1] is humidity * 100 (in pct) + // uuid is a temperature * 100 (in Celcius) + auto battery_level = mnfData.data[5]; + auto temperature = mnfData.uuid.get_uuid().uuid.uuid16 / 100.0f; + auto humidity = ((mnfData.data[1] << 8) + mnfData.data[0]) / 100.0f; + + if (this->temperature_ != nullptr) { + this->temperature_->publish_state(temperature); + } + if (this->humidity_ != nullptr) { + this->humidity_->publish_state(humidity); + } + if (this->battery_level_ != nullptr) { + this->battery_level_->publish_state(battery_level); + } + + return true; +} + +} // namespace inkbird_ibsth1_mini +} // namespace esphome + +#endif diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h new file mode 100644 index 0000000000..38e72dad17 --- /dev/null +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkbird_ibsth1_mini { + +class InkbirdIBSTH1_MINI : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; } + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } + + protected: + uint64_t address_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace inkbird_ibsth1_mini +} // namespace esphome + +#endif diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py new file mode 100644 index 0000000000..93d90d2e6f --- /dev/null +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ + UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID + +CODEOWNERS = ['@fkirill'] +DEPENDENCIES = ['esp32_ble_tracker'] + +inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace('inkbird_ibsth1_mini') +InkbirdUBSTH1_MINI = inkbird_ibsth1_mini_ns.class_( + 'InkbirdIBSTH1_MINI', esp32_ble_tracker.ESPBTDeviceListener, cg.Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(InkbirdUBSTH1_MINI), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), +}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_TEMPERATURE in config: + sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_HUMIDITY in config: + sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index b975090531..b109aad758 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -181,6 +181,14 @@ sensor: name: 'ATC Battery-Level' battery_voltage: name: 'ATC Battery-Voltage' + - platform: inkbird_ibsth1_mini + mac_address: 38:81:D7:0A:9C:11 + temperature: + name: 'Inkbird IBS-TH1 Temperature' + humidity: + name: 'Inkbird IBS-TH1 Humidity' + battery_level: + name: 'Inkbird IBS-TH1 Battery Level' time: - platform: homeassistant