mirror of
https://github.com/esphome/esphome.git
synced 2024-12-28 16:31:44 +01:00
Merge branch 'dev' into optolink
This commit is contained in:
commit
92e9231d94
107 changed files with 4019 additions and 294 deletions
10
CODEOWNERS
10
CODEOWNERS
|
@ -17,10 +17,11 @@ esphome/components/adc/* @esphome/core
|
|||
esphome/components/adc128s102/* @DeerMaximum
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
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_plus/* @jeromelaban
|
||||
esphome/components/alarm_control_panel/* @grahambrown11
|
||||
esphome/components/alpha3/* @jan-hofmeier
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
esphome/components/am43/sensor/* @buxtronix
|
||||
|
@ -31,6 +32,7 @@ esphome/components/api/* @OttoWinter
|
|||
esphome/components/as7341/* @mrgnr
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/atm90e26/* @danieltwagner
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/ballu/* @bazuchan
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
|
@ -76,6 +78,7 @@ esphome/components/display_menu_base/* @numo68
|
|||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
|
@ -102,6 +105,7 @@ esphome/components/gp8403/* @jesserockz
|
|||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/grove_tb6612fng/* @max246
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/haier/* @paveldn
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
|
@ -201,6 +205,7 @@ esphome/components/output/* @esphome/core
|
|||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pcf8563/* @KoenBreeman
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
|
@ -295,6 +300,7 @@ esphome/components/tof10120/* @wstrzalka
|
|||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/touchscreen/* @jesserockz
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tt21100/* @kroimon
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/number/* @frankiboy1
|
||||
|
@ -311,6 +317,7 @@ esphome/components/version/* @esphome/core
|
|||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/whynter/* @aeonsablaze
|
||||
esphome/components/wiegand/* @ssieb
|
||||
|
@ -322,3 +329,4 @@ esphome/components/xiaomi_mhoc401/* @vevsvevs
|
|||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xl9535/* @mreditor97
|
||||
esphome/components/xpt2046/* @nielsnl68 @numo68
|
||||
esphome/components/zio_ultrasonic/* @kahrendt
|
||||
|
|
|
@ -32,7 +32,7 @@ from esphome.const import (
|
|||
SECRETS_FILES,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.helpers import indent
|
||||
from esphome.helpers import indent, is_ip_address
|
||||
from esphome.util import (
|
||||
run_external_command,
|
||||
run_external_process,
|
||||
|
@ -308,8 +308,10 @@ def upload_program(config, args, host):
|
|||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
|
||||
if (
|
||||
get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED]
|
||||
) and CONF_MQTT in config:
|
||||
not is_ip_address(CORE.address)
|
||||
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
|
||||
and CONF_MQTT in config
|
||||
):
|
||||
from esphome import mqtt
|
||||
|
||||
host = mqtt.get_esphome_device_ip(
|
||||
|
|
|
@ -58,6 +58,6 @@ async def to_code(config):
|
|||
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -3,26 +3,31 @@ import esphome.config_validation as cv
|
|||
from esphome.components import sensor, ble_client
|
||||
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TVOC,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TVOC,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_RADIATOR,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@ncareau", "@jeromelaban"]
|
||||
CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
|
||||
|
||||
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
|
||||
AirthingsWaveBase = airthings_wave_base_ns.class_(
|
||||
"AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
|
||||
|
@ -34,9 +39,9 @@ BASE_SCHEMA = (
|
|||
{
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=0,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
|
@ -52,11 +57,21 @@ BASE_SCHEMA = (
|
|||
),
|
||||
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
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"))
|
||||
|
@ -69,15 +84,20 @@ async def wave_base_to_code(var, config):
|
|||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
if config_humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(config_humidity)
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
if config_temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(config_temperature)
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
if config_pressure := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(config_pressure)
|
||||
cg.add(var.set_pressure(sens))
|
||||
if CONF_TVOC in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
if config_tvoc := config.get(CONF_TVOC):
|
||||
sens = await sensor.new_sensor(config_tvoc)
|
||||
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))
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#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
|
||||
|
||||
namespace esphome {
|
||||
|
@ -18,22 +21,26 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
|||
}
|
||||
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->handle_ = 0;
|
||||
this->acp_handle_ = 0;
|
||||
this->cccd_handle_ = 0;
|
||||
ESP_LOGW(TAG, "Disconnected!");
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle_ = 0;
|
||||
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());
|
||||
break;
|
||||
if (this->request_read_values_()) {
|
||||
if (!this->read_battery_next_update_) {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
} else {
|
||||
// delay setting node_state to ESTABLISHED until confirmation of the notify registration
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -50,6 +57,20 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
|||
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:
|
||||
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; }
|
||||
|
||||
void AirthingsWaveBase::update() {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
if (!this->parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
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_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (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
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#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
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
@ -14,6 +17,11 @@
|
|||
namespace esphome {
|
||||
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 {
|
||||
public:
|
||||
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_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
|
||||
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:
|
||||
bool is_valid_voc_value_(uint16_t voc);
|
||||
|
||||
virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0;
|
||||
void request_read_values_();
|
||||
bool request_read_values_();
|
||||
virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *tvoc_sensor_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
|
||||
uint16_t handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
|
||||
espbt::ESPBTUUID service_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
|
||||
|
|
|
@ -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)) {
|
||||
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() {
|
||||
|
@ -42,11 +39,14 @@ void AirthingsWaveMini::dump_config() {
|
|||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||
}
|
||||
|
||||
AirthingsWaveMini::AirthingsWaveMini() {
|
||||
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID);
|
||||
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
|
||||
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_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
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
namespace esphome {
|
||||
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 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 {
|
||||
public:
|
||||
|
@ -17,7 +20,7 @@ class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
|
|||
void dump_config() override;
|
||||
|
||||
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 {
|
||||
uint16_t unused01;
|
||||
|
|
|
@ -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)) {
|
||||
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 {
|
||||
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; }
|
||||
|
@ -66,6 +63,7 @@ void AirthingsWavePlus::dump_config() {
|
|||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||
|
||||
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
|
||||
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
|
||||
|
@ -73,8 +71,10 @@ void AirthingsWavePlus::dump_config() {
|
|||
}
|
||||
|
||||
AirthingsWavePlus::AirthingsWavePlus() {
|
||||
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID);
|
||||
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
|
||||
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_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
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
namespace esphome {
|
||||
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 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 {
|
||||
public:
|
||||
|
@ -24,7 +27,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
|
|||
bool is_valid_radon_value_(uint16_t radon);
|
||||
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_long_term_sensor_{nullptr};
|
||||
|
|
|
@ -53,12 +53,12 @@ async def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await airthings_wave_base.wave_base_to_code(var, config)
|
||||
|
||||
if CONF_RADON in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON])
|
||||
if config_radon := config.get(CONF_RADON):
|
||||
sens = await sensor.new_sensor(config_radon)
|
||||
cg.add(var.set_radon(sens))
|
||||
if CONF_RADON_LONG_TERM in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
|
||||
if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
|
||||
sens = await sensor.new_sensor(config_radon_long_term)
|
||||
cg.add(var.set_radon_long_term(sens))
|
||||
if CONF_CO2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
||||
if config_co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(config_co2)
|
||||
cg.add(var.set_co2(sens))
|
||||
|
|
1
esphome/components/alpha3/__init__.py
Normal file
1
esphome/components/alpha3/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@jan-hofmeier"]
|
189
esphome/components/alpha3/alpha3.cpp
Normal file
189
esphome/components/alpha3/alpha3.cpp
Normal 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
|
73
esphome/components/alpha3/alpha3.h
Normal file
73
esphome/components/alpha3/alpha3.h
Normal 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
|
85
esphome/components/alpha3/sensor.py
Normal file
85
esphome/components/alpha3/sensor.py
Normal 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))
|
1
esphome/components/atm90e26/__init__.py
Normal file
1
esphome/components/atm90e26/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@danieltwagner"]
|
235
esphome/components/atm90e26/atm90e26.cpp
Normal file
235
esphome/components/atm90e26/atm90e26.cpp
Normal 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
|
72
esphome/components/atm90e26/atm90e26.h
Normal file
72
esphome/components/atm90e26/atm90e26.h
Normal 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
|
70
esphome/components/atm90e26/atm90e26_reg.h
Normal file
70
esphome/components/atm90e26/atm90e26_reg.h
Normal 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
|
157
esphome/components/atm90e26/sensor.py
Normal file
157
esphome/components/atm90e26/sensor.py
Normal 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]))
|
|
@ -359,6 +359,18 @@ def validate_multi_click_timing(value):
|
|||
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(
|
||||
{
|
||||
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.Optional(CONF_ON_CLICK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
cv.Optional(CONF_ON_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
validate_click_timing,
|
||||
),
|
||||
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
validate_click_timing,
|
||||
),
|
||||
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
|
||||
{
|
||||
|
|
|
@ -275,6 +275,10 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
|
|||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
|
||||
return this->proxy_->get_advertisement_parser_type();
|
||||
}
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -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,
|
||||
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;
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
esp_err_t read_characteristic(uint16_t handle);
|
||||
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
|
||||
|
|
|
@ -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) {
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->get_address() == address)
|
||||
|
@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
|
|||
}
|
||||
this->api_connection_ = api_connection;
|
||||
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
|
||||
this->parent_->recalculate_advertisement_parser_types();
|
||||
}
|
||||
|
||||
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->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) {
|
||||
|
|
|
@ -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;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
this->connections_.push_back(connection);
|
||||
|
|
|
@ -6,8 +6,10 @@ from esphome.const import (
|
|||
CONF_HUMIDITY,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
|
@ -17,8 +19,6 @@ from esphome.const import (
|
|||
UNIT_PERCENT,
|
||||
ICON_GAS_CYLINDER,
|
||||
ICON_GAUGE,
|
||||
ICON_THERMOMETER,
|
||||
ICON_WATER_PERCENT,
|
||||
)
|
||||
from . import (
|
||||
BME680BSECComponent,
|
||||
|
@ -35,7 +35,6 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
|
|||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
UNIT_IAQ = "IAQ"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
ICON_TEST_TUBE = "mdi:test-tube"
|
||||
|
||||
TYPES = [
|
||||
CONF_TEMPERATURE,
|
||||
|
@ -53,7 +52,6 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
|
@ -62,16 +60,14 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_WATER_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
|
@ -97,14 +93,14 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
),
|
||||
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_TEST_TUBE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_TEST_TUBE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
|
|||
if (use_extended_id) {
|
||||
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
|
||||
} 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)
|
||||
size = CAN_MAX_DATA_LENGTH;
|
||||
|
|
|
@ -21,7 +21,6 @@ CONFIG_SCHEMA = cv.All(
|
|||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
)
|
||||
|
||||
|
@ -34,8 +33,9 @@ async def to_code(config):
|
|||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("DNSServer", None)
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("DNSServer", None)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "captive_portal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
@ -46,10 +44,12 @@ void CaptivePortal::start() {
|
|||
this->base_->add_ota_handler();
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
this->dns_server_->start(53, "*", (uint32_t) ip);
|
||||
#endif
|
||||
|
||||
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
||||
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) {
|
||||
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");
|
||||
req->send(response);
|
||||
return;
|
||||
|
@ -91,5 +91,3 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
|
|||
|
||||
} // namespace captive_portal
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include <memory>
|
||||
#ifdef USE_ARDUINO
|
||||
#include <DNSServer.h>
|
||||
#endif
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
@ -18,18 +18,22 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
|||
CaptivePortal(web_server_base::WebServerBase *base);
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
#ifdef USE_ARDUINO
|
||||
void loop() override {
|
||||
if (this->dns_server_ != nullptr)
|
||||
this->dns_server_->processNextRequest();
|
||||
}
|
||||
#endif
|
||||
float get_setup_priority() const override;
|
||||
void start();
|
||||
bool is_active() const { return this->active_; }
|
||||
void end() {
|
||||
this->active_ = false;
|
||||
this->base_->deinit();
|
||||
#ifdef USE_ARDUINO
|
||||
this->dns_server_->stop();
|
||||
this->dns_server_ = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest *request) override {
|
||||
|
@ -58,12 +62,12 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
|||
web_server_base::WebServerBase *base_;
|
||||
bool initialized_{false};
|
||||
bool active_{false};
|
||||
#ifdef USE_ARDUINO
|
||||
std::unique_ptr<DNSServer> dns_server_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace captive_portal
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -18,10 +18,11 @@ from esphome.core import coroutine_with_priority
|
|||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
display_ns = cg.esphome_ns.namespace("display")
|
||||
Display = display_ns.class_("Display")
|
||||
DisplayBuffer = display_ns.class_("DisplayBuffer")
|
||||
DisplayPage = display_ns.class_("DisplayPage")
|
||||
DisplayPagePtr = DisplayPage.operator("ptr")
|
||||
DisplayBufferRef = DisplayBuffer.operator("ref")
|
||||
DisplayRef = Display.operator("ref")
|
||||
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
|
||||
DisplayPageShowNextAction = display_ns.class_(
|
||||
"DisplayPageShowNextAction", automation.Action
|
||||
|
@ -96,7 +97,7 @@ async def setup_display_core_(var, config):
|
|||
pages = []
|
||||
for conf in config[CONF_PAGES]:
|
||||
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_)
|
||||
pages.append(page)
|
||||
|
|
|
@ -133,12 +133,10 @@ enum DisplayRotation {
|
|||
};
|
||||
|
||||
class Display;
|
||||
class DisplayBuffer;
|
||||
class DisplayPage;
|
||||
class DisplayOnPageChangeTrigger;
|
||||
|
||||
using display_writer_t = std::function<void(Display &)>;
|
||||
using display_buffer_writer_t = std::function<void(DisplayBuffer &)>;
|
||||
|
||||
#define LOG_DISPLAY(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
|
@ -411,10 +409,6 @@ class Display {
|
|||
|
||||
/// Internal method to set the display writer lambda.
|
||||
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_next_page();
|
||||
|
@ -499,9 +493,6 @@ class Display {
|
|||
class DisplayPage {
|
||||
public:
|
||||
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_next();
|
||||
void show_prev();
|
||||
|
|
1
esphome/components/duty_time/__init__.py
Normal file
1
esphome/components/duty_time/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@dudanov"]
|
103
esphome/components/duty_time/duty_time_sensor.cpp
Normal file
103
esphome/components/duty_time/duty_time_sensor.cpp
Normal 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
|
88
esphome/components/duty_time/duty_time_sensor.h
Normal file
88
esphome/components/duty_time/duty_time_sensor.h
Normal 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
|
121
esphome/components/duty_time/sensor.py
Normal file
121
esphome/components/duty_time/sensor.py
Normal 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)
|
|
@ -107,16 +107,16 @@ void ESP32BLETracker::loop() {
|
|||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||
}
|
||||
|
||||
bool bulk_parsed = false;
|
||||
|
||||
for (auto *listener : this->listeners_) {
|
||||
bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
if (this->raw_advertisements_) {
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
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++) {
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
@ -284,6 +284,32 @@ void ESP32BLETracker::end_of_scan_() {
|
|||
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||
client->app_id = ++this->app_id_;
|
||||
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) {
|
||||
|
|
|
@ -27,6 +27,11 @@ using namespace esp32_ble;
|
|||
|
||||
using adv_data_t = std::vector<uint8_t>;
|
||||
|
||||
enum AdvertisementParserType {
|
||||
PARSED_ADVERTISEMENTS,
|
||||
RAW_ADVERTISEMENTS,
|
||||
};
|
||||
|
||||
struct ServiceData {
|
||||
ESPBTUUID uuid;
|
||||
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) {
|
||||
return false;
|
||||
};
|
||||
virtual AdvertisementParserType get_advertisement_parser_type() {
|
||||
return AdvertisementParserType::PARSED_ADVERTISEMENTS;
|
||||
};
|
||||
void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
|
||||
|
||||
protected:
|
||||
|
@ -184,12 +192,9 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
|
|||
|
||||
void loop() override;
|
||||
|
||||
void register_listener(ESPBTDeviceListener *listener) {
|
||||
listener->set_parent(this);
|
||||
this->listeners_.push_back(listener);
|
||||
}
|
||||
|
||||
void register_listener(ESPBTDeviceListener *listener);
|
||||
void register_client(ESPBTClient *client);
|
||||
void recalculate_advertisement_parser_types();
|
||||
|
||||
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_active_;
|
||||
bool scanner_idle_;
|
||||
bool raw_advertisements_{false};
|
||||
bool parse_advertisements_{false};
|
||||
SemaphoreHandle_t scan_result_lock_;
|
||||
SemaphoreHandle_t scan_end_lock_;
|
||||
size_t scan_result_index_{0};
|
||||
|
|
|
@ -35,6 +35,7 @@ ETHERNET_TYPES = {
|
|||
"IP101": EthernetType.ETHERNET_TYPE_IP101,
|
||||
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
||||
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
|
||||
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
|
||||
}
|
||||
|
||||
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||
|
|
|
@ -84,7 +84,8 @@ void EthernetComponent::setup() {
|
|||
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
|
||||
break;
|
||||
}
|
||||
case ETHERNET_TYPE_KSZ8081: {
|
||||
case ETHERNET_TYPE_KSZ8081:
|
||||
case ETHERNET_TYPE_KSZ8081RNA: {
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
|
||||
#else
|
||||
|
@ -102,6 +103,12 @@ void EthernetComponent::setup() {
|
|||
this->eth_handle_ = nullptr;
|
||||
err = esp_eth_driver_install(ð_config, &this->eth_handle_);
|
||||
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 */
|
||||
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif attach error");
|
||||
|
@ -184,6 +191,10 @@ void EthernetComponent::dump_config() {
|
|||
eth_type = "KSZ8081";
|
||||
break;
|
||||
|
||||
case ETHERNET_TYPE_KSZ8081RNA:
|
||||
eth_type = "KSZ8081RNA";
|
||||
break;
|
||||
|
||||
default:
|
||||
eth_type = "Unknown";
|
||||
break;
|
||||
|
@ -385,6 +396,37 @@ bool EthernetComponent::powerdown() {
|
|||
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 esphome
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ enum EthernetType {
|
|||
ETHERNET_TYPE_IP101,
|
||||
ETHERNET_TYPE_JL1101,
|
||||
ETHERNET_TYPE_KSZ8081,
|
||||
ETHERNET_TYPE_KSZ8081RNA,
|
||||
};
|
||||
|
||||
struct ManualIP {
|
||||
|
@ -67,6 +68,8 @@ class EthernetComponent : public Component {
|
|||
|
||||
void start_connect_();
|
||||
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_;
|
||||
uint8_t phy_addr_{0};
|
||||
|
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
|||
import hashlib
|
||||
import os
|
||||
import re
|
||||
from packaging import version
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -66,13 +67,18 @@ def validate_pillow_installed(value):
|
|||
except ImportError as err:
|
||||
raise cv.Invalid(
|
||||
"Please install the pillow python package to use this feature. "
|
||||
"(pip install pillow)"
|
||||
'(pip install pillow">4.0.0,<10.0.0")'
|
||||
) from err
|
||||
|
||||
if PIL.__version__[0] < "4":
|
||||
if version.parse(PIL.__version__) < version.parse("4.0.0"):
|
||||
raise cv.Invalid(
|
||||
"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
|
||||
|
|
152
esphome/components/grove_tb6612fng/__init__.py
Normal file
152
esphome/components/grove_tb6612fng/__init__.py
Normal 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
|
171
esphome/components/grove_tb6612fng/grove_tb6612fng.cpp
Normal file
171
esphome/components/grove_tb6612fng/grove_tb6612fng.cpp
Normal 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
|
208
esphome/components/grove_tb6612fng/grove_tb6612fng.h
Normal file
208
esphome/components/grove_tb6612fng/grove_tb6612fng.h
Normal 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
|
|
@ -121,7 +121,7 @@ async def to_code(config):
|
|||
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ async def to_code(config):
|
|||
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ McpMode = mcp2515_ns.enum("CANCTRL_REQOP_MODE")
|
|||
|
||||
CAN_CLOCK = {
|
||||
"8MHZ": CanClock.MCP_8MHZ,
|
||||
"12MHZ": CanClock.MCP_12MHZ,
|
||||
"16MHZ": CanClock.MCP_16MHZ,
|
||||
"20MHZ": CanClock.MCP_20MHZ,
|
||||
}
|
||||
|
|
|
@ -16,11 +16,14 @@ const struct MCP2515::RxBnRegs MCP2515::RXB[N_RXBUFFERS] = {{MCP_RXB0CTRL, MCP_R
|
|||
bool MCP2515::setup_internal() {
|
||||
this->spi_setup();
|
||||
|
||||
if (this->reset_() == canbus::ERROR_FAIL)
|
||||
if (this->reset_() != canbus::ERROR_OK)
|
||||
return false;
|
||||
this->set_bitrate_(this->bit_rate_, this->mcp_clock_);
|
||||
this->set_mode_(this->mcp_mode_);
|
||||
ESP_LOGV(TAG, "setup done");
|
||||
if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK)
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -38,7 +41,7 @@ canbus::Error MCP2515::reset_() {
|
|||
set_registers_(MCP_TXB0CTRL, zeros, 14);
|
||||
set_registers_(MCP_TXB1CTRL, 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_RXB1CTRL, 0);
|
||||
|
@ -114,16 +117,12 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
|
|||
modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode);
|
||||
|
||||
uint32_t end_time = millis() + 10;
|
||||
bool mode_match = false;
|
||||
while (millis() < end_time) {
|
||||
uint8_t new_mode = read_register_(MCP_CANSTAT);
|
||||
new_mode &= CANSTAT_OPMOD;
|
||||
mode_match = new_mode == mode;
|
||||
if (mode_match) {
|
||||
break;
|
||||
}
|
||||
if ((read_register_(MCP_CANSTAT) & CANSTAT_OPMOD) == mode)
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
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) {
|
||||
|
@ -451,6 +450,78 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo
|
|||
}
|
||||
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):
|
||||
switch (can_speed) {
|
||||
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
|
||||
return canbus::ERROR_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid frequency/bitrate combination: %d/%d", can_clock, can_speed);
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ static const uint32_t SPI_CLOCK = 10000000; // 10MHz
|
|||
|
||||
static const int N_TXBUFFERS = 3;
|
||||
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 RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 };
|
||||
enum RXBn { RXB0 = 0, RXB1 = 1 };
|
||||
|
|
|
@ -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_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
|
||||
*/
|
||||
|
|
|
@ -77,7 +77,7 @@ void MPU6050Component::setup() {
|
|||
accel_config &= 0b11100111;
|
||||
accel_config |= (MPU6050_RANGE_2G << 3);
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,6 @@ async def to_code(config):
|
|||
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
0
esphome/components/pcf8563/__init__.py
Normal file
0
esphome/components/pcf8563/__init__.py
Normal file
109
esphome/components/pcf8563/pcf8563.cpp
Normal file
109
esphome/components/pcf8563/pcf8563.cpp
Normal 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
|
124
esphome/components/pcf8563/pcf8563.h
Normal file
124
esphome/components/pcf8563/pcf8563.h
Normal 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
|
62
esphome/components/pcf8563/time.py
Normal file
62
esphome/components/pcf8563/time.py
Normal 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)
|
|
@ -29,7 +29,7 @@ float PIDController::update(float setpoint, float process_value) {
|
|||
bool PIDController::in_deadband() {
|
||||
// return (fabs(error) < deadband_threshold);
|
||||
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_() {
|
||||
|
|
|
@ -62,19 +62,19 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
|||
# The default/recommended arduino framework version
|
||||
# - https://github.com/earlephilhower/arduino-pico/releases
|
||||
# - 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
|
||||
# - https://github.com/platformio/platform-raspberrypi/releases
|
||||
# - 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):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(2, 6, 4), "https://github.com/earlephilhower/arduino-pico"),
|
||||
"latest": (cv.Version(2, 6, 4), None),
|
||||
"dev": (cv.Version(3, 3, 0), "https://github.com/earlephilhower/arduino-pico"),
|
||||
"latest": (cv.Version(3, 3, 0), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,6 @@ async def setup_ssd1306(var, config):
|
|||
cg.add(var.init_invert(config[CONF_INVERT]))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -46,6 +46,6 @@ async def setup_ssd1322(var, config):
|
|||
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -50,6 +50,6 @@ async def setup_ssd1325(var, config):
|
|||
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -37,6 +37,6 @@ async def setup_ssd1327(var, config):
|
|||
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -28,6 +28,6 @@ async def setup_ssd1331(var, config):
|
|||
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -38,6 +38,6 @@ async def setup_ssd1351(var, config):
|
|||
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
|
|
@ -77,7 +77,7 @@ async def setup_st7735(var, config):
|
|||
cg.add(var.set_reset_pin(reset))
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ async def to_code(config):
|
|||
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ void TemplateBinarySensor::setup() {
|
|||
return;
|
||||
|
||||
if (this->f_ != nullptr) {
|
||||
this->publish_initial_state(*this->f_());
|
||||
this->publish_initial_state(this->f_().value_or(false));
|
||||
} else {
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
|
|
|
@ -300,6 +300,7 @@ uint8_t TM1637Display::read_byte_() {
|
|||
uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
|
||||
// ESP_LOGV(TAG, "Print at %d: %s", start_pos, str);
|
||||
uint8_t pos = start_pos;
|
||||
bool use_dot = false;
|
||||
for (; *str != '\0'; str++) {
|
||||
uint8_t data = TM1637_UNKNOWN_CHAR;
|
||||
if (*str >= ' ' && *str <= '~')
|
||||
|
@ -312,14 +313,14 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
|
|||
// XABCDEFG, but TM1637 is // XGFEDCBA
|
||||
if (this->inverted_) {
|
||||
// XABCDEFG > XGCBAFED
|
||||
data = ((data & 0x80) ? 0x80 : 0) | // no move X
|
||||
((data & 0x40) ? 0x8 : 0) | // A
|
||||
((data & 0x20) ? 0x10 : 0) | // B
|
||||
((data & 0x10) ? 0x20 : 0) | // C
|
||||
((data & 0x8) ? 0x1 : 0) | // D
|
||||
((data & 0x4) ? 0x2 : 0) | // E
|
||||
((data & 0x2) ? 0x4 : 0) | // F
|
||||
((data & 0x1) ? 0x40 : 0); // G
|
||||
data = ((data & 0x80) || use_dot ? 0x80 : 0) | // no move X
|
||||
((data & 0x40) ? 0x8 : 0) | // A
|
||||
((data & 0x20) ? 0x10 : 0) | // B
|
||||
((data & 0x10) ? 0x20 : 0) | // C
|
||||
((data & 0x8) ? 0x1 : 0) | // D
|
||||
((data & 0x4) ? 0x2 : 0) | // E
|
||||
((data & 0x2) ? 0x4 : 0) | // F
|
||||
((data & 0x1) ? 0x40 : 0); // G
|
||||
} else {
|
||||
// XABCDEFG > XGFEDCBA
|
||||
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 & 0x1) ? 0x40 : 0); // G
|
||||
}
|
||||
if (*str == '.') {
|
||||
if (pos != start_pos)
|
||||
pos--;
|
||||
this->buffer_[pos] |= 0b10000000;
|
||||
use_dot = *str == '.';
|
||||
if (use_dot) {
|
||||
if ((!this->inverted_) && (pos != start_pos)) {
|
||||
this->buffer_[pos - 1] |= 0b10000000;
|
||||
}
|
||||
} else {
|
||||
if (pos >= 6) {
|
||||
ESP_LOGE(TAG, "String is too long for the display!");
|
||||
break;
|
||||
}
|
||||
this->buffer_[pos] = data;
|
||||
this->buffer_[pos++] = data;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return pos - start_pos;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
namespace esphome {
|
||||
namespace touchscreen {
|
||||
|
||||
void TouchscreenBinarySensor::setup() {
|
||||
this->parent_->register_listener(this);
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
|
||||
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_);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
|
|||
public TouchListener,
|
||||
public Parented<Touchscreen> {
|
||||
public:
|
||||
void setup() override { this->parent_->register_listener(this); }
|
||||
void setup() override;
|
||||
|
||||
/// 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) {
|
||||
|
|
5
esphome/components/tt21100/__init__.py
Normal file
5
esphome/components/tt21100/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@kroimon"]
|
||||
|
||||
tt21100_ns = cg.esphome_ns.namespace("tt21100")
|
31
esphome/components/tt21100/binary_sensor/__init__.py
Normal file
31
esphome/components/tt21100/binary_sensor/__init__.py
Normal 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]))
|
27
esphome/components/tt21100/binary_sensor/tt21100_button.cpp
Normal file
27
esphome/components/tt21100/binary_sensor/tt21100_button.cpp
Normal 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
|
28
esphome/components/tt21100/binary_sensor/tt21100_button.h
Normal file
28
esphome/components/tt21100/binary_sensor/tt21100_button.h
Normal 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
|
44
esphome/components/tt21100/touchscreen/__init__.py
Normal file
44
esphome/components/tt21100/touchscreen/__init__.py
Normal 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))
|
175
esphome/components/tt21100/touchscreen/tt21100.cpp
Normal file
175
esphome/components/tt21100/touchscreen/tt21100.cpp
Normal 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
|
49
esphome/components/tt21100/touchscreen/tt21100.h
Normal file
49
esphome/components/tt21100/touchscreen/tt21100.h
Normal 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
|
|
@ -168,7 +168,7 @@ void TuyaLight::write_state(light::LightState *state) {
|
|||
|
||||
if (brightness > 0.0f || !color_interlock_) {
|
||||
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_) {
|
||||
color_temp_int = this->color_temperature_max_value_ - color_temp_int;
|
||||
}
|
||||
|
|
|
@ -246,6 +246,7 @@ def final_validate_device_schema(
|
|||
baud_rate: Optional[int] = None,
|
||||
require_tx: bool = False,
|
||||
require_rx: bool = False,
|
||||
data_bits: Optional[int] = None,
|
||||
parity: Optional[str] = None,
|
||||
stop_bits: Optional[int] = None,
|
||||
):
|
||||
|
@ -268,6 +269,13 @@ def final_validate_device_schema(
|
|||
|
||||
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):
|
||||
if value != parity:
|
||||
raise cv.Invalid(
|
||||
|
@ -278,7 +286,7 @@ def final_validate_device_schema(
|
|||
def validate_stop_bits(value):
|
||||
if value != stop_bits:
|
||||
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
|
||||
|
||||
|
@ -304,6 +312,8 @@ def final_validate_device_schema(
|
|||
] = validate_pin(CONF_RX_PIN, device)
|
||||
if baud_rate is not None:
|
||||
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:
|
||||
hub_schema[cv.Required(CONF_PARITY)] = validate_parity
|
||||
if stop_bits is not None:
|
||||
|
|
|
@ -153,7 +153,7 @@ async def to_code(config):
|
|||
|
||||
if CONF_LAMBDA in config:
|
||||
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_))
|
||||
if CONF_RESET_PIN in config:
|
||||
|
|
|
@ -47,6 +47,12 @@ def validate_local(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(
|
||||
cv.Schema(
|
||||
{
|
||||
|
@ -71,15 +77,17 @@ CONFIG_SCHEMA = cv.All(
|
|||
web_server_base.WebServerBase
|
||||
),
|
||||
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_LOCAL): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
default_url,
|
||||
validate_local,
|
||||
validate_ota,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "list_entities.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
@ -103,5 +101,3 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
|
|||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/component_iterator.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
@ -59,5 +57,3 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "web_server.h"
|
||||
|
||||
#include "esphome/components/json/json_util.h"
|
||||
|
@ -9,7 +7,9 @@
|
|||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "StreamString.h"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
@ -181,7 +181,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
|||
stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
|
||||
#endif
|
||||
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(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) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
std::string state;
|
||||
if (isnan(value)) {
|
||||
if (std::isnan(value)) {
|
||||
state = "NA";
|
||||
} else {
|
||||
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);
|
||||
} else if (match.method == "turn_on") {
|
||||
auto call = obj->turn_on();
|
||||
if (request->hasParam("speed")) {
|
||||
String speed = request->getParam("speed")->value();
|
||||
}
|
||||
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());
|
||||
if (!val.has_value()) {
|
||||
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);
|
||||
}
|
||||
if (request->hasParam("oscillation")) {
|
||||
String speed = request->getParam("oscillation")->value();
|
||||
auto speed = request->getParam("oscillation")->value();
|
||||
auto val = parse_on_off(speed.c_str());
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
|
@ -585,29 +582,54 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
|||
request->send(200);
|
||||
} else if (match.method == "turn_on") {
|
||||
auto call = obj->turn_on();
|
||||
if (request->hasParam("brightness"))
|
||||
call.set_brightness(request->getParam("brightness")->value().toFloat() / 255.0f);
|
||||
if (request->hasParam("r"))
|
||||
call.set_red(request->getParam("r")->value().toFloat() / 255.0f);
|
||||
if (request->hasParam("g"))
|
||||
call.set_green(request->getParam("g")->value().toFloat() / 255.0f);
|
||||
if (request->hasParam("b"))
|
||||
call.set_blue(request->getParam("b")->value().toFloat() / 255.0f);
|
||||
if (request->hasParam("white_value"))
|
||||
call.set_white(request->getParam("white_value")->value().toFloat() / 255.0f);
|
||||
if (request->hasParam("color_temp"))
|
||||
call.set_color_temperature(request->getParam("color_temp")->value().toFloat());
|
||||
|
||||
if (request->hasParam("brightness")) {
|
||||
auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str());
|
||||
if (brightness.has_value()) {
|
||||
call.set_brightness(*brightness / 255.0f);
|
||||
}
|
||||
}
|
||||
if (request->hasParam("r")) {
|
||||
auto r = parse_number<float>(request->getParam("r")->value().c_str());
|
||||
if (r.has_value()) {
|
||||
call.set_red(*r / 255.0f);
|
||||
}
|
||||
}
|
||||
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")) {
|
||||
float length_s = request->getParam("flash")->value().toFloat();
|
||||
call.set_flash_length(static_cast<uint32_t>(length_s * 1000));
|
||||
auto flash = parse_number<uint32_t>(request->getParam("flash")->value().c_str());
|
||||
if (flash.has_value()) {
|
||||
call.set_flash_length(*flash * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (request->hasParam("transition")) {
|
||||
float length_s = request->getParam("transition")->value().toFloat();
|
||||
call.set_transition_length(static_cast<uint32_t>(length_s * 1000));
|
||||
auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
|
||||
if (transition.has_value()) {
|
||||
call.set_transition_length(*transition * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (request->hasParam("effect")) {
|
||||
const char *effect = request->getParam("effect")->value().c_str();
|
||||
call.set_effect(effect);
|
||||
|
@ -618,8 +640,10 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
|||
} else if (match.method == "turn_off") {
|
||||
auto call = obj->turn_off();
|
||||
if (request->hasParam("transition")) {
|
||||
auto length = (uint32_t) request->getParam("transition")->value().toFloat() * 1000;
|
||||
call.set_transition_length(length);
|
||||
auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
|
||||
if (transition.has_value()) {
|
||||
call.set_transition_length(*transition * 1000);
|
||||
}
|
||||
}
|
||||
this->schedule_([call]() mutable { call.perform(); });
|
||||
request->send(200);
|
||||
|
@ -681,10 +705,18 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
|||
return;
|
||||
}
|
||||
|
||||
if (request->hasParam("position"))
|
||||
call.set_position(request->getParam("position")->value().toFloat());
|
||||
if (request->hasParam("tilt"))
|
||||
call.set_tilt(request->getParam("tilt")->value().toFloat());
|
||||
if (request->hasParam("position")) {
|
||||
auto position = parse_number<float>(request->getParam("position")->value().c_str());
|
||||
if (position.has_value()) {
|
||||
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(); });
|
||||
request->send(200);
|
||||
|
@ -725,10 +757,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
|||
|
||||
auto call = obj->make_call();
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_value(*value_f);
|
||||
auto value = parse_number<float>(request->getParam("value")->value().c_str());
|
||||
if (value.has_value())
|
||||
call.set_value(*value);
|
||||
}
|
||||
|
||||
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["mode"] = (int) obj->traits.get_mode();
|
||||
}
|
||||
if (isnan(value)) {
|
||||
if (std::isnan(value)) {
|
||||
root["value"] = "\"NaN\"";
|
||||
root["state"] = "NA";
|
||||
} else {
|
||||
|
@ -784,7 +815,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
|||
auto call = obj->make_call();
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -834,29 +865,26 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
|
|||
auto call = obj->make_call();
|
||||
|
||||
if (request->hasParam("mode")) {
|
||||
String mode = request->getParam("mode")->value();
|
||||
auto mode = request->getParam("mode")->value();
|
||||
call.set_mode(mode.c_str());
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature_high")) {
|
||||
String value = request->getParam("target_temperature_high")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature_high(*value_f);
|
||||
auto target_temperature_high = parse_number<float>(request->getParam("target_temperature_high")->value().c_str());
|
||||
if (target_temperature_high.has_value())
|
||||
call.set_target_temperature_high(*target_temperature_high);
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature_low")) {
|
||||
String value = request->getParam("target_temperature_low")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature_low(*value_f);
|
||||
auto target_temperature_low = parse_number<float>(request->getParam("target_temperature_low")->value().c_str());
|
||||
if (target_temperature_low.has_value())
|
||||
call.set_target_temperature_low(*target_temperature_low);
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature")) {
|
||||
String value = request->getParam("target_temperature")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature(*value_f);
|
||||
auto target_temperature = parse_number<float>(request->getParam("target_temperature")->value().c_str());
|
||||
if (target_temperature.has_value())
|
||||
call.set_target_temperature(*target_temperature);
|
||||
}
|
||||
|
||||
this->schedule_([call]() mutable { call.perform(); });
|
||||
|
@ -1231,5 +1259,3 @@ void WebServer::schedule_(std::function<void()> &&f) {
|
|||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "list_entities.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 esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -5,7 +5,15 @@ from esphome.core import coroutine_with_priority, CORE
|
|||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
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")
|
||||
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])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFi", None)
|
||||
cg.add_library("FS", None)
|
||||
cg.add_library("Update", None)
|
||||
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
|
||||
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFi", None)
|
||||
cg.add_library("FS", None)
|
||||
cg.add_library("Update", None)
|
||||
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
|
||||
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "web_server_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <StreamString.h>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <StreamString.h>
|
||||
#ifdef USE_ESP32
|
||||
#include <Update.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include <Updater.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_base {
|
||||
|
@ -24,18 +25,22 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
|||
handler = new internal::AuthMiddlewareHandler(handler, &credentials_);
|
||||
}
|
||||
this->handlers_.push_back(handler);
|
||||
if (this->server_ != nullptr)
|
||||
if (this->server_ != nullptr) {
|
||||
this->server_->addHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void report_ota_error() {
|
||||
#ifdef USE_ARDUINO
|
||||
StreamString ss;
|
||||
Update.printError(ss);
|
||||
ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
|
||||
uint8_t *data, size_t len, bool final) {
|
||||
#ifdef USE_ARDUINO
|
||||
bool success;
|
||||
if (index == 0) {
|
||||
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)
|
||||
success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
if (Update.isRunning())
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
if (Update.isRunning()) {
|
||||
Update.abort();
|
||||
}
|
||||
success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
|
||||
#endif
|
||||
if (!success) {
|
||||
|
@ -85,8 +91,10 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin
|
|||
report_ota_error();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
#ifdef USE_ARDUINO
|
||||
AsyncWebServerResponse *response;
|
||||
if (!Update.hasError()) {
|
||||
response = request->beginResponse(200, "text/plain", "Update Successful!");
|
||||
|
@ -98,10 +106,13 @@ void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
|||
}
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebServerBase::add_ota_handler() {
|
||||
#ifdef USE_ARDUINO
|
||||
this->add_handler(new OTARequestHandler(this)); // NOLINT
|
||||
#endif
|
||||
}
|
||||
float WebServerBase::get_setup_priority() const {
|
||||
// Before WiFi (captive portal)
|
||||
|
@ -110,5 +121,3 @@ float WebServerBase::get_setup_priority() const {
|
|||
|
||||
} // namespace web_server_base
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#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 web_server_base {
|
||||
|
@ -141,5 +144,3 @@ class OTARequestHandler : public AsyncWebHandler {
|
|||
|
||||
} // namespace web_server_base
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
14
esphome/components/web_server_idf/__init__.py
Normal file
14
esphome/components/web_server_idf/__init__.py
Normal 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)
|
374
esphome/components/web_server_idf/web_server_idf.cpp
Normal file
374
esphome/components/web_server_idf/web_server_idf.cpp
Normal 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)
|
277
esphome/components/web_server_idf/web_server_idf.h
Normal file
277
esphome/components/web_server_idf/web_server_idf.h
Normal 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)
|
0
esphome/components/zio_ultrasonic/__init__.py
Normal file
0
esphome/components/zio_ultrasonic/__init__.py
Normal file
34
esphome/components/zio_ultrasonic/sensor.py
Normal file
34
esphome/components/zio_ultrasonic/sensor.py
Normal 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)
|
31
esphome/components/zio_ultrasonic/zio_ultrasonic.cpp
Normal file
31
esphome/components/zio_ultrasonic/zio_ultrasonic.cpp
Normal 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
|
22
esphome/components/zio_ultrasonic/zio_ultrasonic.h
Normal file
22
esphome/components/zio_ultrasonic/zio_ultrasonic.h
Normal 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
|
|
@ -1,6 +1,6 @@
|
|||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2023.7.0-dev"
|
||||
__version__ = "2023.8.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@ -262,6 +262,7 @@ CONF_FINGER_ID = "finger_id"
|
|||
CONF_FINGERPRINT_COUNT = "fingerprint_count"
|
||||
CONF_FLASH_LENGTH = "flash_length"
|
||||
CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length"
|
||||
CONF_FLOW = "flow"
|
||||
CONF_FLOW_CONTROL_PIN = "flow_control_pin"
|
||||
CONF_FOR = "for"
|
||||
CONF_FORCE_UPDATE = "force_update"
|
||||
|
@ -286,6 +287,7 @@ CONF_GPIO = "gpio"
|
|||
CONF_GREEN = "green"
|
||||
CONF_GROUP = "group"
|
||||
CONF_HARDWARE_UART = "hardware_uart"
|
||||
CONF_HEAD = "head"
|
||||
CONF_HEARTBEAT = "heartbeat"
|
||||
CONF_HEAT_ACTION = "heat_action"
|
||||
CONF_HEAT_DEADBAND = "heat_deadband"
|
||||
|
@ -891,6 +893,7 @@ UNIT_CENTIMETER = "cm"
|
|||
UNIT_COUNT_DECILITRE = "/dL"
|
||||
UNIT_COUNTS_PER_CUBIC_METER = "#/m³"
|
||||
UNIT_CUBIC_METER = "m³"
|
||||
UNIT_CUBIC_METER_PER_HOUR = "m³/h"
|
||||
UNIT_DECIBEL = "dB"
|
||||
UNIT_DECIBEL_MILLIWATT = "dBm"
|
||||
UNIT_DEGREE_PER_SECOND = "°/s"
|
||||
|
@ -928,6 +931,7 @@ UNIT_PERCENT = "%"
|
|||
UNIT_PH = "pH"
|
||||
UNIT_PULSES = "pulses"
|
||||
UNIT_PULSES_PER_MINUTE = "pulses/min"
|
||||
UNIT_REVOLUTIONS_PER_MINUTE = "RPM"
|
||||
UNIT_SECOND = "s"
|
||||
UNIT_STEPS = "steps"
|
||||
UNIT_VOLT = "V"
|
||||
|
|
|
@ -57,21 +57,6 @@ struct Color {
|
|||
inline bool operator!=(uint32_t colorcode) { // NOLINT
|
||||
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 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),
|
||||
|
|
|
@ -158,7 +158,7 @@ board_build.filesystem_size = 0.5m
|
|||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||
platform_packages =
|
||||
; 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
|
||||
lib_deps =
|
||||
|
|
|
@ -9,7 +9,7 @@ pyserial==3.5
|
|||
platformio==6.1.7 # When updating platformio, also update Dockerfile
|
||||
esptool==4.6.2
|
||||
click==8.1.3
|
||||
esphome-dashboard==20230621.0
|
||||
esphome-dashboard==20230711.0
|
||||
aioesphomeapi==15.0.0
|
||||
zeroconf==0.69.0
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue