mirror of
https://github.com/esphome/esphome.git
synced 2024-12-24 22:44:54 +01:00
Add files via upload
This commit is contained in:
parent
9d8cc14da9
commit
071b5cbdbd
5 changed files with 910 additions and 0 deletions
35
esphome/components/bthome/__init__.py
Normal file
35
esphome/components/bthome/__init__.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID
|
||||
|
||||
|
||||
CODEOWNERS = ["@badrpc"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_ENCRYPTION_KEY = "encryption_key"
|
||||
|
||||
bthome_ns = cg.esphome_ns.namespace("bthome")
|
||||
BTHome = bthome_ns.class_("BTHome", esp32_ble_tracker.ESPBTDeviceListener, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BTHome),
|
||||
cv.Optional(CONF_ENCRYPTION_KEY): cv.bind_key,
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
cg.add(var.set_encryption_key(config[CONF_ENCRYPTION_KEY]))
|
33
esphome/components/bthome/binary_sensor.py
Normal file
33
esphome/components/bthome/binary_sensor.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
from . import BTHome
|
||||
|
||||
|
||||
CONF_WINDOW = "window"
|
||||
|
||||
|
||||
DEPENDENCIES = ["bthome"]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BTHome),
|
||||
cv.Optional(CONF_WINDOW): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_WINDOW
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_WINDOW in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_WINDOW])
|
||||
cg.add(parent.set_window(sens))
|
536
esphome/components/bthome/bthome.cpp
Normal file
536
esphome/components/bthome/bthome.cpp
Normal file
|
@ -0,0 +1,536 @@
|
|||
#include "bthome.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "mbedtls/ccm.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bthome {
|
||||
|
||||
// 0x00 packet id uint8 (1 byte) 0009 9
|
||||
// 0x01 battery uint8 (1 byte) 1 0161 97 %
|
||||
// 0x02 temperature sint16 (2 bytes) 0.01 02CA09 25.06 °C
|
||||
// 0x03 humidity uint16 (2 bytes) 0.01 03BF13 50.55 %
|
||||
// 0x04 pressure uint24 (3 bytes) 0.01 04138A01 1008.83 hPa
|
||||
// 0x05 illuminance uint24 (3 bytes) 0.01 05138A14 13460.67 lux
|
||||
// 0x06 mass (kg) uint16 (2 byte) 0.01 065E1F 80.3 kg
|
||||
// 0x07 mass (lb) uint16 (2 byte) 0.01 073E1D 74.86 lb
|
||||
// 0x08 dewpoint sint16 (2 bytes) 0.01 08CA06 17.38 °C
|
||||
// 0x09 count uint (1 bytes) 1 0960 96
|
||||
// 0x0A energy uint24 (3 bytes) 0.001 0A138A14 1346.067 kWh
|
||||
// 0x0B power uint24 (3 bytes) 0.01 0B021B00 69.14 W
|
||||
// 0x0C voltage uint16 (2 bytes) 0.001 0C020C 3.074 V
|
||||
// 0x0D pm2.5 uint16 (2 bytes) 1 0D120C 3090 ug/m3
|
||||
// 0x0E pm10 uint16 (2 bytes) 1 0E021C 7170 ug/m3
|
||||
// 0x0F generic boolean uint8 (1 byte) 0F01 0 (False = Off) 1 (True = On)
|
||||
// 0x10 power uint8 (1 byte) 1001 0 (False = Off) 1 (True = On)
|
||||
// 0x11 opening uint8 (1 byte) 1100 0 (False = Closed) 1 (True = Open)
|
||||
// 0x12 co2 uint16 (2 bytes) 1 12E204 1250 ppm
|
||||
// 0x13 tvoc uint16 (2 bytes) 1 133301 307 ug/m3
|
||||
// 0x14 moisture uint16 (2 bytes) 0.01 14020C 30.74 %
|
||||
// 0x15 battery uint8 (1 byte) 1501 0 (False = Normal) 1 (True = Low)
|
||||
// 0x16 battery charging uint8 (1 byte) 1601 0 (False = Not Charging) 1 (True = Charging)
|
||||
// 0x17 carbon monoxide uint8 (1 byte) 1700 0 (False = Not detected) 1 (True = Detected)
|
||||
// 0x18 cold uint8 (1 byte) 1801 0 (False = Normal) 1 (True = Cold)
|
||||
// 0x19 connectivity uint8 (1 byte) 1900 0 (False = Disconnected) 1 (True = Connected)
|
||||
// 0x1A door uint8 (1 byte) 1A00 0 (False = Closed) 1 (True = Open)
|
||||
// 0x1B garage door uint8 (1 byte) 1B01 0 (False = Closed) 1 (True = Open)
|
||||
// 0x1C gas uint8 (1 byte) 1C01 0 (False = Clear) 1 (True = Detected)
|
||||
// 0x1D heat uint8 (1 byte) 1D00 0 (False = Normal) 1 (True = Hot)
|
||||
// 0x1E light uint8 (1 byte) 1E01 0 (False = No light) 1 (True = Light detected)
|
||||
// 0x1F lock uint8 (1 byte) 1F01 0 (False = Locked) 1 (True = Unlocked)
|
||||
// 0x20 moisture uint8 (1 byte) 2001 0 (False = Dry) 1 (True = Wet)
|
||||
// 0x21 motion uint8 (1 byte) 2100 0 (False = Clear) 1 (True = Detected)
|
||||
// 0x22 moving uint8 (1 byte) 2201 0 (False = Not moving) 1 (True = Moving)
|
||||
// 0x23 occupancy uint8 (1 byte) 2301 0 (False = Clear) 1 (True = Detected)
|
||||
// 0x24 plug uint8 (1 byte) 2400 0 (False = Unplugged) 1 (True = Plugged in)
|
||||
// 0x25 presence uint8 (1 byte) 2500 0 (False = Away) 1 (True = Home)
|
||||
// 0x26 problem uint8 (1 byte) 2601 0 (False = OK) 1 (True = Problem)
|
||||
// 0x27 running uint8 (1 byte) 2701 0 (False = Not Running) 1 (True = Running)
|
||||
// 0x28 safety uint8 (1 byte) 2800 0 (False = Unsafe) 1 (True = Safe)
|
||||
// 0x29 smoke uint8 (1 byte) 2901 0 (False = Clear) 1 (True = Detected)
|
||||
// 0x2A sound uint8 (1 byte) 2A00 0 (False = Clear) 1 (True = Detected)
|
||||
// 0x2B tamper uint8 (1 byte) 2B00 0 (False = Off) 1 (True = On)
|
||||
// 0x2C vibration uint8 (1 byte) 2C01 0 (False = Clear) 1 (True = Detected)
|
||||
// 0x2D window uint8 (1 byte) 2D01 0 (False = Closed) 1 (True = Open)
|
||||
// 0x2E humidity uint8 (1 byte) 1 2E23 35 %
|
||||
// 0x2F moisture uint8 (1 byte) 1 2F23 35 %
|
||||
// 0x3A button 0x00 None 3A00 0x01 press 3A01 press 0x02 double_press 3A02 double_press 0x03 triple_press 3A03 triple_press 0x04 long_press 3A04 long_press 0x05 long_double_press 3A05 long_double_press 0x06 long_triple_press 3A06 long_triple_press 0x80 hold_press 3A80 hold_press
|
||||
// 0x3C dimmer 0x00 None 3C0000 0x01 rotate left # steps 3C0103 rotate left 3 steps 0x02 rotate right # steps 3C020A rotate right 10 steps
|
||||
// 0x3D count uint (2 bytes) 1 3D0960 24585
|
||||
// 0x3E count uint (4 bytes) 1 3E2A2C0960 1611213866
|
||||
// 0x3F rotation sint16 (2 bytes) 0.1 3F020C 307.4 °
|
||||
// 0x40 distance (mm) uint16 (2 bytes) 1 400C00 12 mm
|
||||
// 0x41 distance (m) uint16 (2 bytes) 0.1 414E00 7.8 m
|
||||
// 0x42 duration uint24 (3 bytes) 0.001 424E3400 13.390 s
|
||||
// 0x43 current uint16 (2 bytes) 0.001 434E34 13.39 A
|
||||
// 0x44 speed uint16 (2 bytes) 0.01 444E34 133.90 m/s
|
||||
// 0x45 temperature sint16 (2 bytes) 0.1 451101 27.3 °C
|
||||
// 0x46 UV index uint8 (1 byte) 0.1 4632 5.0
|
||||
// 0x47 volume uint16 (2 bytes) 0.1 478756 2215.1 L
|
||||
// 0x48 volume uint16 (2 bytes) 1 48DC87 34780 mL
|
||||
// 0x49 volume Flow Rate uint16 (2 bytes) 0.001 49DC87 34.780 m3/hr
|
||||
// 0x4A voltage uint16 (2 bytes) 0.1 4A020C 307.4 V
|
||||
// 0x4B gas uint24 (3 bytes) 0.001 4B138A14 1346.067 m3
|
||||
// 0x4C gas uint32 (4 bytes) 0.001 4C41018A01 25821.505 m3
|
||||
// 0x4D energy uint32 (4 bytes) 0.001 4d12138a14 344593.170 kWh
|
||||
// 0x4E volume uint32 (4 bytes) 0.001 4E87562A01 19551.879 L
|
||||
// 0x4F water uint32 (4 bytes) 0.001 4F87562A01 19551.879
|
||||
// 0x50 timestamp uint48 (4 bytes) - 505d396164 see below
|
||||
// 0x51 acceleration uint16 (2 bytes) 0.001 518756 22.151 m/s²
|
||||
// 0x52 gyroscope uint16 (2 bytes) 0.001 528756 22.151 °/s
|
||||
// 0x53 text see below - 530C48656C6C6F 20576F726C6421 Hello World!
|
||||
// 0x54 raw see below - 540C48656C6C6F 20576F726C6421 48656c6c6f20 576f726c6421
|
||||
// 0x55 volume storage uint32 (4 bytes) 0.001 5587562A01 19551.879 L
|
||||
// 0xF0 device type id uint16 (2 bytes) F00100 1
|
||||
// 0xF1 firmware version uint32 (4 bytes) F100010204 4.2.1.0
|
||||
// 0xF2 firmware version uint24 (3 bytes) F2000106 6.1.0
|
||||
|
||||
static const char *const TAG = "bthome";
|
||||
|
||||
const OIDUInt8<0x00> oid_pid;
|
||||
const OIDUFixedPoint<0x01, 1> oid_battery_percent;
|
||||
const OIDSFixedPoint<0x02, 2, 1, 100> oid_temperature_celsius_x100;
|
||||
const OIDUFixedPoint<0x03, 2, 1, 100> oid_humidity_percent_x100;
|
||||
const OIDUFixedPoint<0x04, 3, 1, 100> oid_pressure_hpa_x100;
|
||||
const OIDUFixedPoint<0x05, 3, 1, 100> oid_illuminance_lux_x100;
|
||||
const OIDUFixedPoint<0x06, 2, 1, 100> oid_mass_kg_x100;
|
||||
const OIDUFixedPoint<0x07, 2, 45359237, 100000000> oid_mass_lb_x100; // Converts to kg.
|
||||
const OIDSFixedPoint<0x08, 2> oid_dewpoint_celsius_x100;
|
||||
const OIDUFixedPoint<0x09, 1> oid_counter8;
|
||||
const OIDUFixedPoint<0x0a, 3, 1, 1000> oid_energy_kwh_x1000;
|
||||
const OIDUFixedPoint<0x0b, 3, 1, 100> oid_power_w_x100;
|
||||
const OIDUFixedPoint<0x0c, 2, 1, 1000> oid_voltage_v_x1000;
|
||||
const OIDUFixedPoint<0x0d, 2> oid_pm2_5_ug_m3;
|
||||
const OIDUFixedPoint<0x0e, 2> oid_pm10_ug_m3;
|
||||
const OIDBool<0x0f> oid_bool;
|
||||
const OIDBool<0x10> oid_power;
|
||||
const OIDBool<0x11> oid_opening;
|
||||
const OIDUFixedPoint<0x12, 2> oid_co2_concentration_ppm;
|
||||
const OIDUFixedPoint<0x13, 2> oid_tvoc_ug_m3;
|
||||
const OIDUFixedPoint<0x14, 2, 1, 100> oid_moisture_percent_x100;
|
||||
const OIDBool<0x15> oid_battery;
|
||||
const OIDBool<0x16> oid_battery_charging;
|
||||
const OIDBool<0x17> oid_carbon_monoxide;
|
||||
const OIDBool<0x18> oid_cold;
|
||||
const OIDBool<0x19> oid_connectivity;
|
||||
const OIDBool<0x1a> oid_door;
|
||||
const OIDBool<0x1b> oid_garage_door;
|
||||
const OIDBool<0x1c> oid_gas;
|
||||
const OIDBool<0x1d> oid_heat;
|
||||
const OIDBool<0x1e> oid_light;
|
||||
const OIDBool<0x1f> oid_lock;
|
||||
const OIDBool<0x20> oid_moisture;
|
||||
const OIDBool<0x21> oid_motion;
|
||||
const OIDBool<0x22> oid_moving;
|
||||
const OIDBool<0x23> oid_occupancy;
|
||||
const OIDBool<0x24> oid_plug;
|
||||
const OIDBool<0x25> oid_presence;
|
||||
const OIDBool<0x26> oid_problem;
|
||||
const OIDBool<0x27> oid_running;
|
||||
const OIDBool<0x28> oid_safety;
|
||||
const OIDBool<0x29> oid_smoke;
|
||||
const OIDBool<0x2a> oid_sound;
|
||||
const OIDBool<0x2b> oid_tamper;
|
||||
const OIDBool<0x2c> oid_vibration;
|
||||
const OIDBool<0x2d> oid_window;
|
||||
const OIDUFixedPoint<0x2e, 1> oid_humidity_percent;
|
||||
const OIDUFixedPoint<0x2f, 1> oid_moisture_percent;
|
||||
// 0x30 - 0x39
|
||||
const OIDUFixedPoint<0x3a, 1> oid_button_event;
|
||||
// 0x3b
|
||||
// 0x3C dimmer 0x00 None 3C0000 0x01 rotate left # steps 3C0103 rotate left 3 steps 0x02 rotate right # steps 3C020A rotate right 10 steps
|
||||
const OIDUFixedPoint<0x3d, 2> oid_counter16;
|
||||
const OIDUFixedPoint<0x3e, 4> oid_counter32;
|
||||
const OIDUFixedPoint<0x3f, 2, 1, 10> oid_angle_degrees_x10;
|
||||
// 0x40 distance (mm) uint16 (2 bytes) 1 400C00 12 mm
|
||||
// 0x41 distance (m) uint16 (2 bytes) 0.1 414E00 7.8 m
|
||||
// 0x42 duration uint24 (3 bytes) 0.001 424E3400 13.390 s
|
||||
// 0x43 current uint16 (2 bytes) 0.001 434E34 13.39 A
|
||||
// 0x44 speed uint16 (2 bytes) 0.01 444E34 133.90 m/s
|
||||
// 0x45 temperature sint16 (2 bytes) 0.1 451101 27.3 °C
|
||||
// 0x46 UV index uint8 (1 byte) 0.1 4632 5.0
|
||||
// 0x47 volume uint16 (2 bytes) 0.1 478756 2215.1 L
|
||||
// 0x48 volume uint16 (2 bytes) 1 48DC87 34780 mL
|
||||
// 0x49 volume Flow Rate uint16 (2 bytes) 0.001 49DC87 34.780 m3/hr
|
||||
// 0x4A voltage uint16 (2 bytes) 0.1 4A020C 307.4 V
|
||||
// 0x4B gas uint24 (3 bytes) 0.001 4B138A14 1346.067 m3
|
||||
// 0x4C gas uint32 (4 bytes) 0.001 4C41018A01 25821.505 m3
|
||||
// 0x4D energy uint32 (4 bytes) 0.001 4d12138a14 344593.170 kWh
|
||||
// 0x4E volume uint32 (4 bytes) 0.001 4E87562A01 19551.879 L
|
||||
// 0x4F water uint32 (4 bytes) 0.001 4F87562A01 19551.879
|
||||
// 0x50 timestamp uint48 (4 bytes) - 505d396164 see below
|
||||
// 0x51 acceleration uint16 (2 bytes) 0.001 518756 22.151 m/s²
|
||||
// 0x52 gyroscope uint16 (2 bytes) 0.001 528756 22.151 °/s
|
||||
// 0x53 text see below - 530C48656C6C6F 20576F726C6421 Hello World!
|
||||
// 0x54 raw see below - 540C48656C6C6F 20576F726C6421 48656c6c6f20 576f726c6421
|
||||
// 0x55 volume storage uint32 (4 bytes) 0.001 5587562A01 19551.879 L
|
||||
// 0x56 - 0xef
|
||||
// 0xF0 device type id uint16 (2 bytes) F00100 1
|
||||
// 0xF1 firmware version uint32 (4 bytes) F100010204 4.2.1.0
|
||||
// 0xF2 firmware version uint24 (3 bytes) F2000106 6.1.0
|
||||
|
||||
#define OID_ENTRY(oid) [oid.oid_] = oid.scan
|
||||
|
||||
static scan_func_t* oids[256] = {
|
||||
OID_ENTRY(oid_pid),
|
||||
OID_ENTRY(oid_battery_percent),
|
||||
OID_ENTRY(oid_temperature_celsius_x100),
|
||||
OID_ENTRY(oid_humidity_percent_x100),
|
||||
OID_ENTRY(oid_pressure_hpa_x100),
|
||||
OID_ENTRY(oid_illuminance_lux_x100),
|
||||
OID_ENTRY(oid_mass_kg_x100),
|
||||
OID_ENTRY(oid_mass_lb_x100),
|
||||
OID_ENTRY(oid_dewpoint_celsius_x100),
|
||||
OID_ENTRY(oid_counter8),
|
||||
OID_ENTRY(oid_energy_kwh_x1000),
|
||||
OID_ENTRY(oid_power_w_x100),
|
||||
OID_ENTRY(oid_voltage_v_x1000),
|
||||
OID_ENTRY(oid_pm2_5_ug_m3),
|
||||
OID_ENTRY(oid_pm10_ug_m3),
|
||||
OID_ENTRY(oid_bool),
|
||||
OID_ENTRY(oid_power),
|
||||
OID_ENTRY(oid_opening),
|
||||
OID_ENTRY(oid_co2_concentration_ppm),
|
||||
OID_ENTRY(oid_tvoc_ug_m3),
|
||||
OID_ENTRY(oid_moisture_percent_x100),
|
||||
OID_ENTRY(oid_battery),
|
||||
OID_ENTRY(oid_battery_charging),
|
||||
OID_ENTRY(oid_carbon_monoxide),
|
||||
OID_ENTRY(oid_cold),
|
||||
OID_ENTRY(oid_connectivity),
|
||||
OID_ENTRY(oid_door),
|
||||
OID_ENTRY(oid_garage_door),
|
||||
OID_ENTRY(oid_gas),
|
||||
OID_ENTRY(oid_heat),
|
||||
OID_ENTRY(oid_light),
|
||||
OID_ENTRY(oid_lock),
|
||||
OID_ENTRY(oid_moisture),
|
||||
OID_ENTRY(oid_motion),
|
||||
OID_ENTRY(oid_moving),
|
||||
OID_ENTRY(oid_occupancy),
|
||||
OID_ENTRY(oid_plug),
|
||||
OID_ENTRY(oid_presence),
|
||||
OID_ENTRY(oid_problem),
|
||||
OID_ENTRY(oid_running),
|
||||
OID_ENTRY(oid_safety),
|
||||
OID_ENTRY(oid_smoke),
|
||||
OID_ENTRY(oid_sound),
|
||||
OID_ENTRY(oid_tamper),
|
||||
OID_ENTRY(oid_vibration),
|
||||
OID_ENTRY(oid_window),
|
||||
[0x2e] = nullptr,
|
||||
[0x2f] = nullptr,
|
||||
[0x30] = nullptr,
|
||||
[0x31] = nullptr,
|
||||
[0x32] = nullptr,
|
||||
[0x33] = nullptr,
|
||||
[0x34] = nullptr,
|
||||
[0x35] = nullptr,
|
||||
[0x36] = nullptr,
|
||||
[0x37] = nullptr,
|
||||
[0x38] = nullptr,
|
||||
[0x39] = nullptr,
|
||||
OID_ENTRY(oid_button_event),
|
||||
[0x3b] = nullptr,
|
||||
[0x3c] = nullptr,
|
||||
OID_ENTRY(oid_counter16),
|
||||
OID_ENTRY(oid_counter32),
|
||||
OID_ENTRY(oid_angle_degrees_x10),
|
||||
};
|
||||
|
||||
uint32_t read_uint(size_t size, const uint8_t *data) {
|
||||
if (size < 1 || size > 4) {
|
||||
ESP_LOGE(TAG, "read_uint() called with invalid size %zu, must be in range [1, 4]", size);
|
||||
return 0;
|
||||
}
|
||||
uint32_t val = 0;
|
||||
int shift = 0;
|
||||
for (int i=0; i<size; i++, shift+=8) {
|
||||
val |= data[i] << shift;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
int32_t read_sint(size_t size, const uint8_t *data) {
|
||||
if (size < 1 || size > 4) {
|
||||
ESP_LOGE(TAG, "read_sint() called with invalid size %zu, must be in range [1, 4]", size);
|
||||
return 0;
|
||||
}
|
||||
bool positive = true;
|
||||
if (data[size-1] > 127) {
|
||||
positive = false;
|
||||
}
|
||||
|
||||
int32_t val = 0;
|
||||
int shift = (size-1)*8;
|
||||
for (int i=size-1; i>=0; i--, shift-=8) {
|
||||
if (positive) {
|
||||
val |= data[i] << shift;
|
||||
} else {
|
||||
val |= (0xff - data[i]) << shift;
|
||||
}
|
||||
}
|
||||
if (!positive) {
|
||||
val = -val - 1;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// BTHome Device Information
|
||||
// From https://bthome.io/format/
|
||||
//
|
||||
// The first byte after the UUID is the BTHome device info byte, which has
|
||||
// several bits indicating the capabilities of the device.
|
||||
//
|
||||
// bit 0: “Encryption flag”
|
||||
// The Encryption flag is telling the receiver wether the device is
|
||||
// sending non-encrypted data (bit 0 = 0) or encrypted data (bit 0 = 1).
|
||||
// bit 1: “Reserved for future use”
|
||||
// bit 2: “Trigger based device flag”
|
||||
// The trigger based device flag is telling the receiver that it should
|
||||
// expect that the device is sending BLE advertisements at a regular
|
||||
// interval (bit 2 = 0) or at an irregular interval (bit 2 = 1), e.g.
|
||||
// only when someone pushes a button. This can be useful information
|
||||
// for a receiver, e.g. to prevent the device from going to
|
||||
// unavailable.
|
||||
// bit 3-4: “Reserved for future use”
|
||||
// bit 5-7: “BTHome Version”
|
||||
// This represents the BTHome verion. Currently only BTHome version 1
|
||||
// or 2 are allowed, where 2 is the latest version (bit 5-7 = 010).
|
||||
//
|
||||
struct device_information {
|
||||
bool encryption;
|
||||
bool trigger_based;
|
||||
uint8_t bthome_version;
|
||||
|
||||
device_information(uint8_t v) {
|
||||
encryption = v & 0b000000001;
|
||||
trigger_based = (v & 0b00000100) >> 2;
|
||||
bthome_version = (v & 0b11100000) >> 5;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// data is a full data received from device. Must be > 9 bytes log.
|
||||
// mac_address has to be 6 bytes long.
|
||||
// cleartext has to be at least data_len - 9 bytes long.
|
||||
int decrypt(const uint8_t *data, ssize_t data_len, const uint8_t *mac_address, uint16_t uuid, const uint8_t *key, ssize_t keybits, uint8_t *cleartext) {
|
||||
mbedtls_ccm_context ctx;
|
||||
mbedtls_ccm_init(&ctx);
|
||||
int ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, keybits);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "mbedtls_ccm_setkey() failed: %04x", ret);
|
||||
mbedtls_ccm_free(&ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Nonce:
|
||||
// MAC address 6 bytes
|
||||
// UUID 2 bytes
|
||||
// Device data 1 byte
|
||||
// Counter 4 bytes
|
||||
uint8_t nonce[] = {
|
||||
[0x00] = mac_address[0x00],
|
||||
[0x01] = mac_address[0x01],
|
||||
[0x02] = mac_address[0x02],
|
||||
[0x03] = mac_address[0x03],
|
||||
[0x04] = mac_address[0x04],
|
||||
[0x05] = mac_address[0x05],
|
||||
[0x06] = (uint8_t) (uuid & 0xff),
|
||||
[0x07] = (uint8_t) ((uuid >> 8) & 0xff),
|
||||
[0x08] = data[0x00],
|
||||
[0x09] = data[data_len-8],
|
||||
[0x0A] = data[data_len-7],
|
||||
[0x0B] = data[data_len-6],
|
||||
[0x0C] = data[data_len-5],
|
||||
};
|
||||
|
||||
uint8_t tag[] = {
|
||||
[0x00] = data[data_len-4],
|
||||
[0x01] = data[data_len-3],
|
||||
[0x02] = data[data_len-2],
|
||||
[0x03] = data[data_len-1],
|
||||
};
|
||||
|
||||
ret = mbedtls_ccm_auth_decrypt(&ctx, data_len-9, nonce, sizeof(nonce)/sizeof(nonce[0]), NULL, 0, data+1, cleartext, tag, sizeof(tag)/sizeof(tag[0]));
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "mbedtls_ccm_auth_decrypt() failed: %04x", ret);
|
||||
mbedtls_ccm_free(&ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mbedtls_ccm_free(&ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BTHome::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BTHome");
|
||||
if (this->encrypted_) {
|
||||
// ESP_LOGCONFIG(TAG, " Encryption key: %s", format_hex_pretty(this->encryption_key_, 16).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Encryption key set");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Encryption key not set");
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Window", this->window_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BTHome::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != address_) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
const auto BTHomeServiceDataUUID = 0xfcd2;
|
||||
if (service_data.uuid != esp32_ble::ESPBTUUID::from_uint16(BTHomeServiceDataUUID)) {
|
||||
continue;
|
||||
}
|
||||
if (service_data.data.size() < 1) {
|
||||
ESP_LOGW(TAG, "BTHome service data (UUID %#04x) without data (size %zd)", BTHomeServiceDataUUID, service_data.data.size());
|
||||
continue;
|
||||
}
|
||||
device_information di(service_data.data[0]);
|
||||
if (di.bthome_version != 2) {
|
||||
ESP_LOGW(TAG, "BTHome version %d is not supported (only version 2 is supported)", di.bthome_version);
|
||||
continue;
|
||||
}
|
||||
const uint8_t *data;
|
||||
size_t data_len;
|
||||
uint8_t cleartext[service_data.data.size()-9];
|
||||
if (di.encryption) {
|
||||
if (!this->encrypted_) {
|
||||
ESP_LOGW(TAG, "Encrypted packet but encryption key is not set");
|
||||
continue;
|
||||
}
|
||||
uint32_t counter =
|
||||
service_data.data[service_data.data.size()-8]
|
||||
+ ((uint32_t)(service_data.data[service_data.data.size()-7]) << 8)
|
||||
+ ((uint32_t)(service_data.data[service_data.data.size()-6]) << 16)
|
||||
+ ((uint32_t)(service_data.data[service_data.data.size()-5]) << 24);
|
||||
if (this->last_pid_ != -1 && counter <= this->last_counter_) {
|
||||
ESP_LOGV(TAG, "Packet counter already seen, skipping");
|
||||
continue;
|
||||
}
|
||||
this->last_counter_ = counter;
|
||||
const auto keybits = sizeof(this->encryption_key_) / sizeof(this->encryption_key_[0]) * 8;
|
||||
int ret = decrypt(service_data.data.data(), service_data.data.size(), device.address(), BTHomeServiceDataUUID, this->encryption_key_, keybits, cleartext);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Could not decrypt packet, error code: %04x", ret);
|
||||
continue;
|
||||
}
|
||||
data = cleartext;
|
||||
data_len = sizeof(cleartext) / sizeof(cleartext[0]);
|
||||
} else {
|
||||
if (this->encrypted_) {
|
||||
ESP_LOGW(TAG, "Ignoring unencrypted packet when encryption key is set");
|
||||
continue;
|
||||
}
|
||||
data = service_data.data.data() + 1;
|
||||
data_len = service_data.data.size() - 1;
|
||||
}
|
||||
for (const uint8_t *p = data; p < data + data_len;) {
|
||||
auto oid = *p++;
|
||||
ESP_LOGVV(TAG, "OID %#02x", oid);
|
||||
|
||||
if (oid == oid_pid.oid_) {
|
||||
UInt32Value v = oid_pid.read(p, data + data_len - p);
|
||||
if (v.value == this->last_pid_) {
|
||||
ESP_LOGV(TAG, "Packet ID %d already seen, skipping", v.value);
|
||||
break;
|
||||
}
|
||||
this->last_pid_ = v.value;
|
||||
p = v.next_ptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t *next_ptr = this->publish(oid, p, data + data_len - p);
|
||||
if (next_ptr != nullptr) {
|
||||
p = next_ptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto scan = oids[oid];
|
||||
if (scan == NULL) {
|
||||
ESP_LOGW(TAG, "Unknown OID %#02x - parsing aborted", oid);
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Skipping OID %#02x", oid);
|
||||
ScanResult sr = scan(p, data + data_len - p);
|
||||
p = sr.next_ptr;
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
const uint8_t *BTHome::publish(uint8_t oid, const uint8_t *data, size_t size) {
|
||||
for (auto pub : this->publishers_) {
|
||||
if (pub->oid() == oid) {
|
||||
return pub->publish(data, size);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BTHome::set_publisher(Publisher *publisher) {
|
||||
for (int i = 0; i < this->publishers_.size(); i++) {
|
||||
if (this->publishers_[i]->oid() == publisher->oid()) {
|
||||
auto old = this->publishers_[i];
|
||||
this->publishers_[i] = publisher;
|
||||
delete old;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->publishers_.push_back(publisher);
|
||||
}
|
||||
|
||||
void BTHome::set_encryption_key(const std::string &encryption_key) {
|
||||
memset(this->encryption_key_, 0, 16);
|
||||
if (encryption_key.size() != 32) {
|
||||
return;
|
||||
}
|
||||
char temp[3] = {0};
|
||||
for (int i = 0; i < 16; i++) {
|
||||
strncpy(temp, &(encryption_key.c_str()[i * 2]), 2);
|
||||
this->encryption_key_[i] = std::strtoul(temp, nullptr, 16);
|
||||
}
|
||||
this->encrypted_ = true;
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void BTHome::set_window(binary_sensor::BinarySensor *window) {
|
||||
this->window_ = window;
|
||||
this->set_publisher(oid_window.new_publisher(window));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void BTHome::set_angle(sensor::Sensor *angle) {
|
||||
this->angle_ = angle;
|
||||
this->set_publisher(oid_angle_degrees_x10.new_publisher(angle));
|
||||
}
|
||||
void BTHome::set_illuminance(sensor::Sensor *illuminance) {
|
||||
this->illuminance_ = illuminance;
|
||||
this->set_publisher(oid_illuminance_lux_x100.new_publisher(illuminance));
|
||||
}
|
||||
void BTHome::set_battery_level(sensor::Sensor *battery_level) {
|
||||
this->battery_level_ = battery_level;
|
||||
this->set_publisher(oid_battery_percent.new_publisher(battery_level));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace bthome
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
244
esphome/components/bthome/bthome.h
Normal file
244
esphome/components/bthome/bthome.h
Normal file
|
@ -0,0 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bthome {
|
||||
|
||||
uint32_t read_uint(size_t size, const uint8_t *data);
|
||||
int32_t read_sint(size_t size, const uint8_t *data);
|
||||
|
||||
struct BoolValue {
|
||||
bool value;
|
||||
const uint8_t *next_ptr;
|
||||
};
|
||||
|
||||
struct UInt32Value {
|
||||
uint32_t value;
|
||||
const uint8_t *next_ptr;
|
||||
};
|
||||
|
||||
struct FloatValue {
|
||||
float value;
|
||||
const uint8_t *next_ptr;
|
||||
};
|
||||
|
||||
class Publisher {
|
||||
public:
|
||||
Publisher(uint8_t oid): oid_(oid) {}
|
||||
virtual ~Publisher() {}
|
||||
uint8_t oid() const { return this->oid_; }
|
||||
virtual const uint8_t *publish(const uint8_t *data, size_t size) = 0;
|
||||
|
||||
protected:
|
||||
uint8_t oid_;
|
||||
};
|
||||
|
||||
class SensorPublisher: public Publisher {
|
||||
public:
|
||||
SensorPublisher(uint8_t oid, FloatValue (*read)(const uint8_t *data, size_t size), sensor::Sensor* sensor): Publisher(oid), sensor_(sensor), read_(read) {}
|
||||
virtual ~SensorPublisher() {}
|
||||
|
||||
virtual const uint8_t *publish(const uint8_t *data, size_t size) {
|
||||
FloatValue v = this->read_(data, size);
|
||||
sensor_->publish_state(v.value);
|
||||
return v.next_ptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
sensor::Sensor *sensor_;
|
||||
FloatValue (*read_)(const uint8_t *data, size_t size);
|
||||
};
|
||||
|
||||
class BinarySensorPublisher: public Publisher {
|
||||
public:
|
||||
BinarySensorPublisher(uint8_t oid, BoolValue (*read)(const uint8_t *data, size_t size), binary_sensor::BinarySensor* sensor): Publisher(oid), sensor_(sensor), read_(read) {}
|
||||
virtual ~BinarySensorPublisher() {}
|
||||
|
||||
virtual const uint8_t *publish(const uint8_t *data, size_t size) {
|
||||
BoolValue v = this->read_(data, size);
|
||||
sensor_->publish_state(v.value);
|
||||
return v.next_ptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *sensor_;
|
||||
BoolValue (*read_)(const uint8_t *data, size_t size);
|
||||
};
|
||||
|
||||
struct ScanResult {
|
||||
const uint8_t *value_ptr;
|
||||
size_t value_size;
|
||||
const uint8_t *next_ptr;
|
||||
};
|
||||
|
||||
typedef ScanResult scan_func_t(const uint8_t *data, size_t size);
|
||||
|
||||
template <uint8_t oid, size_t size_bytes>
|
||||
class OIDFixedSize {
|
||||
public:
|
||||
static ScanResult scan(const uint8_t *data, size_t size) {
|
||||
if (size < size_bytes) {
|
||||
return ScanResult {
|
||||
.value_ptr = nullptr,
|
||||
.value_size = 0,
|
||||
.next_ptr = data + size,
|
||||
};
|
||||
}
|
||||
return ScanResult {
|
||||
.value_ptr = data,
|
||||
.value_size = size_bytes,
|
||||
.next_ptr = data + size_bytes,
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr uint8_t oid_ = {oid};
|
||||
static constexpr size_t size_bytes_ = {size_bytes};
|
||||
};
|
||||
|
||||
template <uint8_t oid>
|
||||
class OIDBool: public OIDFixedSize <oid, 1> {
|
||||
public:
|
||||
static BoolValue read(const uint8_t *data, size_t size) {
|
||||
ScanResult sr = OIDBool::scan(data, size);
|
||||
if (sr.value_ptr == nullptr || sr.value_size == 0) {
|
||||
return BoolValue {
|
||||
.value = false,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
return BoolValue {
|
||||
.value = read_uint(sr.value_size, sr.value_ptr) != 0,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
|
||||
BinarySensorPublisher *new_publisher(binary_sensor::BinarySensor *sensor) const {
|
||||
return new BinarySensorPublisher(oid, OIDBool::read, sensor);
|
||||
}
|
||||
};
|
||||
|
||||
template <uint8_t oid, size_t size_bytes>
|
||||
class OIDUInt: public OIDFixedSize <oid, size_bytes> {
|
||||
public:
|
||||
static UInt32Value read(const uint8_t *data, size_t size) {
|
||||
ScanResult sr = OIDUInt::scan(data, size);
|
||||
if (sr.value_ptr == nullptr || sr.value_size == 0) {
|
||||
return UInt32Value {
|
||||
.value = 0,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
return UInt32Value {
|
||||
.value = read_uint(sr.value_size, sr.value_ptr),
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <uint8_t oid, size_t size_bytes, int f_num=1, int f_denom=1>
|
||||
class OIDSFixedPoint: public OIDFixedSize <oid, size_bytes> {
|
||||
public:
|
||||
static FloatValue read(const uint8_t *data, size_t size) {
|
||||
ScanResult sr = OIDSFixedPoint::scan(data, size);
|
||||
if (sr.value_ptr == nullptr || sr.value_size == 0) {
|
||||
return FloatValue {
|
||||
.value = 0.0,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
return FloatValue {
|
||||
.value = read_sint(sr.value_size, sr.value_ptr) * f_num / f_denom,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr int f_num_ = {f_num};
|
||||
static constexpr int f_denom_ = {f_denom};
|
||||
};
|
||||
|
||||
template <uint8_t oid, size_t size_bytes, int f_num=1, int f_denom=1>
|
||||
class OIDUFixedPoint: public OIDFixedSize <oid, size_bytes> {
|
||||
public:
|
||||
static FloatValue read(const uint8_t *data, size_t size) {
|
||||
ScanResult sr = OIDUFixedPoint::scan(data, size);
|
||||
if (sr.value_ptr == nullptr || sr.value_size == 0) {
|
||||
return FloatValue {
|
||||
.value = 0.0,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
return FloatValue {
|
||||
.value = static_cast<float>(read_uint(sr.value_size, sr.value_ptr)) * f_num / f_denom,
|
||||
.next_ptr = sr.next_ptr,
|
||||
};
|
||||
}
|
||||
|
||||
SensorPublisher *new_publisher(sensor::Sensor *sensor) const {
|
||||
return new SensorPublisher(oid, OIDUFixedPoint::read, sensor);
|
||||
}
|
||||
|
||||
static constexpr int f_num_ = {f_num};
|
||||
static constexpr int f_denom_ = {f_denom};
|
||||
};
|
||||
|
||||
template <uint8_t oid>
|
||||
class OIDUInt8: public OIDUInt <oid, 1> {
|
||||
};
|
||||
|
||||
class BTHome : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { this->address_ = address; };
|
||||
void set_encryption_key(const std::string &encryption_key);
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_window(binary_sensor::BinarySensor *window);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void set_angle(sensor::Sensor *angle);
|
||||
void set_illuminance(sensor::Sensor *illuminance);
|
||||
void set_battery_level(sensor::Sensor *battery_level);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
const uint8_t *publish(uint8_t oid, const uint8_t *data, size_t size);
|
||||
void set_publisher(Publisher *publisher);
|
||||
|
||||
uint64_t address_;
|
||||
bool encrypted_{false};
|
||||
uint8_t encryption_key_[16];
|
||||
|
||||
int16_t last_pid_{-1}; // -1 indicates that no packets have been received yet.
|
||||
uint32_t last_counter_{0};
|
||||
|
||||
std::vector <Publisher*> publishers_;
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *window_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *angle_{nullptr};
|
||||
sensor::Sensor *illuminance_{nullptr};
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace bthome
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
62
esphome/components/bthome/sensor.py
Normal file
62
esphome/components/bthome/sensor.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_ID,
|
||||
CONF_ILLUMINANCE,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DEGREES,
|
||||
UNIT_LUX,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
from . import BTHome
|
||||
|
||||
DEPENDENCIES = ["bthome"]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(BTHome),
|
||||
cv.Optional(CONF_ANGLE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=1,
|
||||
# device_class=DEVICE_CLASS_OPENING,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_LUX,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_ANGLE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ANGLE])
|
||||
cg.add(parent.set_angle(sens))
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(parent.set_battery_level(sens))
|
||||
|
||||
if CONF_ILLUMINANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
|
||||
cg.add(parent.set_illuminance(sens))
|
Loading…
Reference in a new issue