Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-07-14 11:54:11 +02:00 committed by GitHub
commit 92e9231d94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 4019 additions and 294 deletions

View file

@ -17,10 +17,11 @@ esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @ncareau esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11 esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix esphome/components/am43/sensor/* @buxtronix
@ -31,6 +32,7 @@ esphome/components/api/* @OttoWinter
esphome/components/as7341/* @mrgnr esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter esphome/components/async_tcp/* @OttoWinter
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
@ -76,6 +78,7 @@ esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81 esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/* @jesserockz esphome/components/ektf2232/* @jesserockz
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
@ -102,6 +105,7 @@ esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/haier/* @paveldn esphome/components/haier/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
@ -201,6 +205,7 @@ esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @hwstar esphome/components/pca9554/* @hwstar
esphome/components/pcf85063/* @brogon esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984 esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie esphome/components/pm1006/* @habbie
@ -295,6 +300,7 @@ esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81 esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz esphome/components/touchscreen/* @jesserockz
esphome/components/tsl2591/* @wjcarpenter esphome/components/tsl2591/* @wjcarpenter
esphome/components/tt21100/* @kroimon
esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1 esphome/components/tuya/number/* @frankiboy1
@ -311,6 +317,7 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54 esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/whirlpool/* @glmnet esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb esphome/components/wiegand/* @ssieb
@ -322,3 +329,4 @@ esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97 esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68 esphome/components/xpt2046/* @nielsnl68 @numo68
esphome/components/zio_ultrasonic/* @kahrendt

View file

@ -32,7 +32,7 @@ from esphome.const import (
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent from esphome.helpers import indent, is_ip_address
from esphome.util import ( from esphome.util import (
run_external_command, run_external_command,
run_external_process, run_external_process,
@ -308,8 +308,10 @@ def upload_program(config, args, host):
password = ota_conf.get(CONF_PASSWORD, "") password = ota_conf.get(CONF_PASSWORD, "")
if ( if (
get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED] not is_ip_address(CORE.address)
) and CONF_MQTT in config: and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
):
from esphome import mqtt from esphome import mqtt
host = mqtt.get_esphome_device_ip( host = mqtt.get_esphome_device_ip(

View file

@ -58,6 +58,6 @@ async def to_code(config):
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -3,26 +3,31 @@ import esphome.config_validation as cv
from esphome.components import sensor, ble_client from esphome.components import sensor, ble_client
from esphome.const import ( from esphome.const import (
DEVICE_CLASS_HUMIDITY, CONF_BATTERY_VOLTAGE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_TVOC,
CONF_PRESSURE, CONF_PRESSURE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_TVOC,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PARTS_PER_BILLION, UNIT_PARTS_PER_BILLION,
ICON_RADIATOR, UNIT_PERCENT,
UNIT_VOLT,
) )
CODEOWNERS = ["@ncareau", "@jeromelaban"] CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base") airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
AirthingsWaveBase = airthings_wave_base_ns.class_( AirthingsWaveBase = airthings_wave_base_ns.class_(
"AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode "AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
@ -34,9 +39,9 @@ BASE_SCHEMA = (
{ {
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
), ),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
@ -52,11 +57,21 @@ BASE_SCHEMA = (
), ),
cv.Optional(CONF_TVOC): sensor.sensor_schema( cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION, unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(
CONF_BATTERY_UPDATE_INTERVAL,
default="24h",
): cv.update_interval,
} }
) )
.extend(cv.polling_component_schema("5min")) .extend(cv.polling_component_schema("5min"))
@ -69,15 +84,20 @@ async def wave_base_to_code(var, config):
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config: if config_humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(config_humidity)
cg.add(var.set_humidity(sens)) cg.add(var.set_humidity(sens))
if CONF_TEMPERATURE in config: if config_temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(config_temperature)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config: if config_pressure := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(config[CONF_PRESSURE]) sens = await sensor.new_sensor(config_pressure)
cg.add(var.set_pressure(sens)) cg.add(var.set_pressure(sens))
if CONF_TVOC in config: if config_tvoc := config.get(CONF_TVOC):
sens = await sensor.new_sensor(config[CONF_TVOC]) sens = await sensor.new_sensor(config_tvoc)
cg.add(var.set_tvoc(sens)) cg.add(var.set_tvoc(sens))
if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config_battery_voltage)
cg.add(var.set_battery_voltage(sens))
if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL):
cg.add(var.set_battery_update_interval(config_battery_update_interval))

View file

@ -1,5 +1,8 @@
#include "airthings_wave_base.h" #include "airthings_wave_base.h"
// All information related to reading battery information came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
@ -18,22 +21,26 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
} }
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
this->handle_ = 0;
this->acp_handle_ = 0;
this->cccd_handle_ = 0;
ESP_LOGW(TAG, "Disconnected!"); ESP_LOGW(TAG, "Disconnected!");
break; break;
} }
case ESP_GATTC_SEARCH_CMPL_EVT: { case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0; if (this->request_read_values_()) {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); if (!this->read_battery_next_update_) {
if (chr == nullptr) { this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), } else {
this->sensors_data_characteristic_uuid_.to_string().c_str()); // delay setting node_state to ESTABLISHED until confirmation of the notify registration
break; this->request_battery_();
}
} }
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
this->request_read_values_(); // ensure that the client will be disconnected even if no responses arrive
this->set_response_timeout_();
break; break;
} }
@ -50,6 +57,20 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->get_conn_id())
break;
if (param->notify.handle == this->acp_handle_) {
this->read_battery_(param->notify.value, param->notify.value_len);
}
break;
}
default: default:
break; break;
} }
@ -58,7 +79,7 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
void AirthingsWaveBase::update() { void AirthingsWaveBase::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (this->node_state != espbt::ClientState::ESTABLISHED) {
if (!this->parent()->enabled) { if (!this->parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device"); ESP_LOGW(TAG, "Reconnecting to device");
this->parent()->set_enabled(true); this->parent()->set_enabled(true);
@ -69,12 +90,119 @@ void AirthingsWaveBase::update() {
} }
} }
void AirthingsWaveBase::request_read_values_() { bool AirthingsWaveBase::request_read_values_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
return false;
}
this->handle_ = chr->handle;
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
return false;
} }
this->response_pending_();
return true;
}
bool AirthingsWaveBase::request_battery_() {
uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
uint8_t cccd_value[2] = {1, 0};
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
if (descr == nullptr) {
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto reg_status =
esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
if (reg_status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
return false;
}
this->acp_handle_ = chr->handle;
this->cccd_handle_ = descr->handle;
auto descr_status =
esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (descr_status) {
ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
return false;
}
auto chr_status =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
&battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (chr_status) {
ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
return false;
}
this->response_pending_();
return true;
}
void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
auto *value = (AccessControlPointResponse *) (&raw_value[2]);
if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
if (this->battery_voltage_ != nullptr) {
float voltage = value->battery / 1000.0f;
this->battery_voltage_->publish_state(voltage);
}
// read the battery again at the configured update interval
if (this->battery_update_interval_ != this->update_interval_) {
this->read_battery_next_update_ = false;
this->set_timeout("battery", this->battery_update_interval_,
[this]() { this->read_battery_next_update_ = true; });
}
}
this->response_received_();
}
void AirthingsWaveBase::response_pending_() {
this->responses_pending_++;
this->set_response_timeout_();
}
void AirthingsWaveBase::response_received_() {
if (--this->responses_pending_ == 0) {
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
this->parent()->set_enabled(false);
}
}
void AirthingsWaveBase::set_response_timeout_() {
this->set_timeout("response_timeout", 30 * 1000, [this]() {
this->responses_pending_ = 1;
this->response_received_();
});
} }
} // namespace airthings_wave_base } // namespace airthings_wave_base

View file

@ -1,5 +1,8 @@
#pragma once #pragma once
// All information related to reading battery levels came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gattc_api.h> #include <esp_gattc_api.h>
@ -14,6 +17,11 @@
namespace esphome { namespace esphome {
namespace airthings_wave_base { namespace airthings_wave_base {
namespace espbt = esphome::esp32_ble_tracker;
static const uint8_t ACCESS_CONTROL_POINT_COMMAND = 0x6d;
static const auto CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = espbt::ESPBTUUID::from_uint16(0x2902);
class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode { class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode {
public: public:
AirthingsWaveBase() = default; AirthingsWaveBase() = default;
@ -27,21 +35,53 @@ class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientN
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
void set_battery_voltage(sensor::Sensor *voltage) {
battery_voltage_ = voltage;
this->read_battery_next_update_ = true;
}
void set_battery_update_interval(uint32_t interval) { battery_update_interval_ = interval; }
protected: protected:
bool is_valid_voc_value_(uint16_t voc); bool is_valid_voc_value_(uint16_t voc);
virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0; bool request_read_values_();
void request_read_values_(); virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr}; sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
uint16_t handle_; uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_; espbt::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; espbt::ESPBTUUID sensors_data_characteristic_uuid_;
uint16_t acp_handle_{0};
uint16_t cccd_handle_{0};
espbt::ESPBTUUID access_control_point_characteristic_uuid_;
uint8_t responses_pending_{0};
void response_pending_();
void response_received_();
void set_response_timeout_();
// default to *not* reading battery voltage from the device; the
// set_* function for the battery sensor will set this to 'true'
bool read_battery_next_update_{false};
bool request_battery_();
void read_battery_(uint8_t *raw_value, uint16_t value_len);
uint32_t battery_update_interval_{};
struct AccessControlPointResponse {
uint32_t unused1;
uint8_t unused2;
uint8_t illuminance;
uint8_t unused3[10];
uint16_t unused4[4];
uint16_t battery;
uint16_t unused5;
};
}; };
} // namespace airthings_wave_base } // namespace airthings_wave_base

View file

@ -26,12 +26,9 @@ void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(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).
this->parent()->set_enabled(false);
} }
this->response_received_();
} }
void AirthingsWaveMini::dump_config() { void AirthingsWaveMini::dump_config() {
@ -42,11 +39,14 @@ void AirthingsWaveMini::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
} }
AirthingsWaveMini::AirthingsWaveMini() { AirthingsWaveMini::AirthingsWaveMini() {
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
} }
} // namespace airthings_wave_mini } // namespace airthings_wave_mini

View file

@ -7,8 +7,11 @@
namespace esphome { namespace esphome {
namespace airthings_wave_mini { namespace airthings_wave_mini {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase { class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
public: public:
@ -17,7 +20,7 @@ class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
void dump_config() override; void dump_config() override;
protected: protected:
void read_sensors(uint8_t *value, uint16_t value_len) override; void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
struct WaveMiniReadings { struct WaveMiniReadings {
uint16_t unused01; uint16_t unused01;

View file

@ -43,15 +43,12 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(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).
this->parent()->set_enabled(false);
} else { } else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
} }
} }
this->response_received_();
} }
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
@ -66,6 +63,7 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
@ -73,8 +71,10 @@ void AirthingsWavePlus::dump_config() {
} }
AirthingsWavePlus::AirthingsWavePlus() { AirthingsWavePlus::AirthingsWavePlus() {
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
} }
} // namespace airthings_wave_plus } // namespace airthings_wave_plus

View file

@ -7,8 +7,11 @@
namespace esphome { namespace esphome {
namespace airthings_wave_plus { namespace airthings_wave_plus {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
public: public:
@ -24,7 +27,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
bool is_valid_radon_value_(uint16_t radon); bool is_valid_radon_value_(uint16_t radon);
bool is_valid_co2_value_(uint16_t co2); bool is_valid_co2_value_(uint16_t co2);
void read_sensors(uint8_t *value, uint16_t value_len) override; void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr};

View file

@ -53,12 +53,12 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await airthings_wave_base.wave_base_to_code(var, config) await airthings_wave_base.wave_base_to_code(var, config)
if CONF_RADON in config: if config_radon := config.get(CONF_RADON):
sens = await sensor.new_sensor(config[CONF_RADON]) sens = await sensor.new_sensor(config_radon)
cg.add(var.set_radon(sens)) cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config: if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) sens = await sensor.new_sensor(config_radon_long_term)
cg.add(var.set_radon_long_term(sens)) cg.add(var.set_radon_long_term(sens))
if CONF_CO2 in config: if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config[CONF_CO2]) sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens)) cg.add(var.set_co2(sens))

View file

@ -0,0 +1 @@
CODEOWNERS = ["@jan-hofmeier"]

View file

@ -0,0 +1,189 @@
#include "alpha3.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <lwip/sockets.h> //gives ntohl
#ifdef USE_ESP32
namespace esphome {
namespace alpha3 {
static const char *const TAG = "alpha3";
void Alpha3::dump_config() {
ESP_LOGCONFIG(TAG, "ALPHA3");
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Head", this->head_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
}
void Alpha3::setup() {}
void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor) {
if (sensor == nullptr)
return;
// we need to handle cases where a value is split over two packets
const int16_t value_length = 4; // 32bit float
// offset inside current response packet
auto rel_offset = value_offset - response_offset;
if (rel_offset <= -value_length)
return; // aready passed the value completly
if (rel_offset >= length)
return; // value not in this packet
auto start_offset = std::max(0, rel_offset);
auto end_offset = std::min((int16_t) (rel_offset + value_length), length);
auto copy_length = end_offset - start_offset;
auto buffer_offset = std::max(-rel_offset, 0);
std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length);
if (rel_offset + value_length <= length) {
// we have the whole value
void *buffer = this->buffer_; // to prevent warnings when casting the pointer
*((int32_t *) buffer) = ntohl(*((int32_t *) buffer)); // values are big endian
float fvalue = *((float *) buffer);
sensor->publish_state(fvalue * factor);
}
}
bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH);
}
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
if (this->response_offset_ >= this->response_length_) {
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
if (length < GENI_RESPONSE_HEADER_LENGTH) {
ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
return;
}
if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
response[0], response[1], response[2], response[3], response[4]);
return;
}
this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2; // maybe 2 byte checksum
this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH;
std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH);
}
auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor,
float factor) {
this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor);
};
if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
} else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F);
} else {
ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1],
this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5],
this->response_type_[6], this->response_type_[7]);
}
this->response_offset_ += length;
}
void Alpha3::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: {
this->response_offset_ = 0;
this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
break;
}
case ESP_GATTC_CONNECT_EVT: {
if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0)
return;
auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT);
if (ret) {
ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret);
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->node_state = espbt::ClientState::IDLE;
if (this->flow_sensor_ != nullptr)
this->flow_sensor_->publish_state(NAN);
if (this->head_sensor_ != nullptr)
this->head_sensor_->publish_state(NAN);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(NAN);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->speed_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->voltage_sensor_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
break;
}
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
this->geni_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle == this->geni_handle_) {
this->handle_geni_response_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
void Alpha3::send_request_(uint8_t *request, size_t len) {
auto status =
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
void Alpha3::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) {
uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31};
this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head));
delay(25); // need to wait between requests
}
if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr ||
this->voltage_sensor_ != nullptr) {
uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205};
this->send_request_(geni_request_power, sizeof(geni_request_power));
delay(25); // need to wait between requests
}
}
} // namespace alpha3
} // namespace esphome
#endif

View file

@ -0,0 +1,73 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24};
static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0;
static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4;
static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37};
static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0;
static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4;
static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8;
static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() 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 dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; }
protected:
sensor::Sensor *flow_sensor_{nullptr};
sensor::Sensor *head_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *speed_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
uint16_t geni_handle_;
int16_t response_length_;
int16_t response_offset_;
uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH];
uint8_t buffer_[4];
void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor);
void handle_geni_response_(const uint8_t *response, uint16_t length);
void send_request_(uint8_t *request, size_t len);
bool is_current_response_type_(const uint8_t *response_type);
};
} // namespace alpha3
} // namespace esphome
#endif

View file

@ -0,0 +1,85 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_CURRENT,
CONF_FLOW,
CONF_HEAD,
CONF_POWER,
CONF_SPEED,
CONF_VOLTAGE,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_METER,
UNIT_CUBIC_METER_PER_HOUR,
UNIT_REVOLUTIONS_PER_MINUTE,
)
alpha3_ns = cg.esphome_ns.namespace("alpha3")
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Alpha3),
cv.Optional(CONF_FLOW): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR,
accuracy_decimals=2,
),
cv.Optional(CONF_HEAD): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
accuracy_decimals=2,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
accuracy_decimals=2,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("15s"))
)
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_FLOW in config:
sens = await sensor.new_sensor(config[CONF_FLOW])
cg.add(var.set_flow_sensor(sens))
if CONF_HEAD in config:
sens = await sensor.new_sensor(config[CONF_HEAD])
cg.add(var.set_head_sensor(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(var.set_power_sensor(sens))
if CONF_CURRENT in config:
sens = await sensor.new_sensor(config[CONF_CURRENT])
cg.add(var.set_current_sensor(sens))
if CONF_SPEED in config:
sens = await sensor.new_sensor(config[CONF_SPEED])
cg.add(var.set_speed_sensor(sens))
if CONF_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(sens))

View file

@ -0,0 +1 @@
CODEOWNERS = ["@danieltwagner"]

View file

@ -0,0 +1,235 @@
#include "atm90e26.h"
#include "atm90e26_reg.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e26 {
static const char *const TAG = "atm90e26";
void ATM90E26Component::update() {
if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) {
this->status_set_warning();
return;
}
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(this->get_line_voltage_());
}
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(this->get_line_current_());
}
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(this->get_active_power_());
}
if (this->reactive_power_sensor_ != nullptr) {
this->reactive_power_sensor_->publish_state(this->get_reactive_power_());
}
if (this->power_factor_sensor_ != nullptr) {
this->power_factor_sensor_->publish_state(this->get_power_factor_());
}
if (this->forward_active_energy_sensor_ != nullptr) {
this->forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_());
}
if (this->reverse_active_energy_sensor_ != nullptr) {
this->reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_());
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
}
this->status_clear_warning();
}
void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
mmode |= (gain_pga_ & 0x7) << 13;
mmode |= (n_line_gain_ & 0x3) << 11;
this->write16_(ATM90E26_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
this->write16_(ATM90E26_REGISTER_FUNCEN,
0x0030); // Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0
uint16_t read = this->read16_(ATM90E26_REGISTER_LASTDATA);
if (read != 0x0030) {
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC, check SPI settings: %d", read);
this->mark_failed();
return;
}
// TODO: 100 * <nominal voltage, e.g. 230> * sqrt(2) * <fraction of nominal, e.g. 0.9> / (4 * gain_voltage/32768)
this->write16_(ATM90E26_REGISTER_SAGTH, 0x17DD); // Voltage sag threshhold 0x1F2F
// Set metering calibration values
this->write16_(ATM90E26_REGISTER_CALSTART, 0x5678); // CAL Metering calibration startup command
// Configure
this->write16_(ATM90E26_REGISTER_MMODE, mmode); // Metering Mode Configuration (see above)
this->write16_(ATM90E26_REGISTER_PLCONSTH, (pl_const_ >> 16)); // PL Constant MSB
this->write16_(ATM90E26_REGISTER_PLCONSTL, pl_const_ & 0xFFFF); // PL Constant LSB
// Calibrate this to be 1 pulse per Wh
this->write16_(ATM90E26_REGISTER_LGAIN, gain_metering_); // L Line Calibration Gain (active power metering)
this->write16_(ATM90E26_REGISTER_LPHI, 0x0000); // L Line Calibration Angle
this->write16_(ATM90E26_REGISTER_NGAIN, 0x0000); // N Line Calibration Gain
this->write16_(ATM90E26_REGISTER_NPHI, 0x0000); // N Line Calibration Angle
this->write16_(ATM90E26_REGISTER_PSTARTTH, 0x08BD); // Active Startup Power Threshold (default) = 2237
this->write16_(ATM90E26_REGISTER_PNOLTH, 0x0000); // Active No-Load Power Threshold
this->write16_(ATM90E26_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold (default) = 2796
this->write16_(ATM90E26_REGISTER_QNOLTH, 0x0000); // Reactive No-Load Power Threshold
// Compute Checksum for the registers we set above
// low byte = sum of all bytes
uint16_t cs =
((mmode >> 8) + (mmode & 0xFF) + (pl_const_ >> 24) + ((pl_const_ >> 16) & 0xFF) + ((pl_const_ >> 8) & 0xFF) +
(pl_const_ & 0xFF) + (gain_metering_ >> 8) + (gain_metering_ & 0xFF) + 0x08 + 0xBD + 0x0A + 0xEC) &
0xFF;
// high byte = XOR of all bytes
cs |= ((mmode >> 8) ^ (mmode & 0xFF) ^ (pl_const_ >> 24) ^ ((pl_const_ >> 16) & 0xFF) ^ ((pl_const_ >> 8) & 0xFF) ^
(pl_const_ & 0xFF) ^ (gain_metering_ >> 8) ^ (gain_metering_ & 0xFF) ^ 0x08 ^ 0xBD ^ 0x0A ^ 0xEC)
<< 8;
this->write16_(ATM90E26_REGISTER_CS1, cs);
ESP_LOGVV(TAG, "Set CS1 to: 0x%04X", cs);
// Set measurement calibration values
this->write16_(ATM90E26_REGISTER_ADJSTART, 0x5678); // Measurement calibration startup command, registers 31-3A
this->write16_(ATM90E26_REGISTER_UGAIN, gain_voltage_); // Voltage RMS gain
this->write16_(ATM90E26_REGISTER_IGAINL, gain_ct_); // L line current RMS gain
this->write16_(ATM90E26_REGISTER_IGAINN, 0x7530); // N Line Current RMS Gain
this->write16_(ATM90E26_REGISTER_UOFFSET, 0x0000); // Voltage Offset
this->write16_(ATM90E26_REGISTER_IOFFSETL, 0x0000); // L Line Current Offset
this->write16_(ATM90E26_REGISTER_IOFFSETN, 0x0000); // N Line Current Offse
this->write16_(ATM90E26_REGISTER_POFFSETL, 0x0000); // L Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETL, 0x0000); // L Line Reactive Power Offset
this->write16_(ATM90E26_REGISTER_POFFSETN, 0x0000); // N Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETN, 0x0000); // N Line Reactive Power Offset
// Compute Checksum for the registers we set above
cs = ((gain_voltage_ >> 8) + (gain_voltage_ & 0xFF) + (gain_ct_ >> 8) + (gain_ct_ & 0xFF) + 0x75 + 0x30) & 0xFF;
cs |= ((gain_voltage_ >> 8) ^ (gain_voltage_ & 0xFF) ^ (gain_ct_ >> 8) ^ (gain_ct_ & 0xFF) ^ 0x75 ^ 0x30) << 8;
this->write16_(ATM90E26_REGISTER_CS2, cs);
ESP_LOGVV(TAG, "Set CS2 to: 0x%04X", cs);
this->write16_(ATM90E26_REGISTER_CALSTART,
0x8765); // Checks correctness of 21-2B registers and starts normal metering if ok
this->write16_(ATM90E26_REGISTER_ADJSTART,
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok
uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
if (sys_status & 0xC000) { // Checksum 1 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS1));
this->mark_failed();
}
if (sys_status & 0x3000) { // Checksum 2 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS2 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS2));
this->mark_failed();
}
}
void ATM90E26Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E26:");
LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_);
LOG_SENSOR(" ", "Current A", this->current_sensor_);
LOG_SENSOR(" ", "Power A", this->power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
}
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
uint8_t data[2];
uint16_t output;
this->enable();
delayMicroseconds(4);
this->write_byte(a_register | 0x80);
delayMicroseconds(4);
this->read_array(data, 2);
this->disable();
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
return output;
}
void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
this->enable();
delayMicroseconds(4);
this->write_byte(a_register & 0x7F);
delayMicroseconds(4);
this->write_byte((val >> 8) & 0xFF);
this->write_byte(val & 0xFF);
this->disable();
}
float ATM90E26Component::get_line_current_() {
uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
return current / 1000.0f;
}
float ATM90E26Component::get_line_voltage_() {
uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
return voltage / 100.0f;
}
float ATM90E26Component::get_active_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_reactive_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_power_factor_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
if (val & 0x8000) {
return -(val & 0x7FF) / 1000.0f;
} else {
return val / 1000.0f;
}
}
float ATM90E26Component::get_forward_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
this->cumulative_forward_active_energy_ += val;
} else {
this->cumulative_forward_active_energy_ = val;
}
// The register holds thenths of pulses, we want to output Wh
return (this->cumulative_forward_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_reverse_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
this->cumulative_reverse_active_energy_ += val;
} else {
this->cumulative_reverse_active_energy_ = val;
}
return (this->cumulative_reverse_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
return freq / 100.0f;
}
} // namespace atm90e26
} // namespace esphome

View file

@ -0,0 +1,72 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace atm90e26 {
class ATM90E26Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }
void set_current_sensor(sensor::Sensor *obj) { this->current_sensor_ = obj; }
void set_power_sensor(sensor::Sensor *obj) { this->power_sensor_ = obj; }
void set_reactive_power_sensor(sensor::Sensor *obj) { this->reactive_power_sensor_ = obj; }
void set_forward_active_energy_sensor(sensor::Sensor *obj) { this->forward_active_energy_sensor_ = obj; }
void set_reverse_active_energy_sensor(sensor::Sensor *obj) { this->reverse_active_energy_sensor_ = obj; }
void set_power_factor_sensor(sensor::Sensor *obj) { this->power_factor_sensor_ = obj; }
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
void set_line_freq(int freq) { line_freq_ = freq; }
void set_meter_constant(float val) { meter_constant_ = val; }
void set_pl_const(uint32_t pl_const) { pl_const_ = pl_const; }
void set_gain_metering(uint16_t gain) { this->gain_metering_ = gain; }
void set_gain_voltage(uint16_t gain) { this->gain_voltage_ = gain; }
void set_gain_ct(uint16_t gain) { this->gain_ct_ = gain; }
void set_gain_pga(uint16_t gain) { gain_pga_ = gain; }
void set_n_line_gain(uint16_t gain) { n_line_gain_ = gain; }
protected:
uint16_t read16_(uint8_t a_register);
int read32_(uint8_t addr_h, uint8_t addr_l);
void write16_(uint8_t a_register, uint16_t val);
float get_line_voltage_();
float get_line_current_();
float get_active_power_();
float get_reactive_power_();
float get_power_factor_();
float get_forward_active_energy_();
float get_reverse_active_energy_();
float get_frequency_();
float get_chip_temperature_();
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
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};
uint16_t gain_metering_{7481};
uint16_t gain_voltage_{26400};
uint16_t gain_ct_{31251};
uint16_t gain_pga_{0x4};
uint16_t n_line_gain_{0x2};
int line_freq_{60};
float meter_constant_{3200.0f};
uint32_t pl_const_{1429876};
};
} // namespace atm90e26
} // namespace esphome

View file

@ -0,0 +1,70 @@
#pragma once
namespace esphome {
namespace atm90e26 {
/* Status and Special Register */
static const uint8_t ATM90E26_REGISTER_SOFTRESET = 0x00; // Software Reset
static const uint8_t ATM90E26_REGISTER_SYSSTATUS = 0x01; // System Status
static const uint8_t ATM90E26_REGISTER_FUNCEN = 0x02; // Function Enable
static const uint8_t ATM90E26_REGISTER_SAGTH = 0x03; // Voltage Sag Threshold
static const uint8_t ATM90E26_REGISTER_SMALLPMOD = 0x04; // Small-Power Mode
static const uint8_t ATM90E26_REGISTER_LASTDATA = 0x06; // Last Read/Write SPI/UART Value
/* Metering Calibration and Configuration Register */
static const uint8_t ATM90E26_REGISTER_LSB = 0x08; // RMS/Power 16-bit LSB
static const uint8_t ATM90E26_REGISTER_CALSTART = 0x20; // Calibration Start Command
static const uint8_t ATM90E26_REGISTER_PLCONSTH = 0x21; // High Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_PLCONSTL = 0x22; // Low Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_LGAIN = 0x23; // L Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_LPHI = 0x24; // L Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_NGAIN = 0x25; // N Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_NPHI = 0x26; // N Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_PSTARTTH = 0x27; // Active Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_PNOLTH = 0x28; // Active No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_QSTARTTH = 0x29; // Reactive Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_QNOLTH = 0x2A; // Reactive No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_MMODE = 0x2B; // Metering Mode Configuration
static const uint8_t ATM90E26_REGISTER_CS1 = 0x2C; // Checksum 1
/* Measurement Calibration Register */
static const uint8_t ATM90E26_REGISTER_ADJSTART = 0x30; // Measurement Calibration Start Command
static const uint8_t ATM90E26_REGISTER_UGAIN = 0x31; // Voltage RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINL = 0x32; // L Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINN = 0x33; // N Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_UOFFSET = 0x34; // Voltage Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETL = 0x35; // L Line Current Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETN = 0x36; // N Line Current Offse
static const uint8_t ATM90E26_REGISTER_POFFSETL = 0x37; // L Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETL = 0x38; // L Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_POFFSETN = 0x39; // N Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETN = 0x3A; // N Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_CS2 = 0x3B; // Checksum 2
/* Energy Register */
static const uint8_t ATM90E26_REGISTER_APENERGY = 0x40; // Forward Active Energy
static const uint8_t ATM90E26_REGISTER_ANENERGY = 0x41; // Reverse Active Energy
static const uint8_t ATM90E26_REGISTER_ATENERGY = 0x42; // Absolute Active Energy
static const uint8_t ATM90E26_REGISTER_RPENERGY = 0x43; // Forward (Inductive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RNENERG = 0x44; // Reverse (Capacitive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RTENERGY = 0x45; // Absolute Reactive Energy
static const uint8_t ATM90E26_REGISTER_ENSTATUS = 0x46; // Metering Status
/* Measurement Register */
static const uint8_t ATM90E26_REGISTER_IRMS = 0x48; // L Line Current RMS
static const uint8_t ATM90E26_REGISTER_URMS = 0x49; // Voltage RMS
static const uint8_t ATM90E26_REGISTER_PMEAN = 0x4A; // L Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN = 0x4B; // L Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_FREQ = 0x4C; // Voltage Frequency
static const uint8_t ATM90E26_REGISTER_POWERF = 0x4D; // L Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE = 0x4E; // Phase Angle between Voltage and L Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN = 0x4F; // L Line Mean Apparent Power
static const uint8_t ATM90E26_REGISTER_IRMS2 = 0x68; // N Line Current rms
static const uint8_t ATM90E26_REGISTER_PMEAN2 = 0x6A; // N Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN2 = 0x6B; // N Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_POWERF2 = 0x6D; // N Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE2 = 0x6E; // Phase Angle between Voltage and N Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN2 = 0x6F; // N Line Mean Apparent Power
} // namespace atm90e26
} // namespace esphome

View file

@ -0,0 +1,157 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
)
CONF_LINE_FREQUENCY = "line_frequency"
CONF_METER_CONSTANT = "meter_constant"
CONF_PL_CONST = "pl_const"
CONF_GAIN_PGA = "gain_pga"
CONF_GAIN_METERING = "gain_metering"
CONF_GAIN_VOLTAGE = "gain_voltage"
CONF_GAIN_CT = "gain_ct"
LINE_FREQS = {
"50HZ": 50,
"60HZ": 60,
}
PGA_GAINS = {
"1X": 0x4,
"4X": 0x0,
"8X": 0x1,
"16X": 0x2,
"24X": 0x3,
}
atm90e26_ns = cg.esphome_ns.namespace("atm90e26")
ATM90E26Component = atm90e26_ns.class_(
"ATM90E26Component", cg.PollingComponent, spi.SPIDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ATM90E26Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Required(CONF_METER_CONSTANT): cv.positive_float,
cv.Optional(CONF_PL_CONST, default=1429876): cv.uint32_t,
cv.Optional(CONF_GAIN_METERING, default=7481): cv.uint16_t,
cv.Optional(CONF_GAIN_VOLTAGE, default=26400): cv.int_range(
min=0, max=32767
),
cv.Optional(CONF_GAIN_CT, default=31251): cv.uint16_t,
cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(spi.spi_device_schema())
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
if CONF_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
sens = await sensor.new_sensor(config[CONF_CURRENT])
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(var.set_power_sensor(sens))
if CONF_REACTIVE_POWER in config:
sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER])
cg.add(var.set_reactive_power_sensor(sens))
if CONF_POWER_FACTOR in config:
sens = await sensor.new_sensor(config[CONF_POWER_FACTOR])
cg.add(var.set_power_factor_sensor(sens))
if CONF_FORWARD_ACTIVE_ENERGY in config:
sens = await sensor.new_sensor(config[CONF_FORWARD_ACTIVE_ENERGY])
cg.add(var.set_forward_active_energy_sensor(sens))
if CONF_REVERSE_ACTIVE_ENERGY in config:
sens = await sensor.new_sensor(config[CONF_REVERSE_ACTIVE_ENERGY])
cg.add(var.set_reverse_active_energy_sensor(sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
cg.add(var.set_freq_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))
cg.add(var.set_pl_const(config[CONF_PL_CONST]))
cg.add(var.set_gain_metering(config[CONF_GAIN_METERING]))
cg.add(var.set_gain_voltage(config[CONF_GAIN_VOLTAGE]))
cg.add(var.set_gain_ct(config[CONF_GAIN_CT]))
cg.add(var.set_gain_pga(config[CONF_GAIN_PGA]))

View file

@ -359,6 +359,18 @@ def validate_multi_click_timing(value):
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
def validate_click_timing(value):
for v in value:
min_length = v.get(CONF_MIN_LENGTH)
max_length = v.get(CONF_MAX_LENGTH)
if max_length < min_length:
raise cv.Invalid(
f"Max length ({max_length}) must be larger than min length ({min_length})."
)
return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_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.GenerateID(): cv.declare_id(BinarySensor),
@ -378,27 +390,33 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
} }
), ),
cv.Optional(CONF_ON_CLICK): automation.validate_automation( cv.Optional(CONF_ON_CLICK): cv.All(
{ automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), {
cv.Optional( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
CONF_MIN_LENGTH, default="50ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MIN_LENGTH, default="50ms"
cv.Optional( ): cv.positive_time_period_milliseconds,
CONF_MAX_LENGTH, default="350ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MAX_LENGTH, default="350ms"
} ): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
), ),
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
{ automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), {
cv.Optional( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
CONF_MIN_LENGTH, default="50ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MIN_LENGTH, default="50ms"
cv.Optional( ): cv.positive_time_period_milliseconds,
CONF_MAX_LENGTH, default="350ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MAX_LENGTH, default="350ms"
} ): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
), ),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{ {

View file

@ -275,6 +275,10 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
return ESP_OK; return ESP_OK;
} }
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
return this->proxy_->get_advertisement_parser_type();
}
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

View file

@ -14,6 +14,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle); esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);

View file

@ -198,6 +198,12 @@ void BluetoothProxy::loop() {
} }
} }
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
if (this->raw_advertisements_)
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS;
}
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) { BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() == address) if (connection->get_address() == address)
@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
} }
this->api_connection_ = api_connection; this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
} }
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) { void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@ -444,6 +451,7 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti
} }
this->api_connection_ = nullptr; this->api_connection_ = nullptr;
this->raw_advertisements_ = false; this->raw_advertisements_ = false;
this->parent_->recalculate_advertisement_parser_types();
} }
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {

View file

@ -51,6 +51,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override; bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) { void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection); this->connections_.push_back(connection);

View file

@ -6,8 +6,10 @@ from esphome.const import (
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_PRESSURE, CONF_PRESSURE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
@ -17,8 +19,6 @@ from esphome.const import (
UNIT_PERCENT, UNIT_PERCENT,
ICON_GAS_CYLINDER, ICON_GAS_CYLINDER,
ICON_GAUGE, ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
) )
from . import ( from . import (
BME680BSECComponent, BME680BSECComponent,
@ -35,7 +35,6 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ" UNIT_IAQ = "IAQ"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
TYPES = [ TYPES = [
CONF_TEMPERATURE, CONF_TEMPERATURE,
@ -53,7 +52,6 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@ -62,16 +60,14 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema( cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL, unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
).extend( ).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
), ),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@ -97,14 +93,14 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
} }

View file

@ -30,7 +30,7 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
if (use_extended_id) { if (use_extended_id) {
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} else { } else {
ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); ESP_LOGD(TAG, "send standard id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} }
if (size > CAN_MAX_DATA_LENGTH) if (size > CAN_MAX_DATA_LENGTH)
size = CAN_MAX_DATA_LENGTH; size = CAN_MAX_DATA_LENGTH;

View file

@ -21,7 +21,6 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
cv.only_on(["esp32", "esp8266"]), cv.only_on(["esp32", "esp8266"]),
) )
@ -34,8 +33,9 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL") cg.add_define("USE_CAPTIVE_PORTAL")
if CORE.is_esp32: if CORE.using_arduino:
cg.add_library("DNSServer", None) if CORE.is_esp32:
cg.add_library("WiFi", None) cg.add_library("DNSServer", None)
if CORE.is_esp8266: cg.add_library("WiFi", None)
cg.add_library("DNSServer", None) if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View file

@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "captive_portal.h" #include "captive_portal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
@ -46,10 +44,12 @@ void CaptivePortal::start() {
this->base_->add_ota_handler(); this->base_->add_ota_handler();
} }
#ifdef USE_ARDUINO
this->dns_server_ = make_unique<DNSServer>(); this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", (uint32_t) ip); this->dns_server_->start(53, "*", (uint32_t) ip);
#endif
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
@ -67,7 +67,7 @@ void CaptivePortal::start() {
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == "/") { if (req->url() == "/") {
AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
req->send(response); req->send(response);
return; return;
@ -91,5 +91,3 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
} // namespace captive_portal } // namespace captive_portal
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,9 +1,9 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include <memory> #include <memory>
#ifdef USE_ARDUINO
#include <DNSServer.h> #include <DNSServer.h>
#endif
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
@ -18,18 +18,22 @@ class CaptivePortal : public AsyncWebHandler, public Component {
CaptivePortal(web_server_base::WebServerBase *base); CaptivePortal(web_server_base::WebServerBase *base);
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
#ifdef USE_ARDUINO
void loop() override { void loop() override {
if (this->dns_server_ != nullptr) if (this->dns_server_ != nullptr)
this->dns_server_->processNextRequest(); this->dns_server_->processNextRequest();
} }
#endif
float get_setup_priority() const override; float get_setup_priority() const override;
void start(); void start();
bool is_active() const { return this->active_; } bool is_active() const { return this->active_; }
void end() { void end() {
this->active_ = false; this->active_ = false;
this->base_->deinit(); this->base_->deinit();
#ifdef USE_ARDUINO
this->dns_server_->stop(); this->dns_server_->stop();
this->dns_server_ = nullptr; this->dns_server_ = nullptr;
#endif
} }
bool canHandle(AsyncWebServerRequest *request) override { bool canHandle(AsyncWebServerRequest *request) override {
@ -58,12 +62,12 @@ class CaptivePortal : public AsyncWebHandler, public Component {
web_server_base::WebServerBase *base_; web_server_base::WebServerBase *base_;
bool initialized_{false}; bool initialized_{false};
bool active_{false}; bool active_{false};
#ifdef USE_ARDUINO
std::unique_ptr<DNSServer> dns_server_{nullptr}; std::unique_ptr<DNSServer> dns_server_{nullptr};
#endif
}; };
extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace captive_portal } // namespace captive_portal
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -18,10 +18,11 @@ from esphome.core import coroutine_with_priority
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
display_ns = cg.esphome_ns.namespace("display") display_ns = cg.esphome_ns.namespace("display")
Display = display_ns.class_("Display")
DisplayBuffer = display_ns.class_("DisplayBuffer") DisplayBuffer = display_ns.class_("DisplayBuffer")
DisplayPage = display_ns.class_("DisplayPage") DisplayPage = display_ns.class_("DisplayPage")
DisplayPagePtr = DisplayPage.operator("ptr") DisplayPagePtr = DisplayPage.operator("ptr")
DisplayBufferRef = DisplayBuffer.operator("ref") DisplayRef = Display.operator("ref")
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action) DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
DisplayPageShowNextAction = display_ns.class_( DisplayPageShowNextAction = display_ns.class_(
"DisplayPageShowNextAction", automation.Action "DisplayPageShowNextAction", automation.Action
@ -96,7 +97,7 @@ async def setup_display_core_(var, config):
pages = [] pages = []
for conf in config[CONF_PAGES]: for conf in config[CONF_PAGES]:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
conf[CONF_LAMBDA], [(DisplayBufferRef, "it")], return_type=cg.void conf[CONF_LAMBDA], [(DisplayRef, "it")], return_type=cg.void
) )
page = cg.new_Pvariable(conf[CONF_ID], lambda_) page = cg.new_Pvariable(conf[CONF_ID], lambda_)
pages.append(page) pages.append(page)

View file

@ -133,12 +133,10 @@ enum DisplayRotation {
}; };
class Display; class Display;
class DisplayBuffer;
class DisplayPage; class DisplayPage;
class DisplayOnPageChangeTrigger; class DisplayOnPageChangeTrigger;
using display_writer_t = std::function<void(Display &)>; using display_writer_t = std::function<void(Display &)>;
using display_buffer_writer_t = std::function<void(DisplayBuffer &)>;
#define LOG_DISPLAY(prefix, type, obj) \ #define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \ if ((obj) != nullptr) { \
@ -411,10 +409,6 @@ class Display {
/// Internal method to set the display writer lambda. /// Internal method to set the display writer lambda.
void set_writer(display_writer_t &&writer); void set_writer(display_writer_t &&writer);
void set_writer(const display_buffer_writer_t &writer) {
// Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef`
this->set_writer([writer](Display &display) { return writer((display::DisplayBuffer &) display); });
}
void show_page(DisplayPage *page); void show_page(DisplayPage *page);
void show_next_page(); void show_next_page();
@ -499,9 +493,6 @@ class Display {
class DisplayPage { class DisplayPage {
public: public:
DisplayPage(display_writer_t writer); DisplayPage(display_writer_t writer);
// Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef`
DisplayPage(const display_buffer_writer_t &writer)
: DisplayPage([writer](Display &display) { return writer((display::DisplayBuffer &) display); }) {}
void show(); void show();
void show_next(); void show_next();
void show_prev(); void show_prev();

View file

@ -0,0 +1 @@
CODEOWNERS = ["@dudanov"]

View file

@ -0,0 +1,103 @@
#include "duty_time_sensor.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace duty_time_sensor {
static const char *const TAG = "duty_time_sensor";
void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) {
sensor->add_on_state_callback([this](bool state) { this->process_state_(state); });
}
void DutyTimeSensor::start() {
if (!this->last_state_)
this->process_state_(true);
}
void DutyTimeSensor::stop() {
if (this->last_state_)
this->process_state_(false);
}
void DutyTimeSensor::update() {
if (this->last_state_)
this->process_state_(true);
}
void DutyTimeSensor::loop() {
if (this->func_ == nullptr)
return;
const bool state = this->func_();
if (state != this->last_state_)
this->process_state_(state);
}
void DutyTimeSensor::setup() {
uint32_t seconds = 0;
if (this->restore_) {
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash());
this->pref_.load(&seconds);
}
this->set_value_(seconds);
}
void DutyTimeSensor::set_value_(const uint32_t sec) {
this->last_time_ = 0;
if (this->last_state_)
this->last_time_ = millis(); // last time with 0 ms correction
this->publish_and_save_(sec, 0);
}
void DutyTimeSensor::process_state_(const bool state) {
const uint32_t now = millis();
if (this->last_state_) {
// update or falling edge
const uint32_t tm = now - this->last_time_;
const uint32_t ms = tm % 1000;
this->publish_and_save_(this->total_sec_ + tm / 1000, ms);
this->last_time_ = now - ms; // store time with ms correction
if (!state) {
// falling edge
this->last_time_ = ms; // temporary store ms correction only
this->last_state_ = false;
if (this->last_duty_time_sensor_ != nullptr) {
const uint32_t turn_on_ms = now - this->edge_time_;
this->last_duty_time_sensor_->publish_state(turn_on_ms * 1e-3f);
}
}
} else if (state) {
// rising edge
this->last_time_ = now - this->last_time_; // store time with ms correction
this->edge_time_ = now; // store turn-on start time
this->last_state_ = true;
}
}
void DutyTimeSensor::publish_and_save_(const uint32_t sec, const uint32_t ms) {
this->total_sec_ = sec;
this->publish_state(sec + ms * 1e-3f);
if (this->restore_)
this->pref_.save(&sec);
}
void DutyTimeSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Duty Time:");
ESP_LOGCONFIG(TAG, " Update Interval: %dms", this->get_update_interval());
ESP_LOGCONFIG(TAG, " Restore: %s", ONOFF(this->restore_));
LOG_SENSOR(" ", "Duty Time Sensor:", this);
LOG_SENSOR(" ", "Last Duty Time Sensor:", this->last_duty_time_sensor_);
}
} // namespace duty_time_sensor
} // namespace esphome

View file

@ -0,0 +1,88 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace duty_time_sensor {
class DutyTimeSensor : public sensor::Sensor, public PollingComponent {
public:
void setup() override;
void update() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void start();
void stop();
bool is_running() const { return this->last_state_; }
void reset() { this->set_value_(0); }
void set_lambda(std::function<bool()> &&func) { this->func_ = func; }
void set_sensor(binary_sensor::BinarySensor *sensor);
void set_last_duty_time_sensor(sensor::Sensor *sensor) { this->last_duty_time_sensor_ = sensor; }
void set_restore(bool restore) { this->restore_ = restore; }
protected:
void set_value_(uint32_t sec);
void process_state_(bool state);
void publish_and_save_(uint32_t sec, uint32_t ms);
std::function<bool()> func_{nullptr};
sensor::Sensor *last_duty_time_sensor_{nullptr};
ESPPreferenceObject pref_;
uint32_t total_sec_;
uint32_t last_time_;
uint32_t edge_time_;
bool last_state_{false};
bool restore_;
};
template<typename... Ts> class StartAction : public Action<Ts...> {
public:
explicit StartAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->start(); }
protected:
DutyTimeSensor *parent_;
};
template<typename... Ts> class StopAction : public Action<Ts...> {
public:
explicit StopAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->stop(); }
protected:
DutyTimeSensor *parent_;
};
template<typename... Ts> class ResetAction : public Action<Ts...> {
public:
explicit ResetAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->reset(); }
protected:
DutyTimeSensor *parent_;
};
template<typename... Ts> class RunningCondition : public Condition<Ts...> {
public:
explicit RunningCondition(DutyTimeSensor *parent, bool state) : parent_(parent), state_(state) {}
bool check(Ts... x) override { return this->parent_->is_running() == this->state_; }
protected:
DutyTimeSensor *parent_;
bool state_;
};
} // namespace duty_time_sensor
} // namespace esphome

View file

@ -0,0 +1,121 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.automation import (
Action,
Condition,
maybe_simple_id,
register_action,
register_condition,
)
from esphome.components import binary_sensor, sensor
from esphome.const import (
CONF_ID,
CONF_SENSOR,
CONF_RESTORE,
CONF_LAMBDA,
UNIT_SECOND,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
DEVICE_CLASS_DURATION,
ENTITY_CATEGORY_DIAGNOSTIC,
)
CONF_LAST_TIME = "last_time"
duty_time_sensor_ns = cg.esphome_ns.namespace("duty_time_sensor")
DutyTimeSensor = duty_time_sensor_ns.class_(
"DutyTimeSensor", sensor.Sensor, cg.PollingComponent
)
StartAction = duty_time_sensor_ns.class_("StartAction", Action)
StopAction = duty_time_sensor_ns.class_("StopAction", Action)
ResetAction = duty_time_sensor_ns.class_("ResetAction", Action)
SetAction = duty_time_sensor_ns.class_("SetAction", Action)
RunningCondition = duty_time_sensor_ns.class_("RunningCondition", Condition)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
DutyTimeSensor,
unit_of_measurement=UNIT_SECOND,
icon="mdi:timer-play-outline",
accuracy_decimals=3,
state_class=STATE_CLASS_TOTAL_INCREASING,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
)
.extend(
{
cv.Optional(CONF_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_LAMBDA): cv.lambda_,
cv.Optional(CONF_RESTORE, default=False): cv.boolean,
cv.Optional(CONF_LAST_TIME): sensor.sensor_schema(
unit_of_measurement=UNIT_SECOND,
icon="mdi:timer-marker-outline",
accuracy_decimals=3,
state_class=STATE_CLASS_TOTAL,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
.extend(cv.polling_component_schema("60s")),
cv.has_at_most_one_key(CONF_SENSOR, CONF_LAMBDA),
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
cg.add(var.set_restore(config[CONF_RESTORE]))
if CONF_SENSOR in config:
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_sensor(sens))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.bool_)
cg.add(var.set_lambda(lambda_))
if CONF_LAST_TIME in config:
sens = await sensor.new_sensor(config[CONF_LAST_TIME])
cg.add(var.set_last_duty_time_sensor(sens))
# AUTOMATIONS
DUTY_TIME_ID_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(DutyTimeSensor),
}
)
@register_action("sensor.duty_time.start", StartAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_start_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@register_action("sensor.duty_time.stop", StopAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_stop_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@register_action("sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_reset_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@register_condition(
"sensor.duty_time.is_running", RunningCondition, DUTY_TIME_ID_SCHEMA
)
async def duty_time_is_running_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@register_condition(
"sensor.duty_time.is_not_running", RunningCondition, DUTY_TIME_ID_SCHEMA
)
async def duty_time_is_not_running_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)

View file

@ -107,16 +107,16 @@ void ESP32BLETracker::loop() {
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
} }
bool bulk_parsed = false; if (this->raw_advertisements_) {
for (auto *listener : this->listeners_) {
for (auto *listener : this->listeners_) { listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_); }
} for (auto *client : this->clients_) {
for (auto *client : this->clients_) { client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_); }
} }
if (!bulk_parsed) { if (this->parse_advertisements_) {
for (size_t i = 0; i < index; i++) { for (size_t i = 0; i < index; i++) {
ESPBTDevice device; ESPBTDevice device;
device.parse_scan_rst(this->scan_result_buffer_[i]); device.parse_scan_rst(this->scan_result_buffer_[i]);
@ -284,6 +284,32 @@ void ESP32BLETracker::end_of_scan_() {
void ESP32BLETracker::register_client(ESPBTClient *client) { void ESP32BLETracker::register_client(ESPBTClient *client) {
client->app_id = ++this->app_id_; client->app_id = ++this->app_id_;
this->clients_.push_back(client); this->clients_.push_back(client);
this->recalculate_advertisement_parser_types();
}
void ESP32BLETracker::register_listener(ESPBTDeviceListener *listener) {
listener->set_parent(this);
this->listeners_.push_back(listener);
this->recalculate_advertisement_parser_types();
}
void ESP32BLETracker::recalculate_advertisement_parser_types() {
this->raw_advertisements_ = false;
this->parse_advertisements_ = false;
for (auto *listener : this->listeners_) {
if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
this->parse_advertisements_ = true;
} else {
this->raw_advertisements_ = true;
}
}
for (auto *client : this->clients_) {
if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) {
this->parse_advertisements_ = true;
} else {
this->raw_advertisements_ = true;
}
}
} }
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {

View file

@ -27,6 +27,11 @@ using namespace esp32_ble;
using adv_data_t = std::vector<uint8_t>; using adv_data_t = std::vector<uint8_t>;
enum AdvertisementParserType {
PARSED_ADVERTISEMENTS,
RAW_ADVERTISEMENTS,
};
struct ServiceData { struct ServiceData {
ESPBTUUID uuid; ESPBTUUID uuid;
adv_data_t data; adv_data_t data;
@ -116,6 +121,9 @@ class ESPBTDeviceListener {
virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
return false; return false;
}; };
virtual AdvertisementParserType get_advertisement_parser_type() {
return AdvertisementParserType::PARSED_ADVERTISEMENTS;
};
void set_parent(ESP32BLETracker *parent) { parent_ = parent; } void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
protected: protected:
@ -184,12 +192,9 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
void loop() override; void loop() override;
void register_listener(ESPBTDeviceListener *listener) { void register_listener(ESPBTDeviceListener *listener);
listener->set_parent(this);
this->listeners_.push_back(listener);
}
void register_client(ESPBTClient *client); void register_client(ESPBTClient *client);
void recalculate_advertisement_parser_types();
void print_bt_device_info(const ESPBTDevice &device); void print_bt_device_info(const ESPBTDevice &device);
@ -231,6 +236,8 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
bool scan_continuous_; bool scan_continuous_;
bool scan_active_; bool scan_active_;
bool scanner_idle_; bool scanner_idle_;
bool raw_advertisements_{false};
bool parse_advertisements_{false};
SemaphoreHandle_t scan_result_lock_; SemaphoreHandle_t scan_result_lock_;
SemaphoreHandle_t scan_end_lock_; SemaphoreHandle_t scan_end_lock_;
size_t scan_result_index_{0}; size_t scan_result_index_{0};

View file

@ -35,6 +35,7 @@ ETHERNET_TYPES = {
"IP101": EthernetType.ETHERNET_TYPE_IP101, "IP101": EthernetType.ETHERNET_TYPE_IP101,
"JL1101": EthernetType.ETHERNET_TYPE_JL1101, "JL1101": EthernetType.ETHERNET_TYPE_JL1101,
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
} }
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")

View file

@ -84,7 +84,8 @@ void EthernetComponent::setup() {
this->phy_ = esp_eth_phy_new_jl1101(&phy_config); this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
break; break;
} }
case ETHERNET_TYPE_KSZ8081: { case ETHERNET_TYPE_KSZ8081:
case ETHERNET_TYPE_KSZ8081RNA: {
#if ESP_IDF_VERSION_MAJOR >= 5 #if ESP_IDF_VERSION_MAJOR >= 5
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config); this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
#else #else
@ -102,6 +103,12 @@ void EthernetComponent::setup() {
this->eth_handle_ = nullptr; this->eth_handle_ = nullptr;
err = esp_eth_driver_install(&eth_config, &this->eth_handle_); err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
ESPHL_ERROR_CHECK(err, "ETH driver install error"); ESPHL_ERROR_CHECK(err, "ETH driver install error");
if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) {
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
this->ksz8081_set_clock_reference_(mac);
}
/* attach Ethernet driver to TCP/IP stack */ /* attach Ethernet driver to TCP/IP stack */
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
ESPHL_ERROR_CHECK(err, "ETH netif attach error"); ESPHL_ERROR_CHECK(err, "ETH netif attach error");
@ -184,6 +191,10 @@ void EthernetComponent::dump_config() {
eth_type = "KSZ8081"; eth_type = "KSZ8081";
break; break;
case ETHERNET_TYPE_KSZ8081RNA:
eth_type = "KSZ8081RNA";
break;
default: default:
eth_type = "Unknown"; eth_type = "Unknown";
break; break;
@ -385,6 +396,37 @@ bool EthernetComponent::powerdown() {
return true; return true;
} }
void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
#define KSZ80XX_PC2R_REG_ADDR (0x1F)
esp_err_t err;
uint32_t phy_control_2;
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
* KSZ8081RNA:
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII 25 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII 50 MHz clock mode.
* KSZ8081RND:
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII 50 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII 25 MHz clock mode.
*/
if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
phy_control_2 |= 1 << 7;
err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2);
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
}
#undef KSZ80XX_PC2R_REG_ADDR
}
} // namespace ethernet } // namespace ethernet
} // namespace esphome } // namespace esphome

View file

@ -21,6 +21,7 @@ enum EthernetType {
ETHERNET_TYPE_IP101, ETHERNET_TYPE_IP101,
ETHERNET_TYPE_JL1101, ETHERNET_TYPE_JL1101,
ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081,
ETHERNET_TYPE_KSZ8081RNA,
}; };
struct ManualIP { struct ManualIP {
@ -67,6 +68,8 @@ class EthernetComponent : public Component {
void start_connect_(); void start_connect_();
void dump_connect_params_(); void dump_connect_params_();
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
std::string use_address_; std::string use_address_;
uint8_t phy_addr_{0}; uint8_t phy_addr_{0};

View file

@ -3,6 +3,7 @@ from pathlib import Path
import hashlib import hashlib
import os import os
import re import re
from packaging import version
import requests import requests
@ -66,13 +67,18 @@ def validate_pillow_installed(value):
except ImportError as err: except ImportError as err:
raise cv.Invalid( raise cv.Invalid(
"Please install the pillow python package to use this feature. " "Please install the pillow python package to use this feature. "
"(pip install pillow)" '(pip install pillow">4.0.0,<10.0.0")'
) from err ) from err
if PIL.__version__[0] < "4": if version.parse(PIL.__version__) < version.parse("4.0.0"):
raise cv.Invalid( raise cv.Invalid(
"Please update your pillow installation to at least 4.0.x. " "Please update your pillow installation to at least 4.0.x. "
"(pip install -U pillow)" '(pip install pillow">4.0.0,<10.0.0")'
)
if version.parse(PIL.__version__) >= version.parse("10.0.0"):
raise cv.Invalid(
"Please downgrade your pillow installation to below 10.0.0. "
'(pip install pillow">4.0.0,<10.0.0")'
) )
return value return value

View file

@ -0,0 +1,152 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import i2c
from esphome.const import (
CONF_ID,
CONF_CHANNEL,
CONF_SPEED,
CONF_DIRECTION,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@max246"]
grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng")
GROVE_TB6612FNG = grove_tb6612fng_ns.class_(
"GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice
)
GROVETB6612FNGMotorRunAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorRunAction", automation.Action
)
GROVETB6612FNGMotorBrakeAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorBrakeAction", automation.Action
)
GROVETB6612FNGMotorStopAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorStopAction", automation.Action
)
GROVETB6612FNGMotorStandbyAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorStandbyAction", automation.Action
)
GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorNoStandbyAction", automation.Action
)
DIRECTION_TYPE = {
"FORWARD": 1,
"BACKWARD": 2,
}
CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(GROVE_TB6612FNG),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x14))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
@automation.register_action(
"grove_tb6612fng.run",
GROVETB6612FNGMotorRunAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
cv.Required(CONF_SPEED): cv.templatable(cv.int_range(min=0, max=255)),
cv.Required(CONF_DIRECTION): cv.enum(DIRECTION_TYPE, upper=True),
}
),
)
async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16)
template_speed = (
template_speed if config[CONF_DIRECTION] == "FORWARD" else -template_speed
)
cg.add(var.set_channel(template_channel))
cg.add(var.set_speed(template_speed))
return var
@automation.register_action(
"grove_tb6612fng.break",
GROVETB6612FNGMotorBrakeAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
}
),
)
async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
cg.add(var.set_channel(template_channel))
return var
@automation.register_action(
"grove_tb6612fng.stop",
GROVETB6612FNGMotorStopAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)),
}
),
)
async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
cg.add(var.set_channel(template_channel))
return var
@automation.register_action(
"grove_tb6612fng.standby",
GROVETB6612FNGMotorStandbyAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
}
),
)
async def grove_tb6612fng_standby_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"grove_tb6612fng.no_standby",
GROVETB6612FNGMotorNoStandbyAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
}
),
)
async def grove_tb6612fng_no_standby_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View file

@ -0,0 +1,171 @@
#include "grove_tb6612fng.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace grove_tb6612fng {
static const char *const TAG = "GroveMotorDriveTB6612FNG";
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE = 0x00;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STOP = 0x01;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_CW = 0x02;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_CCW = 0x03;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STANDBY = 0x04;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_NOT_STANDBY = 0x05;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN = 0x06;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_STOP = 0x07;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_KEEP_RUN = 0x08;
static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_SET_ADDR = 0x11;
void GroveMotorDriveTB6612FNG::dump_config() {
ESP_LOGCONFIG(TAG, "GroveMotorDriveTB6612FNG:");
LOG_I2C_DEVICE(this);
}
void GroveMotorDriveTB6612FNG::setup() {
ESP_LOGCONFIG(TAG, "Setting up Grove Motor Drive TB6612FNG ...");
if (!this->standby()) {
this->mark_failed();
return;
}
}
bool GroveMotorDriveTB6612FNG::standby() {
uint8_t status = 0;
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STANDBY, &status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set standby failed!");
this->status_set_warning();
return false;
}
return true;
}
bool GroveMotorDriveTB6612FNG::not_standby() {
uint8_t status = 0;
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_NOT_STANDBY, &status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set not standby failed!");
this->status_set_warning();
return false;
}
return true;
}
void GroveMotorDriveTB6612FNG::set_i2c_addr(uint8_t addr) {
if (addr == 0x00 || addr >= 0x80) {
return;
}
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_SET_ADDR, &addr, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set new i2c address failed!");
this->status_set_warning();
return;
}
this->set_i2c_address(addr);
}
void GroveMotorDriveTB6612FNG::dc_motor_run(uint8_t channel, int16_t speed) {
speed = clamp<int16_t>(speed, -255, 255);
buffer_[0] = channel;
if (speed >= 0) {
buffer_[1] = speed;
} else {
buffer_[1] = (uint8_t) (-speed);
}
if (speed >= 0) {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_CW, buffer_, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Run motor failed!");
this->status_set_warning();
return;
}
} else {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_CCW, buffer_, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Run motor failed!");
this->status_set_warning();
return;
}
}
}
void GroveMotorDriveTB6612FNG::dc_motor_brake(uint8_t channel) {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE, &channel, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Break motor failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::dc_motor_stop(uint8_t channel) {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STOP, &channel, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Stop dc motor failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::stepper_run(StepperModeTypeT mode, int16_t steps, uint16_t rpm) {
uint8_t cw = 0;
// 0.1ms_per_step
uint16_t ms_per_step = 0;
if (steps > 0) {
cw = 1;
}
// stop
else if (steps == 0) {
this->stepper_stop();
return;
} else if (steps == INT16_MIN) {
steps = INT16_MAX;
} else {
steps = -steps;
}
rpm = clamp<uint16_t>(rpm, 1, 300);
ms_per_step = (uint16_t) (3000.0 / (float) rpm);
buffer_[0] = mode;
buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw
buffer_[2] = steps;
buffer_[3] = (steps >> 8);
buffer_[4] = ms_per_step;
buffer_[5] = (ms_per_step >> 8);
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN, buffer_, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Run stepper failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::stepper_stop() {
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_STOP, nullptr, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Send stop stepper failed!");
this->status_set_warning();
return;
}
}
void GroveMotorDriveTB6612FNG::stepper_keep_run(StepperModeTypeT mode, uint16_t rpm, bool is_cw) {
// 4=>infinite ccw 5=>infinite cw
uint8_t cw = (is_cw) ? 5 : 4;
// 0.1ms_per_step
uint16_t ms_per_step = 0;
rpm = clamp<uint16_t>(rpm, 1, 300);
ms_per_step = (uint16_t) (3000.0 / (float) rpm);
buffer_[0] = mode;
buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw
buffer_[2] = ms_per_step;
buffer_[3] = (ms_per_step >> 8);
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_KEEP_RUN, buffer_, 4) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Write stepper keep run failed");
this->status_set_warning();
return;
}
}
} // namespace grove_tb6612fng
} // namespace esphome

View file

@ -0,0 +1,208 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/automation.h"
//#include "esphome/core/helpers.h"
/*
Grove_Motor_Driver_TB6612FNG.h
A library for the Grove - Motor Driver(TB6612FNG)
Copyright (c) 2018 seeed technology co., ltd.
Website : www.seeed.cc
Author : Jerry Yip
Create Time: 2018-06
Version : 0.1
Change Log :
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
namespace esphome {
namespace grove_tb6612fng {
enum MotorChannelTypeT {
MOTOR_CHA = 0,
MOTOR_CHB = 1,
};
enum StepperModeTypeT {
FULL_STEP = 0,
WAVE_DRIVE = 1,
HALF_STEP = 2,
MICRO_STEPPING = 3,
};
class GroveMotorDriveTB6612FNG : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
/*************************************************************
Description
Enter standby mode. Normally you don't need to call this, except that
you have called notStandby() before.
Parameter
Null.
Return
True/False.
*************************************************************/
bool standby();
/*************************************************************
Description
Exit standby mode. Motor driver does't do any action at this mode.
Parameter
Null.
Return
True/False.
*************************************************************/
bool not_standby();
/*************************************************************
Description
Set an new I2C address.
Parameter
addr: 0x01~0x7f
Return
Null.
*************************************************************/
void set_i2c_addr(uint8_t addr);
/*************************************************************
Description
Drive a motor.
Parameter
chl: MOTOR_CHA or MOTOR_CHB
speed: -255~255, if speed > 0, motor moves clockwise.
Note that there is always a starting speed(a starting voltage) for motor.
If the input voltage is 5V, the starting speed should larger than 100 or
smaller than -100.
Return
Null.
*************************************************************/
void dc_motor_run(uint8_t channel, int16_t speed);
/*************************************************************
Description
Brake, stop the motor immediately
Parameter
chl: MOTOR_CHA or MOTOR_CHB
Return
Null.
*************************************************************/
void dc_motor_brake(uint8_t channel);
/*************************************************************
Description
Stop the motor slowly.
Parameter
chl: MOTOR_CHA or MOTOR_CHB
Return
Null.
*************************************************************/
void dc_motor_stop(uint8_t channel);
/*************************************************************
Description
Drive a stepper.
Parameter
mode: 4 driver mode: FULL_STEP,WAVE_DRIVE, HALF_STEP, MICRO_STEPPING,
for more information: https://en.wikipedia.org/wiki/Stepper_motor#/media/File:Drive.png
steps: The number of steps to run, range from -32768 to 32767.
When steps = 0, the stepper stops.
When steps > 0, the stepper runs clockwise. When steps < 0, the stepper runs anticlockwise.
rpm: Revolutions per minute, the speed of a stepper, range from 1 to 300.
Note that high rpm will lead to step lose, so rpm should not be larger than 150.
Return
Null.
*************************************************************/
void stepper_run(StepperModeTypeT mode, int16_t steps, uint16_t rpm);
/*************************************************************
Description
Stop a stepper.
Parameter
Null.
Return
Null.
*************************************************************/
void stepper_stop();
// keeps moving(direction same as the last move, default to clockwise)
/*************************************************************
Description
Keep a stepper running.
Parameter
mode: 4 driver mode: FULL_STEP,WAVE_DRIVE, HALF_STEP, MICRO_STEPPING,
for more information: https://en.wikipedia.org/wiki/Stepper_motor#/media/File:Drive.png
rpm: Revolutions per minute, the speed of a stepper, range from 1 to 300.
Note that high rpm will lead to step lose, so rpm should not be larger than 150.
is_cw: Set the running direction, true for clockwise and false for anti-clockwise.
Return
Null.
*************************************************************/
void stepper_keep_run(StepperModeTypeT mode, uint16_t rpm, bool is_cw);
private:
uint8_t buffer_[16];
};
template<typename... Ts>
class GROVETB6612FNGMotorRunAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
TEMPLATABLE_VALUE(uint16_t, speed)
void play(Ts... x) override {
auto channel = this->channel_.value(x...);
auto speed = this->speed_.value(x...);
this->parent_->dc_motor_run(channel, speed);
}
};
template<typename... Ts>
class GROVETB6612FNGMotorBrakeAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
void play(Ts... x) override { this->parent_->dc_motor_brake(this->channel_.value(x...)); }
};
template<typename... Ts>
class GROVETB6612FNGMotorStopAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, channel)
void play(Ts... x) override { this->parent_->dc_motor_stop(this->channel_.value(x...)); }
};
template<typename... Ts>
class GROVETB6612FNGMotorStandbyAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
void play(Ts... x) override { this->parent_->standby(); }
};
template<typename... Ts>
class GROVETB6612FNGMotorNoStandbyAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
void play(Ts... x) override { this->parent_->not_standby(); }
};
} // namespace grove_tb6612fng
} // namespace esphome

View file

@ -121,7 +121,7 @@ async def to_code(config):
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -115,7 +115,7 @@ async def to_code(config):
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -16,6 +16,7 @@ McpMode = mcp2515_ns.enum("CANCTRL_REQOP_MODE")
CAN_CLOCK = { CAN_CLOCK = {
"8MHZ": CanClock.MCP_8MHZ, "8MHZ": CanClock.MCP_8MHZ,
"12MHZ": CanClock.MCP_12MHZ,
"16MHZ": CanClock.MCP_16MHZ, "16MHZ": CanClock.MCP_16MHZ,
"20MHZ": CanClock.MCP_20MHZ, "20MHZ": CanClock.MCP_20MHZ,
} }

View file

@ -16,11 +16,14 @@ const struct MCP2515::RxBnRegs MCP2515::RXB[N_RXBUFFERS] = {{MCP_RXB0CTRL, MCP_R
bool MCP2515::setup_internal() { bool MCP2515::setup_internal() {
this->spi_setup(); this->spi_setup();
if (this->reset_() == canbus::ERROR_FAIL) if (this->reset_() != canbus::ERROR_OK)
return false; return false;
this->set_bitrate_(this->bit_rate_, this->mcp_clock_); if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
this->set_mode_(this->mcp_mode_); return false;
ESP_LOGV(TAG, "setup done"); if (this->set_mode_(this->mcp_mode_) != canbus::ERROR_OK)
return false;
uint8_t err_flags = this->get_error_flags_();
ESP_LOGD(TAG, "mcp2515 setup done, error_flags = %02X", err_flags);
return true; return true;
} }
@ -38,7 +41,7 @@ canbus::Error MCP2515::reset_() {
set_registers_(MCP_TXB0CTRL, zeros, 14); set_registers_(MCP_TXB0CTRL, zeros, 14);
set_registers_(MCP_TXB1CTRL, zeros, 14); set_registers_(MCP_TXB1CTRL, zeros, 14);
set_registers_(MCP_TXB2CTRL, zeros, 14); set_registers_(MCP_TXB2CTRL, zeros, 14);
ESP_LOGD(TAG, "reset() CLEARED TXB registers"); ESP_LOGV(TAG, "reset() CLEARED TXB registers");
set_register_(MCP_RXB0CTRL, 0); set_register_(MCP_RXB0CTRL, 0);
set_register_(MCP_RXB1CTRL, 0); set_register_(MCP_RXB1CTRL, 0);
@ -114,16 +117,12 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode); modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode);
uint32_t end_time = millis() + 10; uint32_t end_time = millis() + 10;
bool mode_match = false;
while (millis() < end_time) { while (millis() < end_time) {
uint8_t new_mode = read_register_(MCP_CANSTAT); if ((read_register_(MCP_CANSTAT) & CANSTAT_OPMOD) == mode)
new_mode &= CANSTAT_OPMOD; return canbus::ERROR_OK;
mode_match = new_mode == mode;
if (mode_match) {
break;
}
} }
return mode_match ? canbus::ERROR_OK : canbus::ERROR_FAIL; ESP_LOGE(TAG, "Failed to set mode");
return canbus::ERROR_FAIL;
} }
canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) {
@ -451,6 +450,78 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo
} }
break; break;
case (MCP_12MHZ):
switch (can_speed) {
case (canbus::CAN_5KBPS): // 5Kbps
cfg1 = MCP_12MHZ_5KBPS_CFG1;
cfg2 = MCP_12MHZ_5KBPS_CFG2;
cfg3 = MCP_12MHZ_5KBPS_CFG3;
break;
case (canbus::CAN_10KBPS): // 10Kbps
cfg1 = MCP_12MHZ_10KBPS_CFG1;
cfg2 = MCP_12MHZ_10KBPS_CFG2;
cfg3 = MCP_12MHZ_10KBPS_CFG3;
break;
case (canbus::CAN_20KBPS): // 20Kbps
cfg1 = MCP_12MHZ_20KBPS_CFG1;
cfg2 = MCP_12MHZ_20KBPS_CFG2;
cfg3 = MCP_12MHZ_20KBPS_CFG3;
break;
case (canbus::CAN_33KBPS): // 33.333Kbps
cfg1 = MCP_12MHZ_33K3BPS_CFG1;
cfg2 = MCP_12MHZ_33K3BPS_CFG2;
cfg3 = MCP_12MHZ_33K3BPS_CFG3;
break;
case (canbus::CAN_40KBPS): // 40Kbps
cfg1 = MCP_12MHZ_40KBPS_CFG1;
cfg2 = MCP_12MHZ_40KBPS_CFG2;
cfg3 = MCP_12MHZ_40KBPS_CFG3;
break;
case (canbus::CAN_50KBPS): // 50Kbps
cfg2 = MCP_12MHZ_50KBPS_CFG2;
cfg3 = MCP_12MHZ_50KBPS_CFG3;
break;
case (canbus::CAN_80KBPS): // 80Kbps
cfg1 = MCP_12MHZ_80KBPS_CFG1;
cfg2 = MCP_12MHZ_80KBPS_CFG2;
cfg3 = MCP_12MHZ_80KBPS_CFG3;
break;
case (canbus::CAN_100KBPS): // 100Kbps
cfg1 = MCP_12MHZ_100KBPS_CFG1;
cfg2 = MCP_12MHZ_100KBPS_CFG2;
cfg3 = MCP_12MHZ_100KBPS_CFG3;
break;
case (canbus::CAN_125KBPS): // 125Kbps
cfg1 = MCP_12MHZ_125KBPS_CFG1;
cfg2 = MCP_12MHZ_125KBPS_CFG2;
cfg3 = MCP_12MHZ_125KBPS_CFG3;
break;
case (canbus::CAN_200KBPS): // 200Kbps
cfg1 = MCP_12MHZ_200KBPS_CFG1;
cfg2 = MCP_12MHZ_200KBPS_CFG2;
cfg3 = MCP_12MHZ_200KBPS_CFG3;
break;
case (canbus::CAN_250KBPS): // 250Kbps
cfg1 = MCP_12MHZ_250KBPS_CFG1;
cfg2 = MCP_12MHZ_250KBPS_CFG2;
cfg3 = MCP_12MHZ_250KBPS_CFG3;
break;
case (canbus::CAN_500KBPS): // 500Kbps
cfg1 = MCP_12MHZ_500KBPS_CFG1;
cfg2 = MCP_12MHZ_500KBPS_CFG2;
cfg3 = MCP_12MHZ_500KBPS_CFG3;
break;
case (canbus::CAN_1000KBPS): // 1Mbps
cfg1 = MCP_12MHZ_1000KBPS_CFG1;
cfg2 = MCP_12MHZ_1000KBPS_CFG2;
cfg3 = MCP_12MHZ_1000KBPS_CFG3;
break;
default:
set = 0;
break;
}
break;
case (MCP_16MHZ): case (MCP_16MHZ):
switch (can_speed) { switch (can_speed) {
case (canbus::CAN_5KBPS): // 5Kbps case (canbus::CAN_5KBPS): // 5Kbps
@ -602,6 +673,7 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo
set_register_(MCP_CNF3, cfg3); // NOLINT set_register_(MCP_CNF3, cfg3); // NOLINT
return canbus::ERROR_OK; return canbus::ERROR_OK;
} else { } else {
ESP_LOGE(TAG, "Invalid frequency/bitrate combination: %d/%d", can_clock, can_speed);
return canbus::ERROR_FAIL; return canbus::ERROR_FAIL;
} }
} }

View file

@ -11,7 +11,7 @@ static const uint32_t SPI_CLOCK = 10000000; // 10MHz
static const int N_TXBUFFERS = 3; static const int N_TXBUFFERS = 3;
static const int N_RXBUFFERS = 2; static const int N_RXBUFFERS = 2;
enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_8MHZ }; enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_12MHZ, MCP_8MHZ };
enum MASK { MASK0, MASK1 }; enum MASK { MASK0, MASK1 };
enum RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 }; enum RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 };
enum RXBn { RXB0 = 0, RXB1 = 1 }; enum RXBn { RXB0 = 0, RXB1 = 1 };

View file

@ -207,6 +207,62 @@ static const uint8_t MCP_8MHZ_5KBPS_CFG1 = 0x1F;
static const uint8_t MCP_8MHZ_5KBPS_CFG2 = 0xBF; static const uint8_t MCP_8MHZ_5KBPS_CFG2 = 0xBF;
static const uint8_t MCP_8MHZ_5KBPS_CFG3 = 0x87; static const uint8_t MCP_8MHZ_5KBPS_CFG3 = 0x87;
/*
* Speed 12M
*/
static const uint8_t MCP_12MHZ_1000KBPS_CFG1 = 0x00;
static const uint8_t MCP_12MHZ_1000KBPS_CFG2 = 0x88;
static const uint8_t MCP_12MHZ_1000KBPS_CFG3 = 0x81;
static const uint8_t MCP_12MHZ_500KBPS_CFG1 = 0x00;
static const uint8_t MCP_12MHZ_500KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_500KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_250KBPS_CFG1 = 0x01;
static const uint8_t MCP_12MHZ_250KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_250KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_200KBPS_CFG1 = 0x01;
static const uint8_t MCP_12MHZ_200KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_200KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_125KBPS_CFG1 = 0x03;
static const uint8_t MCP_12MHZ_125KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_125KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_100KBPS_CFG1 = 0x03;
static const uint8_t MCP_12MHZ_100KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_100KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_80KBPS_CFG1 = 0x04;
static const uint8_t MCP_12MHZ_80KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_80KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_50KBPS_CFG1 = 0x07;
static const uint8_t MCP_12MHZ_50KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_50KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_40KBPS_CFG1 = 0x09;
static const uint8_t MCP_12MHZ_40KBPS_CFG2 = 0xA4;
static const uint8_t MCP_12MHZ_40KBPS_CFG3 = 0x83;
static const uint8_t MCP_12MHZ_33K3BPS_CFG1 = 0x08;
static const uint8_t MCP_12MHZ_33K3BPS_CFG2 = 0xB6;
static const uint8_t MCP_12MHZ_33K3BPS_CFG3 = 0x84;
static const uint8_t MCP_12MHZ_20KBPS_CFG1 = 0x0E;
static const uint8_t MCP_12MHZ_20KBPS_CFG2 = 0xB6;
static const uint8_t MCP_12MHZ_20KBPS_CFG3 = 0x84;
static const uint8_t MCP_12MHZ_10KBPS_CFG1 = 0x31;
static const uint8_t MCP_12MHZ_10KBPS_CFG2 = 0x9B;
static const uint8_t MCP_12MHZ_10KBPS_CFG3 = 0x82;
static const uint8_t MCP_12MHZ_5KBPS_CFG1 = 0x3B;
static const uint8_t MCP_12MHZ_5KBPS_CFG2 = 0xB6;
static const uint8_t MCP_12MHZ_5KBPS_CFG3 = 0x84;
/* /*
* speed 16M * speed 16M
*/ */

View file

@ -77,7 +77,7 @@ void MPU6050Component::setup() {
accel_config &= 0b11100111; accel_config &= 0b11100111;
accel_config |= (MPU6050_RANGE_2G << 3); accel_config |= (MPU6050_RANGE_2G << 3);
ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
if (!this->write_byte(MPU6050_REGISTER_GYRO_CONFIG, gyro_config)) { if (!this->write_byte(MPU6050_REGISTER_ACCEL_CONFIG, accel_config)) {
this->mark_failed(); this->mark_failed();
return; return;
} }

View file

@ -52,6 +52,6 @@ async def to_code(config):
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

View file

@ -0,0 +1,109 @@
#include "pcf8563.h"
#include "esphome/core/log.h"
// Datasheet:
// - https://nl.mouser.com/datasheet/2/302/PCF8563-1127619.pdf
namespace esphome {
namespace pcf8563 {
static const char *const TAG = "PCF8563";
void PCF8563Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up PCF8563...");
if (!this->read_rtc_()) {
this->mark_failed();
}
}
void PCF8563Component::update() { this->read_time(); }
void PCF8563Component::dump_config() {
ESP_LOGCONFIG(TAG, "PCF8563:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with PCF8563 failed!");
}
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
}
float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; }
void PCF8563Component::read_time() {
if (!this->read_rtc_()) {
return;
}
if (pcf8563_.reg.stop) {
ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
return;
}
ESPTime rtc_time{
.second = uint8_t(pcf8563_.reg.second + 10 * pcf8563_.reg.second_10),
.minute = uint8_t(pcf8563_.reg.minute + 10u * pcf8563_.reg.minute_10),
.hour = uint8_t(pcf8563_.reg.hour + 10u * pcf8563_.reg.hour_10),
.day_of_week = uint8_t(pcf8563_.reg.weekday),
.day_of_month = uint8_t(pcf8563_.reg.day + 10u * pcf8563_.reg.day_10),
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
.month = uint8_t(pcf8563_.reg.month + 10u * pcf8563_.reg.month_10),
.year = uint16_t(pcf8563_.reg.year + 10u * pcf8563_.reg.year_10 + 2000),
.is_dst = false, // not used
.timestamp = 0, // overwritten by recalc_timestamp_utc(false)
};
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
}
void PCF8563Component::write_time() {
auto now = time::RealTimeClock::utcnow();
if (!now.is_valid()) {
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
return;
}
pcf8563_.reg.year = (now.year - 2000) % 10;
pcf8563_.reg.year_10 = (now.year - 2000) / 10 % 10;
pcf8563_.reg.month = now.month % 10;
pcf8563_.reg.month_10 = now.month / 10;
pcf8563_.reg.day = now.day_of_month % 10;
pcf8563_.reg.day_10 = now.day_of_month / 10;
pcf8563_.reg.weekday = now.day_of_week;
pcf8563_.reg.hour = now.hour % 10;
pcf8563_.reg.hour_10 = now.hour / 10;
pcf8563_.reg.minute = now.minute % 10;
pcf8563_.reg.minute_10 = now.minute / 10;
pcf8563_.reg.second = now.second % 10;
pcf8563_.reg.second_10 = now.second / 10;
pcf8563_.reg.stop = false;
this->write_rtc_();
}
bool PCF8563Component::read_rtc_() {
if (!this->read_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.raw))) {
ESP_LOGE(TAG, "Can't read I2C data.");
return false;
}
ESP_LOGD(TAG, "Read %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u STOP:%s CLKOUT:%0u", pcf8563_.reg.hour_10,
pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second,
pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10,
pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled);
return true;
}
bool PCF8563Component::write_rtc_() {
if (!this->write_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.raw))) {
ESP_LOGE(TAG, "Can't write I2C data.");
return false;
}
ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u OSC:%s CLKOUT:%0u", pcf8563_.reg.hour_10,
pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second,
pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10,
pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled);
return true;
}
} // namespace pcf8563
} // namespace esphome

View file

@ -0,0 +1,124 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome {
namespace pcf8563 {
class PCF8563Component : public time::RealTimeClock, public i2c::I2CDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override;
void read_time();
void write_time();
protected:
bool read_rtc_();
bool write_rtc_();
union PCF8563Reg {
struct {
// Control_1 register
bool : 3;
bool power_on_reset : 1;
bool : 1;
bool stop : 1;
bool : 1;
bool ext_test : 1;
// Control_2 register
bool time_int : 1;
bool alarm_int : 1;
bool timer_flag : 1;
bool alarm_flag : 1;
bool timer_int_timer_pulse : 1;
bool : 3;
// Seconds register
uint8_t second : 4;
uint8_t second_10 : 3;
bool clock_int : 1;
// Minutes register
uint8_t minute : 4;
uint8_t minute_10 : 3;
uint8_t : 1;
// Hours register
uint8_t hour : 4;
uint8_t hour_10 : 2;
uint8_t : 2;
// Days register
uint8_t day : 4;
uint8_t day_10 : 2;
uint8_t : 2;
// Weekdays register
uint8_t weekday : 3;
uint8_t unused_3 : 5;
// Months register
uint8_t month : 4;
uint8_t month_10 : 1;
uint8_t : 2;
uint8_t century : 1;
// Years register
uint8_t year : 4;
uint8_t year_10 : 4;
// Minute Alarm register
uint8_t minute_alarm : 4;
uint8_t minute_alarm_10 : 3;
bool minute_alarm_enabled : 1;
// Hour Alarm register
uint8_t hour_alarm : 4;
uint8_t hour_alarm_10 : 2;
uint8_t : 1;
bool hour_alarm_enabled : 1;
// Day Alarm register
uint8_t day_alarm : 4;
uint8_t day_alarm_10 : 2;
uint8_t : 1;
bool day_alarm_enabled : 1;
// Weekday Alarm register
uint8_t weekday_alarm : 3;
uint8_t : 4;
bool weekday_alarm_enabled : 1;
// CLKout control register
uint8_t frequency_output : 2;
uint8_t : 5;
uint8_t clkout_enabled : 1;
// Timer control register
uint8_t timer_source_frequency : 2;
uint8_t : 5;
uint8_t timer_enabled : 1;
// Timer register
uint8_t countdown_period : 8;
} reg;
mutable uint8_t raw[sizeof(reg)];
} pcf8563_;
};
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<PCF8563Component> {
public:
void play(Ts... x) override { this->parent_->write_time(); }
};
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<PCF8563Component> {
public:
void play(Ts... x) override { this->parent_->read_time(); }
};
} // namespace pcf8563
} // namespace esphome

View file

@ -0,0 +1,62 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome import automation
from esphome.components import i2c, time
from esphome.const import CONF_ID
CODEOWNERS = ["@KoenBreeman"]
DEPENDENCIES = ["i2c"]
pcf8563_ns = cg.esphome_ns.namespace("pcf8563")
pcf8563Component = pcf8563_ns.class_(
"PCF8563Component", time.RealTimeClock, i2c.I2CDevice
)
WriteAction = pcf8563_ns.class_("WriteAction", automation.Action)
ReadAction = pcf8563_ns.class_("ReadAction", automation.Action)
CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(pcf8563Component),
}
).extend(i2c.i2c_device_schema(0xA3))
@automation.register_action(
"pcf8563.write_time",
WriteAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(pcf8563Component),
}
),
)
async def pcf8563_write_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"pcf8563.read_time",
ReadAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(pcf8563Component),
}
),
)
async def pcf8563_read_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await time.register_time(var, config)

View file

@ -29,7 +29,7 @@ float PIDController::update(float setpoint, float process_value) {
bool PIDController::in_deadband() { bool PIDController::in_deadband() {
// return (fabs(error) < deadband_threshold); // return (fabs(error) < deadband_threshold);
float err = -error_; float err = -error_;
return ((err > 0 && err < threshold_high_) || (err < 0 && err > threshold_low_)); return (threshold_low_ < err && err < threshold_high_);
} }
void PIDController::calculate_proportional_term_() { void PIDController::calculate_proportional_term_() {

View file

@ -62,19 +62,19 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# The default/recommended arduino framework version # The default/recommended arduino framework version
# - https://github.com/earlephilhower/arduino-pico/releases # - https://github.com/earlephilhower/arduino-pico/releases
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 6, 4) RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 3, 0)
# The platformio/raspberrypi version to use for arduino frameworks # The platformio/raspberrypi version to use for arduino frameworks
# - https://github.com/platformio/platform-raspberrypi/releases # - https://github.com/platformio/platform-raspberrypi/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi # - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi
ARDUINO_PLATFORM_VERSION = cv.Version(1, 7, 0) ARDUINO_PLATFORM_VERSION = cv.Version(1, 9, 0)
def _arduino_check_versions(value): def _arduino_check_versions(value):
value = value.copy() value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(2, 6, 4), "https://github.com/earlephilhower/arduino-pico"), "dev": (cv.Version(3, 3, 0), "https://github.com/earlephilhower/arduino-pico"),
"latest": (cv.Version(2, 6, 4), None), "latest": (cv.Version(3, 3, 0), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
} }

View file

@ -96,6 +96,6 @@ async def setup_ssd1306(var, config):
cg.add(var.init_invert(config[CONF_INVERT])) cg.add(var.init_invert(config[CONF_INVERT]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -46,6 +46,6 @@ async def setup_ssd1322(var, config):
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -50,6 +50,6 @@ async def setup_ssd1325(var, config):
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -37,6 +37,6 @@ async def setup_ssd1327(var, config):
cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -28,6 +28,6 @@ async def setup_ssd1331(var, config):
cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -38,6 +38,6 @@ async def setup_ssd1351(var, config):
cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -77,7 +77,7 @@ async def setup_st7735(var, config):
cg.add(var.set_reset_pin(reset)) cg.add(var.set_reset_pin(reset))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -121,7 +121,7 @@ async def to_code(config):
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -11,7 +11,7 @@ void TemplateBinarySensor::setup() {
return; return;
if (this->f_ != nullptr) { if (this->f_ != nullptr) {
this->publish_initial_state(*this->f_()); this->publish_initial_state(this->f_().value_or(false));
} else { } else {
this->publish_initial_state(false); this->publish_initial_state(false);
} }

View file

@ -300,6 +300,7 @@ uint8_t TM1637Display::read_byte_() {
uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
// ESP_LOGV(TAG, "Print at %d: %s", start_pos, str); // ESP_LOGV(TAG, "Print at %d: %s", start_pos, str);
uint8_t pos = start_pos; uint8_t pos = start_pos;
bool use_dot = false;
for (; *str != '\0'; str++) { for (; *str != '\0'; str++) {
uint8_t data = TM1637_UNKNOWN_CHAR; uint8_t data = TM1637_UNKNOWN_CHAR;
if (*str >= ' ' && *str <= '~') if (*str >= ' ' && *str <= '~')
@ -312,14 +313,14 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
// XABCDEFG, but TM1637 is // XGFEDCBA // XABCDEFG, but TM1637 is // XGFEDCBA
if (this->inverted_) { if (this->inverted_) {
// XABCDEFG > XGCBAFED // XABCDEFG > XGCBAFED
data = ((data & 0x80) ? 0x80 : 0) | // no move X data = ((data & 0x80) || use_dot ? 0x80 : 0) | // no move X
((data & 0x40) ? 0x8 : 0) | // A ((data & 0x40) ? 0x8 : 0) | // A
((data & 0x20) ? 0x10 : 0) | // B ((data & 0x20) ? 0x10 : 0) | // B
((data & 0x10) ? 0x20 : 0) | // C ((data & 0x10) ? 0x20 : 0) | // C
((data & 0x8) ? 0x1 : 0) | // D ((data & 0x8) ? 0x1 : 0) | // D
((data & 0x4) ? 0x2 : 0) | // E ((data & 0x4) ? 0x2 : 0) | // E
((data & 0x2) ? 0x4 : 0) | // F ((data & 0x2) ? 0x4 : 0) | // F
((data & 0x1) ? 0x40 : 0); // G ((data & 0x1) ? 0x40 : 0); // G
} else { } else {
// XABCDEFG > XGFEDCBA // XABCDEFG > XGFEDCBA
data = ((data & 0x80) ? 0x80 : 0) | // no move X data = ((data & 0x80) ? 0x80 : 0) | // no move X
@ -331,18 +332,18 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
((data & 0x2) ? 0x20 : 0) | // F ((data & 0x2) ? 0x20 : 0) | // F
((data & 0x1) ? 0x40 : 0); // G ((data & 0x1) ? 0x40 : 0); // G
} }
if (*str == '.') { use_dot = *str == '.';
if (pos != start_pos) if (use_dot) {
pos--; if ((!this->inverted_) && (pos != start_pos)) {
this->buffer_[pos] |= 0b10000000; this->buffer_[pos - 1] |= 0b10000000;
}
} else { } else {
if (pos >= 6) { if (pos >= 6) {
ESP_LOGE(TAG, "String is too long for the display!"); ESP_LOGE(TAG, "String is too long for the display!");
break; break;
} }
this->buffer_[pos] = data; this->buffer_[pos++] = data;
} }
pos++;
} }
return pos - start_pos; return pos - start_pos;
} }

View file

@ -3,6 +3,11 @@
namespace esphome { namespace esphome {
namespace touchscreen { namespace touchscreen {
void TouchscreenBinarySensor::setup() {
this->parent_->register_listener(this);
this->publish_initial_state(false);
}
void TouchscreenBinarySensor::touch(TouchPoint tp) { void TouchscreenBinarySensor::touch(TouchPoint tp) {
bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_);

View file

@ -14,7 +14,7 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
public TouchListener, public TouchListener,
public Parented<Touchscreen> { public Parented<Touchscreen> {
public: public:
void setup() override { this->parent_->register_listener(this); } void setup() override;
/// Set the touch screen area where the button will detect the touch. /// Set the touch screen area where the button will detect the touch.
void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {

View file

@ -0,0 +1,5 @@
import esphome.codegen as cg
CODEOWNERS = ["@kroimon"]
tt21100_ns = cg.esphome_ns.namespace("tt21100")

View file

@ -0,0 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_INDEX
from .. import tt21100_ns
from ..touchscreen import TT21100Touchscreen, TT21100ButtonListener
CONF_TT21100_ID = "tt21100_id"
TT21100Button = tt21100_ns.class_(
"TT21100Button",
binary_sensor.BinarySensor,
cg.Component,
TT21100ButtonListener,
cg.Parented.template(TT21100Touchscreen),
)
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TT21100Button).extend(
{
cv.GenerateID(CONF_TT21100_ID): cv.use_id(TT21100Touchscreen),
cv.Required(CONF_INDEX): cv.int_range(min=0, max=3),
}
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_TT21100_ID])
cg.add(var.set_index(config[CONF_INDEX]))

View file

@ -0,0 +1,27 @@
#include "tt21100_button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace tt21100 {
static const char *const TAG = "tt21100.binary_sensor";
void TT21100Button::setup() {
this->parent_->register_button_listener(this);
this->publish_initial_state(false);
}
void TT21100Button::dump_config() {
LOG_BINARY_SENSOR("", "TT21100 Button", this);
ESP_LOGCONFIG(TAG, " Index: %u", this->index_);
}
void TT21100Button::update_button(uint8_t index, uint16_t state) {
if (index != this->index_)
return;
this->publish_state(state > 0);
}
} // namespace tt21100
} // namespace esphome

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/tt21100/touchscreen/tt21100.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace tt21100 {
class TT21100Button : public binary_sensor::BinarySensor,
public Component,
public TT21100ButtonListener,
public Parented<TT21100Touchscreen> {
public:
void setup() override;
void dump_config() override;
void set_index(uint8_t index) { this->index_ = index; }
void update_button(uint8_t index, uint16_t state) override;
protected:
uint8_t index_;
};
} // namespace tt21100
} // namespace esphome

View file

@ -0,0 +1,44 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, touchscreen
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import tt21100_ns
DEPENDENCIES = ["i2c"]
TT21100Touchscreen = tt21100_ns.class_(
"TT21100Touchscreen",
touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice,
)
TT21100ButtonListener = tt21100_ns.class_("TT21100ButtonListener")
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(TT21100Touchscreen),
cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
)
.extend(i2c.i2c_device_schema(0x24))
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await touchscreen.register_touchscreen(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin))
if CONF_RESET_PIN in config:
rts_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(rts_pin))

View file

@ -0,0 +1,175 @@
#include "tt21100.h"
#include "esphome/core/log.h"
namespace esphome {
namespace tt21100 {
static const char *const TAG = "tt21100";
static const uint8_t MAX_BUTTONS = 4;
static const uint8_t MAX_TOUCH_POINTS = 5;
static const uint8_t MAX_DATA_LEN = (7 + MAX_TOUCH_POINTS * 10); // 7 Header + (Points * 10 data bytes)
struct TT21100ButtonReport {
uint16_t length; // Always 14 (0x000E)
uint8_t report_id; // Always 0x03
uint16_t timestamp; // Number in units of 100 us
uint8_t btn_value; // Only use bit 0..3
uint16_t btn_signal[MAX_BUTTONS];
} __attribute__((packed));
struct TT21100TouchRecord {
uint8_t : 5;
uint8_t touch_type : 3;
uint8_t tip : 1;
uint8_t event_id : 2;
uint8_t touch_id : 5;
uint16_t x;
uint16_t y;
uint8_t pressure;
uint16_t major_axis_length;
uint8_t orientation;
} __attribute__((packed));
struct TT21100TouchReport {
uint16_t length;
uint8_t report_id;
uint16_t timestamp;
uint8_t : 2;
uint8_t large_object : 1;
uint8_t record_num : 5;
uint8_t report_counter : 2;
uint8_t : 3;
uint8_t noise_effect : 3;
TT21100TouchRecord touch_record[MAX_TOUCH_POINTS];
} __attribute__((packed));
void TT21100TouchscreenStore::gpio_intr(TT21100TouchscreenStore *store) { store->touch = true; }
float TT21100Touchscreen::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
void TT21100Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up TT21100 Touchscreen...");
// Register interrupt pin
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr();
this->interrupt_pin_->attach_interrupt(TT21100TouchscreenStore::gpio_intr, &this->store_,
gpio::INTERRUPT_FALLING_EDGE);
// Perform reset if necessary
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_();
}
// Update display dimensions if they were updated during display setup
this->display_width_ = this->display_->get_width();
this->display_height_ = this->display_->get_height();
this->rotation_ = static_cast<TouchRotation>(this->display_->get_rotation());
// Trigger initial read to activate the interrupt
this->store_.touch = true;
}
void TT21100Touchscreen::loop() {
if (!this->store_.touch)
return;
this->store_.touch = false;
// Read report length
uint16_t data_len;
this->read((uint8_t *) &data_len, sizeof(data_len));
// Read report data
uint8_t data[MAX_DATA_LEN];
if (data_len > 0 && data_len < sizeof(data)) {
this->read(data, data_len);
if (data_len == 14) {
// Button event
auto *report = (TT21100ButtonReport *) data;
ESP_LOGV(TAG, "Button report: Len=%d, ID=%d, Time=%5u, Value=[%u], Signal=[%04X][%04X][%04X][%04X]",
report->length, report->report_id, report->timestamp, report->btn_value, report->btn_signal[0],
report->btn_signal[1], report->btn_signal[2], report->btn_signal[3]);
for (uint8_t i = 0; i < 4; i++) {
for (auto *listener : this->button_listeners_)
listener->update_button(i, report->btn_signal[i]);
}
} else if (data_len >= 7) {
// Touch point event
auto *report = (TT21100TouchReport *) data;
ESP_LOGV(TAG,
"Touch report: Len=%d, ID=%d, Time=%5u, LargeObject=%u, RecordNum=%u, RecordCounter=%u, NoiseEffect=%u",
report->length, report->report_id, report->timestamp, report->large_object, report->record_num,
report->report_counter, report->noise_effect);
uint8_t touch_count = (data_len - (sizeof(*report) - sizeof(report->touch_record))) / sizeof(TT21100TouchRecord);
if (touch_count == 0) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
for (int i = 0; i < touch_count; i++) {
auto *touch = &report->touch_record[i];
ESP_LOGV(TAG,
"Touch %d: Type=%u, Tip=%u, EventId=%u, TouchId=%u, X=%u, Y=%u, Pressure=%u, MajorAxisLen=%u, "
"Orientation=%u",
i, touch->touch_type, touch->tip, touch->event_id, touch->touch_id, touch->x, touch->y,
touch->pressure, touch->major_axis_length, touch->orientation);
TouchPoint tp;
switch (this->rotation_) {
case ROTATE_0_DEGREES:
// Origin is top right, so mirror X by default
tp.x = this->display_width_ - touch->x;
tp.y = touch->y;
break;
case ROTATE_90_DEGREES:
tp.x = touch->y;
tp.y = touch->x;
break;
case ROTATE_180_DEGREES:
tp.x = touch->x;
tp.y = this->display_height_ - touch->y;
break;
case ROTATE_270_DEGREES:
tp.x = this->display_height_ - touch->y;
tp.y = this->display_width_ - touch->x;
break;
}
tp.id = touch->tip;
tp.state = touch->pressure;
this->defer([this, tp]() { this->send_touch_(tp); });
}
}
}
}
void TT21100Touchscreen::reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
delay(10);
}
}
void TT21100Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "TT21100 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
}
} // namespace tt21100
} // namespace esphome

View file

@ -0,0 +1,49 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace tt21100 {
using namespace touchscreen;
struct TT21100TouchscreenStore {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(TT21100TouchscreenStore *store);
};
class TT21100ButtonListener {
public:
virtual void update_button(uint8_t index, uint16_t state) = 0;
};
class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
void register_button_listener(TT21100ButtonListener *listener) { this->button_listeners_.push_back(listener); }
protected:
void reset_();
TT21100TouchscreenStore store_;
InternalGPIOPin *interrupt_pin_;
GPIOPin *reset_pin_{nullptr};
std::vector<TT21100ButtonListener *> button_listeners_;
};
} // namespace tt21100
} // namespace esphome

View file

@ -168,7 +168,7 @@ void TuyaLight::write_state(light::LightState *state) {
if (brightness > 0.0f || !color_interlock_) { if (brightness > 0.0f || !color_interlock_) {
if (this->color_temperature_id_.has_value()) { if (this->color_temperature_id_.has_value()) {
uint32_t color_temp_int = static_cast<uint32_t>(color_temperature * this->color_temperature_max_value_); uint32_t color_temp_int = static_cast<uint32_t>(roundf(color_temperature * this->color_temperature_max_value_));
if (this->color_temperature_invert_) { if (this->color_temperature_invert_) {
color_temp_int = this->color_temperature_max_value_ - color_temp_int; color_temp_int = this->color_temperature_max_value_ - color_temp_int;
} }

View file

@ -246,6 +246,7 @@ def final_validate_device_schema(
baud_rate: Optional[int] = None, baud_rate: Optional[int] = None,
require_tx: bool = False, require_tx: bool = False,
require_rx: bool = False, require_rx: bool = False,
data_bits: Optional[int] = None,
parity: Optional[str] = None, parity: Optional[str] = None,
stop_bits: Optional[int] = None, stop_bits: Optional[int] = None,
): ):
@ -268,6 +269,13 @@ def final_validate_device_schema(
return validator return validator
def validate_data_bits(value):
if value != data_bits:
raise cv.Invalid(
f"Component {name} requires {data_bits} data bits for the uart bus"
)
return value
def validate_parity(value): def validate_parity(value):
if value != parity: if value != parity:
raise cv.Invalid( raise cv.Invalid(
@ -278,7 +286,7 @@ def final_validate_device_schema(
def validate_stop_bits(value): def validate_stop_bits(value):
if value != stop_bits: if value != stop_bits:
raise cv.Invalid( raise cv.Invalid(
f"Component {name} requires stop bits {stop_bits} for the uart bus" f"Component {name} requires {stop_bits} stop bits for the uart bus"
) )
return value return value
@ -304,6 +312,8 @@ def final_validate_device_schema(
] = validate_pin(CONF_RX_PIN, device) ] = validate_pin(CONF_RX_PIN, device)
if baud_rate is not None: if baud_rate is not None:
hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate
if data_bits is not None:
hub_schema[cv.Required(CONF_DATA_BITS)] = validate_data_bits
if parity is not None: if parity is not None:
hub_schema[cv.Required(CONF_PARITY)] = validate_parity hub_schema[cv.Required(CONF_PARITY)] = validate_parity
if stop_bits is not None: if stop_bits is not None:

View file

@ -153,7 +153,7 @@ async def to_code(config):
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))
if CONF_RESET_PIN in config: if CONF_RESET_PIN in config:

View file

@ -47,6 +47,12 @@ def validate_local(config):
return config return config
def validate_ota(config):
if CORE.using_esp_idf and config[CONF_OTA]:
raise cv.Invalid("Enabling 'ota' is not supported for IDF framework yet")
return config
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@ -71,15 +77,17 @@ CONFIG_SCHEMA = cv.All(
web_server_base.WebServerBase web_server_base.WebServerBase
), ),
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
cv.Optional(CONF_OTA, default=True): cv.boolean, cv.SplitDefault(
CONF_OTA, esp8266=True, esp32_arduino=True, esp32_idf=False
): cv.boolean,
cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean,
cv.Optional(CONF_LOCAL): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
cv.only_on(["esp32", "esp8266"]), cv.only_on(["esp32", "esp8266"]),
default_url, default_url,
validate_local, validate_local,
validate_ota,
) )

View file

@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "list_entities.h" #include "list_entities.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@ -103,5 +101,3 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/component_iterator.h" #include "esphome/core/component_iterator.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
@ -59,5 +57,3 @@ class ListEntitiesIterator : public ComponentIterator {
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "web_server.h" #include "web_server.h"
#include "esphome/components/json/json_util.h" #include "esphome/components/json/json_util.h"
@ -9,7 +7,9 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
#ifdef USE_ARDUINO
#include "StreamString.h" #include "StreamString.h"
#endif
#include <cstdlib> #include <cstdlib>
@ -181,7 +181,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">")); stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
#endif #endif
if (strlen(this->css_url_) > 0) { if (strlen(this->css_url_) > 0) {
stream->print(F("<link rel=\"stylesheet\" href=\"")); stream->print(F(R"(<link rel="stylesheet" href=")"));
stream->print(this->css_url_); stream->print(this->css_url_);
stream->print(F("\">")); stream->print(F("\">"));
} }
@ -381,7 +381,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM
std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) { return json::build_json([obj, value, start_config](JsonObject root) {
std::string state; std::string state;
if (isnan(value)) { if (std::isnan(value)) {
state = "NA"; state = "NA";
} else { } else {
state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
@ -524,11 +524,8 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
request->send(200); request->send(200);
} else if (match.method == "turn_on") { } else if (match.method == "turn_on") {
auto call = obj->turn_on(); auto call = obj->turn_on();
if (request->hasParam("speed")) {
String speed = request->getParam("speed")->value();
}
if (request->hasParam("speed_level")) { if (request->hasParam("speed_level")) {
String speed_level = request->getParam("speed_level")->value(); auto speed_level = request->getParam("speed_level")->value();
auto val = parse_number<int>(speed_level.c_str()); auto val = parse_number<int>(speed_level.c_str());
if (!val.has_value()) { if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str()); ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str());
@ -537,7 +534,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
call.set_speed(*val); call.set_speed(*val);
} }
if (request->hasParam("oscillation")) { if (request->hasParam("oscillation")) {
String speed = request->getParam("oscillation")->value(); auto speed = request->getParam("oscillation")->value();
auto val = parse_on_off(speed.c_str()); auto val = parse_on_off(speed.c_str());
switch (val) { switch (val) {
case PARSE_ON: case PARSE_ON:
@ -585,29 +582,54 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
request->send(200); request->send(200);
} else if (match.method == "turn_on") { } else if (match.method == "turn_on") {
auto call = obj->turn_on(); auto call = obj->turn_on();
if (request->hasParam("brightness")) if (request->hasParam("brightness")) {
call.set_brightness(request->getParam("brightness")->value().toFloat() / 255.0f); auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str());
if (request->hasParam("r")) if (brightness.has_value()) {
call.set_red(request->getParam("r")->value().toFloat() / 255.0f); call.set_brightness(*brightness / 255.0f);
if (request->hasParam("g")) }
call.set_green(request->getParam("g")->value().toFloat() / 255.0f); }
if (request->hasParam("b")) if (request->hasParam("r")) {
call.set_blue(request->getParam("b")->value().toFloat() / 255.0f); auto r = parse_number<float>(request->getParam("r")->value().c_str());
if (request->hasParam("white_value")) if (r.has_value()) {
call.set_white(request->getParam("white_value")->value().toFloat() / 255.0f); call.set_red(*r / 255.0f);
if (request->hasParam("color_temp")) }
call.set_color_temperature(request->getParam("color_temp")->value().toFloat()); }
if (request->hasParam("g")) {
auto g = parse_number<float>(request->getParam("g")->value().c_str());
if (g.has_value()) {
call.set_green(*g / 255.0f);
}
}
if (request->hasParam("b")) {
auto b = parse_number<float>(request->getParam("b")->value().c_str());
if (b.has_value()) {
call.set_blue(*b / 255.0f);
}
}
if (request->hasParam("white_value")) {
auto white_value = parse_number<float>(request->getParam("white_value")->value().c_str());
if (white_value.has_value()) {
call.set_white(*white_value / 255.0f);
}
}
if (request->hasParam("color_temp")) {
auto color_temp = parse_number<float>(request->getParam("color_temp")->value().c_str());
if (color_temp.has_value()) {
call.set_color_temperature(*color_temp);
}
}
if (request->hasParam("flash")) { if (request->hasParam("flash")) {
float length_s = request->getParam("flash")->value().toFloat(); auto flash = parse_number<uint32_t>(request->getParam("flash")->value().c_str());
call.set_flash_length(static_cast<uint32_t>(length_s * 1000)); if (flash.has_value()) {
call.set_flash_length(*flash * 1000);
}
} }
if (request->hasParam("transition")) { if (request->hasParam("transition")) {
float length_s = request->getParam("transition")->value().toFloat(); auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
call.set_transition_length(static_cast<uint32_t>(length_s * 1000)); if (transition.has_value()) {
call.set_transition_length(*transition * 1000);
}
} }
if (request->hasParam("effect")) { if (request->hasParam("effect")) {
const char *effect = request->getParam("effect")->value().c_str(); const char *effect = request->getParam("effect")->value().c_str();
call.set_effect(effect); call.set_effect(effect);
@ -618,8 +640,10 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
} else if (match.method == "turn_off") { } else if (match.method == "turn_off") {
auto call = obj->turn_off(); auto call = obj->turn_off();
if (request->hasParam("transition")) { if (request->hasParam("transition")) {
auto length = (uint32_t) request->getParam("transition")->value().toFloat() * 1000; auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
call.set_transition_length(length); if (transition.has_value()) {
call.set_transition_length(*transition * 1000);
}
} }
this->schedule_([call]() mutable { call.perform(); }); this->schedule_([call]() mutable { call.perform(); });
request->send(200); request->send(200);
@ -681,10 +705,18 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
return; return;
} }
if (request->hasParam("position")) if (request->hasParam("position")) {
call.set_position(request->getParam("position")->value().toFloat()); auto position = parse_number<float>(request->getParam("position")->value().c_str());
if (request->hasParam("tilt")) if (position.has_value()) {
call.set_tilt(request->getParam("tilt")->value().toFloat()); call.set_position(*position);
}
}
if (request->hasParam("tilt")) {
auto tilt = parse_number<float>(request->getParam("tilt")->value().c_str());
if (tilt.has_value()) {
call.set_tilt(*tilt);
}
}
this->schedule_([call]() mutable { call.perform(); }); this->schedule_([call]() mutable { call.perform(); });
request->send(200); request->send(200);
@ -725,10 +757,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
auto call = obj->make_call(); auto call = obj->make_call();
if (request->hasParam("value")) { if (request->hasParam("value")) {
String value = request->getParam("value")->value(); auto value = parse_number<float>(request->getParam("value")->value().c_str());
optional<float> value_f = parse_number<float>(value.c_str()); if (value.has_value())
if (value_f.has_value()) call.set_value(*value);
call.set_value(*value_f);
} }
this->schedule_([call]() mutable { call.perform(); }); this->schedule_([call]() mutable { call.perform(); });
@ -747,7 +778,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
root["step"] = obj->traits.get_step(); root["step"] = obj->traits.get_step();
root["mode"] = (int) obj->traits.get_mode(); root["mode"] = (int) obj->traits.get_mode();
} }
if (isnan(value)) { if (std::isnan(value)) {
root["value"] = "\"NaN\""; root["value"] = "\"NaN\"";
root["state"] = "NA"; root["state"] = "NA";
} else { } else {
@ -784,7 +815,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
auto call = obj->make_call(); auto call = obj->make_call();
if (request->hasParam("option")) { if (request->hasParam("option")) {
String option = request->getParam("option")->value(); auto option = request->getParam("option")->value();
call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations)
} }
@ -834,29 +865,26 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
auto call = obj->make_call(); auto call = obj->make_call();
if (request->hasParam("mode")) { if (request->hasParam("mode")) {
String mode = request->getParam("mode")->value(); auto mode = request->getParam("mode")->value();
call.set_mode(mode.c_str()); call.set_mode(mode.c_str());
} }
if (request->hasParam("target_temperature_high")) { if (request->hasParam("target_temperature_high")) {
String value = request->getParam("target_temperature_high")->value(); auto target_temperature_high = parse_number<float>(request->getParam("target_temperature_high")->value().c_str());
optional<float> value_f = parse_number<float>(value.c_str()); if (target_temperature_high.has_value())
if (value_f.has_value()) call.set_target_temperature_high(*target_temperature_high);
call.set_target_temperature_high(*value_f);
} }
if (request->hasParam("target_temperature_low")) { if (request->hasParam("target_temperature_low")) {
String value = request->getParam("target_temperature_low")->value(); auto target_temperature_low = parse_number<float>(request->getParam("target_temperature_low")->value().c_str());
optional<float> value_f = parse_number<float>(value.c_str()); if (target_temperature_low.has_value())
if (value_f.has_value()) call.set_target_temperature_low(*target_temperature_low);
call.set_target_temperature_low(*value_f);
} }
if (request->hasParam("target_temperature")) { if (request->hasParam("target_temperature")) {
String value = request->getParam("target_temperature")->value(); auto target_temperature = parse_number<float>(request->getParam("target_temperature")->value().c_str());
optional<float> value_f = parse_number<float>(value.c_str()); if (target_temperature.has_value())
if (value_f.has_value()) call.set_target_temperature(*target_temperature);
call.set_target_temperature(*value_f);
} }
this->schedule_([call]() mutable { call.perform(); }); this->schedule_([call]() mutable { call.perform(); });
@ -1231,5 +1259,3 @@ void WebServer::schedule_(std::function<void()> &&f) {
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "list_entities.h" #include "list_entities.h"
#include "esphome/components/web_server_base/web_server_base.h" #include "esphome/components/web_server_base/web_server_base.h"
@ -291,5 +289,3 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -5,7 +5,15 @@ from esphome.core import coroutine_with_priority, CORE
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
AUTO_LOAD = ["async_tcp"]
def AUTO_LOAD():
if CORE.using_arduino:
return ["async_tcp"]
if CORE.using_esp_idf:
return ["web_server_idf"]
return []
web_server_base_ns = cg.esphome_ns.namespace("web_server_base") web_server_base_ns = cg.esphome_ns.namespace("web_server_base")
WebServerBase = web_server_base_ns.class_("WebServerBase", cg.Component) WebServerBase = web_server_base_ns.class_("WebServerBase", cg.Component)
@ -23,9 +31,10 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
if CORE.is_esp32: if CORE.using_arduino:
cg.add_library("WiFi", None) if CORE.is_esp32:
cg.add_library("FS", None) cg.add_library("WiFi", None)
cg.add_library("Update", None) cg.add_library("FS", None)
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json cg.add_library("Update", None)
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0") # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")

View file

@ -1,16 +1,17 @@
#ifdef USE_ARDUINO
#include "web_server_base.h" #include "web_server_base.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include <StreamString.h> #include "esphome/core/helpers.h"
#ifdef USE_ARDUINO
#include <StreamString.h>
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <Update.h> #include <Update.h>
#endif #endif
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#include <Updater.h> #include <Updater.h>
#endif #endif
#endif
namespace esphome { namespace esphome {
namespace web_server_base { namespace web_server_base {
@ -24,18 +25,22 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
handler = new internal::AuthMiddlewareHandler(handler, &credentials_); handler = new internal::AuthMiddlewareHandler(handler, &credentials_);
} }
this->handlers_.push_back(handler); this->handlers_.push_back(handler);
if (this->server_ != nullptr) if (this->server_ != nullptr) {
this->server_->addHandler(handler); this->server_->addHandler(handler);
}
} }
void report_ota_error() { void report_ota_error() {
#ifdef USE_ARDUINO
StreamString ss; StreamString ss;
Update.printError(ss); Update.printError(ss);
ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str()); ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str());
#endif
} }
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
uint8_t *data, size_t len, bool final) { uint8_t *data, size_t len, bool final) {
#ifdef USE_ARDUINO
bool success; bool success;
if (index == 0) { if (index == 0) {
ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str());
@ -45,9 +50,10 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin
// NOLINTNEXTLINE(readability-static-accessed-through-instance) // NOLINTNEXTLINE(readability-static-accessed-through-instance)
success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32_FRAMEWORK_ARDUINO
if (Update.isRunning()) if (Update.isRunning()) {
Update.abort(); Update.abort();
}
success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
#endif #endif
if (!success) { if (!success) {
@ -85,8 +91,10 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin
report_ota_error(); report_ota_error();
} }
} }
#endif
} }
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
#ifdef USE_ARDUINO
AsyncWebServerResponse *response; AsyncWebServerResponse *response;
if (!Update.hasError()) { if (!Update.hasError()) {
response = request->beginResponse(200, "text/plain", "Update Successful!"); response = request->beginResponse(200, "text/plain", "Update Successful!");
@ -98,10 +106,13 @@ void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
} }
response->addHeader("Connection", "close"); response->addHeader("Connection", "close");
request->send(response); request->send(response);
#endif
} }
void WebServerBase::add_ota_handler() { void WebServerBase::add_ota_handler() {
#ifdef USE_ARDUINO
this->add_handler(new OTARequestHandler(this)); // NOLINT this->add_handler(new OTARequestHandler(this)); // NOLINT
#endif
} }
float WebServerBase::get_setup_priority() const { float WebServerBase::get_setup_priority() const {
// Before WiFi (captive portal) // Before WiFi (captive portal)
@ -110,5 +121,3 @@ float WebServerBase::get_setup_priority() const {
} // namespace web_server_base } // namespace web_server_base
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,14 +1,17 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include <memory> #include <memory>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "esphome/core/component.h" #include "esphome/core/component.h"
#ifdef USE_ARDUINO
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#elif USE_ESP_IDF
#include "esphome/core/hal.h"
#include "esphome/components/web_server_idf/web_server_idf.h"
#endif
namespace esphome { namespace esphome {
namespace web_server_base { namespace web_server_base {
@ -141,5 +144,3 @@ class OTARequestHandler : public AsyncWebHandler {
} // namespace web_server_base } // namespace web_server_base
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -0,0 +1,14 @@
import esphome.config_validation as cv
from esphome.components.esp32 import add_idf_sdkconfig_option
CODEOWNERS = ["@dentra"]
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
cv.only_with_esp_idf,
)
async def to_code(config):
# Increase the maximum supported size of headers section in HTTP request packet to be processed by the server
add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024)

View file

@ -0,0 +1,374 @@
#ifdef USE_ESP_IDF
#include <cstdarg>
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esp_tls_crypto.h"
#include "web_server_idf.h"
namespace esphome {
namespace web_server_idf {
#ifndef HTTPD_409
#define HTTPD_409 "409 Conflict"
#endif
#define CRLF_STR "\r\n"
#define CRLF_LEN (sizeof(CRLF_STR) - 1)
static const char *const TAG = "web_server_idf";
void AsyncWebServer::end() {
if (this->server_) {
httpd_stop(this->server_);
this->server_ = nullptr;
}
}
void AsyncWebServer::begin() {
if (this->server_) {
this->end();
}
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = this->port_;
config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
if (httpd_start(&this->server_, &config) == ESP_OK) {
const httpd_uri_t handler_get = {
.uri = "",
.method = HTTP_GET,
.handler = AsyncWebServer::request_handler,
.user_ctx = this,
};
httpd_register_uri_handler(this->server_, &handler_get);
const httpd_uri_t handler_post = {
.uri = "",
.method = HTTP_POST,
.handler = AsyncWebServer::request_handler,
.user_ctx = this,
};
httpd_register_uri_handler(this->server_, &handler_post);
}
}
esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) {
ESP_LOGV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
AsyncWebServerRequest req(r);
auto *server = static_cast<AsyncWebServer *>(r->user_ctx);
for (auto *handler : server->handlers_) {
if (handler->canHandle(&req)) {
// At now process only basic requests.
// OTA requires multipart request support and handleUpload for it
handler->handleRequest(&req);
return ESP_OK;
}
}
if (server->on_not_found_) {
server->on_not_found_(&req);
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
AsyncWebServerRequest::~AsyncWebServerRequest() {
delete this->rsp_;
for (const auto &pair : this->params_) {
delete pair.second; // NOLINT(cppcoreguidelines-owning-memory)
}
}
optional<std::string> AsyncWebServerRequest::get_header(const char *name) const {
size_t buf_len = httpd_req_get_hdr_value_len(*this, name);
if (buf_len == 0) {
return {};
}
auto buf = std::unique_ptr<char[]>(new char[++buf_len]);
if (!buf) {
ESP_LOGE(TAG, "No enough memory for get header %s", name);
return {};
}
if (httpd_req_get_hdr_value_str(*this, name, buf.get(), buf_len) != ESP_OK) {
return {};
}
return {buf.get()};
}
std::string AsyncWebServerRequest::url() const {
auto *str = strchr(this->req_->uri, '?');
if (str == nullptr) {
return this->req_->uri;
}
return std::string(this->req_->uri, str - this->req_->uri);
}
std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }
void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
}
void AsyncWebServerRequest::send(int code, const char *content_type, const char *content) {
this->init_response_(nullptr, code, content_type);
if (content) {
httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
} else {
httpd_resp_send(*this, nullptr, 0);
}
}
void AsyncWebServerRequest::redirect(const std::string &url) {
httpd_resp_set_status(*this, "302 Found");
httpd_resp_set_hdr(*this, "Location", url.c_str());
httpd_resp_send(*this, nullptr, 0);
}
void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) {
httpd_resp_set_status(*this, code == 200 ? HTTPD_200
: code == 404 ? HTTPD_404
: code == 409 ? HTTPD_409
: to_string(code).c_str());
if (content_type && *content_type) {
httpd_resp_set_type(*this, content_type);
}
httpd_resp_set_hdr(*this, "Accept-Ranges", "none");
for (const auto &pair : DefaultHeaders::Instance().headers_) {
httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str());
}
delete this->rsp_;
this->rsp_ = rsp;
}
bool AsyncWebServerRequest::authenticate(const char *username, const char *password) const {
if (username == nullptr || password == nullptr || *username == 0) {
return true;
}
auto auth = this->get_header("Authorization");
if (!auth.has_value()) {
return false;
}
auto *auth_str = auth.value().c_str();
const auto auth_prefix_len = sizeof("Basic ") - 1;
if (strncmp("Basic ", auth_str, auth_prefix_len) != 0) {
ESP_LOGW(TAG, "Only Basic authorization supported yet");
return false;
}
std::string user_info;
user_info += username;
user_info += ':';
user_info += password;
size_t n = 0, out;
esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
auto digest = std::unique_ptr<char[]>(new char[n + 1]);
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
return strncmp(digest.get(), auth_str + auth_prefix_len, auth.value().size() - auth_prefix_len) == 0;
}
void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
httpd_resp_set_hdr(*this, "Connection", "keep-alive");
auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required");
httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str());
httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr);
}
static std::string url_decode(const std::string &in) {
std::string out;
out.reserve(in.size());
for (std::size_t i = 0; i < in.size(); ++i) {
if (in[i] == '%') {
++i;
if (i + 1 < in.size()) {
auto c = parse_hex<uint8_t>(&in[i], 2);
if (c.has_value()) {
out += static_cast<char>(*c);
++i;
} else {
out += '%';
out += in[i++];
out += in[i];
}
} else {
out += '%';
out += in[i];
}
} else if (in[i] == '+') {
out += ' ';
} else {
out += in[i];
}
}
return out;
}
AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
auto find = this->params_.find(name);
if (find != this->params_.end()) {
return find->second;
}
auto query_len = httpd_req_get_url_query_len(this->req_);
if (query_len == 0) {
return nullptr;
}
auto query_str = std::unique_ptr<char[]>(new char[++query_len]);
if (!query_str) {
ESP_LOGE(TAG, "No enough memory for get query param");
return nullptr;
}
auto res = httpd_req_get_url_query_str(*this, query_str.get(), query_len);
if (res != ESP_OK) {
ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res));
return nullptr;
}
auto query_val = std::unique_ptr<char[]>(new char[query_len]);
if (!query_val) {
ESP_LOGE(TAG, "No enough memory for get query param value");
return nullptr;
}
res = httpd_query_key_value(query_str.get(), name.c_str(), query_val.get(), query_len);
if (res != ESP_OK) {
this->params_.insert({name, nullptr});
return nullptr;
}
query_str.release();
auto decoded = url_decode(query_val.get());
query_val.release();
auto *param = new AsyncWebParameter(decoded); // NOLINT(cppcoreguidelines-owning-memory)
this->params_.insert(std::make_pair(name, param));
return param;
}
void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
httpd_resp_set_hdr(*this->req_, name, value);
}
void AsyncResponseStream::print(float value) { this->print(to_string(value)); }
void AsyncResponseStream::printf(const char *fmt, ...) {
std::string str;
va_list args;
va_start(args, fmt);
size_t length = vsnprintf(nullptr, 0, fmt, args);
va_end(args);
str.resize(length);
va_start(args, fmt);
vsnprintf(&str[0], length + 1, fmt, args);
va_end(args);
this->print(str);
}
AsyncEventSource::~AsyncEventSource() {
for (auto *ses : this->sessions_) {
delete ses; // NOLINT(cppcoreguidelines-owning-memory)
}
}
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
auto *rsp = new AsyncEventSourceResponse(request, this); // NOLINT(cppcoreguidelines-owning-memory)
if (this->on_connect_) {
this->on_connect_(rsp);
}
this->sessions_.insert(rsp);
}
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
for (auto *ses : this->sessions_) {
ses->send(message, event, id, reconnect);
}
}
AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server)
: server_(server) {
httpd_req_t *req = *request;
httpd_resp_set_status(req, HTTPD_200);
httpd_resp_set_type(req, "text/event-stream");
httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
httpd_resp_set_hdr(req, "Connection", "keep-alive");
httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
req->sess_ctx = this;
req->free_ctx = AsyncEventSourceResponse::destroy;
this->hd_ = req->handle;
this->fd_ = httpd_req_to_sockfd(req);
}
void AsyncEventSourceResponse::destroy(void *ptr) {
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
rsp->server_->sessions_.erase(rsp);
delete rsp; // NOLINT(cppcoreguidelines-owning-memory)
}
void AsyncEventSourceResponse::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
if (this->fd_ == 0) {
return;
}
std::string ev;
if (reconnect) {
ev.append("retry: ", sizeof("retry: ") - 1);
ev.append(to_string(reconnect));
ev.append(CRLF_STR, CRLF_LEN);
}
if (id) {
ev.append("id: ", sizeof("id: ") - 1);
ev.append(to_string(id));
ev.append(CRLF_STR, CRLF_LEN);
}
if (event && *event) {
ev.append("event: ", sizeof("event: ") - 1);
ev.append(event);
ev.append(CRLF_STR, CRLF_LEN);
}
if (message && *message) {
ev.append("data: ", sizeof("data: ") - 1);
ev.append(message);
ev.append(CRLF_STR, CRLF_LEN);
}
if (ev.empty()) {
return;
}
ev.append(CRLF_STR, CRLF_LEN);
// Sending chunked content prelude
auto cs = str_snprintf("%x" CRLF_STR, 4 * sizeof(ev.size()) + CRLF_LEN, ev.size());
httpd_socket_send(this->hd_, this->fd_, cs.c_str(), cs.size(), 0);
// Sendiing content chunk
httpd_socket_send(this->hd_, this->fd_, ev.c_str(), ev.size(), 0);
// Indicate end of chunk
httpd_socket_send(this->hd_, this->fd_, CRLF_STR, CRLF_LEN, 0);
}
} // namespace web_server_idf
} // namespace esphome
#endif // !defined(USE_ESP_IDF)

View file

@ -0,0 +1,277 @@
#pragma once
#ifdef USE_ESP_IDF
#include <esp_http_server.h>
#include <string>
#include <functional>
#include <vector>
#include <map>
#include <set>
namespace esphome {
namespace web_server_idf {
#define F(string_literal) (string_literal)
#define PGM_P const char *
#define strncpy_P strncpy
using String = std::string;
class AsyncWebParameter {
public:
AsyncWebParameter(std::string value) : value_(std::move(value)) {}
const std::string &value() const { return this->value_; }
protected:
std::string value_;
};
class AsyncWebServerRequest;
class AsyncWebServerResponse {
public:
AsyncWebServerResponse(const AsyncWebServerRequest *req) : req_(req) {}
virtual ~AsyncWebServerResponse() {}
// NOLINTNEXTLINE(readability-identifier-naming)
void addHeader(const char *name, const char *value);
virtual const char *get_content_data() const = 0;
virtual size_t get_content_size() const = 0;
protected:
const AsyncWebServerRequest *req_;
};
class AsyncWebServerResponseEmpty : public AsyncWebServerResponse {
public:
AsyncWebServerResponseEmpty(const AsyncWebServerRequest *req) : AsyncWebServerResponse(req) {}
const char *get_content_data() const override { return nullptr; };
size_t get_content_size() const override { return 0; };
};
class AsyncWebServerResponseContent : public AsyncWebServerResponse {
public:
AsyncWebServerResponseContent(const AsyncWebServerRequest *req, std::string content)
: AsyncWebServerResponse(req), content_(std::move(content)) {}
const char *get_content_data() const override { return this->content_.c_str(); };
size_t get_content_size() const override { return this->content_.size(); };
protected:
std::string content_;
};
class AsyncResponseStream : public AsyncWebServerResponse {
public:
AsyncResponseStream(const AsyncWebServerRequest *req) : AsyncWebServerResponse(req) {}
const char *get_content_data() const override { return this->content_.c_str(); };
size_t get_content_size() const override { return this->content_.size(); };
void print(const char *str) { this->content_.append(str); }
void print(const std::string &str) { this->content_.append(str); }
void print(float value);
void printf(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
protected:
std::string content_;
};
class AsyncWebServerResponseProgmem : public AsyncWebServerResponse {
public:
AsyncWebServerResponseProgmem(const AsyncWebServerRequest *req, const uint8_t *data, const size_t size)
: AsyncWebServerResponse(req), data_(data), size_(size) {}
const char *get_content_data() const override { return reinterpret_cast<const char *>(this->data_); };
size_t get_content_size() const override { return this->size_; };
protected:
const uint8_t *data_;
const size_t size_;
};
class AsyncWebServerRequest {
// FIXME friend class AsyncWebServerResponse;
friend class AsyncWebServer;
public:
~AsyncWebServerRequest();
http_method method() const { return static_cast<http_method>(this->req_->method); }
std::string url() const;
std::string host() const;
// NOLINTNEXTLINE(readability-identifier-naming)
size_t contentLength() const { return this->req_->content_len; }
bool authenticate(const char *username, const char *password) const;
// NOLINTNEXTLINE(readability-identifier-naming)
void requestAuthentication(const char *realm = nullptr) const;
void redirect(const std::string &url);
void send(AsyncWebServerResponse *response);
void send(int code, const char *content_type = nullptr, const char *content = nullptr);
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncWebServerResponse *beginResponse(int code, const char *content_type) {
auto *res = new AsyncWebServerResponseEmpty(this); // NOLINT(cppcoreguidelines-owning-memory)
this->init_response_(res, 200, content_type);
return res;
}
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncWebServerResponse *beginResponse(int code, const char *content_type, const std::string &content) {
auto *res = new AsyncWebServerResponseContent(this, content); // NOLINT(cppcoreguidelines-owning-memory)
this->init_response_(res, code, content_type);
return res;
}
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncWebServerResponse *beginResponse_P(int code, const char *content_type, const uint8_t *data,
const size_t data_size) {
auto *res = new AsyncWebServerResponseProgmem(this, data, data_size); // NOLINT(cppcoreguidelines-owning-memory)
this->init_response_(res, code, content_type);
return res;
}
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncResponseStream *beginResponseStream(const char *content_type) {
auto *res = new AsyncResponseStream(this); // NOLINT(cppcoreguidelines-owning-memory)
this->init_response_(res, 200, content_type);
return res;
}
// NOLINTNEXTLINE(readability-identifier-naming)
bool hasParam(const std::string &name) { return this->getParam(name) != nullptr; }
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncWebParameter *getParam(const std::string &name);
// NOLINTNEXTLINE(readability-identifier-naming)
bool hasArg(const char *name) { return this->hasParam(name); }
std::string arg(const std::string &name) {
auto *param = this->getParam(name);
if (param) {
return param->value();
}
return {};
}
operator httpd_req_t *() const { return this->req_; }
optional<std::string> get_header(const char *name) const;
protected:
httpd_req_t *req_;
AsyncWebServerResponse *rsp_{};
std::map<std::string, AsyncWebParameter *> params_;
AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type);
};
class AsyncWebHandler;
class AsyncWebServer {
public:
AsyncWebServer(uint16_t port) : port_(port){};
~AsyncWebServer() { this->end(); }
// NOLINTNEXTLINE(readability-identifier-naming)
void onNotFound(std::function<void(AsyncWebServerRequest *request)> fn) { on_not_found_ = std::move(fn); }
void begin();
void end();
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncWebHandler &addHandler(AsyncWebHandler *handler) {
this->handlers_.push_back(handler);
return *handler;
}
protected:
uint16_t port_{};
httpd_handle_t server_{};
static esp_err_t request_handler(httpd_req_t *r);
std::vector<AsyncWebHandler *> handlers_;
std::function<void(AsyncWebServerRequest *request)> on_not_found_{};
};
class AsyncWebHandler {
public:
virtual ~AsyncWebHandler() {}
// NOLINTNEXTLINE(readability-identifier-naming)
virtual bool canHandle(AsyncWebServerRequest *request) { return false; }
// NOLINTNEXTLINE(readability-identifier-naming)
virtual void handleRequest(AsyncWebServerRequest *request) {}
// NOLINTNEXTLINE(readability-identifier-naming)
virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data,
size_t len, bool final) {}
// NOLINTNEXTLINE(readability-identifier-naming)
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {}
// NOLINTNEXTLINE(readability-identifier-naming)
virtual bool isRequestHandlerTrivial() { return true; }
};
class AsyncEventSource;
class AsyncEventSourceResponse {
friend class AsyncEventSource;
public:
void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
protected:
AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server);
static void destroy(void *p);
AsyncEventSource *server_;
httpd_handle_t hd_{};
int fd_{};
};
using AsyncEventSourceClient = AsyncEventSourceResponse;
class AsyncEventSource : public AsyncWebHandler {
friend class AsyncEventSourceResponse;
using connect_handler_t = std::function<void(AsyncEventSourceClient *)>;
public:
AsyncEventSource(std::string url) : url_(std::move(url)) {}
~AsyncEventSource() override;
// NOLINTNEXTLINE(readability-identifier-naming)
bool canHandle(AsyncWebServerRequest *request) override {
return request->method() == HTTP_GET && request->url() == this->url_;
}
// NOLINTNEXTLINE(readability-identifier-naming)
void handleRequest(AsyncWebServerRequest *request) override;
// NOLINTNEXTLINE(readability-identifier-naming)
void onConnect(connect_handler_t cb) { this->on_connect_ = std::move(cb); }
void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
protected:
std::string url_;
std::set<AsyncEventSourceResponse *> sessions_;
connect_handler_t on_connect_{};
};
class DefaultHeaders {
friend class AsyncWebServerRequest;
public:
// NOLINTNEXTLINE(readability-identifier-naming)
void addHeader(const char *name, const char *value) { this->headers_.emplace_back(name, value); }
// NOLINTNEXTLINE(readability-identifier-naming)
static DefaultHeaders &Instance() {
static DefaultHeaders instance;
return instance;
}
protected:
std::vector<std::pair<std::string, std::string>> headers_;
};
} // namespace web_server_idf
} // namespace esphome
using namespace esphome::web_server_idf; // NOLINT(google-global-names-in-headers)
#endif // !defined(USE_ESP_IDF)

View file

@ -0,0 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
DEVICE_CLASS_DISTANCE,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@kahrendt"]
zio_ultrasonic_ns = cg.esphome_ns.namespace("zio_ultrasonic")
ZioUltrasonicComponent = zio_ultrasonic_ns.class_(
"ZioUltrasonicComponent", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
ZioUltrasonicComponent,
unit_of_measurement="mm",
accuracy_decimals=0,
device_class=DEVICE_CLASS_DISTANCE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x00))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,31 @@
#include "zio_ultrasonic.h"
#include "esphome/core/log.h"
namespace esphome {
namespace zio_ultrasonic {
static const char *const TAG = "zio_ultrasonic";
void ZioUltrasonicComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Zio Ultrasonic Sensor:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Sensor:", this);
}
void ZioUltrasonicComponent::update() {
uint16_t distance;
// Read an unsigned two byte integerfrom register 0x01 which gives distance in mm
if (!this->read_byte_16(0x01, &distance)) {
ESP_LOGE(TAG, "Error reading data from Zio Ultrasonic");
this->publish_state(NAN);
} else {
this->publish_state(distance);
}
}
} // namespace zio_ultrasonic
} // namespace esphome

View file

@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
static const char *const TAG = "Zio Ultrasonic";
namespace esphome {
namespace zio_ultrasonic {
class ZioUltrasonicComponent : public i2c::I2CDevice, public PollingComponent, public sensor::Sensor {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void dump_config() override;
void update() override;
};
} // namespace zio_ultrasonic
} // namespace esphome

View file

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2023.7.0-dev" __version__ = "2023.8.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (
@ -262,6 +262,7 @@ CONF_FINGER_ID = "finger_id"
CONF_FINGERPRINT_COUNT = "fingerprint_count" CONF_FINGERPRINT_COUNT = "fingerprint_count"
CONF_FLASH_LENGTH = "flash_length" CONF_FLASH_LENGTH = "flash_length"
CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length" CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length"
CONF_FLOW = "flow"
CONF_FLOW_CONTROL_PIN = "flow_control_pin" CONF_FLOW_CONTROL_PIN = "flow_control_pin"
CONF_FOR = "for" CONF_FOR = "for"
CONF_FORCE_UPDATE = "force_update" CONF_FORCE_UPDATE = "force_update"
@ -286,6 +287,7 @@ CONF_GPIO = "gpio"
CONF_GREEN = "green" CONF_GREEN = "green"
CONF_GROUP = "group" CONF_GROUP = "group"
CONF_HARDWARE_UART = "hardware_uart" CONF_HARDWARE_UART = "hardware_uart"
CONF_HEAD = "head"
CONF_HEARTBEAT = "heartbeat" CONF_HEARTBEAT = "heartbeat"
CONF_HEAT_ACTION = "heat_action" CONF_HEAT_ACTION = "heat_action"
CONF_HEAT_DEADBAND = "heat_deadband" CONF_HEAT_DEADBAND = "heat_deadband"
@ -891,6 +893,7 @@ UNIT_CENTIMETER = "cm"
UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNT_DECILITRE = "/dL"
UNIT_COUNTS_PER_CUBIC_METER = "#/m³" UNIT_COUNTS_PER_CUBIC_METER = "#/m³"
UNIT_CUBIC_METER = "" UNIT_CUBIC_METER = ""
UNIT_CUBIC_METER_PER_HOUR = "m³/h"
UNIT_DECIBEL = "dB" UNIT_DECIBEL = "dB"
UNIT_DECIBEL_MILLIWATT = "dBm" UNIT_DECIBEL_MILLIWATT = "dBm"
UNIT_DEGREE_PER_SECOND = "°/s" UNIT_DEGREE_PER_SECOND = "°/s"
@ -928,6 +931,7 @@ UNIT_PERCENT = "%"
UNIT_PH = "pH" UNIT_PH = "pH"
UNIT_PULSES = "pulses" UNIT_PULSES = "pulses"
UNIT_PULSES_PER_MINUTE = "pulses/min" UNIT_PULSES_PER_MINUTE = "pulses/min"
UNIT_REVOLUTIONS_PER_MINUTE = "RPM"
UNIT_SECOND = "s" UNIT_SECOND = "s"
UNIT_STEPS = "steps" UNIT_STEPS = "steps"
UNIT_VOLT = "V" UNIT_VOLT = "V"

View file

@ -57,21 +57,6 @@ struct Color {
inline bool operator!=(uint32_t colorcode) { // NOLINT inline bool operator!=(uint32_t colorcode) { // NOLINT
return this->raw_32 != colorcode; return this->raw_32 != colorcode;
} }
inline Color &operator=(const Color &rhs) ALWAYS_INLINE { // NOLINT
this->r = rhs.r;
this->g = rhs.g;
this->b = rhs.b;
this->w = rhs.w;
return *this;
}
inline Color &operator=(uint32_t colorcode) ALWAYS_INLINE {
this->w = (colorcode >> 24) & 0xFF;
this->r = (colorcode >> 16) & 0xFF;
this->g = (colorcode >> 8) & 0xFF;
this->b = (colorcode >> 0) & 0xFF;
return *this;
}
inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; } inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; }
inline Color operator*(uint8_t scale) const ALWAYS_INLINE { inline Color operator*(uint8_t scale) const ALWAYS_INLINE {
return Color(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale), return Color(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale),

View file

@ -158,7 +158,7 @@ board_build.filesystem_size = 0.5m
platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform_packages = platform_packages =
; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.3.0/rp2040-3.3.0.zip
framework = arduino framework = arduino
lib_deps = lib_deps =

View file

@ -9,7 +9,7 @@ pyserial==3.5
platformio==6.1.7 # When updating platformio, also update Dockerfile platformio==6.1.7 # When updating platformio, also update Dockerfile
esptool==4.6.2 esptool==4.6.2
click==8.1.3 click==8.1.3
esphome-dashboard==20230621.0 esphome-dashboard==20230711.0
aioesphomeapi==15.0.0 aioesphomeapi==15.0.0
zeroconf==0.69.0 zeroconf==0.69.0

Some files were not shown because too many files have changed in this diff Show more