mirror of https://github.com/esphome/esphome.git
Merge branch 'dev' into host-target
This commit is contained in:
commit
1ab4928959
12
.clang-tidy
12
.clang-tidy
|
@ -135,10 +135,14 @@ CheckOptions:
|
|||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
|
||||
- key: readability-identifier-naming.PrivateMethodPrefix
|
||||
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
|
||||
- key: readability-identifier-naming.PrivateMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.PrivateMethodCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMethodSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
* text=auto eol=lf
|
||||
# Normalize line endings to LF in the repository
|
||||
* text eol=lf
|
||||
|
|
|
@ -126,3 +126,4 @@ tests/.esphome/
|
|||
.pio/
|
||||
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
|
|
15
CODEOWNERS
15
CODEOWNERS
|
@ -15,6 +15,7 @@ esphome/components/ac_dimmer/* @glmnet
|
|||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/airthings_ble/* @jeromelaban
|
||||
esphome/components/airthings_wave_mini/* @ncareau
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
|
@ -39,6 +40,9 @@ esphome/components/coolix/* @glmnet
|
|||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
|
@ -73,6 +77,7 @@ esphome/components/json/* @OttoWinter
|
|||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
|
@ -86,6 +91,13 @@ esphome/components/mcp9808/* @k7hpn
|
|||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
esphome/components/modbus_controller/number/* @martgras
|
||||
esphome/components/modbus_controller/output/* @martgras
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||
|
@ -104,6 +116,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
|
|||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/rc522/* @glmnet
|
||||
|
@ -113,6 +126,8 @@ esphome/components/restart/* @esphome/core
|
|||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @paulmonigatti
|
||||
esphome/components/scd4x/* @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
# One of "docker", "hassio"
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.0.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.0.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.0.0 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20210816-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20210816-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20210816-slim AS base-docker-armv7
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-armv7
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
|
|
|
@ -4,15 +4,21 @@
|
|||
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
|
||||
|
||||
if [[ -d /cache ]]; then
|
||||
export PLATFORMIO_CORE_DIR=/cache/platformio
|
||||
pio_cache_base=/cache/platformio
|
||||
else
|
||||
export PLATFORMIO_CORE_DIR=/config/.esphome/platformio
|
||||
pio_cache_base=/config/.esphome/platformio
|
||||
fi
|
||||
|
||||
if [[ ! -d "${PLATFORMIO_CORE_DIR}" ]]; then
|
||||
echo "Creating cache directory ${PLATFORMIO_CORE_DIR}"
|
||||
if [[ ! -d "${pio_cache_base}" ]]; then
|
||||
echo "Creating cache directory ${pio_cache_base}"
|
||||
echo "You can change this behavior by mounting a directory to the container's /cache directory."
|
||||
mkdir -p "${PLATFORMIO_CORE_DIR}"
|
||||
mkdir -p "${pio_cache_base}"
|
||||
fi
|
||||
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
exec esphome "$@"
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
# This files creates all directories used by esphome
|
||||
# ==============================================================================
|
||||
|
||||
PLATFORMIO_CORE_DIR=/data/cache/platformio
|
||||
pio_cache_base=/data/cache/platformio
|
||||
|
||||
mkdir -p "${PLATFORMIO_CORE_DIR}"
|
||||
mkdir -p "${pio_cache_base}"
|
||||
|
|
|
@ -22,7 +22,13 @@ if bashio::config.has_value 'relative_url'; then
|
|||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
||||
export PLATFORMIO_CORE_DIR=/data/cache/platformio
|
||||
pio_cache_base=/data/cache/platformio
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
|
|
|
@ -184,12 +184,30 @@ def compile_program(args, config):
|
|||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
from esphome import platformio_api
|
||||
|
||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||
"upload_speed", 460800
|
||||
)
|
||||
|
||||
def run_esptool(baud_rate):
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
|
||||
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
|
||||
flash_images = [
|
||||
platformio_api.FlashImage(
|
||||
path=idedata.firmware_bin_path,
|
||||
offset=firmware_offset,
|
||||
),
|
||||
*idedata.extra_flash_images,
|
||||
]
|
||||
|
||||
mcu = "esp8266"
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
|
||||
mcu = get_esp32_variant().lower()
|
||||
|
||||
cmd = [
|
||||
"esptool.py",
|
||||
"--before",
|
||||
|
@ -198,14 +216,15 @@ def upload_using_esptool(config, port):
|
|||
"hard_reset",
|
||||
"--baud",
|
||||
str(baud_rate),
|
||||
"--chip",
|
||||
"esp8266",
|
||||
"--port",
|
||||
port,
|
||||
"--chip",
|
||||
mcu,
|
||||
"write_flash",
|
||||
"0x0",
|
||||
path,
|
||||
"-z",
|
||||
]
|
||||
for img in flash_images:
|
||||
cmd += [img.offset, img.path]
|
||||
|
||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||
import esptool
|
||||
|
@ -229,11 +248,7 @@ def upload_using_esptool(config, port):
|
|||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == "SERIAL":
|
||||
from esphome import platformio_api
|
||||
|
||||
if CORE.is_esp8266:
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||
return upload_using_esptool(config, host)
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ from esphome.cpp_types import ( # noqa
|
|||
NAN,
|
||||
esphome_ns,
|
||||
App,
|
||||
Nameable,
|
||||
EntityBase,
|
||||
Component,
|
||||
ComponentPtr,
|
||||
PollingComponent,
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
#include <Esp.h>
|
||||
ADC_MODE(ADC_VCC)
|
||||
#else
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@ncareau"]
|
|
@ -0,0 +1,113 @@
|
|||
#include "airthings_wave_mini.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_mini {
|
||||
|
||||
static const char *const TAG = "airthings_wave_mini";
|
||||
|
||||
void AirthingsWaveMini::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 = esp32_ble_tracker::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 AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto value = (WaveMiniReadings *) raw_value;
|
||||
|
||||
if (sizeof(WaveMiniReadings) <= value_len) {
|
||||
this->humidity_sensor_->publish_state(value->humidity / 100.0f);
|
||||
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
|
||||
this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
|
||||
|
||||
void AirthingsWaveMini::update() {
|
||||
if (this->node_state != esp32_ble_tracker::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 AirthingsWaveMini::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 AirthingsWaveMini::dump_config() {
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
}
|
||||
|
||||
AirthingsWaveMini::AirthingsWaveMini()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
|
||||
|
||||
} // namespace airthings_wave_mini
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#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/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_mini {
|
||||
|
||||
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
|
||||
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
|
||||
|
||||
class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
|
||||
public:
|
||||
AirthingsWaveMini();
|
||||
|
||||
void dump_config() override;
|
||||
void update() 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_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||
|
||||
protected:
|
||||
bool is_valid_voc_value_(uint16_t voc);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void request_read_values_();
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *tvoc_sensor_{nullptr};
|
||||
|
||||
uint16_t handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
|
||||
|
||||
struct WaveMiniReadings {
|
||||
uint16_t unused01;
|
||||
uint16_t temperature;
|
||||
uint16_t pressure;
|
||||
uint16_t humidity;
|
||||
uint16_t voc;
|
||||
uint16_t unused02;
|
||||
uint32_t unused03;
|
||||
uint32_t unused04;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace airthings_wave_mini
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
|
@ -0,0 +1,82 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client
|
||||
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_ID,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TVOC,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_RADIATOR,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
|
||||
AirthingsWaveMini = airthings_wave_mini_ns.class_(
|
||||
"AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=2,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5min"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure(sens))
|
||||
if CONF_TVOC in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
|
@ -1,6 +1,6 @@
|
|||
#include "airthings_wave_plus.h"
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_plus {
|
||||
|
@ -31,7 +31,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
|||
break;
|
||||
}
|
||||
this->handle_ = chr->handle;
|
||||
this->node_state = esp32_ble_tracker::ClientState::Established;
|
||||
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
|
||||
|
||||
request_read_values_();
|
||||
break;
|
||||
|
@ -96,10 +96,8 @@ bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && v
|
|||
|
||||
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
|
||||
|
||||
void AirthingsWavePlus::loop() {}
|
||||
|
||||
void AirthingsWavePlus::update() {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::Established) {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
|
@ -128,17 +126,12 @@ void AirthingsWavePlus::dump_config() {
|
|||
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_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt);
|
||||
sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt);
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::setup() {}
|
||||
AirthingsWavePlus::AirthingsWavePlus()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
|
||||
|
||||
} // namespace airthings_wave_plus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#endif // USE_ESP32
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <BLEDevice.h>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.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/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_plus {
|
||||
|
||||
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
|
||||
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
|
||||
|
||||
class AirthingsWavePlus : public PollingComponent, public ble_client::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;
|
||||
|
@ -72,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientN
|
|||
} // namespace airthings_wave_plus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#endif // USE_ESP32
|
||||
|
|
|
@ -82,10 +82,8 @@ CONFIG_SCHEMA = cv.All(
|
|||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5mins"))
|
||||
.extend(cv.polling_component_schema("5min"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
# Until BLEUUID reference removed
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
|||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->logged_in_ = false;
|
||||
this->node_state = espbt::ClientState::Idle;
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
if (this->battery_ != nullptr)
|
||||
this->battery_->publish_state(NAN);
|
||||
if (this->illuminance_ != nullptr)
|
||||
|
@ -54,7 +54,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
|||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->update();
|
||||
break;
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
|||
}
|
||||
|
||||
void Am43::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ void Am43Component::setup() {
|
|||
}
|
||||
|
||||
void Am43Component::loop() {
|
||||
if (this->node_state == espbt::ClientState::Established && !this->logged_in_) {
|
||||
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
|
||||
auto packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
|
@ -46,7 +46,7 @@ CoverTraits Am43Component::get_traits() {
|
|||
}
|
||||
|
||||
void Am43Component::control(const CoverCall &call) {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
|
|
|
@ -5,7 +5,7 @@ from esphome.components import display, font
|
|||
import esphome.components.image as espImage
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -15,8 +15,6 @@ MULTI_CONF = True
|
|||
|
||||
Animation_ = display.display_ns.class_("Animation")
|
||||
|
||||
CONF_RAW_DATA_ID = "raw_data_id"
|
||||
|
||||
ANIMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
|
|
|
@ -72,7 +72,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
|||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->current_request_ = 0;
|
||||
this->update();
|
||||
break;
|
||||
|
@ -129,7 +129,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
|||
void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
|
||||
|
||||
void Anova::update() {
|
||||
if (this->node_state != espbt::ClientState::Established)
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return;
|
||||
|
||||
if (this->current_request_ < 2) {
|
||||
|
|
|
@ -27,7 +27,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
|||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
climate::ClimateTraits traits() {
|
||||
climate::ClimateTraits traits() override {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_heat_mode(true);
|
||||
|
|
|
@ -121,7 +121,7 @@ async def to_code(config):
|
|||
decoded = base64.b64decode(conf[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.1")
|
||||
cg.add_library("esphome/noise-c", "0.1.3")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
|
|
|
@ -215,6 +215,7 @@ message ListEntitiesBinarySensorResponse {
|
|||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
bool disabled_by_default = 7;
|
||||
string icon = 8;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
|
@ -245,6 +246,7 @@ message ListEntitiesCoverResponse {
|
|||
bool supports_tilt = 7;
|
||||
string device_class = 8;
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
|
@ -313,6 +315,7 @@ message ListEntitiesFanResponse {
|
|||
bool supports_direction = 7;
|
||||
int32 supported_speed_count = 8;
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
|
@ -388,6 +391,7 @@ message ListEntitiesLightResponse {
|
|||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
bool disabled_by_default = 13;
|
||||
string icon = 14;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
|
@ -790,6 +794,7 @@ message ListEntitiesClimateResponse {
|
|||
repeated ClimatePreset supported_presets = 16;
|
||||
repeated string supported_custom_presets = 17;
|
||||
bool disabled_by_default = 18;
|
||||
string icon = 19;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "api_connection.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
@ -143,8 +144,8 @@ void APIConnection::loop() {
|
|||
}
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
|
||||
return App.get_name() + component_type + entity->get_object_id();
|
||||
}
|
||||
|
||||
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
||||
|
@ -180,6 +181,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
|
|||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
|
||||
msg.icon = binary_sensor->get_icon();
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
@ -212,6 +214,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
|||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.device_class = cover->get_device_class();
|
||||
msg.disabled_by_default = cover->is_disabled_by_default();
|
||||
msg.icon = cover->get_icon();
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
|
@ -277,6 +280,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
|||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
msg.disabled_by_default = fan->is_disabled_by_default();
|
||||
msg.icon = fan->get_icon();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
|
@ -339,6 +343,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
|||
msg.unique_id = get_default_unique_id("light", light);
|
||||
|
||||
msg.disabled_by_default = light->is_disabled_by_default();
|
||||
msg.icon = light->get_icon();
|
||||
|
||||
for (auto mode : traits.get_supported_color_modes())
|
||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||
|
@ -529,6 +534,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
|
||||
msg.disabled_by_default = climate->is_disabled_by_default();
|
||||
msg.icon = climate->get_icon();
|
||||
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
|
@ -601,7 +607,7 @@ bool APIConnection::send_number_info(number::Number *number) {
|
|||
msg.object_id = number->get_object_id();
|
||||
msg.name = number->get_name();
|
||||
msg.unique_id = get_default_unique_id("number", number);
|
||||
msg.icon = number->traits.get_icon();
|
||||
msg.icon = number->get_icon();
|
||||
msg.disabled_by_default = number->is_disabled_by_default();
|
||||
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
|
@ -638,7 +644,7 @@ bool APIConnection::send_select_info(select::Select *select) {
|
|||
msg.object_id = select->get_object_id();
|
||||
msg.name = select->get_name();
|
||||
msg.unique_id = get_default_unique_id("select", select);
|
||||
msg.icon = select->traits.get_icon();
|
||||
msg.icon = select->get_icon();
|
||||
msg.disabled_by_default = select->is_disabled_by_default();
|
||||
|
||||
for (const auto &option : select->traits.get_options())
|
||||
|
|
|
@ -127,6 +127,14 @@ APIError APINoiseFrameHelper::init() {
|
|||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
|
||||
// init prologue
|
||||
prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT));
|
||||
|
||||
|
@ -722,6 +730,13 @@ APIError APIPlaintextFrameHelper::init() {
|
|||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
|
||||
state_ = State::DATA;
|
||||
return APIError::OK;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
|
@ -75,8 +76,8 @@ class APIFrameHelper {
|
|||
class APINoiseFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
||||
: socket_(std::move(socket)), ctx_(ctx) {}
|
||||
~APINoiseFrameHelper();
|
||||
: socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
|
||||
~APINoiseFrameHelper() override;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
|
@ -136,7 +137,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
|
||||
~APIPlaintextFrameHelper() = default;
|
||||
~APIPlaintextFrameHelper() override = default;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
|
|
|
@ -11,7 +11,7 @@ using psk_t = std::array<uint8_t, 32>;
|
|||
|
||||
class APINoiseContext {
|
||||
public:
|
||||
void set_psk(psk_t psk) { psk_ = std::move(psk); }
|
||||
void set_psk(psk_t psk) { psk_ = psk; }
|
||||
const psk_t &get_psk() const { return psk_; }
|
||||
|
||||
protected:
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#include "api_pb2.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
@ -532,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen
|
|||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -554,6 +557,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(5, this->device_class);
|
||||
buffer.encode_bool(6, this->is_status_binary_sensor);
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_string(8, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
|
@ -587,6 +591,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
|||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -678,6 +686,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli
|
|||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -702,6 +714,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(7, this->supports_tilt);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
|
@ -743,6 +756,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
|||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -949,6 +966,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
|
|||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -973,6 +994,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(7, this->supports_direction);
|
||||
buffer.encode_int32(8, this->supported_speed_count);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
|
@ -1015,6 +1037,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
|||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -1263,6 +1289,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli
|
|||
this->effects.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -1303,6 +1333,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(11, it, true);
|
||||
}
|
||||
buffer.encode_bool(13, this->disabled_by_default);
|
||||
buffer.encode_string(14, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
|
@ -1366,6 +1397,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
|||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -3073,6 +3108,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
|
|||
this->supported_custom_presets.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
case 19: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -3130,6 +3169,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(17, it, true);
|
||||
}
|
||||
buffer.encode_bool(18, this->disabled_by_default);
|
||||
buffer.encode_string(19, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
|
@ -3222,6 +3262,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
|||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -269,6 +269,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
|||
std::string device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -304,6 +305,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
|||
bool supports_tilt{false};
|
||||
std::string device_class{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -360,6 +362,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -424,6 +427,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
|||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -856,6 +860,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
|||
std::vector<enums::ClimatePreset> supported_presets{};
|
||||
std::vector<std::string> supported_custom_presets{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
|
|
@ -133,6 +133,11 @@ void APIServer::loop() {
|
|||
void APIServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "API Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
|
||||
#ifdef USE_API_NOISE
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
|
||||
#endif
|
||||
}
|
||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||
bool APIServer::check_password(const std::string &password) const {
|
||||
|
|
|
@ -32,7 +32,7 @@ class APIServer : public Component, public Controller {
|
|||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(std::move(psk)); }
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
|
|
|
@ -25,14 +25,14 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
|||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
auto res = parse_header(service_data);
|
||||
auto res = parse_header_(service_data);
|
||||
if (!res.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (!(parse_message(service_data.data, *res))) {
|
||||
if (!(parse_message_(service_data.data, *res))) {
|
||||
continue;
|
||||
}
|
||||
if (!(report_results(res, device.address_str()))) {
|
||||
if (!(report_results_(res, device.address_str()))) {
|
||||
continue;
|
||||
}
|
||||
if (res->temperature.has_value() && this->temperature_ != nullptr)
|
||||
|
@ -49,7 +49,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
|||
return success;
|
||||
}
|
||||
|
||||
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
|
||||
optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) {
|
||||
ParseResult result;
|
||||
if (!service_data.uuid.contains(0x1A, 0x18)) {
|
||||
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
|
||||
|
@ -68,7 +68,7 @@ optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::Se
|
|||
return result;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||
bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||
// Byte 0-5 mac in correct order
|
||||
// Byte 6-7 Temperature in uint16
|
||||
// Byte 8 Humidity in percent
|
||||
|
@ -101,7 +101,7 @@ bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseR
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
|
||||
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const std::string &address) {
|
||||
if (!result.has_value()) {
|
||||
ESP_LOGVV(TAG, "report_results(): no results available.");
|
||||
return false;
|
||||
|
|
|
@ -36,9 +36,9 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
|||
sensor::Sensor *battery_level_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
|
||||
optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results(const optional<ParseResult> &result, const std::string &address);
|
||||
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results_(const optional<ParseResult> &result, const std::string &address);
|
||||
};
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
|
|
|
@ -265,27 +265,57 @@ float ATM90E32Component::get_power_factor_c_() {
|
|||
}
|
||||
float ATM90E32Component::get_forward_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
|
||||
return (float) val * 10 / 3200; // convert register value to WattHours
|
||||
if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) {
|
||||
this->phase_[0].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
|
|
|
@ -77,6 +77,8 @@ class ATM90E32Component : public PollingComponent,
|
|||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||
uint32_t cumulative_forward_active_energy_{0};
|
||||
uint32_t cumulative_reverse_active_energy_{0};
|
||||
} phase_[3];
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
|
|
|
@ -14,6 +14,7 @@ void BParasite::dump_config() {
|
|||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
|
||||
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
|
||||
}
|
||||
|
||||
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
|
@ -36,6 +37,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
|||
|
||||
const auto &data = service_data.data;
|
||||
|
||||
const uint8_t protocol_version = data[0] >> 4;
|
||||
if (protocol_version != 1) {
|
||||
ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some b-parasite versions have an (optional) illuminance sensor.
|
||||
bool has_illuminance = data[0] & 0x1;
|
||||
|
||||
// Counter for deduplicating messages.
|
||||
uint8_t counter = data[1] & 0x0f;
|
||||
if (last_processed_counter_ == counter) {
|
||||
|
@ -59,6 +69,9 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
|||
uint16_t soil_moisture = data[8] << 8 | data[9];
|
||||
float moisture_percent = (100.0f * soil_moisture) / (1 << 16);
|
||||
|
||||
// Ambient light in lux.
|
||||
float illuminance = has_illuminance ? data[16] << 8 | data[17] : 0.0f;
|
||||
|
||||
if (battery_voltage_ != nullptr) {
|
||||
battery_voltage_->publish_state(battery_voltage);
|
||||
}
|
||||
|
@ -71,6 +84,13 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
|||
if (soil_moisture_ != nullptr) {
|
||||
soil_moisture_->publish_state(moisture_percent);
|
||||
}
|
||||
if (illuminance_ != nullptr) {
|
||||
if (has_illuminance) {
|
||||
illuminance_->publish_state(illuminance);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No lux information is present in the BLE packet");
|
||||
}
|
||||
}
|
||||
|
||||
last_processed_counter_ = counter;
|
||||
return true;
|
||||
|
|
|
@ -22,6 +22,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
|
|||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
void set_soil_moisture(sensor::Sensor *soil_moisture) { soil_moisture_ = soil_moisture; }
|
||||
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
|
||||
|
||||
protected:
|
||||
// The received advertisement packet contains an unsigned 4 bits wrap-around counter
|
||||
|
@ -32,6 +33,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
|
|||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *soil_moisture_{nullptr};
|
||||
sensor::Sensor *illuminance_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace b_parasite
|
||||
|
|
|
@ -5,14 +5,17 @@ from esphome.const import (
|
|||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_ILLUMINANCE,
|
||||
CONF_MOISTURE,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_LUX,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
@ -55,6 +58,12 @@ CONFIG_SCHEMA = (
|
|||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_LUX,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
@ -74,6 +83,7 @@ async def to_code(config):
|
|||
(CONF_HUMIDITY, var.set_humidity),
|
||||
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
|
||||
(CONF_MOISTURE, var.set_soil_moisture),
|
||||
(CONF_ILLUMINANCE, var.set_illuminance),
|
||||
]:
|
||||
if config_key in config:
|
||||
sens = await sensor.new_sensor(config[config_key])
|
||||
|
|
|
@ -71,7 +71,7 @@ void BH1750Sensor::update() {
|
|||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void BH1750Sensor::read_data_() {
|
||||
uint16_t raw_value;
|
||||
if (!this->read(reinterpret_cast<uint8_t *>(&raw_value), 2)) {
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_FILTERS,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_INVALID_COOLDOWN,
|
||||
CONF_INVERTED,
|
||||
CONF_MAX_LENGTH,
|
||||
|
@ -88,7 +87,7 @@ DEVICE_CLASSES = [
|
|||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
|
||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable)
|
||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
|
||||
BinarySensorInitiallyOff = binary_sensor_ns.class_(
|
||||
"BinarySensorInitiallyOff", BinarySensor
|
||||
)
|
||||
|
@ -314,7 +313,7 @@ def validate_multi_click_timing(value):
|
|||
|
||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
|
@ -375,10 +374,8 @@ BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exten
|
|||
|
||||
|
||||
async def setup_binary_sensor_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
if CONF_INVERTED in config:
|
||||
|
|
|
@ -42,7 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
|||
}
|
||||
}
|
||||
std::string BinarySensor::device_class() { return ""; }
|
||||
BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {}
|
||||
BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {}
|
||||
BinarySensor::BinarySensor() : BinarySensor("") {}
|
||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string BinarySensor::get_device_class() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
|
||||
|
@ -22,7 +23,7 @@ namespace binary_sensor {
|
|||
* The sub classes should notify the front-end of new states via the publish_state() method which
|
||||
* handles inverted inputs for you.
|
||||
*/
|
||||
class BinarySensor : public Nameable {
|
||||
class BinarySensor : public EntityBase {
|
||||
public:
|
||||
explicit BinarySensor();
|
||||
/** Construct a binary sensor with the specified name
|
||||
|
|
|
@ -11,11 +11,12 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
|||
public:
|
||||
explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
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) {
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -23,11 +24,12 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
|||
public:
|
||||
explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
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) {
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -17,12 +17,12 @@ void BLEClient::setup() {
|
|||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
this->enabled = true;
|
||||
}
|
||||
|
||||
void BLEClient::loop() {
|
||||
if (this->state() == espbt::ClientState::Discovered) {
|
||||
if (this->state() == espbt::ClientState::DISCOVERED) {
|
||||
this->connect();
|
||||
}
|
||||
for (auto *node : this->nodes_)
|
||||
|
@ -39,11 +39,11 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
|
|||
return false;
|
||||
if (device.address_uint64() != this->address)
|
||||
return false;
|
||||
if (this->state() != espbt::ClientState::Idle)
|
||||
if (this->state() != espbt::ClientState::IDLE)
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
|
||||
this->set_states(espbt::ClientState::Discovered);
|
||||
this->set_states_(espbt::ClientState::DISCOVERED);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda[0] = (addr >> 40) & 0xFF;
|
||||
|
@ -69,7 +69,7 @@ std::string BLEClient::address_str() const {
|
|||
void BLEClient::set_enabled(bool enabled) {
|
||||
if (enabled == this->enabled)
|
||||
return;
|
||||
if (!enabled && this->state() != espbt::ClientState::Idle) {
|
||||
if (!enabled && this->state() != espbt::ClientState::IDLE) {
|
||||
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
|
||||
if (ret) {
|
||||
|
@ -84,9 +84,9 @@ void BLEClient::connect() {
|
|||
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_states(espbt::ClientState::Connecting);
|
||||
this->set_states_(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
|
||||
return;
|
||||
|
||||
bool all_established = this->all_nodes_established();
|
||||
bool all_established = this->all_nodes_established_();
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
|
@ -113,7 +113,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
this->conn_id = param->open.conn_id;
|
||||
|
@ -126,7 +126,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||
|
@ -141,7 +141,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
|
@ -160,8 +160,8 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
|
||||
svc->parse_characteristics();
|
||||
}
|
||||
this->set_states(espbt::ClientState::Connected);
|
||||
this->set_state(espbt::ClientState::Established);
|
||||
this->set_states_(espbt::ClientState::CONNECTED);
|
||||
this->set_state(espbt::ClientState::ESTABLISHED);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
|
@ -192,7 +192,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||
node->gattc_event_handler(event, esp_gattc_if, param);
|
||||
|
||||
// Delete characteristics after clients have used them to save RAM.
|
||||
if (!all_established && this->all_nodes_established()) {
|
||||
if (!all_established && this->all_nodes_established_()) {
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
|
|
|
@ -82,10 +82,11 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
|||
void dump_config() 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);
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
void connect();
|
||||
void connect() override;
|
||||
|
||||
void set_address(uint64_t address) { this->address = address; }
|
||||
|
||||
|
@ -116,16 +117,16 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
|||
std::string address_str() const;
|
||||
|
||||
protected:
|
||||
void set_states(espbt::ClientState st) {
|
||||
void set_states_(espbt::ClientState st) {
|
||||
this->set_state(st);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = st;
|
||||
}
|
||||
bool all_nodes_established() {
|
||||
if (this->state() != espbt::ClientState::Established)
|
||||
bool all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_)
|
||||
if (node->node_state != espbt::ClientState::Established)
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,11 @@ namespace ble_client {
|
|||
class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
public:
|
||||
explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
switch (event) {
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->sensor_->node_state = espbt::ClientState::Established;
|
||||
this->sensor_->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
|
|
|
@ -71,7 +71,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
} else {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||
}
|
||||
if (param->read.handle == this->handle) {
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||
this->publish_state(this->parse_data_(param->read.value, param->read.value_len));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -93,11 +93,11 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
|
||||
this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -105,7 +105,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||
}
|
||||
}
|
||||
|
||||
float BLESensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
||||
if (this->data_to_value_func_.has_value()) {
|
||||
std::vector<uint8_t> data(value, value + value_len);
|
||||
return (*this->data_to_value_func_)(data);
|
||||
|
@ -115,7 +115,7 @@ float BLESensor::parse_data(uint8_t *value, uint16_t value_len) {
|
|||
}
|
||||
|
||||
void BLESensor::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -32,13 +32,13 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
|
|||
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_data_to_value(data_to_value_t &&lambda_) { this->data_to_value_func_ = lambda_; }
|
||||
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
uint32_t hash_base() override;
|
||||
float parse_data(uint8_t *value, uint16_t value_len);
|
||||
float parse_data_(uint8_t *value, uint16_t value_len);
|
||||
optional<data_to_value_t> data_to_value_func_{};
|
||||
bool notify_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
|
|
|
@ -21,10 +21,10 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
|||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
this->node_state = espbt::ClientState::Idle;
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -93,8 +93,8 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
|||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
enum MATCH_TYPE { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MATCH_TYPE match_by_;
|
||||
enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MatchType match_by_;
|
||||
|
||||
bool found_{false};
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace ble_scanner {
|
|||
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) +
|
||||
this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) +
|
||||
","
|
||||
"\"address\":\"" +
|
||||
device.address_str() +
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include <map>
|
||||
|
||||
#ifdef USE_BSEC
|
||||
|
|
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
|||
from esphome.components import web_server_base
|
||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
AUTO_LOAD = ["web_server_base"]
|
||||
DEPENDENCIES = ["wifi"]
|
||||
|
@ -32,3 +32,7 @@ async def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
|
|
|
@ -86,8 +86,11 @@ void CCS811Component::setup() {
|
|||
}
|
||||
}
|
||||
void CCS811Component::update() {
|
||||
if (!this->status_has_data_())
|
||||
if (!this->status_has_data_()) {
|
||||
ESP_LOGD(TAG, "Status indicates no data ready!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// page 12 - alg result data
|
||||
auto alg_data = this->read_bytes<4>(0x02);
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ACTION_STATE_TOPIC,
|
||||
CONF_AWAY,
|
||||
CONF_AWAY_COMMAND_TOPIC,
|
||||
CONF_AWAY_STATE_TOPIC,
|
||||
CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
CONF_CUSTOM_PRESET,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_FAN_MODE,
|
||||
CONF_FAN_MODE_COMMAND_TOPIC,
|
||||
CONF_FAN_MODE_STATE_TOPIC,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MODE,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_PRESET,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TARGET_TEMPERATURE_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_HIGH,
|
||||
CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_LOW,
|
||||
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_VISUAL,
|
||||
CONF_MQTT_ID,
|
||||
CONF_NAME,
|
||||
CONF_FAN_MODE,
|
||||
CONF_SWING_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
|
@ -30,7 +44,7 @@ IS_PLATFORM_COMPONENT = True
|
|||
CODEOWNERS = ["@esphome/core"]
|
||||
climate_ns = cg.esphome_ns.namespace("climate")
|
||||
|
||||
Climate = climate_ns.class_("Climate", cg.Nameable)
|
||||
Climate = climate_ns.class_("Climate", cg.EntityBase)
|
||||
ClimateCall = climate_ns.class_("ClimateCall")
|
||||
ClimateTraits = climate_ns.class_("ClimateTraits")
|
||||
|
||||
|
@ -88,7 +102,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
|||
# Actions
|
||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||
|
||||
CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
|
@ -99,16 +113,61 @@ CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ext
|
|||
cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
|
||||
}
|
||||
),
|
||||
# TODO: MQTT topic options
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_climate_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
await setup_entity(var, config)
|
||||
|
||||
visual = config[CONF_VISUAL]
|
||||
if CONF_MIN_TEMPERATURE in visual:
|
||||
cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE]))
|
||||
|
@ -121,6 +180,82 @@ async def setup_climate_core_(var, config):
|
|||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if CONF_ACTION_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_action_state_topic(config[CONF_ACTION_STATE_TOPIC]))
|
||||
if CONF_AWAY_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_away_command_topic(config[CONF_AWAY_COMMAND_TOPIC]))
|
||||
if CONF_AWAY_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_away_state_topic(config[CONF_AWAY_STATE_TOPIC]))
|
||||
if CONF_CURRENT_TEMPERATURE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_current_temperature_state_topic(
|
||||
config[CONF_CURRENT_TEMPERATURE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_FAN_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_fan_mode_command_topic(
|
||||
config[CONF_FAN_MODE_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_FAN_MODE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_fan_mode_state_topic(config[CONF_FAN_MODE_STATE_TOPIC])
|
||||
)
|
||||
if CONF_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
|
||||
if CONF_SWING_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_command_topic(
|
||||
config[CONF_SWING_MODE_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_SWING_MODE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_state_topic(
|
||||
config[CONF_SWING_MODE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_command_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_state_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_high_command_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_high_state_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_low_command_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_state_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
|
|
@ -440,7 +440,7 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o
|
|||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
|
||||
this->visual_temperature_step_override_ = visual_temperature_step_override;
|
||||
}
|
||||
Climate::Climate(const std::string &name) : Nameable(name) {}
|
||||
Climate::Climate(const std::string &name) : EntityBase(name) {}
|
||||
Climate::Climate() : Climate("") {}
|
||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
@ -163,7 +164,7 @@ struct ClimateDeviceRestoreState {
|
|||
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
|
||||
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
|
||||
*/
|
||||
class Climate : public Nameable {
|
||||
class Climate : public EntityBase {
|
||||
public:
|
||||
/// Construct a climate device with empty name (will be set later).
|
||||
Climate();
|
||||
|
|
|
@ -4,18 +4,20 @@ from esphome import automation
|
|||
from esphome.automation import maybe_simple_id, Condition
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_STATE,
|
||||
CONF_POSITION,
|
||||
CONF_POSITION_COMMAND_TOPIC,
|
||||
CONF_POSITION_STATE_TOPIC,
|
||||
CONF_TILT,
|
||||
CONF_TILT_COMMAND_TOPIC,
|
||||
CONF_TILT_STATE_TOPIC,
|
||||
CONF_STOP,
|
||||
CONF_MQTT_ID,
|
||||
CONF_NAME,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
|
@ -36,7 +38,7 @@ DEVICE_CLASSES = [
|
|||
|
||||
cover_ns = cg.esphome_ns.namespace("cover")
|
||||
|
||||
Cover = cover_ns.class_("Cover", cg.Nameable)
|
||||
Cover = cover_ns.class_("Cover", cg.EntityBase)
|
||||
|
||||
COVER_OPEN = cover_ns.COVER_OPEN
|
||||
COVER_CLOSED = cover_ns.COVER_CLOSED
|
||||
|
@ -59,26 +61,36 @@ validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True)
|
|||
OpenAction = cover_ns.class_("OpenAction", automation.Action)
|
||||
CloseAction = cover_ns.class_("CloseAction", automation.Action)
|
||||
StopAction = cover_ns.class_("StopAction", automation.Action)
|
||||
ToggleAction = cover_ns.class_("ToggleAction", automation.Action)
|
||||
ControlAction = cover_ns.class_("ControlAction", automation.Action)
|
||||
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
|
||||
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
|
||||
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
|
||||
|
||||
COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Cover),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
# TODO: MQTT topic options
|
||||
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_cover_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
|
@ -86,6 +98,21 @@ async def setup_cover_core_(var, config):
|
|||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if CONF_POSITION_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_position_state_topic(config[CONF_POSITION_STATE_TOPIC])
|
||||
)
|
||||
if CONF_POSITION_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_position_command_topic(
|
||||
config[CONF_POSITION_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TILT_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_tilt_state_topic(config[CONF_TILT_STATE_TOPIC]))
|
||||
if CONF_TILT_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_tilt_command_topic(config[CONF_TILT_COMMAND_TOPIC]))
|
||||
|
||||
|
||||
async def register_cover(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
@ -119,6 +146,12 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
|
|||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
|
||||
def cover_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Cover),
|
||||
|
|
|
@ -37,6 +37,16 @@ template<typename... Ts> class StopAction : public Action<Ts...> {
|
|||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ToggleAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
||||
|
|
|
@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) {
|
|||
}
|
||||
}
|
||||
|
||||
Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {}
|
||||
Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {}
|
||||
|
||||
uint32_t Cover::hash_base() { return 1727367479UL; }
|
||||
|
||||
|
@ -43,6 +43,8 @@ CoverCall &CoverCall::set_command(const char *command) {
|
|||
this->set_command_close();
|
||||
} else if (strcasecmp(command, "STOP") == 0) {
|
||||
this->set_command_stop();
|
||||
} else if (strcasecmp(command, "TOGGLE") == 0) {
|
||||
this->set_command_toggle();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
|
||||
}
|
||||
|
@ -60,6 +62,10 @@ CoverCall &CoverCall::set_command_stop() {
|
|||
this->stop_ = true;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_toggle() {
|
||||
this->toggle_ = true;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_position(float position) {
|
||||
this->position_ = position;
|
||||
return *this;
|
||||
|
@ -85,10 +91,14 @@ void CoverCall::perform() {
|
|||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
ESP_LOGD(TAG, " Command: TOGGLE");
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
const optional<float> &CoverCall::get_position() const { return this->position_; }
|
||||
const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
|
||||
const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; }
|
||||
void CoverCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
if (this->position_.has_value()) {
|
||||
|
@ -111,6 +121,12 @@ void CoverCall::validate_() {
|
|||
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
if (!traits.get_supports_toggle()) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str());
|
||||
this->toggle_.reset();
|
||||
}
|
||||
}
|
||||
if (this->stop_) {
|
||||
if (this->position_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
|
||||
|
@ -120,6 +136,10 @@ void CoverCall::validate_() {
|
|||
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
|
||||
this->tilt_.reset();
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!");
|
||||
this->toggle_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
CoverCall &CoverCall::set_stop(bool stop) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "cover_traits.h"
|
||||
|
@ -29,7 +30,7 @@ class CoverCall {
|
|||
public:
|
||||
CoverCall(Cover *parent);
|
||||
|
||||
/// Set the command as a string, "STOP", "OPEN", "CLOSE".
|
||||
/// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE".
|
||||
CoverCall &set_command(const char *command);
|
||||
/// Set the command to open the cover.
|
||||
CoverCall &set_command_open();
|
||||
|
@ -37,6 +38,8 @@ class CoverCall {
|
|||
CoverCall &set_command_close();
|
||||
/// Set the command to stop the cover.
|
||||
CoverCall &set_command_stop();
|
||||
/// Set the command to toggle the cover.
|
||||
CoverCall &set_command_toggle();
|
||||
/// Set the call to a certain target position.
|
||||
CoverCall &set_position(float position);
|
||||
/// Set the call to a certain target tilt.
|
||||
|
@ -50,6 +53,7 @@ class CoverCall {
|
|||
const optional<float> &get_position() const;
|
||||
bool get_stop() const;
|
||||
const optional<float> &get_tilt() const;
|
||||
const optional<bool> &get_toggle() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
@ -58,6 +62,7 @@ class CoverCall {
|
|||
bool stop_{false};
|
||||
optional<float> position_{};
|
||||
optional<float> tilt_{};
|
||||
optional<bool> toggle_{};
|
||||
};
|
||||
|
||||
/// Struct used to store the restored state of a cover
|
||||
|
@ -103,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op);
|
|||
* to control all values of the cover. Also implement get_traits() to return what operations
|
||||
* the cover supports.
|
||||
*/
|
||||
class Cover : public Nameable {
|
||||
class Cover : public EntityBase {
|
||||
public:
|
||||
explicit Cover();
|
||||
explicit Cover(const std::string &name);
|
||||
|
|
|
@ -13,11 +13,14 @@ class CoverTraits {
|
|||
void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; }
|
||||
bool get_supports_tilt() const { return this->supports_tilt_; }
|
||||
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
|
||||
bool get_supports_toggle() const { return this->supports_toggle_; }
|
||||
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
|
||||
|
||||
protected:
|
||||
bool is_assumed_state_{false};
|
||||
bool supports_position_{false};
|
||||
bool supports_tilt_{false};
|
||||
bool supports_toggle_{false};
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@djwmarcx"]
|
|
@ -0,0 +1,124 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import sensor, cover
|
||||
from esphome.const import (
|
||||
CONF_CLOSE_ACTION,
|
||||
CONF_CLOSE_DURATION,
|
||||
CONF_ID,
|
||||
CONF_OPEN_ACTION,
|
||||
CONF_OPEN_DURATION,
|
||||
CONF_STOP_ACTION,
|
||||
CONF_MAX_DURATION,
|
||||
)
|
||||
|
||||
|
||||
CONF_OPEN_SENSOR = "open_sensor"
|
||||
CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold"
|
||||
CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD = "open_obstacle_current_threshold"
|
||||
|
||||
CONF_CLOSE_SENSOR = "close_sensor"
|
||||
CONF_CLOSE_MOVING_CURRENT_THRESHOLD = "close_moving_current_threshold"
|
||||
CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD = "close_obstacle_current_threshold"
|
||||
|
||||
CONF_OBSTACLE_ROLLBACK = "obstacle_rollback"
|
||||
CONF_MALFUNCTION_DETECTION = "malfunction_detection"
|
||||
CONF_MALFUNCTION_ACTION = "malfunction_action"
|
||||
CONF_START_SENSING_DELAY = "start_sensing_delay"
|
||||
|
||||
current_based_ns = cg.esphome_ns.namespace("current_based")
|
||||
CurrentBasedCover = current_based_ns.class_(
|
||||
"CurrentBasedCover", cover.Cover, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CurrentBasedCover),
|
||||
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
|
||||
cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
|
||||
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
|
||||
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean,
|
||||
cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_START_SENSING_DELAY, default="500ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield cover.register_cover(var, config)
|
||||
|
||||
yield automation.build_automation(
|
||||
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
|
||||
)
|
||||
|
||||
# OPEN
|
||||
bin = yield cg.get_variable(config[CONF_OPEN_SENSOR])
|
||||
cg.add(var.set_open_sensor(bin))
|
||||
cg.add(
|
||||
var.set_open_moving_current_threshold(
|
||||
config[CONF_OPEN_MOVING_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config:
|
||||
cg.add(
|
||||
var.set_open_obstacle_current_threshold(
|
||||
config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
cg.add(var.set_open_duration(config[CONF_OPEN_DURATION]))
|
||||
yield automation.build_automation(
|
||||
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
|
||||
)
|
||||
|
||||
# CLOSE
|
||||
bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR])
|
||||
cg.add(var.set_close_sensor(bin))
|
||||
cg.add(
|
||||
var.set_close_moving_current_threshold(
|
||||
config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config:
|
||||
cg.add(
|
||||
var.set_close_obstacle_current_threshold(
|
||||
config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
|
||||
yield automation.build_automation(
|
||||
var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
|
||||
)
|
||||
|
||||
cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK]))
|
||||
if CONF_MAX_DURATION in config:
|
||||
cg.add(var.set_max_duration(config[CONF_MAX_DURATION]))
|
||||
cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION]))
|
||||
if CONF_MALFUNCTION_ACTION in config:
|
||||
yield automation.build_automation(
|
||||
var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION]
|
||||
)
|
||||
cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY]))
|
|
@ -0,0 +1,251 @@
|
|||
#include "current_based_cover.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cfloat>
|
||||
|
||||
namespace esphome {
|
||||
namespace current_based {
|
||||
|
||||
static const char *const TAG = "current_based.cover";
|
||||
|
||||
using namespace esphome::cover;
|
||||
|
||||
CoverTraits CurrentBasedCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
void CurrentBasedCover::control(const CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
this->direction_idle_();
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
if (pos == this->position) {
|
||||
// already at target
|
||||
} else {
|
||||
auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING;
|
||||
this->target_position_ = pos;
|
||||
this->start_direction_(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
void CurrentBasedCover::setup() {
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->apply(this);
|
||||
} else {
|
||||
this->position = 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
void CurrentBasedCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->current_operation == COVER_OPERATION_OPENING) {
|
||||
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
|
||||
this->direction_idle_();
|
||||
this->malfunction_trigger_->trigger();
|
||||
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
|
||||
this->name_.c_str());
|
||||
} else if (this->is_opening_blocked_()) { // Blocked
|
||||
ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str());
|
||||
this->direction_idle_();
|
||||
if (this->obstacle_rollback_ != 0) {
|
||||
this->set_timeout("rollback", 300, [this]() {
|
||||
ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
|
||||
this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F);
|
||||
this->start_direction_(COVER_OPERATION_CLOSING);
|
||||
});
|
||||
}
|
||||
} else if (this->is_initial_delay_finished_() && !this->is_opening_()) { // End reached
|
||||
auto dur = (now - this->start_dir_time_) / 1e3f;
|
||||
ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur);
|
||||
this->direction_idle_(COVER_OPEN);
|
||||
}
|
||||
} else if (this->current_operation == COVER_OPERATION_CLOSING) {
|
||||
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
|
||||
this->direction_idle_();
|
||||
this->malfunction_trigger_->trigger();
|
||||
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
|
||||
this->name_.c_str());
|
||||
} else if (this->is_closing_blocked_()) { // Blocked
|
||||
ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str());
|
||||
this->direction_idle_();
|
||||
if (this->obstacle_rollback_ != 0) {
|
||||
this->set_timeout("rollback", 300, [this]() {
|
||||
ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
|
||||
this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F);
|
||||
this->start_direction_(COVER_OPERATION_OPENING);
|
||||
});
|
||||
}
|
||||
} else if (this->is_initial_delay_finished_() && !this->is_closing_()) { // End reached
|
||||
auto dur = (now - this->start_dir_time_) / 1e3f;
|
||||
ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur);
|
||||
this->direction_idle_(COVER_CLOSED);
|
||||
}
|
||||
} else if (now - this->start_dir_time_ > this->max_duration_) {
|
||||
ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str());
|
||||
this->direction_idle_();
|
||||
}
|
||||
|
||||
// Recompute position every loop cycle
|
||||
this->recompute_position_();
|
||||
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) {
|
||||
this->direction_idle_();
|
||||
}
|
||||
|
||||
// Send current position every second
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) {
|
||||
this->publish_state(false);
|
||||
this->last_publish_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void CurrentBasedCover::direction_idle_(float new_position) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
if (new_position != FLT_MAX) {
|
||||
this->position = new_position;
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void CurrentBasedCover::dump_config() {
|
||||
LOG_COVER("", "Endstop Cover", this);
|
||||
LOG_SENSOR(" ", "Open Sensor", this->open_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Open moving current threshold: %.11fA", this->open_moving_current_threshold_);
|
||||
if (this->open_obstacle_current_threshold_ != FLT_MAX) {
|
||||
ESP_LOGCONFIG(TAG, " Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
|
||||
LOG_SENSOR(" ", "Close Sensor", this->close_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Close moving current threshold: %.11fA", this->close_moving_current_threshold_);
|
||||
if (this->close_obstacle_current_threshold_ != FLT_MAX) {
|
||||
ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
|
||||
ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100);
|
||||
if (this->max_duration_ != UINT32_MAX) {
|
||||
ESP_LOGCONFIG(TAG, "Maximun duration: %.1fs", this->max_duration_ / 1e3f);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f);
|
||||
ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_));
|
||||
}
|
||||
|
||||
float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void CurrentBasedCover::stop_prev_trigger_() {
|
||||
if (this->prev_command_trigger_ != nullptr) {
|
||||
this->prev_command_trigger_->stop_action();
|
||||
this->prev_command_trigger_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_opening_() const {
|
||||
return this->open_sensor_->get_state() > this->open_moving_current_threshold_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_opening_blocked_() const {
|
||||
if (this->open_obstacle_current_threshold_ == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_closing_() const {
|
||||
return this->close_sensor_->get_state() > this->close_moving_current_threshold_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_closing_blocked_() const {
|
||||
if (this->close_obstacle_current_threshold_ == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
|
||||
}
|
||||
bool CurrentBasedCover::is_initial_delay_finished_() const {
|
||||
return millis() - this->start_dir_time_ > this->start_sensing_delay_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_at_target_() const {
|
||||
switch (this->current_operation) {
|
||||
case COVER_OPERATION_OPENING:
|
||||
if (this->target_position_ == COVER_OPEN) {
|
||||
if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
|
||||
return false;
|
||||
return !this->is_opening_();
|
||||
}
|
||||
return this->position >= this->target_position_;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
if (this->target_position_ == COVER_CLOSED) {
|
||||
if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
|
||||
return false;
|
||||
return !this->is_closing_();
|
||||
}
|
||||
return this->position <= this->target_position_;
|
||||
case COVER_OPERATION_IDLE:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
void CurrentBasedCover::start_direction_(CoverOperation dir) {
|
||||
if (dir == this->current_operation)
|
||||
return;
|
||||
|
||||
this->recompute_position_();
|
||||
Trigger<> *trig;
|
||||
switch (dir) {
|
||||
case COVER_OPERATION_IDLE:
|
||||
trig = this->stop_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_OPENING:
|
||||
trig = this->open_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
trig = this->close_trigger_;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
this->current_operation = dir;
|
||||
|
||||
this->stop_prev_trigger_();
|
||||
trig->trigger();
|
||||
this->prev_command_trigger_ = trig;
|
||||
|
||||
const auto now = millis();
|
||||
this->start_dir_time_ = now;
|
||||
this->last_recompute_time_ = now;
|
||||
}
|
||||
void CurrentBasedCover::recompute_position_() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
float dir;
|
||||
float action_dur;
|
||||
switch (this->current_operation) {
|
||||
case COVER_OPERATION_OPENING:
|
||||
dir = 1.0F;
|
||||
action_dur = this->open_duration_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
dir = -1.0F;
|
||||
action_dur = this->close_duration_;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = millis();
|
||||
this->position += dir * (now - this->last_recompute_time_) / action_dur;
|
||||
this->position = clamp(this->position, 0.0F, 1.0F);
|
||||
|
||||
this->last_recompute_time_ = now;
|
||||
}
|
||||
|
||||
} // namespace current_based
|
||||
} // namespace esphome
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/cover/cover.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <cfloat>
|
||||
|
||||
namespace esphome {
|
||||
namespace current_based {
|
||||
|
||||
class CurrentBasedCover : public cover::Cover, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
|
||||
|
||||
Trigger<> *get_open_trigger() const { return this->open_trigger_; }
|
||||
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
|
||||
void set_open_moving_current_threshold(float open_moving_current_threshold) {
|
||||
this->open_moving_current_threshold_ = open_moving_current_threshold;
|
||||
}
|
||||
void set_open_obstacle_current_threshold(float open_obstacle_current_threshold) {
|
||||
this->open_obstacle_current_threshold_ = open_obstacle_current_threshold;
|
||||
}
|
||||
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
|
||||
|
||||
Trigger<> *get_close_trigger() const { return this->close_trigger_; }
|
||||
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
|
||||
void set_close_moving_current_threshold(float close_moving_current_threshold) {
|
||||
this->close_moving_current_threshold_ = close_moving_current_threshold;
|
||||
}
|
||||
void set_close_obstacle_current_threshold(float close_obstacle_current_threshold) {
|
||||
this->close_obstacle_current_threshold_ = close_obstacle_current_threshold;
|
||||
}
|
||||
void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; }
|
||||
|
||||
void set_max_duration(uint32_t max_duration) { this->max_duration_ = max_duration; }
|
||||
void set_obstacle_rollback(float obstacle_rollback) { this->obstacle_rollback_ = obstacle_rollback; }
|
||||
|
||||
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
|
||||
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
|
||||
|
||||
Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; }
|
||||
|
||||
cover::CoverTraits get_traits() override;
|
||||
|
||||
protected:
|
||||
void control(const cover::CoverCall &call) override;
|
||||
void stop_prev_trigger_();
|
||||
|
||||
bool is_at_target_() const;
|
||||
bool is_opening_() const;
|
||||
bool is_opening_blocked_() const;
|
||||
bool is_closing_() const;
|
||||
bool is_closing_blocked_() const;
|
||||
bool is_initial_delay_finished_() const;
|
||||
|
||||
void direction_idle_(float new_position = FLT_MAX);
|
||||
void start_direction_(cover::CoverOperation dir);
|
||||
|
||||
void recompute_position_();
|
||||
|
||||
Trigger<> *stop_trigger_{new Trigger<>()};
|
||||
|
||||
sensor::Sensor *open_sensor_{nullptr};
|
||||
Trigger<> *open_trigger_{new Trigger<>()};
|
||||
float open_moving_current_threshold_;
|
||||
float open_obstacle_current_threshold_{FLT_MAX};
|
||||
uint32_t open_duration_;
|
||||
|
||||
sensor::Sensor *close_sensor_{nullptr};
|
||||
Trigger<> *close_trigger_{new Trigger<>()};
|
||||
float close_moving_current_threshold_;
|
||||
float close_obstacle_current_threshold_{FLT_MAX};
|
||||
uint32_t close_duration_;
|
||||
|
||||
uint32_t max_duration_{UINT32_MAX};
|
||||
bool malfunction_detection_{true};
|
||||
Trigger<> *malfunction_trigger_{new Trigger<>()};
|
||||
uint32_t start_sensing_delay_;
|
||||
float obstacle_rollback_;
|
||||
|
||||
Trigger<> *prev_command_trigger_{nullptr};
|
||||
uint32_t last_recompute_time_{0};
|
||||
uint32_t start_dir_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
float target_position_{0};
|
||||
};
|
||||
|
||||
} // namespace current_based
|
||||
} // namespace esphome
|
|
@ -11,13 +11,17 @@ dallas_ns = cg.esphome_ns.namespace("dallas")
|
|||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
|
||||
ESPOneWire = dallas_ns.class_("ESPOneWire")
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
# pin_mode call logs in esp-idf, but InterruptLock is active -> crash
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
|
|
@ -46,5 +46,7 @@ async def to_code(config):
|
|||
if CONF_RESOLUTION in config:
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add(var.set_parent(hub))
|
||||
|
||||
cg.add(hub.register_sensor(var))
|
||||
await sensor.register_sensor(var, config)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@s1lvi0"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor", "binary_sensor"]
|
||||
|
||||
CONF_BMS_DALY_ID = "bms_daly_id"
|
||||
|
||||
daly_bms = cg.esphome_ns.namespace("daly_bms")
|
||||
DalyBmsComponent = daly_bms.class_(
|
||||
"DalyBmsComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(DalyBmsComponent)})
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.polling_component_schema("30s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
|
@ -0,0 +1,49 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
CONF_CHARGING_MOS_ENABLED = "charging_mos_enabled"
|
||||
CONF_DISCHARGING_MOS_ENABLED = "discharging_mos_enabled"
|
||||
|
||||
TYPES = [
|
||||
CONF_CHARGING_MOS_ENABLED,
|
||||
CONF_DISCHARGING_MOS_ENABLED,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_MOS_ENABLED
|
||||
): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_DISCHARGING_MOS_ENABLED
|
||||
): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(sens, conf)
|
||||
cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BMS_DALY_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
|
@ -0,0 +1,181 @@
|
|||
#include "daly_bms.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
|
||||
static const char *const TAG = "daly_bms";
|
||||
|
||||
static const uint8_t DALY_FRAME_SIZE = 13;
|
||||
static const uint8_t DALY_TEMPERATURE_OFFSET = 40;
|
||||
static const uint16_t DALY_CURRENT_OFFSET = 30000;
|
||||
|
||||
static const uint8_t DALY_REQUEST_BATTERY_LEVEL = 0x90;
|
||||
static const uint8_t DALY_REQUEST_MIN_MAX_VOLTAGE = 0x91;
|
||||
static const uint8_t DALY_REQUEST_MIN_MAX_TEMPERATURE = 0x92;
|
||||
static const uint8_t DALY_REQUEST_MOS = 0x93;
|
||||
static const uint8_t DALY_REQUEST_STATUS = 0x94;
|
||||
static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96;
|
||||
|
||||
void DalyBmsComponent::setup() {}
|
||||
|
||||
void DalyBmsComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Daly BMS:");
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
void DalyBmsComponent::update() {
|
||||
this->request_data_(DALY_REQUEST_BATTERY_LEVEL);
|
||||
this->request_data_(DALY_REQUEST_MIN_MAX_VOLTAGE);
|
||||
this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE);
|
||||
this->request_data_(DALY_REQUEST_MOS);
|
||||
this->request_data_(DALY_REQUEST_STATUS);
|
||||
this->request_data_(DALY_REQUEST_TEMPERATURE);
|
||||
|
||||
std::vector<uint8_t> get_battery_level_data;
|
||||
int available_data = this->available();
|
||||
if (available_data >= DALY_FRAME_SIZE) {
|
||||
get_battery_level_data.resize(available_data);
|
||||
this->read_array(get_battery_level_data.data(), available_data);
|
||||
this->decode_data_(get_battery_level_data);
|
||||
}
|
||||
}
|
||||
|
||||
float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void DalyBmsComponent::request_data_(uint8_t data_id) {
|
||||
uint8_t request_message[DALY_FRAME_SIZE];
|
||||
|
||||
request_message[0] = 0xA5; // Start Flag
|
||||
request_message[1] = 0x80; // Communication Module Address
|
||||
request_message[2] = data_id; // Data ID
|
||||
request_message[3] = 0x08; // Data Length (Fixed)
|
||||
request_message[4] = 0x00; // Empty Data
|
||||
request_message[5] = 0x00; // |
|
||||
request_message[6] = 0x00; // |
|
||||
request_message[7] = 0x00; // |
|
||||
request_message[8] = 0x00; // |
|
||||
request_message[9] = 0x00; // |
|
||||
request_message[10] = 0x00; // |
|
||||
request_message[11] = 0x00; // Empty Data
|
||||
request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] +
|
||||
request_message[3]); // Checksum (Lower byte of the other bytes sum)
|
||||
|
||||
this->write_array(request_message, sizeof(request_message));
|
||||
this->flush();
|
||||
}
|
||||
|
||||
void DalyBmsComponent::decode_data_(std::vector<uint8_t> data) {
|
||||
auto it = data.begin();
|
||||
|
||||
while ((it = std::find(it, data.end(), 0xA5)) != data.end()) {
|
||||
if (data.end() - it >= DALY_FRAME_SIZE && it[1] == 0x01) {
|
||||
uint8_t checksum;
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
sum += it[i];
|
||||
}
|
||||
checksum = sum;
|
||||
|
||||
if (checksum == it[12]) {
|
||||
switch (it[2]) {
|
||||
case DALY_REQUEST_BATTERY_LEVEL:
|
||||
if (this->voltage_sensor_) {
|
||||
this->voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 10);
|
||||
}
|
||||
if (this->current_sensor_) {
|
||||
this->current_sensor_->publish_state(((float) (encode_uint16(it[8], it[9]) - DALY_CURRENT_OFFSET) / 10));
|
||||
}
|
||||
if (this->battery_level_sensor_) {
|
||||
this->battery_level_sensor_->publish_state((float) encode_uint16(it[10], it[11]) / 10);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_MIN_MAX_VOLTAGE:
|
||||
if (this->max_cell_voltage_) {
|
||||
this->max_cell_voltage_->publish_state((float) encode_uint16(it[4], it[5]) / 1000);
|
||||
}
|
||||
if (this->max_cell_voltage_number_) {
|
||||
this->max_cell_voltage_number_->publish_state(it[6]);
|
||||
}
|
||||
if (this->min_cell_voltage_) {
|
||||
this->min_cell_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
|
||||
}
|
||||
if (this->min_cell_voltage_number_) {
|
||||
this->min_cell_voltage_number_->publish_state(it[9]);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_MIN_MAX_TEMPERATURE:
|
||||
if (this->max_temperature_) {
|
||||
this->max_temperature_->publish_state(it[4] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
if (this->max_temperature_probe_number_) {
|
||||
this->max_temperature_probe_number_->publish_state(it[5]);
|
||||
}
|
||||
if (this->min_temperature_) {
|
||||
this->min_temperature_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
if (this->min_temperature_probe_number_) {
|
||||
this->min_temperature_probe_number_->publish_state(it[7]);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_MOS:
|
||||
if (this->status_text_sensor_ != nullptr) {
|
||||
switch (it[4]) {
|
||||
case 0:
|
||||
this->status_text_sensor_->publish_state("Stationary");
|
||||
break;
|
||||
case 1:
|
||||
this->status_text_sensor_->publish_state("Charging");
|
||||
break;
|
||||
case 2:
|
||||
this->status_text_sensor_->publish_state("Discharging");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this->charging_mos_enabled_) {
|
||||
this->charging_mos_enabled_->publish_state(it[5]);
|
||||
}
|
||||
if (this->discharging_mos_enabled_) {
|
||||
this->discharging_mos_enabled_->publish_state(it[6]);
|
||||
}
|
||||
if (this->remaining_capacity_) {
|
||||
this->remaining_capacity_->publish_state((float) encode_uint32(it[8], it[9], it[10], it[11]) / 1000);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_STATUS:
|
||||
if (this->cells_number_) {
|
||||
this->cells_number_->publish_state(it[4]);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_TEMPERATURE:
|
||||
if (it[4] == 1) {
|
||||
if (this->temperature_1_sensor_) {
|
||||
this->temperature_1_sensor_->publish_state(it[5] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
if (this->temperature_2_sensor_) {
|
||||
this->temperature_2_sensor_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::advance(it, DALY_FRAME_SIZE);
|
||||
} else {
|
||||
std::advance(it, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace daly_bms
|
||||
} // namespace esphome
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
|
||||
class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
DalyBmsComponent() = default;
|
||||
|
||||
// SENSORS
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_battery_level_sensor(sensor::Sensor *battery_level_sensor) { battery_level_sensor_ = battery_level_sensor; }
|
||||
void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage) { max_cell_voltage_ = max_cell_voltage; }
|
||||
void set_max_cell_voltage_number_sensor(sensor::Sensor *max_cell_voltage_number) {
|
||||
max_cell_voltage_number_ = max_cell_voltage_number;
|
||||
}
|
||||
void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage) { min_cell_voltage_ = min_cell_voltage; }
|
||||
void set_min_cell_voltage_number_sensor(sensor::Sensor *min_cell_voltage_number) {
|
||||
min_cell_voltage_number_ = min_cell_voltage_number;
|
||||
}
|
||||
void set_max_temperature_sensor(sensor::Sensor *max_temperature) { max_temperature_ = max_temperature; }
|
||||
void set_max_temperature_probe_number_sensor(sensor::Sensor *max_temperature_probe_number) {
|
||||
max_temperature_probe_number_ = max_temperature_probe_number;
|
||||
}
|
||||
void set_min_temperature_sensor(sensor::Sensor *min_temperature) { min_temperature_ = min_temperature; }
|
||||
void set_min_temperature_probe_number_sensor(sensor::Sensor *min_temperature_probe_number) {
|
||||
min_temperature_probe_number_ = min_temperature_probe_number;
|
||||
}
|
||||
void set_remaining_capacity_sensor(sensor::Sensor *remaining_capacity) { remaining_capacity_ = remaining_capacity; }
|
||||
void set_cells_number_sensor(sensor::Sensor *cells_number) { cells_number_ = cells_number; }
|
||||
void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; }
|
||||
void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; }
|
||||
// TEXT_SENSORS
|
||||
void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; }
|
||||
// BINARY_SENSORS
|
||||
void set_charging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *charging_mos_enabled) {
|
||||
charging_mos_enabled_ = charging_mos_enabled;
|
||||
}
|
||||
void set_discharging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *discharging_mos_enabled) {
|
||||
discharging_mos_enabled_ = discharging_mos_enabled;
|
||||
}
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void request_data_(uint8_t data_id);
|
||||
void decode_data_(std::vector<uint8_t> data);
|
||||
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *battery_level_sensor_{nullptr};
|
||||
sensor::Sensor *max_cell_voltage_{nullptr};
|
||||
sensor::Sensor *max_cell_voltage_number_{nullptr};
|
||||
sensor::Sensor *min_cell_voltage_{nullptr};
|
||||
sensor::Sensor *min_cell_voltage_number_{nullptr};
|
||||
sensor::Sensor *max_temperature_{nullptr};
|
||||
sensor::Sensor *max_temperature_probe_number_{nullptr};
|
||||
sensor::Sensor *min_temperature_{nullptr};
|
||||
sensor::Sensor *min_temperature_probe_number_{nullptr};
|
||||
sensor::Sensor *remaining_capacity_{nullptr};
|
||||
sensor::Sensor *cells_number_{nullptr};
|
||||
sensor::Sensor *temperature_1_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_2_sensor_{nullptr};
|
||||
|
||||
text_sensor::TextSensor *status_text_sensor_{nullptr};
|
||||
|
||||
binary_sensor::BinarySensor *charging_mos_enabled_{nullptr};
|
||||
binary_sensor::BinarySensor *discharging_mos_enabled_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace daly_bms
|
||||
} // namespace esphome
|
|
@ -0,0 +1,192 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_PERCENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
ICON_FLASH,
|
||||
ICON_PERCENT,
|
||||
ICON_COUNTER,
|
||||
ICON_THERMOMETER,
|
||||
ICON_GAUGE,
|
||||
)
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
CONF_MAX_CELL_VOLTAGE = "max_cell_voltage"
|
||||
CONF_MAX_CELL_VOLTAGE_NUMBER = "max_cell_voltage_number"
|
||||
CONF_MIN_CELL_VOLTAGE = "min_cell_voltage"
|
||||
CONF_MIN_CELL_VOLTAGE_NUMBER = "min_cell_voltage_number"
|
||||
CONF_MAX_TEMPERATURE_PROBE_NUMBER = "max_temperature_probe_number"
|
||||
CONF_MIN_TEMPERATURE_PROBE_NUMBER = "min_temperature_probe_number"
|
||||
CONF_CELLS_NUMBER = "cells_number"
|
||||
|
||||
CONF_REMAINING_CAPACITY = "remaining_capacity"
|
||||
CONF_TEMPERATURE_1 = "temperature_1"
|
||||
CONF_TEMPERATURE_2 = "temperature_2"
|
||||
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
ICON_BATTERY_OUTLINE = "mdi:battery-outline"
|
||||
ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up"
|
||||
ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down"
|
||||
ICON_CAR_BATTERY = "mdi:car-battery"
|
||||
|
||||
UNIT_AMPERE_HOUR = "Ah"
|
||||
|
||||
TYPES = [
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_MAX_CELL_VOLTAGE,
|
||||
CONF_MAX_CELL_VOLTAGE_NUMBER,
|
||||
CONF_MIN_CELL_VOLTAGE,
|
||||
CONF_MIN_CELL_VOLTAGE_NUMBER,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MAX_TEMPERATURE_PROBE_NUMBER,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE_PROBE_NUMBER,
|
||||
CONF_CELLS_NUMBER,
|
||||
CONF_REMAINING_CAPACITY,
|
||||
CONF_TEMPERATURE_1,
|
||||
CONF_TEMPERATURE_2,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_FLASH,
|
||||
1,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE,
|
||||
ICON_CURRENT_DC,
|
||||
1,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_PERCENT,
|
||||
1,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_FLASH,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_FLASH,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER_CHEVRON_UP,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER_CHEVRON_DOWN,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema(
|
||||
UNIT_AMPERE_HOUR,
|
||||
ICON_GAUGE,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BMS_DALY_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
|
@ -0,0 +1,39 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
ICON_CAR_BATTERY = "mdi:car-battery"
|
||||
|
||||
TYPES = [
|
||||
CONF_STATUS,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BMS_DALY_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
|
@ -0,0 +1,45 @@
|
|||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
||||
# payload is in `esphomelib` mdns record, which only exists if api
|
||||
# is enabled
|
||||
DEPENDENCIES = ["api"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
|
||||
def validate_import_url(value):
|
||||
value = cv.string_strict(value)
|
||||
value = cv.Length(max=255)(value)
|
||||
# ignore result, only check if it's a valid shorthand
|
||||
validate_source_shorthand(value)
|
||||
return value
|
||||
|
||||
|
||||
CONF_PACKAGE_IMPORT_URL = "package_import_url"
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_DASHBOARD_IMPORT")
|
||||
cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
|
||||
|
||||
|
||||
def import_config(path: str, name: str, project_name: str, import_url: str) -> None:
|
||||
p = Path(path)
|
||||
|
||||
if p.exists():
|
||||
raise FileExistsError
|
||||
|
||||
config = {"substitutions": {"name": name}, "packages": {project_name: import_url}}
|
||||
p.write_text(dump(config), encoding="utf8")
|
|
@ -0,0 +1,12 @@
|
|||
#include "dashboard_import.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
static std::string g_package_import_url; // NOLINT
|
||||
|
||||
std::string get_package_import_url() { return g_package_import_url; }
|
||||
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
std::string get_package_import_url();
|
||||
void set_package_import_url(std::string url);
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
|
@ -104,10 +104,7 @@ void DebugComponent::dump_config() {
|
|||
|
||||
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
||||
|
||||
uint64_t chip_mac = 0LL;
|
||||
esp_efuse_mac_get_default((uint8_t *) (&chip_mac));
|
||||
std::string mac = uint64_to_string(chip_mac);
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str());
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str());
|
||||
|
||||
const char *reset_reason;
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
|
|
|
@ -79,28 +79,27 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
|||
int8_t i = 0;
|
||||
uint8_t data[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
this->pin_->digital_write(false);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (this->model_ == DHT_MODEL_DHT11) {
|
||||
delayMicroseconds(18000);
|
||||
} else if (this->model_ == DHT_MODEL_SI7021) {
|
||||
delayMicroseconds(500);
|
||||
this->pin_->digital_write(true);
|
||||
delayMicroseconds(40);
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
||||
delayMicroseconds(1000);
|
||||
} else {
|
||||
delayMicroseconds(800);
|
||||
}
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
|
||||
this->pin_->digital_write(false);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (this->model_ == DHT_MODEL_DHT11) {
|
||||
delayMicroseconds(18000);
|
||||
} else if (this->model_ == DHT_MODEL_SI7021) {
|
||||
delayMicroseconds(500);
|
||||
this->pin_->digital_write(true);
|
||||
delayMicroseconds(40);
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
||||
delayMicroseconds(1000);
|
||||
} else {
|
||||
delayMicroseconds(800);
|
||||
}
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
|
||||
// Host pull up 20-40us then DHT response 80us
|
||||
// Start waiting for initial rising edge at the center when we
|
||||
// expect the DHT response (30us+40us)
|
||||
|
|
|
@ -20,19 +20,22 @@ void Dsmr::loop() {
|
|||
}
|
||||
|
||||
void Dsmr::receive_telegram_() {
|
||||
while (available()) {
|
||||
int count = MAX_BYTES_PER_LOOP;
|
||||
while (available() && count-- > 0) {
|
||||
const char c = read();
|
||||
|
||||
if (c == '/') { // header: forward slash
|
||||
// Find a new telegram header, i.e. forward slash.
|
||||
if (c == '/') {
|
||||
ESP_LOGV(TAG, "Header found");
|
||||
header_found_ = true;
|
||||
footer_found_ = false;
|
||||
telegram_len_ = 0;
|
||||
}
|
||||
|
||||
if (!header_found_)
|
||||
continue;
|
||||
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { // Buffer overflow
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
|
||||
header_found_ = false;
|
||||
footer_found_ = false;
|
||||
ESP_LOGE(TAG, "Error: Message larger than buffer");
|
||||
|
@ -45,18 +48,22 @@ void Dsmr::receive_telegram_() {
|
|||
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
|
||||
telegram_len_--;
|
||||
|
||||
// Store the byte in the buffer.
|
||||
telegram_[telegram_len_] = c;
|
||||
telegram_len_++;
|
||||
if (c == '!') { // footer: exclamation mark
|
||||
|
||||
// Check for a footer, i.e. exlamation mark, followed by a hex checksum.
|
||||
if (c == '!') {
|
||||
ESP_LOGV(TAG, "Footer found");
|
||||
footer_found_ = true;
|
||||
} else {
|
||||
if (footer_found_ && c == 10) { // last \n after footer
|
||||
header_found_ = false;
|
||||
// Parse message
|
||||
if (parse_telegram())
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Check for the end of the hex checksum, i.e. a newline.
|
||||
if (footer_found_ && c == '\n') {
|
||||
header_found_ = false;
|
||||
// Parse the telegram and publish sensor values.
|
||||
if (parse_telegram())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace esphome {
|
|||
namespace dsmr {
|
||||
|
||||
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
|
||||
static constexpr uint32_t MAX_BYTES_PER_LOOP = 50;
|
||||
static constexpr uint32_t POLL_TIMEOUT = 1000;
|
||||
|
||||
using namespace ::dsmr::fields;
|
||||
|
|
|
@ -10,6 +10,8 @@ from esphome.const import (
|
|||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
CONF_ADVANCED,
|
||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
|
@ -34,6 +36,7 @@ from .gpio import esp32_pin_to_code # noqa
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["preferences"]
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
|
@ -229,6 +232,11 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
|||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
_esp_idf_check_versions,
|
||||
|
@ -268,6 +276,8 @@ async def to_code(config):
|
|||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
cg.add_platformio_option(
|
||||
|
@ -294,6 +304,12 @@ async def to_code(config):
|
|||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option(
|
||||
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
|
||||
|
|
|
@ -21,7 +21,7 @@ ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const {
|
|||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
uint8_t arduino_mode = DISABLED;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
|
|
|
@ -23,7 +23,7 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin {
|
|||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace esp32 {
|
|||
|
||||
static const char *const TAG = "esp32";
|
||||
|
||||
bool IDFInternalGPIOPin::isr_service_installed_ = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
struct ISRPinArg {
|
||||
gpio_num_t pin;
|
||||
|
@ -22,6 +22,77 @@ ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
|
|||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::setup() {
|
||||
pin_mode(flags_);
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode(flags);
|
||||
conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
}
|
||||
|
||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
|
||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
|
||||
gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
|
||||
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
|
||||
if (flags == gpio::FLAG_NONE) {
|
||||
return GPIO_MODE_DISABLE;
|
||||
} else if (flags == gpio::FLAG_INPUT) {
|
||||
return GPIO_MODE_INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return GPIO_MODE_OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_MODE_OUTPUT_OD;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT_OD;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT;
|
||||
} else {
|
||||
// unsupported
|
||||
return GPIO_MODE_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
idf_type = GPIO_INTR_ANYEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
||||
break;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
||||
break;
|
||||
}
|
||||
gpio_set_intr_type(pin_, idf_type);
|
||||
gpio_intr_enable(pin_);
|
||||
if (!isr_service_installed) {
|
||||
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "attach_interrupt(): call to gpio_install_isr_service() failed, error code: %d", res);
|
||||
return;
|
||||
}
|
||||
isr_service_installed = true;
|
||||
}
|
||||
gpio_isr_handler_add(pin_, func, arg);
|
||||
}
|
||||
|
||||
std::string IDFInternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_));
|
||||
|
|
|
@ -13,22 +13,10 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
|
|||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
void setup() override {
|
||||
pin_mode(flags_);
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
void pin_mode(gpio::Flags flags) override {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1 << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode_(flags);
|
||||
conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
}
|
||||
bool digital_read() override { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
void digital_write(bool value) override { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override { gpio_intr_disable(pin_); }
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
|
@ -36,58 +24,15 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
|
|||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
static gpio_mode_t flags_to_mode_(gpio::Flags flags) {
|
||||
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
|
||||
if (flags == gpio::FLAG_NONE) {
|
||||
return GPIO_MODE_DISABLE;
|
||||
} else if (flags == gpio::FLAG_INPUT) {
|
||||
return GPIO_MODE_INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return GPIO_MODE_OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_MODE_OUTPUT_OD;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT_OD;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT;
|
||||
} else {
|
||||
// unsupported
|
||||
return GPIO_MODE_DISABLE;
|
||||
}
|
||||
}
|
||||
void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override {
|
||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
idf_type = GPIO_INTR_ANYEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
||||
break;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
||||
break;
|
||||
}
|
||||
gpio_set_intr_type(pin_, idf_type);
|
||||
gpio_intr_enable(pin_);
|
||||
if (!isr_service_installed_) {
|
||||
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL5);
|
||||
isr_service_installed_ = true;
|
||||
}
|
||||
gpio_isr_handler_add(pin_, func, arg);
|
||||
}
|
||||
static gpio_mode_t flags_to_mode(gpio::Flags flags);
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
gpio_num_t pin_;
|
||||
bool inverted_;
|
||||
gpio_drive_cap_t drive_strength_;
|
||||
gpio::Flags flags_;
|
||||
static bool isr_service_installed_;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static bool isr_service_installed;
|
||||
};
|
||||
|
||||
} // namespace esp32
|
||||
|
|
|
@ -4,30 +4,53 @@
|
|||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <nvs_flash.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32.preferences";
|
||||
|
||||
struct NVSData {
|
||||
std::string key;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||
public:
|
||||
std::string key;
|
||||
uint32_t nvs_handle;
|
||||
bool save(const uint8_t *data, size_t len) override {
|
||||
esp_err_t err = nvs_set_blob(nvs_handle, key.c_str(), data, len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
err = nvs_commit(nvs_handle);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err));
|
||||
return false;
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
obj.data.assign(data, data + len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
NVSData save{};
|
||||
save.key = key;
|
||||
save.data.assign(data, data + len);
|
||||
s_pending_save.emplace_back(save);
|
||||
return true;
|
||||
}
|
||||
bool load(uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
if (obj.data.size() != len) {
|
||||
// size mismatch
|
||||
return false;
|
||||
}
|
||||
memcpy(data, obj.data.data(), len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t actual_len;
|
||||
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
|
||||
if (err != 0) {
|
||||
|
@ -82,6 +105,37 @@ class ESP32Preferences : public ESPPreferences {
|
|||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
|
||||
bool sync() override {
|
||||
if (s_pending_save.empty())
|
||||
return true;
|
||||
|
||||
ESP_LOGD(TAG, "Saving preferences to flash...");
|
||||
// goal try write all pending saves even if one fails
|
||||
bool any_failed = false;
|
||||
|
||||
// go through vector from back to front (makes erase easier/more efficient)
|
||||
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
|
||||
const auto &save = s_pending_save[i];
|
||||
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size());
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(),
|
||||
esp_err_to_name(err));
|
||||
any_failed = true;
|
||||
continue;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
}
|
||||
|
||||
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
|
||||
esp_err_t err = nvs_commit(nvs_handle);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
return !any_failed;
|
||||
}
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
|
|
|
@ -56,6 +56,12 @@ bool ESP32BLE::ble_setup_() {
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
|
@ -80,6 +86,7 @@ bool ESP32BLE::ble_setup_() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
void *peer_device;
|
||||
bool connected;
|
||||
|
@ -65,6 +66,7 @@ class ESP32BLE : public Component {
|
|||
BLEAdvertising *advertising_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern ESP32BLE *global_ble;
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
|
|
@ -28,33 +28,33 @@ namespace esp32_ble {
|
|||
|
||||
template<class T> class Queue {
|
||||
public:
|
||||
Queue() { m = xSemaphoreCreateMutex(); }
|
||||
Queue() { m_ = xSemaphoreCreateMutex(); }
|
||||
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return;
|
||||
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
|
||||
q.push(element);
|
||||
xSemaphoreGive(m);
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
T *element = nullptr;
|
||||
|
||||
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q.empty()) {
|
||||
element = q.front();
|
||||
q.pop();
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q_.empty()) {
|
||||
element = q_.front();
|
||||
q_.pop();
|
||||
}
|
||||
xSemaphoreGive(m);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::queue<T *> q;
|
||||
SemaphoreHandle_t m;
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
|
@ -105,11 +105,13 @@ class BLEEvent {
|
|||
};
|
||||
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
|
@ -117,6 +119,7 @@ class BLEEvent {
|
|||
uint8_t data[64];
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
|
@ -124,6 +127,7 @@ class BLEEvent {
|
|||
uint8_t data[64];
|
||||
} gatts;
|
||||
} event_;
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include <cstring>
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_beacon {
|
||||
|
||||
|
@ -70,6 +74,12 @@ void ESP32BLEBeacon::ble_setup() {
|
|||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
|
@ -94,6 +104,7 @@ void ESP32BLEBeacon::ble_setup() {
|
|||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
namespace esphome {
|
||||
namespace esp32_ble_beacon {
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
uint8_t flags[3];
|
||||
uint8_t length;
|
||||
|
@ -17,6 +18,7 @@ typedef struct {
|
|||
uint16_t beacon_type;
|
||||
} __attribute__((packed)) esp_ble_ibeacon_head_t;
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
uint8_t proximity_uuid[16];
|
||||
uint16_t major;
|
||||
|
@ -24,6 +26,7 @@ typedef struct {
|
|||
uint8_t measured_power;
|
||||
} __attribute__((packed)) esp_ble_ibeacon_vendor_t;
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
esp_ble_ibeacon_head_t ibeacon_head;
|
||||
esp_ble_ibeacon_vendor_t ibeacon_vendor;
|
||||
|
@ -50,6 +53,7 @@ class ESP32BLEBeacon : public Component {
|
|||
uint16_t minor_{};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern ESP32BLEBeacon *global_esp32_ble_beacon;
|
||||
|
||||
} // namespace esp32_ble_beacon
|
||||
|
|
|
@ -24,7 +24,7 @@ class BLEService;
|
|||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
BLECharacteristic(const ESPBTUUID uuid, uint32_t properties);
|
||||
BLECharacteristic(ESPBTUUID uuid, uint32_t properties);
|
||||
|
||||
void set_value(const uint8_t *data, size_t length);
|
||||
void set_value(std::vector<uint8_t> value);
|
||||
|
@ -49,7 +49,7 @@ class BLECharacteristic {
|
|||
void do_create(BLEService *service);
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
void on_write(const std::function<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = std::move(func); }
|
||||
void on_write(const std::function<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = func; }
|
||||
|
||||
void add_descriptor(BLEDescriptor *descriptor);
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ class BLEServer : public Component {
|
|||
} state_{INIT};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern BLEServer *global_ble_server;
|
||||
|
||||
} // namespace esp32_ble_server
|
||||
|
|
|
@ -50,29 +50,29 @@ void ESP32BLETracker::setup() {
|
|||
return;
|
||||
}
|
||||
|
||||
global_esp32_ble_tracker->start_scan(true);
|
||||
global_esp32_ble_tracker->start_scan_(true);
|
||||
}
|
||||
|
||||
void ESP32BLETracker::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
if (ble_event->type_)
|
||||
this->real_gattc_event_handler(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
else
|
||||
this->real_gap_event_handler(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
|
||||
bool connecting = false;
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->state() == ClientState::Connecting || client->state() == ClientState::Discovered)
|
||||
if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED)
|
||||
connecting = true;
|
||||
}
|
||||
if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
global_esp32_ble_tracker->start_scan(false);
|
||||
global_esp32_ble_tracker->start_scan_(false);
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
||||
|
@ -94,7 +94,7 @@ void ESP32BLETracker::loop() {
|
|||
for (auto *client : this->clients_)
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (client->state() == ClientState::Discovered) {
|
||||
if (client->state() == ClientState::DISCOVERED) {
|
||||
esp_ble_gap_stop_scanning();
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 10L / portTICK_PERIOD_MS)) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
|
@ -132,6 +132,12 @@ bool ESP32BLETracker::ble_setup() {
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
|
@ -156,6 +162,7 @@ bool ESP32BLETracker::ble_setup() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
|
@ -196,7 +203,7 @@ bool ESP32BLETracker::ble_setup() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::start_scan(bool first) {
|
||||
void ESP32BLETracker::start_scan_(bool first) {
|
||||
if (!xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
ESP_LOGW(TAG, "Cannot start scan!");
|
||||
return;
|
||||
|
@ -233,38 +240,38 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
|
|||
global_esp32_ble_tracker->ble_events_.push(gap_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLETracker::real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
global_esp32_ble_tracker->gap_scan_result(param->scan_rst);
|
||||
global_esp32_ble_tracker->gap_scan_result_(param->scan_rst);
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
global_esp32_ble_tracker->gap_scan_set_param_complete(param->scan_param_cmpl);
|
||||
global_esp32_ble_tracker->gap_scan_set_param_complete_(param->scan_param_cmpl);
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
global_esp32_ble_tracker->gap_scan_start_complete(param->scan_start_cmpl);
|
||||
global_esp32_ble_tracker->gap_scan_start_complete_(param->scan_start_cmpl);
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
global_esp32_ble_tracker->gap_scan_stop_complete(param->scan_stop_cmpl);
|
||||
global_esp32_ble_tracker->gap_scan_stop_complete_(param->scan_stop_cmpl);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
||||
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
||||
this->scan_set_param_failed_ = param.status;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
||||
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
||||
this->scan_start_failed_ = param.status;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
||||
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
|
||||
if (this->scan_result_index_ < 16) {
|
||||
|
@ -283,8 +290,8 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
|||
global_esp32_ble_tracker->ble_events_.push(gattc_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLETracker::real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
for (auto *client : global_esp32_ble_tracker->clients_) {
|
||||
client->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
|
@ -310,6 +317,63 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
|
|||
ret.uuid_.uuid.uuid128[i] = data[i];
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
ESPBTUUID ret;
|
||||
if (data.length() == 4) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||
ret.uuid_.uuid.uuid16 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 8) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||
ret.uuid_.uuid.uuid32 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
|
||||
// investigated (lack of time)
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16);
|
||||
} else if (data.length() == 36) {
|
||||
// If the length of the string is 36 bytes then we will assume it is a long hex string in
|
||||
// UUID format.
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
int n = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
if (data.c_str()[i] == '-')
|
||||
i++;
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = uuid.len;
|
||||
|
@ -380,8 +444,8 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() {
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
char sbuf[64];
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
|
|
|
@ -25,6 +25,8 @@ class ESPBTUUID {
|
|||
|
||||
static ESPBTUUID from_raw(const uint8_t *data);
|
||||
|
||||
static ESPBTUUID from_raw(const std::string &data);
|
||||
|
||||
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
|
||||
|
||||
ESPBTUUID as_128bit() const;
|
||||
|
@ -34,9 +36,9 @@ class ESPBTUUID {
|
|||
bool operator==(const ESPBTUUID &uuid) const;
|
||||
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||
|
||||
esp_bt_uuid_t get_uuid();
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
std::string to_string();
|
||||
std::string to_string() const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
|
@ -135,15 +137,15 @@ class ESPBTDeviceListener {
|
|||
|
||||
enum class ClientState {
|
||||
// Connection is idle, no device detected.
|
||||
Idle,
|
||||
IDLE,
|
||||
// Device advertisement found.
|
||||
Discovered,
|
||||
DISCOVERED,
|
||||
// Connection in progress.
|
||||
Connecting,
|
||||
CONNECTING,
|
||||
// Initial connection established.
|
||||
Connected,
|
||||
CONNECTED,
|
||||
// The client and sub-clients have completed setup.
|
||||
Established,
|
||||
ESTABLISHED,
|
||||
};
|
||||
|
||||
class ESPBTClient : public ESPBTDeviceListener {
|
||||
|
@ -185,23 +187,23 @@ class ESP32BLETracker : public Component {
|
|||
/// The FreeRTOS task managing the bluetooth interface.
|
||||
static bool ble_setup();
|
||||
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
||||
void start_scan(bool first);
|
||||
void start_scan_(bool first);
|
||||
/// Callback that will handle all GAP events and redistribute them to other callbacks.
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
void real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received.
|
||||
void gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||
void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received.
|
||||
void gap_scan_set_param_complete(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m);
|
||||
void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_START_COMPLETE_EVT` event is received.
|
||||
void gap_scan_start_complete(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m);
|
||||
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
|
||||
void gap_scan_stop_complete(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||
|
||||
int app_id_;
|
||||
/// Callback that will handle all GATTC events and redistribute them to other callbacks.
|
||||
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
|
||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||
std::vector<uint64_t> already_discovered_;
|
||||
|
@ -225,6 +227,7 @@ class ESP32BLETracker : public Component {
|
|||
Queue<BLEEvent> ble_events_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
extern ESP32BLETracker *global_esp32_ble_tracker;
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
|
|
|
@ -26,33 +26,33 @@ namespace esp32_ble_tracker {
|
|||
|
||||
template<class T> class Queue {
|
||||
public:
|
||||
Queue() { m = xSemaphoreCreateMutex(); }
|
||||
Queue() { m_ = xSemaphoreCreateMutex(); }
|
||||
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return;
|
||||
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
|
||||
q.push(element);
|
||||
xSemaphoreGive(m);
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
T *element = nullptr;
|
||||
|
||||
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q.empty()) {
|
||||
element = q.front();
|
||||
q.pop();
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q_.empty()) {
|
||||
element = q_.front();
|
||||
q_.pop();
|
||||
}
|
||||
xSemaphoreGive(m);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::queue<T *> q;
|
||||
SemaphoreHandle_t m;
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP and GATTC events are only queued, and get processed in the main loop().
|
||||
|
@ -87,12 +87,12 @@ class BLEEvent {
|
|||
};
|
||||
|
||||
union {
|
||||
struct gap_event {
|
||||
struct gap_event { // NOLINT(readability-identifier-naming)
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
struct gattc_event {
|
||||
struct gattc_event { // NOLINT(readability-identifier-naming)
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
|
|
|
@ -21,7 +21,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
|||
DEPENDENCIES = ["esp32", "api"]
|
||||
|
||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable)
|
||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
||||
ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize")
|
||||
FRAME_SIZES = {
|
||||
"160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue