Merge branch 'dev' of https://github.com/esphome/esphome into libmagic

This commit is contained in:
clydeps 2024-09-02 07:26:18 +10:00
commit 372ca95628
105 changed files with 2301 additions and 324 deletions

View file

@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.2.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment

View file

@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.11"

View file

@ -42,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.9"
- name: Set up Docker Buildx

View file

@ -41,7 +41,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment

View file

@ -53,7 +53,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.x"
- name: Set up python environment
@ -85,7 +85,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.9"

View file

@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: 3.12

View file

@ -58,6 +58,7 @@ esphome/components/beken_spi_led_strip/* @Mat931
esphome/components/bh1750/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas
@ -422,6 +423,7 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter

View file

@ -0,0 +1 @@
CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"]

View file

@ -0,0 +1,238 @@
#include "bl0906.h"
#include "constants.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0906 {
static const char *const TAG = "bl0906";
constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
// The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated;
constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) {
return (address + data->l + data->m + data->h) ^ 0xFF;
}
void BL0906::loop() {
if (this->current_channel_ == UINT8_MAX) {
return;
}
while (this->available())
this->flush();
if (this->current_channel_ == 0) {
// Temperature
this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_);
} else if (this->current_channel_ == 1) {
this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_);
this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_);
this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_);
} else if (this->current_channel_ == 2) {
this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_);
this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_);
this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_);
} else if (this->current_channel_ == 3) {
this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_);
this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_);
this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_);
} else if (this->current_channel_ == 4) {
this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_);
this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_);
this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_);
} else if (this->current_channel_ == 5) {
this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_);
this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_);
this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_);
} else if (this->current_channel_ == 6) {
this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_);
this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_);
this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_);
} else if (this->current_channel_ == UINT8_MAX - 2) {
// Frequency
this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_);
// Voltage
this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_);
} else if (this->current_channel_ == UINT8_MAX - 1) {
// Total power
this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_);
// Total Energy
this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_);
} else {
this->current_channel_ = UINT8_MAX - 2; // Go to frequency and voltage
return;
}
this->current_channel_++;
this->handle_actions_();
}
void BL0906::setup() {
while (this->available())
this->flush();
this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE));
// Calibration (1: register address; 2: value before calibration; 3: value after calibration)
this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1
this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0);
this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0);
this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0);
this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0);
this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0); // Calibration current_6
this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD));
}
void BL0906::update() { this->current_channel_ = 0; }
size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) {
this->action_queue_.push_back(function);
return this->action_queue_.size();
}
void BL0906::handle_actions_() {
if (this->action_queue_.empty()) {
return;
}
ActionCallbackFuncPtr ptr_func = nullptr;
for (int i = 0; i < this->action_queue_.size(); i++) {
ptr_func = this->action_queue_[i];
if (ptr_func) {
ESP_LOGI(TAG, "HandleActionCallback[%d]...", i);
(this->*ptr_func)();
}
}
while (this->available()) {
this->read();
}
this->action_queue_.clear();
}
// Reset energy
void BL0906::reset_energy_() {
this->write_array(BL0906_INIT[0], 6);
delay(1);
this->flush();
ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2],
BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]);
}
// Read data
void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) {
if (sensor == nullptr) {
return;
}
DataPacket buffer;
ube24_t data_u24;
sbe24_t data_s24;
float value = 0;
bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF;
this->write_byte(BL0906_READ_COMMAND);
this->write_byte(address);
if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) {
if (bl0906_checksum(address, &buffer) == buffer.checksum) {
if (signed_result) {
data_s24.l = buffer.l;
data_s24.m = buffer.m;
data_s24.h = buffer.h;
} else {
data_u24.l = buffer.l;
data_u24.m = buffer.m;
data_u24.h = buffer.h;
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
return;
}
}
// Power
if (reference == BL0906_PREF) {
value = (float) to_int32_t(data_s24) * reference;
}
// Total power
if (reference == BL0906_WATT) {
value = (float) to_int32_t(data_s24) * reference;
}
// Voltage, current, power, total power
if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) {
value = (float) to_uint32_t(data_u24) * reference;
}
// Frequency
if (reference == BL0906_FREF) {
value = reference / (float) to_uint32_t(data_u24);
}
// Chip temperature
if (reference == BL0906_TREF) {
value = (float) to_int32_t(data_s24);
value = (value - 64) * 12.5 / 59 - 40;
}
sensor->publish_state(value);
}
// RMS offset correction
void BL0906::bias_correction_(uint8_t address, float measurements, float correction) {
DataPacket data;
float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; // Current coefficient
float i_rms0 = measurements * ki;
float i_rms = correction * ki;
int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256;
data.l = value << 24 >> 24;
data.m = value << 16 >> 24;
if (value < 0) {
data.h = (value << 8 >> 24) | 0b10000000;
}
data.address = bl0906_checksum(address, &data);
ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address);
this->write_byte(BL0906_WRITE_COMMAND);
this->write_byte(address);
this->write_byte(data.l);
this->write_byte(data.m);
this->write_byte(data.h);
this->write_byte(data.address);
}
void BL0906::dump_config() {
ESP_LOGCONFIG(TAG, "BL0906:");
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current1", this->current_1_sensor_);
LOG_SENSOR(" ", "Current2", this->current_2_sensor_);
LOG_SENSOR(" ", "Current3", this->current_3_sensor_);
LOG_SENSOR(" ", "Current4", this->current_4_sensor_);
LOG_SENSOR(" ", "Current5", this->current_5_sensor_);
LOG_SENSOR(" ", "Current6", this->current_6_sensor_);
LOG_SENSOR(" ", "Power1", this->power_1_sensor_);
LOG_SENSOR(" ", "Power2", this->power_2_sensor_);
LOG_SENSOR(" ", "Power3", this->power_3_sensor_);
LOG_SENSOR(" ", "Power4", this->power_4_sensor_);
LOG_SENSOR(" ", "Power5", this->power_5_sensor_);
LOG_SENSOR(" ", "Power6", this->power_6_sensor_);
LOG_SENSOR(" ", "Energy1", this->energy_1_sensor_);
LOG_SENSOR(" ", "Energy2", this->energy_2_sensor_);
LOG_SENSOR(" ", "Energy3", this->energy_3_sensor_);
LOG_SENSOR(" ", "Energy4", this->energy_4_sensor_);
LOG_SENSOR(" ", "Energy5", this->energy_5_sensor_);
LOG_SENSOR(" ", "Energy6", this->energy_6_sensor_);
LOG_SENSOR(" ", "Total Power", this->total_power_sensor_);
LOG_SENSOR(" ", "Total Energy", this->total_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,96 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/datatypes.h"
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf
namespace esphome {
namespace bl0906 {
struct DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
uint8_t h{0};
uint8_t checksum; // checksum
uint8_t address;
} __attribute__((packed));
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
uint8_t h{0};
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
int8_t h{0};
} __attribute__((packed));
template<typename... Ts> class ResetEnergyAction;
class BL0906;
using ActionCallbackFuncPtr = void (BL0906::*)();
class BL0906 : public PollingComponent, public uart::UARTDevice {
SUB_SENSOR(voltage)
SUB_SENSOR(current_1)
SUB_SENSOR(current_2)
SUB_SENSOR(current_3)
SUB_SENSOR(current_4)
SUB_SENSOR(current_5)
SUB_SENSOR(current_6)
SUB_SENSOR(power_1)
SUB_SENSOR(power_2)
SUB_SENSOR(power_3)
SUB_SENSOR(power_4)
SUB_SENSOR(power_5)
SUB_SENSOR(power_6)
SUB_SENSOR(total_power)
SUB_SENSOR(energy_1)
SUB_SENSOR(energy_2)
SUB_SENSOR(energy_3)
SUB_SENSOR(energy_4)
SUB_SENSOR(energy_5)
SUB_SENSOR(energy_6)
SUB_SENSOR(total_energy)
SUB_SENSOR(frequency)
SUB_SENSOR(temperature)
public:
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
template<typename... Ts> friend class ResetEnergyAction;
void reset_energy_();
void read_data_(uint8_t address, float reference, sensor::Sensor *sensor);
void bias_correction_(uint8_t address, float measurements, float correction);
uint8_t current_channel_{0};
size_t enqueue_action_(ActionCallbackFuncPtr function);
void handle_actions_();
private:
std::vector<ActionCallbackFuncPtr> action_queue_{};
};
template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> {
public:
void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
};
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,4 @@
# const.py
ICON_ENERGY = "mdi:lightning-bolt"
ICON_FREQUENCY = "mdi:cosine-wave"
ICON_VOLTAGE = "mdi:sine-wave"

View file

@ -0,0 +1,122 @@
#pragma once
#include <cstdint>
namespace esphome {
namespace bl0906 {
// Total power conversion
static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
// Total Energy conversion
static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 /
(3600000 * 16 *
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
// Frequency conversion
static const float BL0906_FREF = 10000000;
// Temperature conversion
static const float BL0906_TREF = 12.5 / 59 - 40;
// Current conversion
static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000);
// Voltage conversion
static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000);
// Power conversion
static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
// Energy conversion
static const float BL0906_EREF = 4194304 * 0.032768 * 16 /
(3600000 * 16 *
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
// Current coefficient
static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;
// Power coefficient
static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 /
(20000 + 20000 + 20000 + 20000 + 20000);
static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7};
static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61};
static const uint8_t BL0906_READ_COMMAND = 0x35;
static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
// Register address
// Voltage
static const uint8_t BL0906_V_RMS = 0x16;
// Total power
static const uint8_t BL0906_WATT_SUM = 0X2C;
// Current1~6
static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
static const uint8_t BL0906_I_2_RMS = 0x0E;
static const uint8_t BL0906_I_3_RMS = 0x0F;
static const uint8_t BL0906_I_4_RMS = 0x10;
static const uint8_t BL0906_I_5_RMS = 0x13;
static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
// Power1~6
static const uint8_t BL0906_WATT_1 = 0X23; // power_1
static const uint8_t BL0906_WATT_2 = 0X24;
static const uint8_t BL0906_WATT_3 = 0X25;
static const uint8_t BL0906_WATT_4 = 0X26;
static const uint8_t BL0906_WATT_5 = 0X29;
static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
// Active pulse count, unsigned
static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
static const uint8_t BL0906_CF_2_CNT = 0X31;
static const uint8_t BL0906_CF_3_CNT = 0X32;
static const uint8_t BL0906_CF_4_CNT = 0X33;
static const uint8_t BL0906_CF_5_CNT = 0X36;
static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
// Total active pulse count, unsigned
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
// Voltage frequency cycle
static const uint8_t BL0906_FREQUENCY = 0X4E;
// Internal temperature
static const uint8_t BL0906_TEMPERATURE = 0X5E;
// Calibration register
// RMS gain adjustment register
static const uint8_t BL0906_RMSGN_1 = 0x6D; // Channel_1
static const uint8_t BL0906_RMSGN_2 = 0x6E;
static const uint8_t BL0906_RMSGN_3 = 0x6F;
static const uint8_t BL0906_RMSGN_4 = 0x70;
static const uint8_t BL0906_RMSGN_5 = 0x73;
static const uint8_t BL0906_RMSGN_6 = 0x74; // Channel_6
// RMS offset correction register
static const uint8_t BL0906_RMSOS_1 = 0x78; // Channel_1
static const uint8_t BL0906_RMSOS_2 = 0x79;
static const uint8_t BL0906_RMSOS_3 = 0x7A;
static const uint8_t BL0906_RMSOS_4 = 0x7B;
static const uint8_t BL0906_RMSOS_5 = 0x7E;
static const uint8_t BL0906_RMSOS_6 = 0x7F; // Channel_6
// Active power gain adjustment register
static const uint8_t BL0906_WATTGN_1 = 0xB7; // Channel_1
static const uint8_t BL0906_WATTGN_2 = 0xB8;
static const uint8_t BL0906_WATTGN_3 = 0xB9;
static const uint8_t BL0906_WATTGN_4 = 0xBA;
static const uint8_t BL0906_WATTGN_5 = 0xBD;
static const uint8_t BL0906_WATTGN_6 = 0xBE; // Channel_6
// User write protection setting register,
// You must first write 0x5555 to the write protection setting register before writing to other registers.
static const uint8_t BL0906_USR_WRPROT = 0x9E;
// Reset Register
static const uint8_t BL0906_SOFT_RESET = 0x9F;
const uint8_t BL0906_INIT[2][6] = {
// Reset to default
{BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52},
// Enable User Operation Write
{BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}};
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,184 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_CHANNEL,
CONF_CURRENT,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_NAME,
CONF_POWER,
CONF_TEMPERATURE,
CONF_TOTAL_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_POWER,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
)
# Import ICONS not included in esphome's const.py, from the local components const.py
from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["bl0906"]
CONF_TOTAL_ENERGY = "total_energy"
bl0906_ns = cg.esphome_ns.namespace("bl0906")
BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice)
ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0906),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
icon=ICON_FREQUENCY,
accuracy_decimals=0,
device_class=DEVICE_CLASS_FREQUENCY,
unit_of_measurement=UNIT_HERTZ,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
icon=ICON_THERMOMETER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
unit_of_measurement=UNIT_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
icon=ICON_VOLTAGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
unit_of_measurement=UNIT_VOLT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema(
icon=ICON_POWER,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
unit_of_measurement=UNIT_WATT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema(
icon=ICON_ENERGY,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
unit_of_measurement=UNIT_KILOWATT_HOURS,
),
}
)
.extend(
cv.Schema(
{
cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema(
{
cv.Optional(CONF_CURRENT): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_CURRENT_AC,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
unit_of_measurement=UNIT_AMPERE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_POWER): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_POWER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
unit_of_measurement=UNIT_WATT,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_ENERGY): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_ENERGY,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
unit_of_measurement=UNIT_KILOWATT_HOURS,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
key=CONF_NAME,
),
}
)
for i in range(6)
}
)
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.polling_component_schema("60s"))
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"bl0906", baud_rate=19200, require_tx=True, require_rx=True
)
@automation.register_action(
"bl0906.reset_energy",
ResetEnergyAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(BL0906),
}
),
)
async def reset_energy_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)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_frequency_sensor(sens))
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
for i in range(6):
if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"):
if current_config := channel_config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens))
if power_config := channel_config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens))
if energy_config := channel_config.get(CONF_ENERGY):
sens = await sensor.new_sensor(energy_config)
cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens))
if total_power_config := config.get(CONF_TOTAL_POWER):
sens = await sensor.new_sensor(total_power_config)
cg.add(var.set_total_power_sensor(sens))
if total_energy_config := config.get(CONF_TOTAL_ENERGY):
sens = await sensor.new_sensor(total_energy_config)
cg.add(var.set_total_energy_sensor(sens))

View file

@ -54,6 +54,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
}
resp.advertisements.push_back(std::move(adv));
ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
}
ESP_LOGV(TAG, "Proxying %d packets", count);
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
@ -87,6 +90,8 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
ESP_LOGCONFIG(TAG, " Connections: %d", this->connections_.size());
ESP_LOGCONFIG(TAG, " Raw advertisements: %s", YESNO(this->raw_advertisements_));
}
int BluetoothProxy::get_bluetooth_connections_free() {

View file

@ -186,7 +186,7 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args):
date_config = config[CONF_DATE]
if cg.is_template(date_config):
template_ = await cg.templatable(date_config, [], cg.ESPTime)
template_ = await cg.templatable(date_config, args, cg.ESPTime)
cg.add(action_var.set_date(template_))
else:
date_struct = cg.StructInitializer(
@ -217,7 +217,7 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args):
time_config = config[CONF_TIME]
if cg.is_template(time_config):
template_ = await cg.templatable(time_config, [], cg.ESPTime)
template_ = await cg.templatable(time_config, args, cg.ESPTime)
cg.add(action_var.set_time(template_))
else:
time_struct = cg.StructInitializer(
@ -248,7 +248,7 @@ async def datetime_datetime_set_to_code(config, action_id, template_arg, args):
datetime_config = config[CONF_DATETIME]
if cg.is_template(datetime_config):
template_ = await cg.templatable(datetime_config, [], cg.ESPTime)
template_ = await cg.templatable(datetime_config, args, cg.ESPTime)
cg.add(action_var.set_datetime(template_))
else:
datetime_struct = cg.StructInitializer(

View file

@ -1,15 +1,15 @@
from esphome import automation, core
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import core, automation
from esphome.automation import maybe_simple_id
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_FROM,
CONF_ID,
CONF_LAMBDA,
CONF_PAGES,
CONF_PAGE_ID,
CONF_PAGES,
CONF_ROTATION,
CONF_FROM,
CONF_TO,
CONF_TRIGGER_ID,
)
@ -195,3 +195,4 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY")

View file

@ -1,18 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
import esphome.codegen as cg
from esphome.components import canbus
from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN
from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE
from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_RX_PIN,
CONF_RX_QUEUE_LEN,
CONF_TX_PIN,
CONF_TX_QUEUE_LEN,
)
CODEOWNERS = ["@Sympatron"]
@ -77,6 +82,8 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate,
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t,
cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t,
}
)
@ -87,3 +94,7 @@ async def to_code(config):
cg.add(var.set_rx(config[CONF_RX_PIN]))
cg.add(var.set_tx(config[CONF_TX_PIN]))
if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None:
cg.add(var.set_rx_queue_len(rx_queue_len))
if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None:
cg.add(var.set_tx_queue_len(tx_queue_len))

View file

@ -69,6 +69,13 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config
bool ESP32Can::setup_internal() {
twai_general_config_t g_config =
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
if (this->tx_queue_len_.has_value()) {
g_config.tx_queue_len = this->tx_queue_len_.value();
}
if (this->rx_queue_len_.has_value()) {
g_config.rx_queue_len = this->rx_queue_len_.value();
}
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
twai_timing_config_t t_config;
@ -111,6 +118,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
.flags = flags,
.identifier = frame->can_id,
.data_length_code = frame->can_data_length_code,
.data = {}, // to suppress warning, data is initialized properly below
};
if (!frame->remote_transmission_request) {
memcpy(message.data, frame->data, frame->can_data_length_code);

View file

@ -12,6 +12,8 @@ class ESP32Can : public canbus::Canbus {
public:
void set_rx(int rx) { rx_ = rx; }
void set_tx(int tx) { tx_ = tx; }
void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; }
void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; }
ESP32Can(){};
protected:
@ -21,6 +23,8 @@ class ESP32Can : public canbus::Canbus {
int rx_{-1};
int tx_{-1};
optional<uint32_t> tx_queue_len_{};
optional<uint32_t> rx_queue_len_{};
};
} // namespace esp32_can

View file

@ -1,43 +1,35 @@
import functools
import hashlib
import logging
import functools
from pathlib import Path
import os
from pathlib import Path
import re
from packaging import version
import requests
from esphome import core
from esphome import external_files
import esphome.config_validation as cv
from esphome import core, external_files
import esphome.codegen as cg
from esphome.helpers import (
copy_file_if_changed,
cpp_string_escape,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_FAMILY,
CONF_FILE,
CONF_GLYPHS,
CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID,
CONF_TYPE,
CONF_REFRESH,
CONF_SIZE,
CONF_PATH,
CONF_WEIGHT,
CONF_TYPE,
CONF_URL,
CONF_WEIGHT,
)
from esphome.core import (
CORE,
HexInt,
)
from esphome.core import CORE, HexInt
from esphome.helpers import copy_file_if_changed, cpp_string_escape
_LOGGER = logging.getLogger(__name__)
DOMAIN = "font"
DEPENDENCIES = ["display"]
MULTI_CONF = True
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
@ -400,10 +392,7 @@ class EFont:
def convert_bitmap_to_pillow_font(filepath):
from PIL import (
PcfFontFile,
BdfFontFile,
)
from PIL import BdfFontFile, PcfFontFile
local_bitmap_font_file = external_files.compute_local_file_dir(
DOMAIN,

View file

@ -1,9 +1,8 @@
#include "font.h"
#include "esphome/core/color.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace font {
@ -68,6 +67,7 @@ int Font::match_next_glyph(const uint8_t *str, int *match_length) {
return -1;
return lo;
}
#ifdef USE_DISPLAY
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
*baseline = this->baseline_;
*height = this->height_;
@ -164,6 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
i += match_length;
}
}
#endif
} // namespace font
} // namespace esphome

View file

@ -1,8 +1,11 @@
#pragma once
#include "esphome/core/datatypes.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/datatypes.h"
#include "esphome/core/defines.h"
#ifdef USE_DISPLAY
#include "esphome/components/display/display.h"
#endif
namespace esphome {
namespace font {
@ -38,7 +41,11 @@ class Glyph {
const GlyphData *glyph_data_;
};
class Font : public display::BaseFont {
class Font
#ifdef USE_DISPLAY
: public display::BaseFont
#endif
{
public:
/** Construct the font with the given glyphs.
*
@ -50,9 +57,11 @@ class Font : public display::BaseFont {
int match_next_glyph(const uint8_t *str, int *match_length);
#ifdef USE_DISPLAY
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
Color background) override;
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override;
#endif
inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; }
inline int get_bpp() { return this->bpp_; }

View file

@ -1,11 +1,10 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID
from .. import gt911_ns
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import gt911_ns
GT911ButtonListener = gt911_ns.class_("GT911ButtonListener")
GT911Touchscreen = gt911_ns.class_(
@ -18,6 +17,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GT911Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
).extend(i2c.i2c_device_schema(0x5D))
@ -29,3 +29,5 @@ async def to_code(config):
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN):
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))

View file

@ -26,6 +26,23 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
void GT911Touchscreen::setup() {
i2c::ErrorCode err;
ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
if (this->interrupt_pin_ != nullptr) {
// The interrupt pin is used as an input during reset to select the I2C address.
this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->interrupt_pin_->setup();
this->interrupt_pin_->digital_write(false);
}
delay(2);
this->reset_pin_->digital_write(true);
delay(50); // NOLINT
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
this->interrupt_pin_->setup();
}
}
// check the configuration of the int line.
uint8_t data[4];

View file

@ -19,12 +19,14 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void dump_config() 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(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); }
protected:
void update_touches() override;
InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{};
std::vector<GT911ButtonListener *> button_listeners_;
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
};

View file

@ -327,6 +327,8 @@ CONFIG_SCHEMA = (
{
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments,
cv.Optional(df.CONF_PAD_ROW): lvalid.pixels,
cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels,
}
)
),

View file

@ -52,9 +52,7 @@ opacity = LValidator(opacity_validator, uint32, retmapper=literal)
def color(value):
if value == SCHEMA_EXTRACT:
return ["hex color value", "color ID"]
if isinstance(value, int):
return value
return cv.use_id(ColorStruct)(value)
return cv.Any(cv.int_, cv.use_id(ColorStruct))(value)
def color_retmapper(value):
@ -82,10 +80,10 @@ def pixels_or_percent_validator(value):
"""A length in one axis - either a number (pixels) or a percentage"""
if value == SCHEMA_EXTRACT:
return ["pixels", "..%"]
value = cv.Any(cv.int_, cv.percentage)(value)
if isinstance(value, int):
return cv.int_(value)
# Will throw an exception if not a percentage.
return f"lv_pct({int(cv.percentage(value) * 100)})"
return value
return f"lv_pct({int(value * 100)})"
pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal)
@ -116,10 +114,7 @@ def size_validator(value):
if value.upper() == "SIZE_CONTENT":
return "LV_SIZE_CONTENT"
raise cv.Invalid("must be 'size_content', a percentage or an integer (pixels)")
if isinstance(value, int):
return cv.int_(value)
# Will throw an exception if not a percentage.
return f"lv_pct({int(cv.percentage(value) * 100)})"
return pixels_or_percent_validator(value)
size = LValidator(size_validator, uint32, retmapper=literal)

View file

@ -359,7 +359,13 @@ LVGL_SCHEMA = cv.Schema(
}
)
ALL_STYLES = {**STYLE_PROPS, **GRID_CELL_SCHEMA, **FLEX_OBJ_SCHEMA}
ALL_STYLES = {
**STYLE_PROPS,
**GRID_CELL_SCHEMA,
**FLEX_OBJ_SCHEMA,
cv.Optional(df.CONF_PAD_ROW): lvalid.pixels,
cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels,
}
def container_validator(schema, widget_type: WidgetType):

View file

@ -13,11 +13,13 @@ from ..defines import (
CONF_KEY_CODE,
CONF_MAIN,
CONF_ONE_CHECKED,
CONF_PAD_COLUMN,
CONF_PAD_ROW,
CONF_ROWS,
CONF_SELECTED,
)
from ..helpers import lvgl_components_required
from ..lv_validation import key_code, lv_bool
from ..lv_validation import key_code, lv_bool, pixels
from ..lvcode import lv, lv_add, lv_expr
from ..schemas import automation_schema
from ..types import (
@ -57,6 +59,8 @@ BUTTONMATRIX_BUTTON_SCHEMA = cv.Schema(
BUTTONMATRIX_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ONE_CHECKED, default=False): lv_bool,
cv.Optional(CONF_PAD_ROW): pixels,
cv.Optional(CONF_PAD_COLUMN): pixels,
cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr),
cv.Required(CONF_ROWS): cv.ensure_list(
cv.Schema(

View file

@ -1,7 +1,8 @@
from esphome.config_validation import Optional
from esphome.const import CONF_TEXT
from ..defines import CONF_INDICATOR, CONF_MAIN
from ..lv_validation import lv_text
from ..defines import CONF_INDICATOR, CONF_MAIN, CONF_PAD_COLUMN
from ..lv_validation import lv_text, pixels
from ..lvcode import lv
from ..schemas import TEXT_SCHEMA
from ..types import LvBoolean
@ -16,7 +17,11 @@ class CheckboxType(WidgetType):
CONF_CHECKBOX,
LvBoolean("lv_checkbox_t"),
(CONF_MAIN, CONF_INDICATOR),
TEXT_SCHEMA,
TEXT_SCHEMA.extend(
{
Optional(CONF_PAD_COLUMN): pixels,
}
),
)
async def to_code(self, w: Widget, config):

View file

@ -1,7 +1,7 @@
from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_ROW, CONF_TRIGGER_ID
from esphome.const import CONF_ID, CONF_ROW
from ..automation import action_to_code
from ..defines import (
@ -29,6 +29,7 @@ lv_tileview_t = LvType(
"lv_tileview_t",
largs=[(lv_obj_t_ptr, "tile")],
lvalue=lambda w: w.get_property("tile_act"),
has_on_value=True,
)
tile_spec = WidgetType("lv_tileview_tile_t", lv_tile_t, (CONF_MAIN,), {})
@ -46,13 +47,6 @@ TILEVIEW_SCHEMA = cv.Schema(
},
)
),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
automation.Trigger.template(lv_obj_t_ptr)
)
}
),
}
)

View file

@ -70,72 +70,62 @@ void MICS4514Component::update() {
if (this->carbon_monoxide_sensor_ != nullptr) {
float co = 0.0f;
if (red_f <= 0.425f) {
co = (0.425f - red_f) / 0.000405f;
if (co < 1.0f)
co = 0.0f;
if (co > 1000.0f)
co = 1000.0f;
if (red_f > 3.4f) {
co = 0.0;
} else if (red_f < 0.01) {
co = 1000.0;
} else {
co = 4.2 / pow(red_f, 1.2);
}
this->carbon_monoxide_sensor_->publish_state(co);
}
if (this->nitrogen_dioxide_sensor_ != nullptr) {
float nitrogendioxide = 0.0f;
if (ox_f >= 1.1f) {
nitrogendioxide = (ox_f - 0.045f) / 6.13f;
if (nitrogendioxide < 0.1f)
nitrogendioxide = 0.0f;
if (nitrogendioxide > 10.0f)
nitrogendioxide = 10.0f;
if (ox_f < 0.3f) {
nitrogendioxide = 0.0;
} else {
nitrogendioxide = 0.164 * pow(ox_f, 0.975);
}
this->nitrogen_dioxide_sensor_->publish_state(nitrogendioxide);
}
if (this->methane_sensor_ != nullptr) {
float methane = 0.0f;
if (red_f <= 0.786f) {
methane = (0.786f - red_f) / 0.000023f;
if (methane < 1000.0f)
methane = 0.0f;
if (methane > 25000.0f)
methane = 25000.0f;
if (red_f > 0.9f || red_f < 0.5) { // outside the range->unlikely
methane = 0.0;
} else {
methane = 630 / pow(red_f, 4.4);
}
this->methane_sensor_->publish_state(methane);
}
if (this->ethanol_sensor_ != nullptr) {
float ethanol = 0.0f;
if (red_f <= 0.306f) {
ethanol = (0.306f - red_f) / 0.00057f;
if (ethanol < 10.0f)
ethanol = 0.0f;
if (ethanol > 500.0f)
ethanol = 500.0f;
if (red_f > 1.0f || red_f < 0.02) { // outside the range->unlikely
ethanol = 0.0;
} else {
ethanol = 1.52 / pow(red_f, 1.55);
}
this->ethanol_sensor_->publish_state(ethanol);
}
if (this->hydrogen_sensor_ != nullptr) {
float hydrogen = 0.0f;
if (red_f <= 0.279f) {
hydrogen = (0.279f - red_f) / 0.00026f;
if (hydrogen < 1.0f)
hydrogen = 0.0f;
if (hydrogen > 1000.0f)
hydrogen = 1000.0f;
if (red_f > 0.9f || red_f < 0.02) { // outside the range->unlikely
hydrogen = 0.0;
} else {
hydrogen = 0.85 / pow(red_f, 1.75);
}
this->hydrogen_sensor_->publish_state(hydrogen);
}
if (this->ammonia_sensor_ != nullptr) {
float ammonia = 0.0f;
if (red_f <= 0.8f) {
ammonia = (0.8f - red_f) / 0.0015f;
if (ammonia < 1.0f)
ammonia = 0.0f;
if (ammonia > 500.0f)
ammonia = 500.0f;
if (red_f > 0.98f || red_f < 0.2532) { // outside the ammonia range->unlikely
ammonia = 0.0;
} else {
ammonia = 0.9 / pow(red_f, 4.6);
}
this->ammonia_sensor_->publish_state(ammonia);
}

View file

@ -1,47 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import (
spi,
display,
)
from esphome.const import (
CONF_DC_PIN,
CONF_HSYNC_PIN,
CONF_RESET_PIN,
CONF_DATA_PINS,
CONF_ID,
CONF_DIMENSIONS,
CONF_VSYNC_PIN,
CONF_WIDTH,
CONF_HEIGHT,
CONF_LAMBDA,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_COLOR_ORDER,
CONF_TRANSFORM,
CONF_OFFSET_HEIGHT,
CONF_OFFSET_WIDTH,
CONF_INVERT_COLORS,
CONF_RED,
CONF_GREEN,
CONF_BLUE,
CONF_NUMBER,
CONF_IGNORE_STRAPPING_WARNING,
)
from esphome.components.esp32 import (
only_on_variant,
const,
)
import esphome.codegen as cg
from esphome.components import display, spi
from esphome.components.esp32 import const, only_on_variant
from esphome.components.rpi_dpi_rgb.display import (
CONF_PCLK_FREQUENCY,
CONF_PCLK_INVERTED,
)
from .init_sequences import (
ST7701S_INITS,
cmd,
import esphome.config_validation as cv
from esphome.const import (
CONF_BLUE,
CONF_COLOR_ORDER,
CONF_DATA_PINS,
CONF_DC_PIN,
CONF_DIMENSIONS,
CONF_GREEN,
CONF_HEIGHT,
CONF_HSYNC_PIN,
CONF_ID,
CONF_IGNORE_STRAPPING_WARNING,
CONF_INVERT_COLORS,
CONF_LAMBDA,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_NUMBER,
CONF_OFFSET_HEIGHT,
CONF_OFFSET_WIDTH,
CONF_RED,
CONF_RESET_PIN,
CONF_TRANSFORM,
CONF_VSYNC_PIN,
CONF_WIDTH,
)
from esphome.core import TimePeriod
from .init_sequences import ST7701S_INITS, cmd
CONF_INIT_SEQUENCE = "init_sequence"
CONF_DE_PIN = "de_pin"
@ -59,6 +51,7 @@ DEPENDENCIES = ["spi", "esp32"]
st7701s_ns = cg.esphome_ns.namespace("st7701s")
ST7701S = st7701s_ns.class_("ST7701S", display.Display, cg.Component, spi.SPIDevice)
ColorOrder = display.display_ns.enum("ColorMode")
ST7701S_DELAY_FLAG = 0xFF
COLOR_ORDERS = {
"RGB": ColorOrder.COLOR_ORDER_RGB,
@ -93,18 +86,23 @@ def map_sequence(value):
"""
An initialisation sequence can be selected from one of the pre-defined sequences in init_sequences.py,
or can be a literal array of data bytes.
The format is a repeated sequence of [CMD, LEN, <data>] where <data> is LEN bytes.
The format is a repeated sequence of [CMD, <data>] where <data> is s a sequence of bytes. The length is inferred
from the length of the sequence and should not be explicit.
A delay can be inserted by specifying "- delay N" where N is in ms
"""
if isinstance(value, str) and value.lower().startswith("delay "):
value = value.lower()[6:]
delay = cv.All(
cv.positive_time_period_milliseconds,
cv.Range(TimePeriod(milliseconds=1), TimePeriod(milliseconds=255)),
)(value)
return [delay, ST7701S_DELAY_FLAG]
if not isinstance(value, list):
value = cv.int_(value)
value = cv.one_of(*ST7701S_INITS)(value)
return ST7701S_INITS[value]
# value = cv.ensure_list(cv.uint8_t)(value)
data_length = len(value)
if data_length == 0:
raise cv.Invalid("Empty sequence")
value = cmd(*value)
return value
value = cv.Length(min=1, max=254)(value)
return cmd(*value)
CONFIG_SCHEMA = cv.All(

View file

@ -138,11 +138,16 @@ void ST7701S::write_init_sequence_() {
for (size_t i = 0; i != this->init_sequence_.size();) {
uint8_t cmd = this->init_sequence_[i++];
size_t len = this->init_sequence_[i++];
this->write_sequence_(cmd, len, &this->init_sequence_[i]);
i += len;
esph_log_v(TAG, "Command %X, %d bytes", cmd, len);
if (cmd == SW_RESET_CMD)
delay(6);
if (len == ST7701S_DELAY_FLAG) {
ESP_LOGV(TAG, "Delay %dms", cmd);
delay(cmd);
} else {
this->write_sequence_(cmd, len, &this->init_sequence_[i]);
i += len;
ESP_LOGV(TAG, "Command %X, %d bytes", cmd, len);
if (cmd == SW_RESET_CMD)
delay(6);
}
}
// st7701 does not appear to support axis swapping
this->write_sequence_(CMD2_BKSEL, sizeof(CMD2_BK0), CMD2_BK0);
@ -153,7 +158,7 @@ void ST7701S::write_init_sequence_() {
val |= 0x10;
this->write_command_(MADCTL_CMD);
this->write_data_(val);
esph_log_d(TAG, "write MADCTL %X", val);
ESP_LOGD(TAG, "write MADCTL %X", val);
this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF);
this->set_timeout(120, [this] {
this->write_command_(SLEEP_OUT);

View file

@ -25,6 +25,7 @@ const uint8_t INVERT_ON = 0x21;
const uint8_t DISPLAY_ON = 0x29;
const uint8_t CMD2_BKSEL = 0xFF;
const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10};
const uint8_t ST7701S_DELAY_FLAG = 0xFF;
class ST7701S : public display::Display,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,

View file

@ -9,6 +9,7 @@ from esphome.const import (
CONF_MULTIPLY,
CONF_STEP,
CONF_INITIAL_VALUE,
CONF_RESTORE_VALUE,
)
from .. import tuya_ns, CONF_TUYA_ID, Tuya, TuyaDatapointType
@ -58,6 +59,7 @@ CONFIG_SCHEMA = cv.All(
DATAPOINT_TYPES, lower=True
),
cv.Optional(CONF_INITIAL_VALUE): cv.float_,
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
}
)
),
@ -90,3 +92,4 @@ async def to_code(config):
hidden_init_value := hidden_config.get(CONF_INITIAL_VALUE, None)
) is not None:
cg.add(var.set_datapoint_initial_value(hidden_init_value))
cg.add(var.set_restore_value(hidden_config[CONF_RESTORE_VALUE]))

View file

@ -7,14 +7,28 @@ namespace tuya {
static const char *const TAG = "tuya.number";
void TuyaNumber::setup() {
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
}
this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) {
if (datapoint.type == TuyaDatapointType::INTEGER) {
ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int);
this->publish_state(datapoint.value_int / multiply_by_);
float value = datapoint.value_int / multiply_by_;
this->publish_state(value);
if (this->restore_value_)
this->pref_.save(&value);
} else if (datapoint.type == TuyaDatapointType::ENUM) {
ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum);
this->publish_state(datapoint.value_enum);
float value = datapoint.value_enum;
this->publish_state(value);
if (this->restore_value_)
this->pref_.save(&value);
} else {
ESP_LOGW(TAG, "Reported type (%d) is not a number!", static_cast<int>(datapoint.type));
return;
}
if ((this->type_) && (this->type_ != datapoint.type)) {
ESP_LOGW(TAG, "Reported type (%d) different than previously set (%d)!", static_cast<int>(datapoint.type),
static_cast<int>(*this->type_));
@ -23,8 +37,26 @@ void TuyaNumber::setup() {
});
this->parent_->add_on_initialized_callback([this] {
if ((this->initial_value_) && (this->type_)) {
this->control(*this->initial_value_);
if (this->type_) {
float value;
if (!this->restore_value_) {
if (this->initial_value_) {
value = *this->initial_value_;
} else {
return;
}
} else {
if (!this->pref_.load(&value)) {
if (this->initial_value_) {
value = *this->initial_value_;
} else {
value = this->traits.get_min_value();
ESP_LOGW(TAG, "Failed to restore and there is no initial value defined. Setting min_value (%f)", value);
}
}
}
this->control(value);
}
});
}
@ -38,6 +70,9 @@ void TuyaNumber::control(float value) {
this->parent_->set_enum_datapoint_value(this->number_id_, value);
}
this->publish_state(value);
if (this->restore_value_)
this->pref_.save(&value);
}
void TuyaNumber::dump_config() {
@ -52,6 +87,8 @@ void TuyaNumber::dump_config() {
if (this->initial_value_) {
ESP_LOGCONFIG(TAG, " Initial Value: %f", *this->initial_value_);
}
ESP_LOGCONFIG(TAG, " Restore Value: %s", YESNO(this->restore_value_));
}
} // namespace tuya

View file

@ -1,9 +1,10 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/tuya/tuya.h"
#include "esphome/components/number/number.h"
#include "esphome/components/tuya/tuya.h"
#include "esphome/core/component.h"
#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace tuya {
@ -16,6 +17,7 @@ class TuyaNumber : public number::Number, public Component {
void set_write_multiply(float factor) { multiply_by_ = factor; }
void set_datapoint_type(TuyaDatapointType type) { type_ = type; }
void set_datapoint_initial_value(float value) { this->initial_value_ = value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
@ -27,6 +29,9 @@ class TuyaNumber : public number::Number, public Component {
float multiply_by_{1.0};
optional<TuyaDatapointType> type_{};
optional<float> initial_value_{};
bool restore_value_{false};
ESPPreferenceObject pref_;
};
} // namespace tuya

View file

@ -0,0 +1,158 @@
import hashlib
import esphome.codegen as cg
from esphome.components.api import CONF_ENCRYPTION
from esphome.components.binary_sensor import BinarySensor
from esphome.components.sensor import Sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_BINARY_SENSORS,
CONF_ID,
CONF_INTERNAL,
CONF_KEY,
CONF_NAME,
CONF_PORT,
CONF_SENSORS,
)
from esphome.cpp_generator import MockObjClass
CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"]
MULTI_CONF = True
udp_ns = cg.esphome_ns.namespace("udp")
UDPComponent = udp_ns.class_("UDPComponent", cg.PollingComponent)
CONF_BROADCAST = "broadcast"
CONF_BROADCAST_ID = "broadcast_id"
CONF_ADDRESSES = "addresses"
CONF_PROVIDER = "provider"
CONF_PROVIDERS = "providers"
CONF_REMOTE_ID = "remote_id"
CONF_UDP_ID = "udp_id"
CONF_PING_PONG_ENABLE = "ping_pong_enable"
CONF_PING_PONG_RECYCLE_TIME = "ping_pong_recycle_time"
CONF_ROLLING_CODE_ENABLE = "rolling_code_enable"
def sensor_validation(cls: MockObjClass):
return cv.maybe_simple_value(
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(cls),
cv.Optional(CONF_BROADCAST_ID): cv.validate_id_name,
}
),
key=CONF_ID,
)
ENCRYPTION_SCHEMA = {
cv.Optional(CONF_ENCRYPTION): cv.maybe_simple_value(
cv.Schema(
{
cv.Required(CONF_KEY): cv.string,
}
),
key=CONF_KEY,
)
}
PROVIDER_SCHEMA = cv.Schema(
{
cv.Required(CONF_NAME): cv.valid_name,
}
).extend(ENCRYPTION_SCHEMA)
def validate_(config):
if CONF_ENCRYPTION in config:
if CONF_SENSORS not in config and CONF_BINARY_SENSORS not in config:
raise cv.Invalid("No sensors or binary sensors to encrypt")
elif config[CONF_ROLLING_CODE_ENABLE]:
raise cv.Invalid("Rolling code requires an encryption key")
if config[CONF_PING_PONG_ENABLE]:
if not any(CONF_ENCRYPTION in p for p in config.get(CONF_PROVIDERS) or ()):
raise cv.Invalid("Ping-pong requires at least one encrypted provider")
return config
CONFIG_SCHEMA = cv.All(
cv.polling_component_schema("15s")
.extend(
{
cv.GenerateID(): cv.declare_id(UDPComponent),
cv.Optional(CONF_PORT, default=18511): cv.port,
cv.Optional(CONF_ADDRESSES, default=["255.255.255.255"]): cv.ensure_list(
cv.ipv4
),
cv.Optional(CONF_ROLLING_CODE_ENABLE, default=False): cv.boolean,
cv.Optional(CONF_PING_PONG_ENABLE, default=False): cv.boolean,
cv.Optional(
CONF_PING_PONG_RECYCLE_TIME, default="600s"
): cv.positive_time_period_seconds,
cv.Optional(CONF_SENSORS): cv.ensure_list(sensor_validation(Sensor)),
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
sensor_validation(BinarySensor)
),
cv.Optional(CONF_PROVIDERS): cv.ensure_list(PROVIDER_SCHEMA),
},
)
.extend(ENCRYPTION_SCHEMA),
validate_,
)
SENSOR_SCHEMA = cv.Schema(
{
cv.Optional(CONF_REMOTE_ID): cv.string_strict,
cv.Required(CONF_PROVIDER): cv.valid_name,
cv.GenerateID(CONF_UDP_ID): cv.use_id(UDPComponent),
}
)
def require_internal_with_name(config):
if CONF_NAME in config and CONF_INTERNAL not in config:
raise cv.Invalid("Must provide internal: config when using name:")
return config
def hash_encryption_key(config: dict):
return list(hashlib.sha256(config[CONF_KEY].encode()).digest())
async def to_code(config):
cg.add_define("USE_UDP")
cg.add_global(udp_ns.using)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_rolling_code_enable(config[CONF_ROLLING_CODE_ENABLE]))
cg.add(var.set_ping_pong_enable(config[CONF_PING_PONG_ENABLE]))
cg.add(
var.set_ping_pong_recycle_time(
config[CONF_PING_PONG_RECYCLE_TIME].total_seconds
)
)
for sens_conf in config.get(CONF_SENSORS, ()):
sens_id = sens_conf[CONF_ID]
sensor = await cg.get_variable(sens_id)
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
cg.add(var.add_sensor(bcst_id, sensor))
for sens_conf in config.get(CONF_BINARY_SENSORS, ()):
sens_id = sens_conf[CONF_ID]
sensor = await cg.get_variable(sens_id)
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
cg.add(var.add_binary_sensor(bcst_id, sensor))
for address in config[CONF_ADDRESSES]:
cg.add(var.add_address(str(address)))
if encryption := config.get(CONF_ENCRYPTION):
cg.add(var.set_encryption_key(hash_encryption_key(encryption)))
for provider in config.get(CONF_PROVIDERS, ()):
name = provider[CONF_NAME]
cg.add(var.add_provider(name))
if encryption := provider.get(CONF_ENCRYPTION):
cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption)))

View file

@ -0,0 +1,27 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.config_validation import All, has_at_least_one_key
from esphome.const import CONF_ID
from . import (
CONF_PROVIDER,
CONF_REMOTE_ID,
CONF_UDP_ID,
SENSOR_SCHEMA,
require_internal_with_name,
)
DEPENDENCIES = ["udp"]
CONFIG_SCHEMA = All(
binary_sensor.binary_sensor_schema().extend(SENSOR_SCHEMA),
has_at_least_one_key(CONF_ID, CONF_REMOTE_ID),
require_internal_with_name,
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
comp = await cg.get_variable(config[CONF_UDP_ID])
remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID))
cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var))

View file

@ -0,0 +1,27 @@
import esphome.codegen as cg
from esphome.components.sensor import new_sensor, sensor_schema
from esphome.config_validation import All, has_at_least_one_key
from esphome.const import CONF_ID
from . import (
CONF_PROVIDER,
CONF_REMOTE_ID,
CONF_UDP_ID,
SENSOR_SCHEMA,
require_internal_with_name,
)
DEPENDENCIES = ["udp"]
CONFIG_SCHEMA = All(
sensor_schema().extend(SENSOR_SCHEMA),
has_at_least_one_key(CONF_ID, CONF_REMOTE_ID),
require_internal_with_name,
)
async def to_code(config):
var = await new_sensor(config)
comp = await cg.get_variable(config[CONF_UDP_ID])
remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID))
cg.add(comp.add_remote_sensor(config[CONF_PROVIDER], remote_id, var))

View file

@ -0,0 +1,616 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/network/util.h"
#include "udp_component.h"
namespace esphome {
namespace udp {
/**
* Structure of a data packet; everything is little-endian
*
* --- In clear text ---
* MAGIC_NUMBER: 16 bits
* host name length: 1 byte
* host name: (length) bytes
* padding: 0 or more null bytes to a 4 byte boundary
*
* --- Encrypted (if key set) ----
* DATA_KEY: 1 byte: OR ROLLING_CODE_KEY:
* Rolling code (if enabled): 8 bytes
* Ping keys: if any
* repeat:
* PING_KEY: 1 byte
* ping code: 4 bytes
* Sensors:
* repeat:
* SENSOR_KEY: 1 byte
* float value: 4 bytes
* name length: 1 byte
* name
* Binary Sensors:
* repeat:
* BINARY_SENSOR_KEY: 1 byte
* bool value: 1 bytes
* name length: 1 byte
* name
*
* Padded to a 4 byte boundary with nulls
*
* Structure of a ping request packet:
* --- In clear text ---
* MAGIC_PING: 16 bits
* host name length: 1 byte
* host name: (length) bytes
* Ping key (4 bytes)
*
*/
static const char *const TAG = "udp";
/**
* XXTEA implementation, using 256 bit key.
*/
static const uint32_t DELTA = 0x9e3779b9;
#define MX ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (k[(p ^ e) & 7] ^ z)))
/**
* Encrypt a block of data in-place
*/
static void xxtea_encrypt(uint32_t *v, size_t n, const uint32_t *k) {
uint32_t z, y, sum, e;
size_t p;
size_t q = 6 + 52 / n;
sum = 0;
z = v[n - 1];
while (q-- != 0) {
sum += DELTA;
e = (sum >> 2);
for (p = 0; p != n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
}
}
static void xxtea_decrypt(uint32_t *v, size_t n, const uint32_t *k) {
uint32_t z, y, sum, e;
size_t p;
size_t q = 6 + 52 / n;
sum = q * DELTA;
y = v[0];
while (q-- != 0) {
e = (sum >> 2);
for (p = n - 1; p != 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
}
}
inline static size_t round4(size_t value) { return (value + 3) & ~3; }
union FuData {
uint32_t u32;
float f32;
};
static const size_t MAX_PACKET_SIZE = 508;
static const uint16_t MAGIC_NUMBER = 0x4553;
static const uint16_t MAGIC_PING = 0x5048;
static const uint32_t PREF_HASH = 0x45535043;
enum DataKey {
ZERO_FILL_KEY,
DATA_KEY,
SENSOR_KEY,
BINARY_SENSOR_KEY,
PING_KEY,
ROLLING_CODE_KEY,
};
static const size_t MAX_PING_KEYS = 4;
static inline void add(std::vector<uint8_t> &vec, uint32_t data) {
vec.push_back(data & 0xFF);
vec.push_back((data >> 8) & 0xFF);
vec.push_back((data >> 16) & 0xFF);
vec.push_back((data >> 24) & 0xFF);
}
static inline uint32_t get_uint32(uint8_t *&buf) {
uint32_t data = *buf++;
data += *buf++ << 8;
data += *buf++ << 16;
data += *buf++ << 24;
return data;
}
static inline uint16_t get_uint16(uint8_t *&buf) {
uint16_t data = *buf++;
data += *buf++ << 8;
return data;
}
static inline void add(std::vector<uint8_t> &vec, uint8_t data) { vec.push_back(data); }
static inline void add(std::vector<uint8_t> &vec, uint16_t data) {
vec.push_back((uint8_t) data);
vec.push_back((uint8_t) (data >> 8));
}
static inline void add(std::vector<uint8_t> &vec, DataKey data) { vec.push_back(data); }
static void add(std::vector<uint8_t> &vec, const char *str) {
auto len = strlen(str);
vec.push_back(len);
for (size_t i = 0; i != len; i++) {
vec.push_back(*str++);
}
}
void UDPComponent::setup() {
this->name_ = App.get_name().c_str();
if (strlen(this->name_) > 255) {
this->mark_failed();
this->status_set_error("Device name exceeds 255 chars");
return;
}
this->resend_ping_key_ = this->ping_pong_enable_;
// restore the upper 32 bits of the rolling code, increment and save.
this->pref_ = global_preferences->make_preference<uint32_t>(PREF_HASH, true);
this->pref_.load(&this->rolling_code_[1]);
this->rolling_code_[1]++;
this->pref_.save(&this->rolling_code_[1]);
this->ping_key_ = random_uint32();
ESP_LOGV(TAG, "Rolling code incremented, upper part now %u", (unsigned) this->rolling_code_[1]);
#ifdef USE_SENSOR
for (auto &sensor : this->sensors_) {
sensor.sensor->add_on_state_callback([this, &sensor](float x) {
this->updated_ = true;
sensor.updated = true;
});
}
#endif
#ifdef USE_BINARY_SENSOR
for (auto &sensor : this->binary_sensors_) {
sensor.sensor->add_on_state_callback([this, &sensor](bool value) {
this->updated_ = true;
sensor.updated = true;
});
}
#endif
this->should_send_ = this->ping_pong_enable_;
#ifdef USE_SENSOR
this->should_send_ |= !this->sensors_.empty();
#endif
#ifdef USE_BINARY_SENSOR
this->should_send_ |= !this->binary_sensors_.empty();
#endif
this->should_listen_ = !this->providers_.empty() || this->is_encrypted_();
// initialise the header. This is invariant.
add(this->header_, MAGIC_NUMBER);
add(this->header_, this->name_);
// pad to a multiple of 4 bytes
while (this->header_.size() & 0x3)
this->header_.push_back(0);
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
for (const auto &address : this->addresses_) {
struct sockaddr saddr {};
socket::set_sockaddr(&saddr, sizeof(saddr), address, this->port_);
this->sockaddrs_.push_back(saddr);
}
// set up broadcast socket
if (this->should_send_) {
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (this->broadcast_socket_ == nullptr) {
this->mark_failed();
this->status_set_error("Could not create socket");
return;
}
int enable = 1;
auto err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
this->status_set_warning("Socket unable to set reuseaddr");
// we can still continue
}
err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int));
if (err != 0) {
this->status_set_warning("Socket unable to set broadcast");
}
}
// create listening socket if we either want to subscribe to providers, or need to listen
// for ping key broadcasts.
if (this->should_listen_) {
this->listen_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (this->listen_socket_ == nullptr) {
this->mark_failed();
this->status_set_error("Could not create socket");
return;
}
auto err = this->listen_socket_->setblocking(false);
if (err < 0) {
ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno);
this->mark_failed();
this->status_set_error("Unable to set nonblocking");
return;
}
int enable = 1;
err = this->listen_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
if (err != 0) {
this->status_set_warning("Socket unable to set reuseaddr");
// we can still continue
}
struct sockaddr_in server {};
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
if (sl == 0) {
ESP_LOGE(TAG, "Socket unable to set sockaddr: errno %d", errno);
this->mark_failed();
this->status_set_error("Unable to set sockaddr");
return;
}
err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed();
this->status_set_error("Unable to bind socket");
return;
}
}
#else
// 8266 and RP2040 `Duino
for (const auto &address : this->addresses_) {
auto ipaddr = IPAddress();
ipaddr.fromString(address.c_str());
this->ipaddrs_.push_back(ipaddr);
}
if (this->should_listen_)
this->udp_client_.begin(this->port_);
#endif
}
void UDPComponent::init_data_() {
this->data_.clear();
if (this->rolling_code_enable_) {
add(this->data_, ROLLING_CODE_KEY);
add(this->data_, this->rolling_code_[0]);
add(this->data_, this->rolling_code_[1]);
this->increment_code_();
} else {
add(this->data_, DATA_KEY);
}
for (auto pkey : this->ping_keys_) {
add(this->data_, PING_KEY);
add(this->data_, pkey.second);
}
}
void UDPComponent::flush_() {
if (!network::is_connected() || this->data_.empty())
return;
uint32_t buffer[MAX_PACKET_SIZE / 4];
memset(buffer, 0, sizeof buffer);
// len must be a multiple of 4
auto header_len = round4(this->header_.size()) / 4;
auto len = round4(data_.size()) / 4;
memcpy(buffer, this->header_.data(), this->header_.size());
memcpy(buffer + header_len, this->data_.data(), this->data_.size());
if (this->is_encrypted_()) {
xxtea_encrypt(buffer + header_len, len, (uint32_t *) this->encryption_key_.data());
}
auto total_len = (header_len + len) * 4;
this->send_packet_(buffer, total_len);
}
void UDPComponent::add_binary_data_(uint8_t key, const char *id, bool data) {
auto len = 1 + 1 + 1 + strlen(id);
if (len + this->header_.size() + this->data_.size() > MAX_PACKET_SIZE) {
this->flush_();
}
add(this->data_, key);
add(this->data_, (uint8_t) data);
add(this->data_, id);
}
void UDPComponent::add_data_(uint8_t key, const char *id, float data) {
FuData udata{.f32 = data};
this->add_data_(key, id, udata.u32);
}
void UDPComponent::add_data_(uint8_t key, const char *id, uint32_t data) {
auto len = 4 + 1 + 1 + strlen(id);
if (len + this->header_.size() + this->data_.size() > MAX_PACKET_SIZE) {
this->flush_();
}
add(this->data_, key);
add(this->data_, data);
add(this->data_, id);
}
void UDPComponent::send_data_(bool all) {
if (!this->should_send_ || !network::is_connected())
return;
this->init_data_();
#ifdef USE_SENSOR
for (auto &sensor : this->sensors_) {
if (all || sensor.updated) {
sensor.updated = false;
this->add_data_(SENSOR_KEY, sensor.id, sensor.sensor->get_state());
}
}
#endif
#ifdef USE_BINARY_SENSOR
for (auto &sensor : this->binary_sensors_) {
if (all || sensor.updated) {
sensor.updated = false;
this->add_binary_data_(BINARY_SENSOR_KEY, sensor.id, sensor.sensor->state);
}
}
#endif
this->flush_();
this->updated_ = false;
this->resend_data_ = false;
}
void UDPComponent::update() {
this->updated_ = true;
this->resend_data_ = this->should_send_;
auto now = millis() / 1000;
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
this->resend_ping_key_ = this->ping_pong_enable_;
this->last_key_time_ = now;
}
}
void UDPComponent::loop() {
uint8_t buf[MAX_PACKET_SIZE];
if (this->should_listen_) {
for (;;) {
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
auto len = this->listen_socket_->read(buf, sizeof(buf));
#else
auto len = this->udp_client_.parsePacket();
if (len > 0)
len = this->udp_client_.read(buf, sizeof(buf));
#endif
if (len > 0) {
this->process_(buf, len);
continue;
}
break;
}
}
if (this->resend_ping_key_)
this->send_ping_pong_request_();
if (this->updated_) {
this->send_data_(this->resend_data_);
}
}
void UDPComponent::add_key_(const char *name, uint32_t key) {
if (!this->is_encrypted_())
return;
if (this->ping_keys_.count(name) == 0 && this->ping_keys_.size() == MAX_PING_KEYS) {
ESP_LOGW(TAG, "Ping key from %s discarded", name);
return;
}
this->ping_keys_[name] = key;
this->resend_data_ = true;
ESP_LOGV(TAG, "Ping key from %s now %X", name, (unsigned) key);
}
void UDPComponent::process_ping_request_(const char *name, uint8_t *ptr, size_t len) {
if (len != 4) {
ESP_LOGW(TAG, "Bad ping request");
return;
}
auto key = get_uint32(ptr);
this->add_key_(name, key);
ESP_LOGV(TAG, "Updated ping key for %s to %08X", name, (unsigned) key);
}
static bool process_rolling_code(Provider &provider, uint8_t *&buf, const uint8_t *end) {
if (end - buf < 8)
return false;
auto code0 = get_uint32(buf);
auto code1 = get_uint32(buf);
if (code1 < provider.last_code[1] || (code1 == provider.last_code[1] && code0 <= provider.last_code[0])) {
ESP_LOGW(TAG, "Rolling code for %s %08lX:%08lX is old", provider.name, (unsigned long) code1,
(unsigned long) code0);
return false;
}
provider.last_code[0] = code0;
provider.last_code[1] = code1;
return true;
}
/**
* Process a received packet
*/
void UDPComponent::process_(uint8_t *buf, const size_t len) {
auto ping_key_seen = !this->ping_pong_enable_;
if (len < 8) {
return ESP_LOGV(TAG, "Bad length %zu", len);
}
char namebuf[256]{};
uint8_t byte;
uint8_t *start_ptr = buf;
const uint8_t *end = buf + len;
FuData rdata{};
auto magic = get_uint16(buf);
if (magic != MAGIC_NUMBER && magic != MAGIC_PING)
return ESP_LOGV(TAG, "Bad magic %X", magic);
auto hlen = *buf++;
if (hlen > len - 3) {
return ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3);
}
memcpy(namebuf, buf, hlen);
if (strcmp(this->name_, namebuf) == 0) {
return ESP_LOGV(TAG, "Ignoring our own data");
}
buf += hlen;
if (magic == MAGIC_PING)
return this->process_ping_request_(namebuf, buf, end - buf);
if (round4(len) != len) {
return ESP_LOGW(TAG, "Bad length %zu", len);
}
hlen = round4(hlen + 3);
buf = start_ptr + hlen;
if (buf == end) {
return ESP_LOGV(TAG, "No data after header");
}
if (this->providers_.count(namebuf) == 0) {
return ESP_LOGVV(TAG, "Unknown hostname %s", namebuf);
}
auto &provider = this->providers_[namebuf];
// if encryption not used with this host, ping check is pointless since it would be easily spoofed.
if (provider.encryption_key.empty())
ping_key_seen = true;
ESP_LOGV(TAG, "Found hostname %s", namebuf);
#ifdef USE_SENSOR
auto &sensors = this->remote_sensors_[namebuf];
#endif
#ifdef USE_BINARY_SENSOR
auto &binary_sensors = this->remote_binary_sensors_[namebuf];
#endif
if (!provider.encryption_key.empty()) {
xxtea_decrypt((uint32_t *) buf, (end - buf) / 4, (uint32_t *) provider.encryption_key.data());
}
byte = *buf++;
if (byte == ROLLING_CODE_KEY) {
if (!process_rolling_code(provider, buf, end))
return;
} else if (byte != DATA_KEY) {
return ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte);
}
while (buf < end) {
byte = *buf++;
if (byte == ZERO_FILL_KEY)
continue;
if (byte == PING_KEY) {
if (end - buf < 4) {
return ESP_LOGV(TAG, "PING_KEY requires 4 more bytes");
}
auto key = get_uint32(buf);
if (key == this->ping_key_) {
ping_key_seen = true;
ESP_LOGV(TAG, "Found good ping key %X", (unsigned) key);
} else {
ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
}
continue;
}
if (!ping_key_seen) {
ESP_LOGW(TAG, "Ping key not seen");
this->resend_ping_key_ = true;
break;
}
if (byte == BINARY_SENSOR_KEY) {
if (end - buf < 3) {
return ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes");
}
rdata.u32 = *buf++;
} else if (byte == SENSOR_KEY) {
if (end - buf < 6) {
return ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes");
}
rdata.u32 = get_uint32(buf);
} else {
return ESP_LOGW(TAG, "Unknown key byte %X", byte);
}
hlen = *buf++;
if (end - buf < hlen) {
return ESP_LOGV(TAG, "Name length of %u not available", hlen);
}
memset(namebuf, 0, sizeof namebuf);
memcpy(namebuf, buf, hlen);
ESP_LOGV(TAG, "Found sensor key %d, id %s, data %lX", byte, namebuf, (unsigned long) rdata.u32);
buf += hlen;
#ifdef USE_SENSOR
if (byte == SENSOR_KEY && sensors.count(namebuf) != 0)
sensors[namebuf]->publish_state(rdata.f32);
#endif
#ifdef USE_BINARY_SENSOR
if (byte == BINARY_SENSOR_KEY && binary_sensors.count(namebuf) != 0)
binary_sensors[namebuf]->publish_state(rdata.u32 != 0);
#endif
}
}
void UDPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "UDP:");
ESP_LOGCONFIG(TAG, " Port: %u", this->port_);
ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(this->is_encrypted_()));
ESP_LOGCONFIG(TAG, " Ping-pong: %s", YESNO(this->ping_pong_enable_));
for (const auto &address : this->addresses_)
ESP_LOGCONFIG(TAG, " Address: %s", address.c_str());
#ifdef USE_SENSOR
for (auto sensor : this->sensors_)
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id);
#endif
#ifdef USE_BINARY_SENSOR
for (auto sensor : this->binary_sensors_)
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id);
#endif
for (const auto &host : this->providers_) {
ESP_LOGCONFIG(TAG, " Remote host: %s", host.first.c_str());
ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(!host.second.encryption_key.empty()));
#ifdef USE_SENSOR
for (const auto &sensor : this->remote_sensors_[host.first.c_str()])
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.first.c_str());
#endif
#ifdef USE_BINARY_SENSOR
for (const auto &sensor : this->remote_binary_sensors_[host.first.c_str()])
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.first.c_str());
#endif
}
}
void UDPComponent::increment_code_() {
if (this->rolling_code_enable_) {
if (++this->rolling_code_[0] == 0) {
this->rolling_code_[1]++;
this->pref_.save(&this->rolling_code_[1]);
}
}
}
void UDPComponent::send_packet_(void *data, size_t len) {
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
for (const auto &saddr : this->sockaddrs_) {
auto result = this->broadcast_socket_->sendto(data, len, 0, &saddr, sizeof(saddr));
if (result < 0)
ESP_LOGW(TAG, "sendto() error %d", errno);
}
#else
auto iface = IPAddress(0, 0, 0, 0);
for (const auto &saddr : this->ipaddrs_) {
if (this->udp_client_.beginPacketMulticast(saddr, this->port_, iface, 128) != 0) {
this->udp_client_.write((const uint8_t *) data, len);
auto result = this->udp_client_.endPacket();
if (result == 0)
ESP_LOGW(TAG, "udp.write() error");
}
}
#endif
}
void UDPComponent::send_ping_pong_request_() {
if (!this->ping_pong_enable_ || !network::is_connected())
return;
this->ping_key_ = random_uint32();
this->ping_header_.clear();
add(this->ping_header_, MAGIC_PING);
add(this->ping_header_, this->name_);
add(this->ping_header_, this->ping_key_);
this->send_packet_(this->ping_header_.data(), this->ping_header_.size());
this->resend_ping_key_ = false;
ESP_LOGV(TAG, "Sent new ping request %08X", (unsigned) this->ping_key_);
}
} // namespace udp
} // namespace esphome

View file

@ -0,0 +1,158 @@
#pragma once
#include "esphome/core/component.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
#include "esphome/components/socket/socket.h"
#else
#include <WiFiUdp.h>
#endif
#include <vector>
#include <map>
namespace esphome {
namespace udp {
struct Provider {
std::vector<uint8_t> encryption_key;
const char *name;
uint32_t last_code[2];
};
#ifdef USE_SENSOR
struct Sensor {
sensor::Sensor *sensor;
const char *id;
bool updated;
};
#endif
#ifdef USE_BINARY_SENSOR
struct BinarySensor {
binary_sensor::BinarySensor *sensor;
const char *id;
bool updated;
};
#endif
class UDPComponent : public PollingComponent {
public:
void setup() override;
void loop() override;
void update() override;
void dump_config() override;
#ifdef USE_SENSOR
void add_sensor(const char *id, sensor::Sensor *sensor) {
Sensor st{sensor, id, true};
this->sensors_.push_back(st);
}
void add_remote_sensor(const char *hostname, const char *remote_id, sensor::Sensor *sensor) {
this->add_provider(hostname);
this->remote_sensors_[hostname][remote_id] = sensor;
}
#endif
#ifdef USE_BINARY_SENSOR
void add_binary_sensor(const char *id, binary_sensor::BinarySensor *sensor) {
BinarySensor st{sensor, id, true};
this->binary_sensors_.push_back(st);
}
void add_remote_binary_sensor(const char *hostname, const char *remote_id, binary_sensor::BinarySensor *sensor) {
this->add_provider(hostname);
this->remote_binary_sensors_[hostname][remote_id] = sensor;
}
#endif
void add_address(const char *addr) { this->addresses_.emplace_back(addr); }
void set_port(uint16_t port) { this->port_ = port; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void add_provider(const char *hostname) {
if (this->providers_.count(hostname) == 0) {
Provider provider;
provider.encryption_key = std::vector<uint8_t>{};
provider.last_code[0] = 0;
provider.last_code[1] = 0;
provider.name = hostname;
this->providers_[hostname] = provider;
#ifdef USE_SENSOR
this->remote_sensors_[hostname] = std::map<std::string, sensor::Sensor *>();
#endif
#ifdef USE_BINARY_SENSOR
this->remote_binary_sensors_[hostname] = std::map<std::string, binary_sensor::BinarySensor *>();
#endif
}
}
void set_encryption_key(std::vector<uint8_t> key) { this->encryption_key_ = std::move(key); }
void set_rolling_code_enable(bool enable) { this->rolling_code_enable_ = enable; }
void set_ping_pong_enable(bool enable) { this->ping_pong_enable_ = enable; }
void set_ping_pong_recycle_time(uint32_t recycle_time) { this->ping_pong_recyle_time_ = recycle_time; }
void set_provider_encryption(const char *name, std::vector<uint8_t> key) {
this->providers_[name].encryption_key = std::move(key);
}
protected:
void send_data_(bool all);
void process_(uint8_t *buf, size_t len);
void flush_();
void add_data_(uint8_t key, const char *id, float data);
void add_data_(uint8_t key, const char *id, uint32_t data);
void increment_code_();
void add_binary_data_(uint8_t key, const char *id, bool data);
void init_data_();
bool updated_{};
uint16_t port_{18511};
uint32_t ping_key_{};
uint32_t rolling_code_[2]{};
bool rolling_code_enable_{};
bool ping_pong_enable_{};
uint32_t ping_pong_recyle_time_{};
uint32_t last_key_time_{};
bool resend_ping_key_{};
bool resend_data_{};
bool should_send_{};
const char *name_{};
bool should_listen_{};
ESPPreferenceObject pref_;
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
std::unique_ptr<socket::Socket> broadcast_socket_ = nullptr;
std::unique_ptr<socket::Socket> listen_socket_ = nullptr;
std::vector<struct sockaddr> sockaddrs_{};
#else
std::vector<IPAddress> ipaddrs_{};
WiFiUDP udp_client_{};
#endif
std::vector<uint8_t> encryption_key_{};
std::vector<std::string> addresses_{};
#ifdef USE_SENSOR
std::vector<Sensor> sensors_{};
std::map<std::string, std::map<std::string, sensor::Sensor *>> remote_sensors_{};
#endif
#ifdef USE_BINARY_SENSOR
std::vector<BinarySensor> binary_sensors_{};
std::map<std::string, std::map<std::string, binary_sensor::BinarySensor *>> remote_binary_sensors_{};
#endif
std::map<std::string, Provider> providers_{};
std::vector<uint8_t> ping_header_{};
std::vector<uint8_t> header_{};
std::vector<uint8_t> data_{};
std::map<const char *, uint32_t> ping_keys_{};
void add_key_(const char *name, uint32_t key);
void send_ping_pong_request_();
void send_packet_(void *data, size_t len);
void process_ping_request_(const char *name, uint8_t *ptr, size_t len);
inline bool is_encrypted_() { return !this->encryption_key_.empty(); }
};
} // namespace udp
} // namespace esphome

View file

@ -1,35 +1,36 @@
from __future__ import annotations
import gzip
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
import esphome.config_validation as cv
from esphome.const import (
CONF_AUTH,
CONF_CSS_INCLUDE,
CONF_CSS_URL,
CONF_ENABLE_PRIVATE_NETWORK_ACCESS,
CONF_ID,
CONF_INCLUDE_INTERNAL,
CONF_JS_INCLUDE,
CONF_JS_URL,
CONF_ENABLE_PRIVATE_NETWORK_ACCESS,
CONF_PORT,
CONF_AUTH,
CONF_USERNAME,
CONF_PASSWORD,
CONF_INCLUDE_INTERNAL,
CONF_OTA,
CONF_LOG,
CONF_VERSION,
CONF_LOCAL,
CONF_LOG,
CONF_OTA,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
CONF_VERSION,
CONF_WEB_SERVER_ID,
CONF_WEB_SERVER_SORTING_WEIGHT,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, coroutine_with_priority
import esphome.final_validate as fv
AUTO_LOAD = ["json", "web_server_base"]
@ -208,7 +209,6 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, config)
cg.add_define("USE_WEBSERVER")
version = config[CONF_VERSION]
cg.add(paren.set_port(config[CONF_PORT]))

View file

@ -1,4 +1,5 @@
#include "list_entities.h"
#ifdef USE_WEBSERVER
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@ -188,3 +189,4 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
} // namespace web_server
} // namespace esphome
#endif

View file

@ -1,8 +1,9 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_WEBSERVER
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace web_server {
@ -78,3 +79,4 @@ class ListEntitiesIterator : public ComponentIterator {
} // namespace web_server
} // namespace esphome
#endif

View file

@ -1,5 +1,5 @@
#include "web_server.h"
#ifdef USE_WEBSERVER
#include "esphome/components/json/json_util.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
@ -1659,3 +1659,4 @@ void WebServer::schedule_(std::function<void()> &&f) {
} // namespace web_server
} // namespace esphome
#endif

View file

@ -3,6 +3,7 @@
#include "list_entities.h"
#include "esphome/components/web_server_base/web_server_base.h"
#ifdef USE_WEBSERVER
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
@ -366,3 +367,4 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
} // namespace web_server
} // namespace esphome
#endif

View file

@ -1,4 +1,5 @@
#include "web_server_base.h"
#ifdef USE_NETWORK
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
@ -121,3 +122,4 @@ float WebServerBase::get_setup_priority() const {
} // namespace web_server_base
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_NETWORK
#include <memory>
#include <utility>
#include <vector>
@ -145,3 +146,4 @@ class OTARequestHandler : public AsyncWebHandler {
} // namespace web_server_base
} // namespace esphome
#endif

View file

@ -730,6 +730,7 @@ CONF_RW_PIN = "rw_pin"
CONF_RX_BUFFER_SIZE = "rx_buffer_size"
CONF_RX_ONLY = "rx_only"
CONF_RX_PIN = "rx_pin"
CONF_RX_QUEUE_LEN = "rx_queue_len"
CONF_SAFE_MODE = "safe_mode"
CONF_SAMPLE_RATE = "sample_rate"
CONF_SAMSUNG = "samsung"
@ -881,6 +882,7 @@ CONF_TVOC = "tvoc"
CONF_TX_BUFFER_SIZE = "tx_buffer_size"
CONF_TX_PIN = "tx_pin"
CONF_TX_POWER = "tx_power"
CONF_TX_QUEUE_LEN = "tx_queue_len"
CONF_TYPE = "type"
CONF_TYPE_ID = "type_id"
CONF_UART_ID = "uart_id"

View file

@ -1,6 +1,9 @@
#include "bytebuffer.h"
#include <cassert>
#include <cstring>
#include "esphome/core/helpers.h"
#include <list>
#include <vector>
namespace esphome {
@ -110,18 +113,13 @@ uint32_t ByteBuffer::get_int24() {
}
float ByteBuffer::get_float() {
assert(this->get_remaining() >= sizeof(float));
auto ui_value = this->get_uint32();
float value;
memcpy(&value, &ui_value, sizeof(float));
return value;
return bit_cast<float>(this->get_uint32());
}
double ByteBuffer::get_double() {
assert(this->get_remaining() >= sizeof(double));
auto ui_value = this->get_uint64();
double value;
memcpy(&value, &ui_value, sizeof(double));
return value;
return bit_cast<double>(this->get_uint64());
}
std::vector<uint8_t> ByteBuffer::get_vector(size_t length) {
assert(this->get_remaining() >= length);
auto start = this->data_.begin() + this->position_;
@ -154,16 +152,12 @@ void ByteBuffer::put_uint(uint64_t value, size_t length) {
void ByteBuffer::put_float(float value) {
static_assert(sizeof(float) == sizeof(uint32_t), "Float sizes other than 32 bit not supported");
assert(this->get_remaining() >= sizeof(float));
uint32_t ui_value;
memcpy(&ui_value, &value, sizeof(float)); // this work-around required to silence compiler warnings
this->put_uint32(ui_value);
this->put_uint32(bit_cast<uint32_t>(value));
}
void ByteBuffer::put_double(double value) {
static_assert(sizeof(double) == sizeof(uint64_t), "Double sizes other than 64 bit not supported");
assert(this->get_remaining() >= sizeof(double));
uint64_t ui_value;
memcpy(&ui_value, &value, sizeof(double));
this->put_uint64(ui_value);
this->put_uint64(bit_cast<uint64_t>(value));
}
void ByteBuffer::put_vector(const std::vector<uint8_t> &value) {
assert(this->get_remaining() >= value.size());

View file

@ -28,6 +28,7 @@
#define USE_DATETIME_DATETIME
#define USE_DATETIME_TIME
#define USE_DEEP_SLEEP
#define USE_DISPLAY
#define USE_EVENT
#define USE_FAN
#define USE_GRAPH

View file

@ -153,6 +153,13 @@ build_flags =
-DUSE_ESP32_FRAMEWORK_ESP_IDF
extra_scripts = post:esphome/components/esp32/post_build.py.script
; This are common settings for the ESP32 using the latest ESP-IDF version.
[common:esp32-idf-5_3]
extends = common:esp32-idf
platform = platformio/espressif32@6.8.0
platform_packages =
platformio/framework-espidf@~3.50300.0
; These are common settings for the RP2040 using Arduino.
[common:rp2040-arduino]
extends = common:arduino
@ -229,6 +236,15 @@ build_flags =
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32
[env:esp32-idf-5_3]
extends = common:esp32-idf-5_3
board = esp32dev
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf
build_flags =
${common:esp32-idf.build_flags}
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32
[env:esp32-idf-tidy]
extends = common:esp32-idf
board = esp32dev
@ -265,6 +281,15 @@ build_flags =
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32C3
[env:esp32c3-idf-5_3]
extends = common:esp32-idf-5_3
board = esp32-c3-devkitm-1
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf
build_flags =
${common:esp32-idf.build_flags}
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32C3
[env:esp32c3-idf-tidy]
extends = common:esp32-idf
board = esp32-c3-devkitm-1
@ -301,6 +326,15 @@ build_flags =
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32S2
[env:esp32s2-idf-5_3]
extends = common:esp32-idf-5_3
board = esp32-s2-kaluga-1
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf
build_flags =
${common:esp32-idf.build_flags}
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32S2
[env:esp32s2-idf-tidy]
extends = common:esp32-idf
board = esp32-s2-kaluga-1
@ -337,6 +371,15 @@ build_flags =
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32S3
[env:esp32s3-idf-5_3]
extends = common:esp32-idf-5_3
board = esp32-s3-devkitc-1
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s3-idf
build_flags =
${common:esp32-idf.build_flags}
${flags:runtime.build_flags}
-DUSE_ESP32_VARIANT_ESP32S3
[env:esp32s3-idf-tidy]
extends = common:esp32-idf
board = esp32-s3-devkitc-1

View file

@ -0,0 +1,62 @@
uart:
- id: uart_bl0906
tx_pin:
number: ${tx_pin}
rx_pin:
number: ${rx_pin}
baud_rate: 19200
sensor:
- platform: bl0906
frequency:
name: 'Frequency'
temperature:
name: 'Temperature'
voltage:
name: 'Voltage'
channel_1:
current:
name: 'Current_1'
power:
name: 'Power_1'
energy:
name: 'Energy_1'
channel_2:
current:
name: 'Current_2'
power:
name: 'Power_2'
energy:
name: 'Energy_2'
channel_3:
current:
name: 'Current_3'
power:
name: 'Power_3'
energy:
name: 'Energy_3'
channel_4:
current:
name: 'Current_4'
power:
name: 'Power_4'
energy:
name: 'Energy_4'
channel_5:
current:
name: 'Current_5'
power:
name: 'Power_5'
energy:
name: 'Energy_5'
channel_6:
current:
name: 'Current_6'
power:
name: 'Power_6'
energy:
name: 'Energy_6'
total_energy:
name: 'Total_Energy'
total_power:
name: 'Total_Power'

View file

@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO12
rx_pin: GPIO14
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO7
rx_pin: GPIO8
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO7
rx_pin: GPIO8
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO12
rx_pin: GPIO14
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO1
rx_pin: GPIO3
<<: !include common.yaml

View file

@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml

View file

@ -12,6 +12,7 @@ display:
dc_pin: GPIO4
reset_pin:
number: GPIO21
invert_colors: false
i2c:
scl: GPIO18

View file

@ -26,6 +26,7 @@ display:
mirror_x: true
mirror_y: true
auto_clear_enabled: false
invert_colors: false
spi:
clk_pin: 14

View file

@ -11,6 +11,7 @@ display:
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: false
lambda: |-
// Draw an analog clock in the center of the screen
int centerX = it.get_width() / 2;

View file

@ -0,0 +1,23 @@
font:
- file: "gfonts://Roboto"
id: roboto
size: 20
glyphs: "0123456789."
extras:
- file: "gfonts://Roboto"
glyphs: ["\u00C4", "\u00C5", "\U000000C7"]
- file: "gfonts://Roboto"
id: roboto_web
size: 20
- file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf"
id: monocraft
size: 20
- file:
type: web
url: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf"
id: monocraft2
size: 24
- file: $component_dir/Monocraft.ttf
id: monocraft3
size: 28

View file

@ -19,6 +19,7 @@ display:
mirror_x: true
mirror_y: true
auto_clear_enabled: false
invert_colors: false
touchscreen:
- platform: ft63x6

View file

@ -0,0 +1,25 @@
i2c:
- id: i2c_gt911
scl: 5
sda: 4
display:
- platform: ssd1306_i2c
id: ssd1306_display
model: SSD1306_128X64
reset_pin: 10
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: gt911
display: ssd1306_display
interrupt_pin: 20
reset_pin: 21
binary_sensor:
- platform: gt911
id: touch_key_911
index: 0

View file

@ -1,24 +1 @@
i2c:
- id: i2c_gt911
scl: 16
sda: 17
display:
- platform: ssd1306_i2c
id: ssd1306_display
model: SSD1306_128X64
reset_pin: 13
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: gt911
display: ssd1306_display
interrupt_pin: 14
binary_sensor:
- platform: gt911
id: touch_key_911
index: 0
<<: !include common.yaml

View file

@ -1,24 +1 @@
i2c:
- id: i2c_gt911
scl: 5
sda: 4
display:
- platform: ssd1306_i2c
id: ssd1306_display
model: SSD1306_128X64
reset_pin: 3
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: gt911
display: ssd1306_display
interrupt_pin: 6
binary_sensor:
- platform: gt911
id: touch_key_911
index: 0
<<: !include common.yaml

View file

@ -1,24 +1 @@
i2c:
- id: i2c_gt911
scl: 5
sda: 4
display:
- platform: ssd1306_i2c
id: ssd1306_display
model: SSD1306_128X64
reset_pin: 3
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: gt911
display: ssd1306_display
interrupt_pin: 6
binary_sensor:
- platform: gt911
id: touch_key_911
index: 0
<<: !include common.yaml

View file

@ -1,24 +1 @@
i2c:
- id: i2c_gt911
scl: 16
sda: 17
display:
- platform: ssd1306_i2c
id: ssd1306_display
model: SSD1306_128X64
reset_pin: 13
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: gt911
display: ssd1306_display
interrupt_pin: 14
binary_sensor:
- platform: gt911
id: touch_key_911
index: 0
<<: !include common.yaml

View file

@ -1,24 +1 @@
i2c:
- id: i2c_gt911
scl: 5
sda: 4
display:
- platform: ssd1306_i2c
id: ssd1306_display
model: SSD1306_128X64
reset_pin: 3
pages:
- id: page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: gt911
display: ssd1306_display
interrupt_pin: 6
binary_sensor:
- platform: gt911
id: touch_key_911
index: 0
<<: !include common.yaml

View file

@ -1,3 +1,18 @@
spi:
- id: spi_main_lcd
clk_pin: 16
mosi_pin: 17
miso_pin: 15
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: true
image:
- id: binary_image
file: ../../pnglogo.png

View file

@ -1,3 +1,18 @@
spi:
- id: spi_main_lcd
clk_pin: 6
mosi_pin: 7
miso_pin: 5
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: true
image:
- id: binary_image
file: ../../pnglogo.png

View file

@ -1,3 +1,18 @@
spi:
- id: spi_main_lcd
clk_pin: 6
mosi_pin: 7
miso_pin: 5
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: true
image:
- id: binary_image
file: ../../pnglogo.png

View file

@ -1,3 +1,18 @@
spi:
- id: spi_main_lcd
clk_pin: 16
mosi_pin: 17
miso_pin: 15
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: true
image:
- id: binary_image
file: ../../pnglogo.png

View file

@ -1,3 +1,18 @@
spi:
- id: spi_main_lcd
clk_pin: 14
mosi_pin: 13
miso_pin: 12
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 5
dc_pin: 15
reset_pin: 16
invert_colors: true
image:
- id: binary_image
file: ../../pnglogo.png

View file

@ -1,3 +1,18 @@
spi:
- id: spi_main_lcd
clk_pin: 2
mosi_pin: 3
miso_pin: 4
display:
- platform: ili9xxx
id: main_lcd
model: ili9342
cs_pin: 20
dc_pin: 21
reset_pin: 22
invert_colors: true
image:
- id: binary_image
file: ../../pnglogo.png

View file

@ -337,11 +337,25 @@ lvgl:
- tileview:
id: tileview_id
scrollbar_mode: active
on_value:
then:
- if:
condition:
lambda: return tile == id(tile_1);
then:
- logger.log: "tile 1 is now showing"
tiles:
- id: page_1
- id: tile_1
row: 0
column: 0
dir: HOR
dir: ALL
widgets:
- obj:
bg_color: 0x000000
- id: tile_2
row: 1
column: 0
dir: [VER, HOR]
widgets:
- obj:
bg_color: 0x000000

View file

@ -13,7 +13,7 @@ display:
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: false
invert_colors: true
lambda: |-
it.fill(Color(0, 0, 0));
it.image(0, 0, id(online_rgba_image));

View file

@ -14,6 +14,7 @@ display:
cs_pin: 15
dc_pin: 3
reset_pin: 1
invert_colors: true
lambda: |-
it.fill(Color(0, 0, 0));
it.image(0, 0, id(online_rgba_image));

View file

@ -11,6 +11,7 @@ display:
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: false
lambda: |-
// Draw a QR code in the center of the screen
auto scale = 2;

View file

@ -11,6 +11,7 @@ display:
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: false
lambda: |-
// Draw a QR code in the center of the screen
auto scale = 2;

View file

@ -11,6 +11,7 @@ display:
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: false
lambda: |-
// Draw a QR code in the center of the screen
auto scale = 2;

View file

@ -11,6 +11,7 @@ display:
cs_pin: 12
dc_pin: 13
reset_pin: 21
invert_colors: false
lambda: |-
// Draw a QR code in the center of the screen
auto scale = 2;

View file

@ -11,6 +11,7 @@ display:
cs_pin: 5
dc_pin: 15
reset_pin: 16
invert_colors: false
lambda: |-
// Draw a QR code in the center of the screen
auto scale = 2;

View file

@ -11,6 +11,7 @@ display:
cs_pin: 20
dc_pin: 21
reset_pin: 22
invert_colors: false
lambda: |-
// Draw a QR code in the center of the screen
auto scale = 2;

View file

@ -38,7 +38,12 @@ display:
hsync_pin: 16
vsync_pin: 17
pclk_pin: 21
init_sequence: 1
init_sequence:
- 1
- [0x23, 0xA, 0xB]
- delay 20ms
- [0x23, 0xA, 0xB]
- delay 0.2s
data_pins:
- number: 0
ignore_strapping_warning: true

View file

@ -18,6 +18,7 @@ display:
data_rate: 40MHz
dimensions: 320x240
update_interval: never
invert_colors: false
transform:
mirror_y: false
mirror_x: false

View file

@ -0,0 +1,35 @@
wifi:
ssid: MySSID
password: password1
udp:
update_interval: 5s
encryption: "our key goes here"
rolling_code_enable: true
ping_pong_enable: true
binary_sensors:
- binary_sensor_id1
- id: binary_sensor_id1
broadcast_id: other_id
sensors:
- sensor_id1
- id: sensor_id1
broadcast_id: other_id
providers:
- name: some-device-name
encryption: "their key goes here"
sensor:
- platform: template
id: sensor_id1
- platform: udp
provider: some-device-name
id: our_id
remote_id: some_sensor_id
binary_sensor:
- platform: udp
provider: unencrypted-device
id: other_binary_sensor_id
- platform: template
id: binary_sensor_id1

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -0,0 +1,4 @@
packages:
common: !include common.yaml
wifi: !remove

View file

@ -0,0 +1 @@
<<: !include common.yaml

View file

@ -12,6 +12,7 @@ display:
cs_pin: 13
dc_pin: 14
reset_pin: 21
invert_colors: false
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());

View file

@ -12,6 +12,7 @@ display:
cs_pin: 8
dc_pin: 9
reset_pin: 10
invert_colors: false
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());

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