mirror of
https://github.com/esphome/esphome.git
synced 2026-01-26 06:22:08 -07:00
Compare commits
45 Commits
wifi_api
...
libretiny_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f3b2f027d | ||
|
|
a3ba3a33d9 | ||
|
|
1c9a9c7536 | ||
|
|
b82149c291 | ||
|
|
a7223a2cd7 | ||
|
|
3bc1ea45ce | ||
|
|
011407ea8b | ||
|
|
1141e83a7c | ||
|
|
214ce95cf3 | ||
|
|
3a7b83ba93 | ||
|
|
cc2f3d85dc | ||
|
|
723f67d5e2 | ||
|
|
70e45706d9 | ||
|
|
56a2a2269f | ||
|
|
d6841ba33a | ||
|
|
10cbd0164a | ||
|
|
d285706b41 | ||
|
|
ef469c20df | ||
|
|
6870d3dc50 | ||
|
|
9cc39621a6 | ||
|
|
c4f7d09553 | ||
|
|
ab1661ef22 | ||
|
|
ccbf17d5ab | ||
|
|
bac96086be | ||
|
|
c32e4bc65b | ||
|
|
993765d732 | ||
|
|
3853e3a4dc | ||
|
|
8d84fe0113 | ||
|
|
58746b737f | ||
|
|
f93e843972 | ||
|
|
60968d311b | ||
|
|
30584e2e96 | ||
|
|
468ae39a9e | ||
|
|
beb9c8d328 | ||
|
|
cdda3fb7cc | ||
|
|
bba00a3906 | ||
|
|
42e50ca178 | ||
|
|
165e362a1b | ||
|
|
e4763f8e71 | ||
|
|
9fddd0659e | ||
|
|
faea546a0e | ||
|
|
069db2e128 | ||
|
|
5f2203b915 | ||
|
|
5c67e04fef | ||
|
|
0cdcacc7fc |
@@ -1 +1 @@
|
||||
15dc295268b2dcf75942f42759b3ddec64eba89f75525698eb39c95a7f4b14ce
|
||||
d565b0589e35e692b5f2fc0c14723a99595b4828a3a3ef96c442e86a23176c00
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||
uses: github/codeql-action/init@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||
uses: github/codeql-action/analyze@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.13
|
||||
rev: v0.14.14
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -88,7 +88,8 @@ esphome/components/bmp3xx/* @latonita
|
||||
esphome/components/bmp3xx_base/* @latonita @martgras
|
||||
esphome/components/bmp3xx_i2c/* @latonita
|
||||
esphome/components/bmp3xx_spi/* @latonita
|
||||
esphome/components/bmp581/* @kahrendt
|
||||
esphome/components/bmp581_base/* @danielkent-net @kahrendt
|
||||
esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/bthome_mithermometer/* @nagyrobi
|
||||
@@ -481,6 +482,7 @@ esphome/components/switch/* @esphome/core
|
||||
esphome/components/switch/binary_sensor/* @ssieb
|
||||
esphome/components/sx126x/* @swoboda1337
|
||||
esphome/components/sx127x/* @swoboda1337
|
||||
esphome/components/sy6970/* @linkedupbits
|
||||
esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
|
||||
@@ -1,164 +1,5 @@
|
||||
import math
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PASCAL,
|
||||
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The bmp581 sensor component has been renamed to bmp581_i2c."
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581")
|
||||
|
||||
Oversampling = bmp581_ns.enum("Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||
"2X": Oversampling.OVERSAMPLING_X2,
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32X": Oversampling.OVERSAMPLING_X32,
|
||||
"64X": Oversampling.OVERSAMPLING_X64,
|
||||
"128X": Oversampling.OVERSAMPLING_X128,
|
||||
}
|
||||
|
||||
IIRFilter = bmp581_ns.enum("IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||
"2X": IIRFilter.IIR_FILTER_2,
|
||||
"4X": IIRFilter.IIR_FILTER_4,
|
||||
"8X": IIRFilter.IIR_FILTER_8,
|
||||
"16X": IIRFilter.IIR_FILTER_16,
|
||||
"32X": IIRFilter.IIR_FILTER_32,
|
||||
"64X": IIRFilter.IIR_FILTER_64,
|
||||
"128X": IIRFilter.IIR_FILTER_128,
|
||||
}
|
||||
|
||||
BMP581Component = bmp581_ns.class_(
|
||||
"BMP581Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
|
||||
def compute_measurement_conversion_time(config):
|
||||
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
||||
# - returns a rounded up time in ms
|
||||
|
||||
# Page 12 of datasheet
|
||||
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.7,
|
||||
"4X": 2.9,
|
||||
"8X": 5.4,
|
||||
"16X": 10.4,
|
||||
"32X": 20.4,
|
||||
"64X": 40.4,
|
||||
"128X": 80.4,
|
||||
}
|
||||
|
||||
# Page 12 of datasheet
|
||||
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.1,
|
||||
"4X": 1.5,
|
||||
"8X": 2.1,
|
||||
"16X": 3.3,
|
||||
"32X": 5.8,
|
||||
"64X": 10.8,
|
||||
"128X": 20.8,
|
||||
}
|
||||
|
||||
pressure_conversion_time = (
|
||||
0.0 # No conversion time necessary without a pressure sensor
|
||||
)
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
pressure_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
temperature_conversion_time = (
|
||||
1.0 # BMP581 always samples the temperature even if only reading pressure
|
||||
)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
temperature_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
# Datasheet indicates a 5% possible error in each conversion time listed
|
||||
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP581Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PASCAL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x46))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
||||
|
||||
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
||||
|
||||
157
esphome/components/bmp581_base/__init__.py
Normal file
157
esphome/components/bmp581_base/__init__.py
Normal file
@@ -0,0 +1,157 @@
|
||||
import math
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PASCAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581_base")
|
||||
|
||||
Oversampling = bmp581_ns.enum("Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||
"2X": Oversampling.OVERSAMPLING_X2,
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32X": Oversampling.OVERSAMPLING_X32,
|
||||
"64X": Oversampling.OVERSAMPLING_X64,
|
||||
"128X": Oversampling.OVERSAMPLING_X128,
|
||||
}
|
||||
|
||||
IIRFilter = bmp581_ns.enum("IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||
"2X": IIRFilter.IIR_FILTER_2,
|
||||
"4X": IIRFilter.IIR_FILTER_4,
|
||||
"8X": IIRFilter.IIR_FILTER_8,
|
||||
"16X": IIRFilter.IIR_FILTER_16,
|
||||
"32X": IIRFilter.IIR_FILTER_32,
|
||||
"64X": IIRFilter.IIR_FILTER_64,
|
||||
"128X": IIRFilter.IIR_FILTER_128,
|
||||
}
|
||||
|
||||
BMP581Component = bmp581_ns.class_("BMP581Component", cg.PollingComponent)
|
||||
|
||||
|
||||
def compute_measurement_conversion_time(config):
|
||||
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
||||
# - returns a rounded up time in ms
|
||||
|
||||
# Page 12 of datasheet
|
||||
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.7,
|
||||
"4X": 2.9,
|
||||
"8X": 5.4,
|
||||
"16X": 10.4,
|
||||
"32X": 20.4,
|
||||
"64X": 40.4,
|
||||
"128X": 80.4,
|
||||
}
|
||||
|
||||
# Page 12 of datasheet
|
||||
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.1,
|
||||
"4X": 1.5,
|
||||
"8X": 2.1,
|
||||
"16X": 3.3,
|
||||
"32X": 5.8,
|
||||
"64X": 10.8,
|
||||
"128X": 20.8,
|
||||
}
|
||||
|
||||
pressure_conversion_time = (
|
||||
0.0 # No conversion time necessary without a pressure sensor
|
||||
)
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
pressure_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
temperature_conversion_time = (
|
||||
1.0 # BMP581 always samples the temperature even if only reading pressure
|
||||
)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
temperature_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
# Datasheet indicates a 5% possible error in each conversion time listed
|
||||
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
||||
|
||||
|
||||
CONFIG_SCHEMA_BASE = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP581Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PASCAL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
|
||||
|
||||
async def to_code_base(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
||||
|
||||
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
||||
return var
|
||||
@@ -10,12 +10,11 @@
|
||||
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
||||
*/
|
||||
|
||||
#include "bmp581.h"
|
||||
#include "bmp581_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp581 {
|
||||
namespace esphome::bmp581_base {
|
||||
|
||||
static const char *const TAG = "bmp581";
|
||||
|
||||
@@ -91,7 +90,6 @@ void BMP581Component::dump_config() {
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
|
||||
@@ -149,7 +147,7 @@ void BMP581Component::setup() {
|
||||
uint8_t chip_id;
|
||||
|
||||
// read chip id from sensor
|
||||
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
|
||||
if (!this->bmp_read_byte(BMP581_CHIP_ID, &chip_id)) {
|
||||
ESP_LOGE(TAG, "Read chip ID failed");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
@@ -172,7 +170,7 @@ void BMP581Component::setup() {
|
||||
// 3) Verify sensor status (check if NVM is okay) //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
if (!this->read_byte(BMP581_STATUS, &this->status_.reg)) {
|
||||
if (!this->bmp_read_byte(BMP581_STATUS, &this->status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read status register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
@@ -359,7 +357,7 @@ bool BMP581Component::check_data_readiness_() {
|
||||
|
||||
uint8_t status;
|
||||
|
||||
if (!this->read_byte(BMP581_INT_STATUS, &status)) {
|
||||
if (!this->bmp_read_byte(BMP581_INT_STATUS, &status)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
return false;
|
||||
}
|
||||
@@ -400,7 +398,7 @@ bool BMP581Component::prime_iir_filter_() {
|
||||
|
||||
// flush the IIR filter with forced measurements (we will only flush once)
|
||||
this->dsp_config_.bit.iir_flush_forced_en = true;
|
||||
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||
|
||||
return false;
|
||||
@@ -430,7 +428,7 @@ bool BMP581Component::prime_iir_filter_() {
|
||||
|
||||
// disable IIR filter flushings on future forced measurements
|
||||
this->dsp_config_.bit.iir_flush_forced_en = false;
|
||||
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||
|
||||
return false;
|
||||
@@ -454,7 +452,7 @@ bool BMP581Component::read_temperature_(float &temperature) {
|
||||
}
|
||||
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
|
||||
if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
|
||||
ESP_LOGW(TAG, "Failed to read measurement");
|
||||
this->status_set_warning();
|
||||
|
||||
@@ -483,7 +481,7 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
|
||||
}
|
||||
|
||||
uint8_t data[6];
|
||||
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
|
||||
if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
|
||||
ESP_LOGW(TAG, "Failed to read measurement");
|
||||
this->status_set_warning();
|
||||
|
||||
@@ -507,7 +505,7 @@ bool BMP581Component::reset_() {
|
||||
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
||||
|
||||
// writes reset command to BMP's command register
|
||||
if (!this->write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||
if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||
ESP_LOGE(TAG, "Failed to write reset command");
|
||||
|
||||
return false;
|
||||
@@ -518,7 +516,7 @@ bool BMP581Component::reset_() {
|
||||
delay(3);
|
||||
|
||||
// read interrupt status register
|
||||
if (!this->read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||
if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
|
||||
return false;
|
||||
@@ -562,7 +560,7 @@ bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter p
|
||||
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
|
||||
// - allows us to write the IIR configuration with one command to both registers
|
||||
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
|
||||
return this->write_bytes(BMP581_DSP, register_data, sizeof(register_data));
|
||||
return this->bmp_write_bytes(BMP581_DSP, register_data, sizeof(register_data));
|
||||
}
|
||||
|
||||
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
||||
@@ -572,7 +570,7 @@ bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
||||
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
|
||||
|
||||
// write interrupt source register
|
||||
return this->write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
|
||||
return this->bmp_write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
|
||||
}
|
||||
|
||||
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
|
||||
@@ -583,7 +581,7 @@ bool BMP581Component::write_oversampling_settings_(Oversampling temperature_over
|
||||
this->osr_config_.bit.osr_t = temperature_oversampling;
|
||||
this->osr_config_.bit.osr_p = pressure_oversampling;
|
||||
|
||||
return this->write_byte(BMP581_OSR, this->osr_config_.reg);
|
||||
return this->bmp_write_byte(BMP581_OSR, this->osr_config_.reg);
|
||||
}
|
||||
|
||||
bool BMP581Component::write_power_mode_(OperationMode mode) {
|
||||
@@ -593,8 +591,7 @@ bool BMP581Component::write_power_mode_(OperationMode mode) {
|
||||
this->odr_config_.bit.pwr_mode = mode;
|
||||
|
||||
// write odr register
|
||||
return this->write_byte(BMP581_ODR, this->odr_config_.reg);
|
||||
return this->bmp_write_byte(BMP581_ODR, this->odr_config_.reg);
|
||||
}
|
||||
|
||||
} // namespace bmp581
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bmp581_base
|
||||
@@ -3,11 +3,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp581 {
|
||||
namespace esphome::bmp581_base {
|
||||
|
||||
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
@@ -59,7 +57,7 @@ enum IIRFilter {
|
||||
IIR_FILTER_128 = 0x7
|
||||
};
|
||||
|
||||
class BMP581Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class BMP581Component : public PollingComponent {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
@@ -84,6 +82,11 @@ class BMP581Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
|
||||
|
||||
protected:
|
||||
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
|
||||
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
|
||||
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
|
||||
@@ -216,5 +219,4 @@ class BMP581Component : public PollingComponent, public i2c::I2CDevice {
|
||||
} odr_config_ = {.reg = 0};
|
||||
};
|
||||
|
||||
} // namespace bmp581
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bmp581_base
|
||||
0
esphome/components/bmp581_i2c/__init__.py
Normal file
0
esphome/components/bmp581_i2c/__init__.py
Normal file
12
esphome/components/bmp581_i2c/bmp581_i2c.cpp
Normal file
12
esphome/components/bmp581_i2c/bmp581_i2c.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "bmp581_i2c.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::bmp581_i2c {
|
||||
|
||||
void BMP581I2CComponent::dump_config() {
|
||||
LOG_I2C_DEVICE(this);
|
||||
BMP581Component::dump_config();
|
||||
}
|
||||
|
||||
} // namespace esphome::bmp581_i2c
|
||||
24
esphome/components/bmp581_i2c/bmp581_i2c.h
Normal file
24
esphome/components/bmp581_i2c/bmp581_i2c.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/bmp581_base/bmp581_base.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::bmp581_i2c {
|
||||
|
||||
static const char *const TAG = "bmp581_i2c.sensor";
|
||||
|
||||
/// This class implements support for the BMP581 Temperature+Pressure i2c sensor.
|
||||
class BMP581I2CComponent : public esphome::bmp581_base::BMP581Component, public i2c::I2CDevice {
|
||||
public:
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
|
||||
return read_bytes(a_register, data, len);
|
||||
}
|
||||
bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
|
||||
return write_bytes(a_register, data, len);
|
||||
}
|
||||
void dump_config() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::bmp581_i2c
|
||||
23
esphome/components/bmp581_i2c/sensor.py
Normal file
23
esphome/components/bmp581_i2c/sensor.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base
|
||||
|
||||
AUTO_LOAD = ["bmp581_base"]
|
||||
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581_i2c")
|
||||
BMP581I2CComponent = bmp581_ns.class_(
|
||||
"BMP581I2CComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
|
||||
i2c.i2c_device_schema(default_address=0x46)
|
||||
).extend({cv.GenerateID(): cv.declare_id(BMP581I2CComponent)})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await to_code_base(config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
@@ -12,6 +12,7 @@ from esphome.const import (
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import add_define
|
||||
|
||||
CODEOWNERS = ["@swoboda1337"]
|
||||
|
||||
@@ -42,6 +43,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
add_define("USE_ESP32_HOSTED")
|
||||
if config[CONF_ACTIVE_HIGH]:
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH",
|
||||
|
||||
@@ -185,7 +185,7 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s
|
||||
}
|
||||
jobs[num_jobs++].command = I2C_MASTER_CMD_STOP;
|
||||
ESP_LOGV(TAG, "Sending %zu jobs", num_jobs);
|
||||
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 20);
|
||||
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 100);
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGV(TAG, "TX to %02X failed: not acked", address);
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
|
||||
@@ -191,10 +191,17 @@ def _notify_old_style(config):
|
||||
|
||||
|
||||
# The dev and latest branches will be at *least* this version, which is what matters.
|
||||
# Use GitHub releases directly to avoid PlatformIO moderation delays.
|
||||
ARDUINO_VERSIONS = {
|
||||
"dev": (cv.Version(1, 9, 2), "https://github.com/libretiny-eu/libretiny.git"),
|
||||
"latest": (cv.Version(1, 9, 2), "libretiny"),
|
||||
"recommended": (cv.Version(1, 9, 2), None),
|
||||
"dev": (cv.Version(1, 11, 0), "https://github.com/libretiny-eu/libretiny.git"),
|
||||
"latest": (
|
||||
cv.Version(1, 11, 0),
|
||||
"https://github.com/libretiny-eu/libretiny.git#v1.11.0",
|
||||
),
|
||||
"recommended": (
|
||||
cv.Version(1, 11, 0),
|
||||
"https://github.com/libretiny-eu/libretiny.git#v1.11.0",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -391,7 +391,10 @@ void LightCall::transform_parameters_() {
|
||||
min_mireds > 0.0f && max_mireds > 0.0f) {
|
||||
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
|
||||
this->parent_->get_name().c_str());
|
||||
if (this->has_color_temperature()) {
|
||||
// Only compute cold_white/warm_white from color_temperature if they're not already explicitly set.
|
||||
// This is important for state restoration, where both color_temperature and cold_white/warm_white
|
||||
// are restored from flash - we want to preserve the saved cold_white/warm_white values.
|
||||
if (this->has_color_temperature() && !this->has_cold_white() && !this->has_warm_white()) {
|
||||
const float color_temp = clamp(this->color_temperature_, min_mireds, max_mireds);
|
||||
const float range = max_mireds - min_mireds;
|
||||
const float ww_fraction = (color_temp - min_mireds) / range;
|
||||
|
||||
@@ -32,7 +32,7 @@ class LabelType(WidgetType):
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
"""For a text object, create and set text"""
|
||||
if value := config.get(CONF_TEXT):
|
||||
if (value := config.get(CONF_TEXT)) is not None:
|
||||
await w.set_property(CONF_TEXT, await lv_text.process(value))
|
||||
await w.set_property(CONF_LONG_MODE, config)
|
||||
await w.set_property(CONF_RECOLOR, config)
|
||||
|
||||
@@ -155,6 +155,9 @@ void MHZ19Component::dump_config() {
|
||||
case MHZ19_DETECTION_RANGE_0_10000PPM:
|
||||
range_str = "0 to 10000ppm";
|
||||
break;
|
||||
default:
|
||||
range_str = "default";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Detection range: %s", range_str);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ st7701s = ST7701S(
|
||||
pclk_frequency="16MHz",
|
||||
pclk_inverted=True,
|
||||
initsequence=(
|
||||
(0x01,), # Software Reset
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10), # Page 0
|
||||
(0xC0, 0x3B, 0x00), (0xC1, 0x0D, 0x02), (0xC2, 0x31, 0x05),
|
||||
(0xB0, 0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18,),
|
||||
|
||||
@@ -279,7 +279,7 @@ def modbus_calc_properties(config):
|
||||
if isinstance(value, str):
|
||||
value = value.encode()
|
||||
config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
|
||||
config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM
|
||||
config[CONF_REGISTER_TYPE] = cv.enum(MODBUS_REGISTER_TYPE)("custom")
|
||||
config[CONF_FORCE_NEW_RANGE] = True
|
||||
return byte_offset, reg_count
|
||||
|
||||
|
||||
@@ -133,14 +133,17 @@ void RD03DComponent::process_frame_() {
|
||||
uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE);
|
||||
|
||||
// Extract raw bytes for this target
|
||||
// Note: Despite datasheet Table 5-2 showing order as X, Y, Speed, Resolution,
|
||||
// actual radar output has Resolution before Speed (verified empirically -
|
||||
// stationary targets were showing non-zero speed with original field order)
|
||||
uint8_t x_low = this->buffer_[offset + 0];
|
||||
uint8_t x_high = this->buffer_[offset + 1];
|
||||
uint8_t y_low = this->buffer_[offset + 2];
|
||||
uint8_t y_high = this->buffer_[offset + 3];
|
||||
uint8_t speed_low = this->buffer_[offset + 4];
|
||||
uint8_t speed_high = this->buffer_[offset + 5];
|
||||
uint8_t res_low = this->buffer_[offset + 6];
|
||||
uint8_t res_high = this->buffer_[offset + 7];
|
||||
uint8_t res_low = this->buffer_[offset + 4];
|
||||
uint8_t res_high = this->buffer_[offset + 5];
|
||||
uint8_t speed_low = this->buffer_[offset + 6];
|
||||
uint8_t speed_high = this->buffer_[offset + 7];
|
||||
|
||||
// Decode values per RD-03D format
|
||||
int16_t x = decode_value(x_low, x_high);
|
||||
|
||||
@@ -23,6 +23,10 @@ RTL87XX_BOARDS = {
|
||||
"name": "WR2 Wi-Fi Module",
|
||||
"family": FAMILY_RTL8710B,
|
||||
},
|
||||
"wbr3": {
|
||||
"name": "WBR3 Wi-Fi Module",
|
||||
"family": FAMILY_RTL8720C,
|
||||
},
|
||||
"generic-rtl8710bn-2mb-468k": {
|
||||
"name": "Generic - RTL8710BN (2M/468k)",
|
||||
"family": FAMILY_RTL8710B,
|
||||
@@ -79,6 +83,10 @@ RTL87XX_BOARDS = {
|
||||
"name": "T103_V1.0",
|
||||
"family": FAMILY_RTL8710B,
|
||||
},
|
||||
"generic-rtl8720cf-2mb-896k": {
|
||||
"name": "Generic - RTL8720CF (2M/896k)",
|
||||
"family": FAMILY_RTL8720C,
|
||||
},
|
||||
"generic-rtl8720cf-2mb-992k": {
|
||||
"name": "Generic - RTL8720CF (2M/992k)",
|
||||
"family": FAMILY_RTL8720C,
|
||||
@@ -221,6 +229,71 @@ RTL87XX_BOARD_PINS = {
|
||||
"D9": 29,
|
||||
"A1": 41,
|
||||
},
|
||||
"wbr3": {
|
||||
"WIRE0_SCL_0": 11,
|
||||
"WIRE0_SCL_1": 2,
|
||||
"WIRE0_SCL_2": 19,
|
||||
"WIRE0_SCL_3": 15,
|
||||
"WIRE0_SDA_0": 3,
|
||||
"WIRE0_SDA_1": 12,
|
||||
"WIRE0_SDA_2": 16,
|
||||
"SERIAL0_RX_0": 12,
|
||||
"SERIAL0_RX_1": 13,
|
||||
"SERIAL0_TX_0": 11,
|
||||
"SERIAL0_TX_1": 14,
|
||||
"SERIAL1_CTS": 4,
|
||||
"SERIAL1_RX_0": 2,
|
||||
"SERIAL1_RX_1": 0,
|
||||
"SERIAL1_TX_0": 3,
|
||||
"SERIAL1_TX_1": 1,
|
||||
"SERIAL2_CTS": 19,
|
||||
"SERIAL2_RX": 15,
|
||||
"SERIAL2_TX": 16,
|
||||
"CS0": 15,
|
||||
"CTS1": 4,
|
||||
"CTS2": 19,
|
||||
"PA00": 0,
|
||||
"PA0": 0,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA11": 11,
|
||||
"PA12": 12,
|
||||
"PA13": 13,
|
||||
"PA14": 14,
|
||||
"PA15": 15,
|
||||
"PA16": 16,
|
||||
"PA17": 17,
|
||||
"PA18": 18,
|
||||
"PA19": 19,
|
||||
"PWM5": 17,
|
||||
"PWM6": 18,
|
||||
"RX2": 15,
|
||||
"SDA0": 16,
|
||||
"TX2": 16,
|
||||
"D0": 7,
|
||||
"D1": 11,
|
||||
"D2": 2,
|
||||
"D3": 3,
|
||||
"D4": 4,
|
||||
"D5": 12,
|
||||
"D6": 16,
|
||||
"D7": 17,
|
||||
"D8": 18,
|
||||
"D9": 19,
|
||||
"D10": 13,
|
||||
"D11": 14,
|
||||
"D12": 15,
|
||||
"D13": 0,
|
||||
"D14": 1,
|
||||
},
|
||||
"generic-rtl8710bn-2mb-468k": {
|
||||
"SPI0_CS": 19,
|
||||
"SPI0_MISO": 22,
|
||||
@@ -1178,6 +1251,104 @@ RTL87XX_BOARD_PINS = {
|
||||
"A0": 19,
|
||||
"A1": 41,
|
||||
},
|
||||
"generic-rtl8720cf-2mb-896k": {
|
||||
"SPI0_CS_0": 2,
|
||||
"SPI0_CS_1": 7,
|
||||
"SPI0_CS_2": 15,
|
||||
"SPI0_MISO_0": 10,
|
||||
"SPI0_MISO_1": 20,
|
||||
"SPI0_MOSI_0": 4,
|
||||
"SPI0_MOSI_1": 9,
|
||||
"SPI0_MOSI_2": 19,
|
||||
"SPI0_SCK_0": 3,
|
||||
"SPI0_SCK_1": 8,
|
||||
"SPI0_SCK_2": 16,
|
||||
"WIRE0_SCL_0": 2,
|
||||
"WIRE0_SCL_1": 11,
|
||||
"WIRE0_SCL_2": 15,
|
||||
"WIRE0_SCL_3": 19,
|
||||
"WIRE0_SDA_0": 3,
|
||||
"WIRE0_SDA_1": 12,
|
||||
"WIRE0_SDA_2": 16,
|
||||
"WIRE0_SDA_3": 20,
|
||||
"SERIAL0_CTS": 10,
|
||||
"SERIAL0_RTS": 9,
|
||||
"SERIAL0_RX_0": 12,
|
||||
"SERIAL0_RX_1": 13,
|
||||
"SERIAL0_TX_0": 11,
|
||||
"SERIAL0_TX_1": 14,
|
||||
"SERIAL1_CTS": 4,
|
||||
"SERIAL1_RX_0": 0,
|
||||
"SERIAL1_RX_1": 2,
|
||||
"SERIAL1_TX_0": 1,
|
||||
"SERIAL1_TX_1": 3,
|
||||
"SERIAL2_CTS": 19,
|
||||
"SERIAL2_RTS": 20,
|
||||
"SERIAL2_RX": 15,
|
||||
"SERIAL2_TX": 16,
|
||||
"CS0": 15,
|
||||
"CTS0": 10,
|
||||
"CTS1": 4,
|
||||
"CTS2": 19,
|
||||
"MOSI0": 19,
|
||||
"PA00": 0,
|
||||
"PA0": 0,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA08": 8,
|
||||
"PA8": 8,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PA11": 11,
|
||||
"PA12": 12,
|
||||
"PA13": 13,
|
||||
"PA14": 14,
|
||||
"PA15": 15,
|
||||
"PA16": 16,
|
||||
"PA17": 17,
|
||||
"PA18": 18,
|
||||
"PA19": 19,
|
||||
"PA20": 20,
|
||||
"PA23": 23,
|
||||
"PWM0": 20,
|
||||
"PWM5": 17,
|
||||
"PWM6": 18,
|
||||
"PWM7": 23,
|
||||
"RTS0": 9,
|
||||
"RTS2": 20,
|
||||
"RX2": 15,
|
||||
"SCK0": 16,
|
||||
"TX2": 16,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 2,
|
||||
"D3": 3,
|
||||
"D4": 4,
|
||||
"D5": 7,
|
||||
"D6": 8,
|
||||
"D7": 9,
|
||||
"D8": 10,
|
||||
"D9": 11,
|
||||
"D10": 12,
|
||||
"D11": 13,
|
||||
"D12": 14,
|
||||
"D13": 15,
|
||||
"D14": 16,
|
||||
"D15": 17,
|
||||
"D16": 18,
|
||||
"D17": 19,
|
||||
"D18": 20,
|
||||
"D19": 23,
|
||||
},
|
||||
"generic-rtl8720cf-2mb-992k": {
|
||||
"SPI0_CS_0": 2,
|
||||
"SPI0_CS_1": 7,
|
||||
|
||||
@@ -30,6 +30,19 @@ static const int8_t SEN5X_INDEX_SCALE_FACTOR = 10; //
|
||||
static const int8_t SEN5X_MIN_INDEX_VALUE = 1 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||
static const int16_t SEN5X_MAX_INDEX_VALUE = 500 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
|
||||
|
||||
static const LogString *type_to_string(Sen5xType type) {
|
||||
switch (type) {
|
||||
case Sen5xType::SEN50:
|
||||
return LOG_STR("SEN50");
|
||||
case Sen5xType::SEN54:
|
||||
return LOG_STR("SEN54");
|
||||
case Sen5xType::SEN55:
|
||||
return LOG_STR("SEN55");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) {
|
||||
switch (mode) {
|
||||
case LOW_ACCELERATION:
|
||||
@@ -43,6 +56,15 @@ static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
// This function performs an in-place conversion of the provided buffer
|
||||
// from uint16_t values to big endianness
|
||||
static inline const char *sensirion_convert_to_string_in_place(uint16_t *array, size_t length) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
array[i] = convert_big_endian(array[i]);
|
||||
}
|
||||
return reinterpret_cast<const char *>(array);
|
||||
}
|
||||
|
||||
void SEN5XComponent::setup() {
|
||||
// the sensor needs 1000 ms to enter the idle state
|
||||
this->set_timeout(1000, [this]() {
|
||||
@@ -75,18 +97,18 @@ void SEN5XComponent::setup() {
|
||||
stop_measurement_delay = 200;
|
||||
}
|
||||
this->set_timeout(stop_measurement_delay, [this]() {
|
||||
uint16_t raw_serial_number[3];
|
||||
if (!this->get_register(SEN5X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 20)) {
|
||||
// note: serial number register is actually 32-bytes long but we grab only the first 16-bytes,
|
||||
// this appears to be all that Sensirion uses for serial numbers, this could change
|
||||
uint16_t raw_serial_number[8];
|
||||
if (!this->get_register(SEN5X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 20)) {
|
||||
ESP_LOGE(TAG, "Failed to read serial number");
|
||||
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->serial_number_[0] = static_cast<bool>(uint16_t(raw_serial_number[0]) & 0xFF);
|
||||
this->serial_number_[1] = static_cast<uint16_t>(raw_serial_number[0] & 0xFF);
|
||||
this->serial_number_[2] = static_cast<uint16_t>(raw_serial_number[1] >> 8);
|
||||
ESP_LOGV(TAG, "Serial number %02d.%02d.%02d", this->serial_number_[0], this->serial_number_[1],
|
||||
this->serial_number_[2]);
|
||||
const char *serial_number = sensirion_convert_to_string_in_place(raw_serial_number, 8);
|
||||
snprintf(this->serial_number_, sizeof(this->serial_number_), "%s", serial_number);
|
||||
ESP_LOGV(TAG, "Serial number %s", this->serial_number_);
|
||||
|
||||
uint16_t raw_product_name[16];
|
||||
if (!this->get_register(SEN5X_CMD_GET_PRODUCT_NAME, raw_product_name, 16, 20)) {
|
||||
@@ -95,50 +117,35 @@ void SEN5XComponent::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// 2 ASCII bytes are encoded in an int
|
||||
const uint16_t *current_int = raw_product_name;
|
||||
char current_char;
|
||||
uint8_t max = 16;
|
||||
do {
|
||||
// first char
|
||||
current_char = *current_int >> 8;
|
||||
if (current_char) {
|
||||
this->product_name_.push_back(current_char);
|
||||
// second char
|
||||
current_char = *current_int & 0xFF;
|
||||
if (current_char) {
|
||||
this->product_name_.push_back(current_char);
|
||||
}
|
||||
}
|
||||
current_int++;
|
||||
} while (current_char && --max);
|
||||
|
||||
Sen5xType sen5x_type = UNKNOWN;
|
||||
if (this->product_name_ == "SEN50") {
|
||||
sen5x_type = SEN50;
|
||||
const char *product_name = sensirion_convert_to_string_in_place(raw_product_name, 16);
|
||||
if (strncmp(product_name, "SEN50", 5) == 0) {
|
||||
this->type_ = Sen5xType::SEN50;
|
||||
} else if (strncmp(product_name, "SEN54", 5) == 0) {
|
||||
this->type_ = Sen5xType::SEN54;
|
||||
} else if (strncmp(product_name, "SEN55", 5) == 0) {
|
||||
this->type_ = Sen5xType::SEN55;
|
||||
} else {
|
||||
if (this->product_name_ == "SEN54") {
|
||||
sen5x_type = SEN54;
|
||||
} else {
|
||||
if (this->product_name_ == "SEN55") {
|
||||
sen5x_type = SEN55;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Product name: %s", this->product_name_.c_str());
|
||||
this->type_ = Sen5xType::UNKNOWN;
|
||||
ESP_LOGE(TAG, "Unknown product name: %.32s", product_name);
|
||||
this->error_code_ = PRODUCT_NAME_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (this->humidity_sensor_ && sen5x_type == SEN50) {
|
||||
|
||||
ESP_LOGD(TAG, "Type: %s", LOG_STR_ARG(type_to_string(this->type_)));
|
||||
if (this->humidity_sensor_ && this->type_ == Sen5xType::SEN50) {
|
||||
ESP_LOGE(TAG, "Relative humidity requires a SEN54 or SEN55");
|
||||
this->humidity_sensor_ = nullptr; // mark as not used
|
||||
}
|
||||
if (this->temperature_sensor_ && sen5x_type == SEN50) {
|
||||
if (this->temperature_sensor_ && this->type_ == Sen5xType::SEN50) {
|
||||
ESP_LOGE(TAG, "Temperature requires a SEN54 or SEN55");
|
||||
this->temperature_sensor_ = nullptr; // mark as not used
|
||||
}
|
||||
if (this->voc_sensor_ && sen5x_type == SEN50) {
|
||||
if (this->voc_sensor_ && this->type_ == Sen5xType::SEN50) {
|
||||
ESP_LOGE(TAG, "VOC requires a SEN54 or SEN55");
|
||||
this->voc_sensor_ = nullptr; // mark as not used
|
||||
}
|
||||
if (this->nox_sensor_ && sen5x_type != SEN55) {
|
||||
if (this->nox_sensor_ && this->type_ != Sen5xType::SEN55) {
|
||||
ESP_LOGE(TAG, "NOx requires a SEN55");
|
||||
this->nox_sensor_ = nullptr; // mark as not used
|
||||
}
|
||||
@@ -153,43 +160,25 @@ void SEN5XComponent::setup() {
|
||||
ESP_LOGV(TAG, "Firmware version %d", this->firmware_version_);
|
||||
|
||||
if (this->voc_sensor_ && this->store_baseline_) {
|
||||
uint32_t combined_serial =
|
||||
encode_uint24(this->serial_number_[0], this->serial_number_[1], this->serial_number_[2]);
|
||||
// Hash with config hash, version, and serial number
|
||||
// This ensures the baseline storage is cleared after OTA
|
||||
// Serial numbers are unique to each sensor, so multiple sensors can be used without conflict
|
||||
uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), combined_serial);
|
||||
this->pref_ = global_preferences->make_preference<Sen5xBaselines>(hash, true);
|
||||
|
||||
if (this->pref_.load(&this->voc_baselines_storage_)) {
|
||||
ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||
this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||
}
|
||||
|
||||
// Initialize storage timestamp
|
||||
this->seconds_since_last_store_ = 0;
|
||||
|
||||
if (this->voc_baselines_storage_.state0 > 0 && this->voc_baselines_storage_.state1 > 0) {
|
||||
ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||
this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||
uint16_t states[4];
|
||||
|
||||
states[0] = this->voc_baselines_storage_.state0 >> 16;
|
||||
states[1] = this->voc_baselines_storage_.state0 & 0xFFFF;
|
||||
states[2] = this->voc_baselines_storage_.state1 >> 16;
|
||||
states[3] = this->voc_baselines_storage_.state1 & 0xFFFF;
|
||||
|
||||
if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE, states, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to set VOC baseline from saved state");
|
||||
// Hash with serial number, serial numbers are unique, so multiple sensors can be used without conflict
|
||||
uint32_t hash = fnv1a_hash(this->serial_number_);
|
||||
this->pref_ = global_preferences->make_preference<uint16_t[4]>(hash, true);
|
||||
this->voc_baseline_time_ = App.get_loop_component_start_time();
|
||||
if (this->pref_.load(&this->voc_baseline_state_)) {
|
||||
if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE, this->voc_baseline_state_, 4)) {
|
||||
ESP_LOGE(TAG, "VOC Baseline State write to sensor failed");
|
||||
} else {
|
||||
ESP_LOGV(TAG, "VOC Baseline State loaded");
|
||||
delay(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool result;
|
||||
if (this->auto_cleaning_interval_.has_value()) {
|
||||
// override default value
|
||||
result = write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL, this->auto_cleaning_interval_.value());
|
||||
result = this->write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL, this->auto_cleaning_interval_.value());
|
||||
} else {
|
||||
result = write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL);
|
||||
result = this->write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL);
|
||||
}
|
||||
if (result) {
|
||||
delay(20);
|
||||
@@ -276,11 +265,10 @@ void SEN5XComponent::dump_config() {
|
||||
}
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Product name: %s\n"
|
||||
" Type: %s\n"
|
||||
" Firmware version: %d\n"
|
||||
" Serial number %02d.%02d.%02d",
|
||||
this->product_name_.c_str(), this->firmware_version_, this->serial_number_[0], this->serial_number_[1],
|
||||
this->serial_number_[2]);
|
||||
" Serial number: %s",
|
||||
LOG_STR_ARG(type_to_string(this->type_)), this->firmware_version_, this->serial_number_);
|
||||
if (this->auto_cleaning_interval_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Auto cleaning interval: %" PRId32 "s", this->auto_cleaning_interval_.value());
|
||||
}
|
||||
@@ -288,6 +276,14 @@ void SEN5XComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " RH/T acceleration mode: %s",
|
||||
LOG_STR_ARG(rht_accel_mode_to_string(this->acceleration_mode_.value())));
|
||||
}
|
||||
if (this->voc_sensor_) {
|
||||
char hex_buf[5 * 4];
|
||||
format_hex_pretty_to(hex_buf, this->voc_baseline_state_, 4, 0);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Store Baseline: %s\n"
|
||||
" State: %s\n",
|
||||
TRUEFALSE(this->store_baseline_), hex_buf);
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "PM 1.0", this->pm_1_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM 2.5", this->pm_2_5_sensor_);
|
||||
@@ -304,36 +300,6 @@ void SEN5XComponent::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store baselines after defined interval or if the difference between current and stored baseline becomes too
|
||||
// much
|
||||
if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) {
|
||||
if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) {
|
||||
// run it a bit later to avoid adding a delay here
|
||||
this->set_timeout(550, [this]() {
|
||||
uint16_t states[4];
|
||||
if (this->read_data(states, 4)) {
|
||||
uint32_t state0 = states[0] << 16 | states[1];
|
||||
uint32_t state1 = states[2] << 16 | states[3];
|
||||
if ((uint32_t) std::abs(static_cast<int32_t>(this->voc_baselines_storage_.state0 - state0)) >
|
||||
MAXIMUM_STORAGE_DIFF ||
|
||||
(uint32_t) std::abs(static_cast<int32_t>(this->voc_baselines_storage_.state1 - state1)) >
|
||||
MAXIMUM_STORAGE_DIFF) {
|
||||
this->seconds_since_last_store_ = 0;
|
||||
this->voc_baselines_storage_.state0 = state0;
|
||||
this->voc_baselines_storage_.state1 = state1;
|
||||
|
||||
if (this->pref_.save(&this->voc_baselines_storage_)) {
|
||||
ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||
this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Could not store VOC baselines");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->write_command(SEN5X_CMD_READ_MEASUREMENT)) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGD(TAG, "Write error: read measurement (%d)", this->last_error_);
|
||||
@@ -402,7 +368,29 @@ void SEN5XComponent::update() {
|
||||
if (this->nox_sensor_ != nullptr) {
|
||||
this->nox_sensor_->publish_state(nox);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
if (!this->voc_sensor_ || !this->store_baseline_ ||
|
||||
(App.get_loop_component_start_time() - this->voc_baseline_time_) < SHORTEST_BASELINE_STORE_INTERVAL) {
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
this->voc_baseline_time_ = App.get_loop_component_start_time();
|
||||
if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
} else {
|
||||
this->set_timeout(20, [this]() {
|
||||
if (!this->read_data(this->voc_baseline_state_, 4)) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
} else {
|
||||
if (this->pref_.save(&this->voc_baseline_state_)) {
|
||||
ESP_LOGD(TAG, "VOC Baseline State saved");
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ enum RhtAccelerationMode : uint16_t {
|
||||
HIGH_ACCELERATION = 2,
|
||||
};
|
||||
|
||||
struct Sen5xBaselines {
|
||||
int32_t state0;
|
||||
int32_t state1;
|
||||
} PACKED; // NOLINT
|
||||
enum class Sen5xType : uint8_t { SEN50, SEN54, SEN55, UNKNOWN };
|
||||
|
||||
struct GasTuning {
|
||||
uint16_t index_offset;
|
||||
@@ -44,11 +41,9 @@ struct TemperatureCompensation {
|
||||
uint16_t time_constant;
|
||||
};
|
||||
|
||||
// Shortest time interval of 3H for storing baseline values.
|
||||
// Shortest time interval of 2H (in milliseconds) for storing baseline values.
|
||||
// Prevents wear of the flash because of too many write operations
|
||||
static const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 10800;
|
||||
// Store anyway if the baseline difference exceeds the max storage diff value
|
||||
static const uint32_t MAXIMUM_STORAGE_DIFF = 50;
|
||||
static const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 2 * 60 * 60 * 1000;
|
||||
|
||||
class SEN5XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
@@ -56,20 +51,20 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
enum Sen5xType { SEN50, SEN54, SEN55, UNKNOWN };
|
||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { this->pm_1_0_sensor_ = pm_1_0; }
|
||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { this->pm_2_5_sensor_ = pm_2_5; }
|
||||
void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { this->pm_4_0_sensor_ = pm_4_0; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { this->pm_10_0_sensor_ = pm_10_0; }
|
||||
|
||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
|
||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
|
||||
void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { pm_4_0_sensor_ = pm_4_0; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; }
|
||||
|
||||
void set_voc_sensor(sensor::Sensor *voc_sensor) { voc_sensor_ = voc_sensor; }
|
||||
void set_nox_sensor(sensor::Sensor *nox_sensor) { nox_sensor_ = nox_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; }
|
||||
void set_acceleration_mode(RhtAccelerationMode mode) { acceleration_mode_ = mode; }
|
||||
void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) { auto_cleaning_interval_ = auto_cleaning_interval; }
|
||||
void set_voc_sensor(sensor::Sensor *voc_sensor) { this->voc_sensor_ = voc_sensor; }
|
||||
void set_nox_sensor(sensor::Sensor *nox_sensor) { this->nox_sensor_ = nox_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_store_baseline(bool store_baseline) { this->store_baseline_ = store_baseline; }
|
||||
void set_acceleration_mode(RhtAccelerationMode mode) { this->acceleration_mode_ = mode; }
|
||||
void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) {
|
||||
this->auto_cleaning_interval_ = auto_cleaning_interval;
|
||||
}
|
||||
void set_voc_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours,
|
||||
uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes,
|
||||
uint16_t std_initial, uint16_t gain_factor) {
|
||||
@@ -80,7 +75,7 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
||||
tuning_params.gating_max_duration_minutes = gating_max_duration_minutes;
|
||||
tuning_params.std_initial = std_initial;
|
||||
tuning_params.gain_factor = gain_factor;
|
||||
voc_tuning_params_ = tuning_params;
|
||||
this->voc_tuning_params_ = tuning_params;
|
||||
}
|
||||
void set_nox_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours,
|
||||
uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes,
|
||||
@@ -92,14 +87,14 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
||||
tuning_params.gating_max_duration_minutes = gating_max_duration_minutes;
|
||||
tuning_params.std_initial = 50;
|
||||
tuning_params.gain_factor = gain_factor;
|
||||
nox_tuning_params_ = tuning_params;
|
||||
this->nox_tuning_params_ = tuning_params;
|
||||
}
|
||||
void set_temperature_compensation(float offset, float normalized_offset_slope, uint16_t time_constant) {
|
||||
TemperatureCompensation temp_comp;
|
||||
temp_comp.offset = offset * 200;
|
||||
temp_comp.normalized_offset_slope = normalized_offset_slope * 10000;
|
||||
temp_comp.time_constant = time_constant;
|
||||
temperature_compensation_ = temp_comp;
|
||||
this->temperature_compensation_ = temp_comp;
|
||||
}
|
||||
bool start_fan_cleaning();
|
||||
|
||||
@@ -107,10 +102,12 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
||||
bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning);
|
||||
bool write_temperature_compensation_(const TemperatureCompensation &compensation);
|
||||
|
||||
uint32_t seconds_since_last_store_;
|
||||
char serial_number_[17] = "UNKNOWN";
|
||||
uint16_t voc_baseline_state_[4]{0};
|
||||
uint32_t voc_baseline_time_;
|
||||
uint16_t firmware_version_;
|
||||
Sen5xType type_{Sen5xType::UNKNOWN};
|
||||
ERRORCODE error_code_;
|
||||
uint8_t serial_number_[4];
|
||||
bool initialized_{false};
|
||||
bool store_baseline_;
|
||||
|
||||
@@ -131,8 +128,6 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri
|
||||
optional<GasTuning> nox_tuning_params_;
|
||||
optional<TemperatureCompensation> temperature_compensation_;
|
||||
ESPPreferenceObject pref_;
|
||||
std::string product_name_;
|
||||
Sen5xBaselines voc_baselines_storage_;
|
||||
};
|
||||
|
||||
} // namespace sen5x
|
||||
|
||||
@@ -210,6 +210,7 @@ SENSOR_MAP = {
|
||||
SETTING_MAP = {
|
||||
CONF_AUTO_CLEANING_INTERVAL: "set_auto_cleaning_interval",
|
||||
CONF_ACCELERATION_MODE: "set_acceleration_mode",
|
||||
CONF_STORE_BASELINE: "set_store_baseline",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,42 +39,23 @@ bool SensirionI2CDevice::read_data(uint16_t *data, const uint8_t len) {
|
||||
*/
|
||||
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
|
||||
const uint8_t data_len) {
|
||||
uint8_t temp_stack[BUFFER_STACK_SIZE];
|
||||
std::unique_ptr<uint8_t[]> temp_heap;
|
||||
uint8_t *temp;
|
||||
size_t required_buffer_len = data_len * 3 + 2;
|
||||
|
||||
// Is a dynamic allocation required ?
|
||||
if (required_buffer_len >= BUFFER_STACK_SIZE) {
|
||||
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
|
||||
temp = temp_heap.get();
|
||||
} else {
|
||||
temp = temp_stack;
|
||||
}
|
||||
SmallBufferWithHeapFallback<BUFFER_STACK_SIZE> buffer(required_buffer_len);
|
||||
uint8_t *temp = buffer.get();
|
||||
// First byte or word is the command
|
||||
uint8_t raw_idx = 0;
|
||||
if (command_len == 1) {
|
||||
temp[raw_idx++] = command & 0xFF;
|
||||
} else {
|
||||
// command is 2 bytes
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
temp[raw_idx++] = command >> 8;
|
||||
temp[raw_idx++] = command & 0xFF;
|
||||
#else
|
||||
temp[raw_idx++] = command & 0xFF;
|
||||
temp[raw_idx++] = command >> 8;
|
||||
#endif
|
||||
}
|
||||
// add parameters followed by crc
|
||||
// skipped if len == 0
|
||||
for (size_t i = 0; i < data_len; i++) {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
temp[raw_idx++] = data[i] >> 8;
|
||||
temp[raw_idx++] = data[i] & 0xFF;
|
||||
#else
|
||||
temp[raw_idx++] = data[i] & 0xFF;
|
||||
temp[raw_idx++] = data[i] >> 8;
|
||||
#endif
|
||||
// Use MSB first since Sensirion devices use CRC-8 with MSB first
|
||||
uint8_t crc = crc8(&temp[raw_idx - 2], 2, 0xFF, CRC_POLYNOMIAL, true);
|
||||
temp[raw_idx++] = crc;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "slow_pwm_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace slow_pwm {
|
||||
@@ -20,7 +21,9 @@ void SlowPWMOutput::set_output_state_(bool new_state) {
|
||||
}
|
||||
if (new_state != current_state_) {
|
||||
if (this->pin_) {
|
||||
ESP_LOGV(TAG, "Switching output pin %s to %s", this->pin_->dump_summary().c_str(), ONOFF(new_state));
|
||||
char pin_summary[GPIO_SUMMARY_MAX_LEN];
|
||||
this->pin_->dump_summary(pin_summary, sizeof(pin_summary));
|
||||
ESP_LOGV(TAG, "Switching output pin %s to %s", pin_summary, ONOFF(new_state));
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Switching to %s", ONOFF(new_state));
|
||||
}
|
||||
|
||||
63
esphome/components/sy6970/__init__.py
Normal file
63
esphome/components/sy6970/__init__.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@linkedupbits"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_SY6970_ID = "sy6970_id"
|
||||
CONF_ENABLE_STATUS_LED = "enable_status_led"
|
||||
CONF_INPUT_CURRENT_LIMIT = "input_current_limit"
|
||||
CONF_CHARGE_VOLTAGE = "charge_voltage"
|
||||
CONF_CHARGE_CURRENT = "charge_current"
|
||||
CONF_PRECHARGE_CURRENT = "precharge_current"
|
||||
CONF_CHARGE_ENABLED = "charge_enabled"
|
||||
CONF_ENABLE_ADC = "enable_adc"
|
||||
|
||||
sy6970_ns = cg.esphome_ns.namespace("sy6970")
|
||||
SY6970Component = sy6970_ns.class_(
|
||||
"SY6970Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
SY6970Listener = sy6970_ns.class_("SY6970Listener")
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SY6970Component),
|
||||
cv.Optional(CONF_ENABLE_STATUS_LED, default=True): cv.boolean,
|
||||
cv.Optional(CONF_INPUT_CURRENT_LIMIT, default=500): cv.int_range(
|
||||
min=100, max=3200
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_VOLTAGE, default=4208): cv.int_range(
|
||||
min=3840, max=4608
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_CURRENT, default=2048): cv.int_range(
|
||||
min=0, max=5056
|
||||
),
|
||||
cv.Optional(CONF_PRECHARGE_CURRENT, default=128): cv.int_range(
|
||||
min=64, max=1024
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_ADC, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
.extend(i2c.i2c_device_schema(0x6A))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_ENABLE_STATUS_LED],
|
||||
config[CONF_INPUT_CURRENT_LIMIT],
|
||||
config[CONF_CHARGE_VOLTAGE],
|
||||
config[CONF_CHARGE_CURRENT],
|
||||
config[CONF_PRECHARGE_CURRENT],
|
||||
config[CONF_CHARGE_ENABLED],
|
||||
config[CONF_ENABLE_ADC],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
56
esphome/components/sy6970/binary_sensor/__init__.py
Normal file
56
esphome/components/sy6970/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_POWER
|
||||
|
||||
from .. import CONF_SY6970_ID, SY6970Component, sy6970_ns
|
||||
|
||||
DEPENDENCIES = ["sy6970"]
|
||||
|
||||
CONF_VBUS_CONNECTED = "vbus_connected"
|
||||
CONF_CHARGING = "charging"
|
||||
CONF_CHARGE_DONE = "charge_done"
|
||||
|
||||
SY6970VbusConnectedBinarySensor = sy6970_ns.class_(
|
||||
"SY6970VbusConnectedBinarySensor", binary_sensor.BinarySensor
|
||||
)
|
||||
SY6970ChargingBinarySensor = sy6970_ns.class_(
|
||||
"SY6970ChargingBinarySensor", binary_sensor.BinarySensor
|
||||
)
|
||||
SY6970ChargeDoneBinarySensor = sy6970_ns.class_(
|
||||
"SY6970ChargeDoneBinarySensor", binary_sensor.BinarySensor
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_SY6970_ID): cv.use_id(SY6970Component),
|
||||
cv.Optional(CONF_VBUS_CONNECTED): binary_sensor.binary_sensor_schema(
|
||||
SY6970VbusConnectedBinarySensor,
|
||||
device_class=DEVICE_CLASS_CONNECTIVITY,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING): binary_sensor.binary_sensor_schema(
|
||||
SY6970ChargingBinarySensor,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_DONE): binary_sensor.binary_sensor_schema(
|
||||
SY6970ChargeDoneBinarySensor,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_SY6970_ID])
|
||||
|
||||
if vbus_connected_config := config.get(CONF_VBUS_CONNECTED):
|
||||
sens = await binary_sensor.new_binary_sensor(vbus_connected_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if charging_config := config.get(CONF_CHARGING):
|
||||
sens = await binary_sensor.new_binary_sensor(charging_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if charge_done_config := config.get(CONF_CHARGE_DONE):
|
||||
sens = await binary_sensor.new_binary_sensor(charge_done_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "../sy6970.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
template<uint8_t REG, uint8_t SHIFT, uint8_t MASK, uint8_t TRUE_VALUE>
|
||||
class StatusBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t value = (data.registers[REG] >> SHIFT) & MASK;
|
||||
this->publish_state(value == TRUE_VALUE);
|
||||
}
|
||||
};
|
||||
|
||||
template<uint8_t REG, uint8_t SHIFT, uint8_t MASK, uint8_t FALSE_VALUE>
|
||||
class InverseStatusBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t value = (data.registers[REG] >> SHIFT) & MASK;
|
||||
this->publish_state(value != FALSE_VALUE);
|
||||
}
|
||||
};
|
||||
|
||||
// Custom binary sensor for charging (true when pre-charge or fast charge)
|
||||
class SY6970ChargingBinarySensor : public SY6970Listener, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t chrg_stat = (data.registers[SY6970_REG_STATUS] >> 3) & 0x03;
|
||||
bool charging = chrg_stat != CHARGE_STATUS_NOT_CHARGING && chrg_stat != CHARGE_STATUS_CHARGE_DONE;
|
||||
this->publish_state(charging);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialized sensor types using templates
|
||||
// VBUS connected: BUS_STATUS != NO_INPUT
|
||||
using SY6970VbusConnectedBinarySensor = InverseStatusBinarySensor<SY6970_REG_STATUS, 5, 0x07, BUS_STATUS_NO_INPUT>;
|
||||
|
||||
// Charge done: CHARGE_STATUS == CHARGE_DONE
|
||||
using SY6970ChargeDoneBinarySensor = StatusBinarySensor<SY6970_REG_STATUS, 3, 0x03, CHARGE_STATUS_CHARGE_DONE>;
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
95
esphome/components/sy6970/sensor/__init__.py
Normal file
95
esphome/components/sy6970/sensor/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_MILLIAMP,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
from .. import CONF_SY6970_ID, SY6970Component, sy6970_ns
|
||||
|
||||
DEPENDENCIES = ["sy6970"]
|
||||
|
||||
CONF_VBUS_VOLTAGE = "vbus_voltage"
|
||||
CONF_SYSTEM_VOLTAGE = "system_voltage"
|
||||
CONF_CHARGE_CURRENT = "charge_current"
|
||||
CONF_PRECHARGE_CURRENT = "precharge_current"
|
||||
|
||||
SY6970VbusVoltageSensor = sy6970_ns.class_("SY6970VbusVoltageSensor", sensor.Sensor)
|
||||
SY6970BatteryVoltageSensor = sy6970_ns.class_(
|
||||
"SY6970BatteryVoltageSensor", sensor.Sensor
|
||||
)
|
||||
SY6970SystemVoltageSensor = sy6970_ns.class_("SY6970SystemVoltageSensor", sensor.Sensor)
|
||||
SY6970ChargeCurrentSensor = sy6970_ns.class_("SY6970ChargeCurrentSensor", sensor.Sensor)
|
||||
SY6970PrechargeCurrentSensor = sy6970_ns.class_(
|
||||
"SY6970PrechargeCurrentSensor", sensor.Sensor
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_SY6970_ID): cv.use_id(SY6970Component),
|
||||
cv.Optional(CONF_VBUS_VOLTAGE): sensor.sensor_schema(
|
||||
SY6970VbusVoltageSensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
SY6970BatteryVoltageSensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_SYSTEM_VOLTAGE): sensor.sensor_schema(
|
||||
SY6970SystemVoltageSensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_CURRENT): sensor.sensor_schema(
|
||||
SY6970ChargeCurrentSensor,
|
||||
unit_of_measurement=UNIT_MILLIAMP,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PRECHARGE_CURRENT): sensor.sensor_schema(
|
||||
SY6970PrechargeCurrentSensor,
|
||||
unit_of_measurement=UNIT_MILLIAMP,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_SY6970_ID])
|
||||
|
||||
if vbus_voltage_config := config.get(CONF_VBUS_VOLTAGE):
|
||||
sens = await sensor.new_sensor(vbus_voltage_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(battery_voltage_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if system_voltage_config := config.get(CONF_SYSTEM_VOLTAGE):
|
||||
sens = await sensor.new_sensor(system_voltage_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if charge_current_config := config.get(CONF_CHARGE_CURRENT):
|
||||
sens = await sensor.new_sensor(charge_current_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if precharge_current_config := config.get(CONF_PRECHARGE_CURRENT):
|
||||
sens = await sensor.new_sensor(precharge_current_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
46
esphome/components/sy6970/sensor/sy6970_sensor.h
Normal file
46
esphome/components/sy6970/sensor/sy6970_sensor.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "../sy6970.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
// Template for voltage sensors (converts mV to V)
|
||||
template<uint8_t REG, uint8_t MASK, uint16_t BASE, uint16_t STEP>
|
||||
class VoltageSensor : public SY6970Listener, public sensor::Sensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t val = data.registers[REG] & MASK;
|
||||
uint16_t voltage_mv = BASE + (val * STEP);
|
||||
this->publish_state(voltage_mv * 0.001f); // Convert mV to V
|
||||
}
|
||||
};
|
||||
|
||||
// Template for current sensors (returns mA)
|
||||
template<uint8_t REG, uint8_t MASK, uint16_t BASE, uint16_t STEP>
|
||||
class CurrentSensor : public SY6970Listener, public sensor::Sensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t val = data.registers[REG] & MASK;
|
||||
uint16_t current_ma = BASE + (val * STEP);
|
||||
this->publish_state(current_ma);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialized sensor types using templates
|
||||
using SY6970VbusVoltageSensor = VoltageSensor<SY6970_REG_VBUS_VOLTAGE, 0x7F, VBUS_BASE_MV, VBUS_STEP_MV>;
|
||||
using SY6970BatteryVoltageSensor = VoltageSensor<SY6970_REG_BATV, 0x7F, VBAT_BASE_MV, VBAT_STEP_MV>;
|
||||
using SY6970SystemVoltageSensor = VoltageSensor<SY6970_REG_VINDPM_STATUS, 0x7F, VSYS_BASE_MV, VSYS_STEP_MV>;
|
||||
using SY6970ChargeCurrentSensor = CurrentSensor<SY6970_REG_CHARGE_CURRENT_MONITOR, 0x7F, 0, CHG_CURRENT_STEP_MA>;
|
||||
|
||||
// Precharge current sensor needs special handling (bit shift)
|
||||
class SY6970PrechargeCurrentSensor : public SY6970Listener, public sensor::Sensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t iprechg = (data.registers[SY6970_REG_PRECHARGE_CURRENT] >> 4) & 0x0F;
|
||||
uint16_t iprechg_ma = PRE_CHG_BASE_MA + (iprechg * PRE_CHG_STEP_MA);
|
||||
this->publish_state(iprechg_ma);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
201
esphome/components/sy6970/sy6970.cpp
Normal file
201
esphome/components/sy6970/sy6970.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "sy6970.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
static const char *const TAG = "sy6970";
|
||||
|
||||
bool SY6970Component::read_all_registers_() {
|
||||
// Read all registers from 0x00 to 0x14 in one transaction (21 bytes)
|
||||
// This includes unused registers 0x0F, 0x10 for performance
|
||||
if (!this->read_bytes(SY6970_REG_INPUT_CURRENT_LIMIT, this->data_.registers, 21)) {
|
||||
ESP_LOGW(TAG, "Failed to read registers 0x00-0x14");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SY6970Component::write_register_(uint8_t reg, uint8_t value) {
|
||||
if (!this->write_byte(reg, value)) {
|
||||
ESP_LOGW(TAG, "Failed to write register 0x%02X", reg);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SY6970Component::update_register_(uint8_t reg, uint8_t mask, uint8_t value) {
|
||||
uint8_t reg_value;
|
||||
if (!this->read_byte(reg, ®_value)) {
|
||||
ESP_LOGW(TAG, "Failed to read register 0x%02X for update", reg);
|
||||
return false;
|
||||
}
|
||||
reg_value = (reg_value & ~mask) | (value & mask);
|
||||
return this->write_register_(reg, reg_value);
|
||||
}
|
||||
|
||||
void SY6970Component::setup() {
|
||||
ESP_LOGV(TAG, "Setting up SY6970...");
|
||||
|
||||
// Try to read chip ID
|
||||
uint8_t reg_value;
|
||||
if (!this->read_byte(SY6970_REG_DEVICE_ID, ®_value)) {
|
||||
ESP_LOGE(TAG, "Failed to communicate with SY6970");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t chip_id = reg_value & 0x03;
|
||||
if (chip_id != 0x00) {
|
||||
ESP_LOGW(TAG, "Unexpected chip ID: 0x%02X (expected 0x00)", chip_id);
|
||||
}
|
||||
|
||||
// Apply configuration options (all have defaults now)
|
||||
ESP_LOGV(TAG, "Setting LED enabled to %s", ONOFF(this->led_enabled_));
|
||||
this->set_led_enabled(this->led_enabled_);
|
||||
|
||||
ESP_LOGV(TAG, "Setting input current limit to %u mA", this->input_current_limit_);
|
||||
this->set_input_current_limit(this->input_current_limit_);
|
||||
|
||||
ESP_LOGV(TAG, "Setting charge voltage to %u mV", this->charge_voltage_);
|
||||
this->set_charge_target_voltage(this->charge_voltage_);
|
||||
|
||||
ESP_LOGV(TAG, "Setting charge current to %u mA", this->charge_current_);
|
||||
this->set_charge_current(this->charge_current_);
|
||||
|
||||
ESP_LOGV(TAG, "Setting precharge current to %u mA", this->precharge_current_);
|
||||
this->set_precharge_current(this->precharge_current_);
|
||||
|
||||
ESP_LOGV(TAG, "Setting charge enabled to %s", ONOFF(this->charge_enabled_));
|
||||
this->set_charge_enabled(this->charge_enabled_);
|
||||
|
||||
ESP_LOGV(TAG, "Setting ADC measurements to %s", ONOFF(this->enable_adc_));
|
||||
this->set_enable_adc_measure(this->enable_adc_);
|
||||
|
||||
ESP_LOGV(TAG, "SY6970 initialized successfully");
|
||||
}
|
||||
|
||||
void SY6970Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"SY6970:\n"
|
||||
" LED Enabled: %s\n"
|
||||
" Input Current Limit: %u mA\n"
|
||||
" Charge Voltage: %u mV\n"
|
||||
" Charge Current: %u mA\n"
|
||||
" Precharge Current: %u mA\n"
|
||||
" Charge Enabled: %s\n"
|
||||
" ADC Enabled: %s",
|
||||
ONOFF(this->led_enabled_), this->input_current_limit_, this->charge_voltage_, this->charge_current_,
|
||||
this->precharge_current_, ONOFF(this->charge_enabled_), ONOFF(this->enable_adc_));
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with SY6970 failed!");
|
||||
}
|
||||
}
|
||||
|
||||
void SY6970Component::update() {
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read all registers in one transaction
|
||||
if (!this->read_all_registers_()) {
|
||||
ESP_LOGW(TAG, "Failed to read registers during update");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
// Notify all listeners with the new data
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_data(this->data_);
|
||||
}
|
||||
}
|
||||
|
||||
void SY6970Component::set_input_current_limit(uint16_t milliamps) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
if (milliamps < INPUT_CURRENT_MIN) {
|
||||
milliamps = INPUT_CURRENT_MIN;
|
||||
}
|
||||
|
||||
uint8_t val = (milliamps - INPUT_CURRENT_MIN) / INPUT_CURRENT_STEP;
|
||||
if (val > 0x3F) {
|
||||
val = 0x3F;
|
||||
}
|
||||
|
||||
this->update_register_(SY6970_REG_INPUT_CURRENT_LIMIT, 0x3F, val);
|
||||
}
|
||||
|
||||
void SY6970Component::set_charge_target_voltage(uint16_t millivolts) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
if (millivolts < CHG_VOLTAGE_BASE) {
|
||||
millivolts = CHG_VOLTAGE_BASE;
|
||||
}
|
||||
|
||||
uint8_t val = (millivolts - CHG_VOLTAGE_BASE) / CHG_VOLTAGE_STEP;
|
||||
if (val > 0x3F) {
|
||||
val = 0x3F;
|
||||
}
|
||||
|
||||
this->update_register_(SY6970_REG_CHARGE_VOLTAGE, 0xFC, val << 2);
|
||||
}
|
||||
|
||||
void SY6970Component::set_precharge_current(uint16_t milliamps) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
if (milliamps < PRE_CHG_BASE_MA) {
|
||||
milliamps = PRE_CHG_BASE_MA;
|
||||
}
|
||||
|
||||
uint8_t val = (milliamps - PRE_CHG_BASE_MA) / PRE_CHG_STEP_MA;
|
||||
if (val > 0x0F) {
|
||||
val = 0x0F;
|
||||
}
|
||||
|
||||
this->update_register_(SY6970_REG_PRECHARGE_CURRENT, 0xF0, val << 4);
|
||||
}
|
||||
|
||||
void SY6970Component::set_charge_current(uint16_t milliamps) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
uint8_t val = milliamps / 64;
|
||||
if (val > 0x7F) {
|
||||
val = 0x7F;
|
||||
}
|
||||
|
||||
this->update_register_(SY6970_REG_CHARGE_CURRENT, 0x7F, val);
|
||||
}
|
||||
|
||||
void SY6970Component::set_charge_enabled(bool enabled) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
this->update_register_(SY6970_REG_SYS_CONTROL, 0x10, enabled ? 0x10 : 0x00);
|
||||
}
|
||||
|
||||
void SY6970Component::set_led_enabled(bool enabled) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
// Bit 6: 0 = LED enabled, 1 = LED disabled
|
||||
this->update_register_(SY6970_REG_TIMER_CONTROL, 0x40, enabled ? 0x00 : 0x40);
|
||||
}
|
||||
|
||||
void SY6970Component::set_enable_adc_measure(bool enabled) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
// Set bits to enable ADC conversion
|
||||
this->update_register_(SY6970_REG_ADC_CONTROL, 0xC0, enabled ? 0xC0 : 0x00);
|
||||
}
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
122
esphome/components/sy6970/sy6970.h
Normal file
122
esphome/components/sy6970/sy6970.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
// SY6970 Register addresses with descriptive names
|
||||
static const uint8_t SY6970_REG_INPUT_CURRENT_LIMIT = 0x00; // Input current limit control
|
||||
static const uint8_t SY6970_REG_VINDPM = 0x01; // Input voltage limit
|
||||
static const uint8_t SY6970_REG_ADC_CONTROL = 0x02; // ADC control and function disable
|
||||
static const uint8_t SY6970_REG_SYS_CONTROL = 0x03; // Charge enable and system config
|
||||
static const uint8_t SY6970_REG_CHARGE_CURRENT = 0x04; // Fast charge current limit
|
||||
static const uint8_t SY6970_REG_PRECHARGE_CURRENT = 0x05; // Pre-charge/termination current
|
||||
static const uint8_t SY6970_REG_CHARGE_VOLTAGE = 0x06; // Charge voltage limit
|
||||
static const uint8_t SY6970_REG_TIMER_CONTROL = 0x07; // Charge timer and status LED control
|
||||
static const uint8_t SY6970_REG_IR_COMP = 0x08; // IR compensation
|
||||
static const uint8_t SY6970_REG_FORCE_DPDM = 0x09; // Force DPDM detection
|
||||
static const uint8_t SY6970_REG_BOOST_CONTROL = 0x0A; // Boost mode voltage/current
|
||||
static const uint8_t SY6970_REG_STATUS = 0x0B; // System status (bus, charge status)
|
||||
static const uint8_t SY6970_REG_FAULT = 0x0C; // Fault status (NTC)
|
||||
static const uint8_t SY6970_REG_VINDPM_STATUS = 0x0D; // Input voltage limit status (also sys voltage)
|
||||
static const uint8_t SY6970_REG_BATV = 0x0E; // Battery voltage
|
||||
static const uint8_t SY6970_REG_VBUS_VOLTAGE = 0x11; // VBUS voltage
|
||||
static const uint8_t SY6970_REG_CHARGE_CURRENT_MONITOR = 0x12; // Charge current
|
||||
static const uint8_t SY6970_REG_INPUT_VOLTAGE_LIMIT = 0x13; // Input voltage limit
|
||||
static const uint8_t SY6970_REG_DEVICE_ID = 0x14; // Part information
|
||||
|
||||
// Constants for voltage and current calculations
|
||||
static const uint16_t VBUS_BASE_MV = 2600; // mV
|
||||
static const uint16_t VBUS_STEP_MV = 100; // mV
|
||||
static const uint16_t VBAT_BASE_MV = 2304; // mV
|
||||
static const uint16_t VBAT_STEP_MV = 20; // mV
|
||||
static const uint16_t VSYS_BASE_MV = 2304; // mV
|
||||
static const uint16_t VSYS_STEP_MV = 20; // mV
|
||||
static const uint16_t CHG_CURRENT_STEP_MA = 50; // mA
|
||||
static const uint16_t PRE_CHG_BASE_MA = 64; // mA
|
||||
static const uint16_t PRE_CHG_STEP_MA = 64; // mA
|
||||
static const uint16_t CHG_VOLTAGE_BASE = 3840; // mV
|
||||
static const uint16_t CHG_VOLTAGE_STEP = 16; // mV
|
||||
static const uint16_t INPUT_CURRENT_MIN = 100; // mA
|
||||
static const uint16_t INPUT_CURRENT_STEP = 50; // mA
|
||||
|
||||
// Bus Status values (REG_0B[7:5])
|
||||
enum BusStatus {
|
||||
BUS_STATUS_NO_INPUT = 0,
|
||||
BUS_STATUS_USB_SDP = 1,
|
||||
BUS_STATUS_USB_CDP = 2,
|
||||
BUS_STATUS_USB_DCP = 3,
|
||||
BUS_STATUS_HVDCP = 4,
|
||||
BUS_STATUS_ADAPTER = 5,
|
||||
BUS_STATUS_NO_STD_ADAPTER = 6,
|
||||
BUS_STATUS_OTG = 7,
|
||||
};
|
||||
|
||||
// Charge Status values (REG_0B[4:3])
|
||||
enum ChargeStatus {
|
||||
CHARGE_STATUS_NOT_CHARGING = 0,
|
||||
CHARGE_STATUS_PRE_CHARGE = 1,
|
||||
CHARGE_STATUS_FAST_CHARGE = 2,
|
||||
CHARGE_STATUS_CHARGE_DONE = 3,
|
||||
};
|
||||
|
||||
// Structure to hold all register data read in one transaction
|
||||
struct SY6970Data {
|
||||
uint8_t registers[21]; // Registers 0x00-0x14 (includes unused 0x0F, 0x10)
|
||||
};
|
||||
|
||||
// Listener interface for components that want to receive SY6970 data updates
|
||||
class SY6970Listener {
|
||||
public:
|
||||
virtual void on_data(const SY6970Data &data) = 0;
|
||||
};
|
||||
|
||||
class SY6970Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
SY6970Component(bool led_enabled, uint16_t input_current_limit, uint16_t charge_voltage, uint16_t charge_current,
|
||||
uint16_t precharge_current, bool charge_enabled, bool enable_adc)
|
||||
: led_enabled_(led_enabled),
|
||||
input_current_limit_(input_current_limit),
|
||||
charge_voltage_(charge_voltage),
|
||||
charge_current_(charge_current),
|
||||
precharge_current_(precharge_current),
|
||||
charge_enabled_(charge_enabled),
|
||||
enable_adc_(enable_adc) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
// Listener registration
|
||||
void add_listener(SY6970Listener *listener) { this->listeners_.push_back(listener); }
|
||||
|
||||
// Configuration methods to be called from lambdas
|
||||
void set_input_current_limit(uint16_t milliamps);
|
||||
void set_charge_target_voltage(uint16_t millivolts);
|
||||
void set_precharge_current(uint16_t milliamps);
|
||||
void set_charge_current(uint16_t milliamps);
|
||||
void set_charge_enabled(bool enabled);
|
||||
void set_led_enabled(bool enabled);
|
||||
void set_enable_adc_measure(bool enabled = true);
|
||||
|
||||
protected:
|
||||
bool read_all_registers_();
|
||||
bool write_register_(uint8_t reg, uint8_t value);
|
||||
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
|
||||
|
||||
SY6970Data data_{};
|
||||
std::vector<SY6970Listener *> listeners_;
|
||||
|
||||
// Configuration values to set during setup()
|
||||
bool led_enabled_;
|
||||
uint16_t input_current_limit_;
|
||||
uint16_t charge_voltage_;
|
||||
uint16_t charge_current_;
|
||||
uint16_t precharge_current_;
|
||||
bool charge_enabled_;
|
||||
bool enable_adc_;
|
||||
};
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
52
esphome/components/sy6970/text_sensor/__init__.py
Normal file
52
esphome/components/sy6970/text_sensor/__init__.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from .. import CONF_SY6970_ID, SY6970Component, sy6970_ns
|
||||
|
||||
DEPENDENCIES = ["sy6970"]
|
||||
|
||||
CONF_BUS_STATUS = "bus_status"
|
||||
CONF_CHARGE_STATUS = "charge_status"
|
||||
CONF_NTC_STATUS = "ntc_status"
|
||||
|
||||
SY6970BusStatusTextSensor = sy6970_ns.class_(
|
||||
"SY6970BusStatusTextSensor", text_sensor.TextSensor
|
||||
)
|
||||
SY6970ChargeStatusTextSensor = sy6970_ns.class_(
|
||||
"SY6970ChargeStatusTextSensor", text_sensor.TextSensor
|
||||
)
|
||||
SY6970NtcStatusTextSensor = sy6970_ns.class_(
|
||||
"SY6970NtcStatusTextSensor", text_sensor.TextSensor
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_SY6970_ID): cv.use_id(SY6970Component),
|
||||
cv.Optional(CONF_BUS_STATUS): text_sensor.text_sensor_schema(
|
||||
SY6970BusStatusTextSensor
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_STATUS): text_sensor.text_sensor_schema(
|
||||
SY6970ChargeStatusTextSensor
|
||||
),
|
||||
cv.Optional(CONF_NTC_STATUS): text_sensor.text_sensor_schema(
|
||||
SY6970NtcStatusTextSensor
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_SY6970_ID])
|
||||
|
||||
if bus_status_config := config.get(CONF_BUS_STATUS):
|
||||
sens = await text_sensor.new_text_sensor(bus_status_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if charge_status_config := config.get(CONF_CHARGE_STATUS):
|
||||
sens = await text_sensor.new_text_sensor(charge_status_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
|
||||
if ntc_status_config := config.get(CONF_NTC_STATUS):
|
||||
sens = await text_sensor.new_text_sensor(ntc_status_config)
|
||||
cg.add(parent.add_listener(sens))
|
||||
96
esphome/components/sy6970/text_sensor/sy6970_text_sensor.h
Normal file
96
esphome/components/sy6970/text_sensor/sy6970_text_sensor.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "../sy6970.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::sy6970 {
|
||||
|
||||
// Bus status text sensor
|
||||
class SY6970BusStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t status = (data.registers[SY6970_REG_STATUS] >> 5) & 0x07;
|
||||
const char *status_str = this->get_bus_status_string_(status);
|
||||
this->publish_state(status_str);
|
||||
}
|
||||
|
||||
protected:
|
||||
const char *get_bus_status_string_(uint8_t status) {
|
||||
switch (status) {
|
||||
case BUS_STATUS_NO_INPUT:
|
||||
return "No Input";
|
||||
case BUS_STATUS_USB_SDP:
|
||||
return "USB SDP";
|
||||
case BUS_STATUS_USB_CDP:
|
||||
return "USB CDP";
|
||||
case BUS_STATUS_USB_DCP:
|
||||
return "USB DCP";
|
||||
case BUS_STATUS_HVDCP:
|
||||
return "HVDCP";
|
||||
case BUS_STATUS_ADAPTER:
|
||||
return "Adapter";
|
||||
case BUS_STATUS_NO_STD_ADAPTER:
|
||||
return "Non-Standard Adapter";
|
||||
case BUS_STATUS_OTG:
|
||||
return "OTG";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Charge status text sensor
|
||||
class SY6970ChargeStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t status = (data.registers[SY6970_REG_STATUS] >> 3) & 0x03;
|
||||
const char *status_str = this->get_charge_status_string_(status);
|
||||
this->publish_state(status_str);
|
||||
}
|
||||
|
||||
protected:
|
||||
const char *get_charge_status_string_(uint8_t status) {
|
||||
switch (status) {
|
||||
case CHARGE_STATUS_NOT_CHARGING:
|
||||
return "Not Charging";
|
||||
case CHARGE_STATUS_PRE_CHARGE:
|
||||
return "Pre-charge";
|
||||
case CHARGE_STATUS_FAST_CHARGE:
|
||||
return "Fast Charge";
|
||||
case CHARGE_STATUS_CHARGE_DONE:
|
||||
return "Charge Done";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// NTC status text sensor
|
||||
class SY6970NtcStatusTextSensor : public SY6970Listener, public text_sensor::TextSensor {
|
||||
public:
|
||||
void on_data(const SY6970Data &data) override {
|
||||
uint8_t status = data.registers[SY6970_REG_FAULT] & 0x07;
|
||||
const char *status_str = this->get_ntc_status_string_(status);
|
||||
this->publish_state(status_str);
|
||||
}
|
||||
|
||||
protected:
|
||||
const char *get_ntc_status_string_(uint8_t status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "Normal";
|
||||
case 2:
|
||||
return "Warm";
|
||||
case 3:
|
||||
return "Cool";
|
||||
case 5:
|
||||
return "Cold";
|
||||
case 6:
|
||||
return "Hot";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
@@ -20,7 +20,7 @@ from .. import template_ns
|
||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||
|
||||
TemplateWaterHeater = template_ns.class_(
|
||||
"TemplateWaterHeater", water_heater.WaterHeater
|
||||
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
||||
)
|
||||
|
||||
TemplateWaterHeaterPublishAction = template_ns.class_(
|
||||
@@ -36,24 +36,29 @@ RESTORE_MODES = {
|
||||
"RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend(
|
||||
{
|
||||
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
|
||||
RESTORE_MODES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
|
||||
cv.Optional(CONF_MODE): cv.returning_lambda,
|
||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||
water_heater.validate_water_heater_mode
|
||||
),
|
||||
}
|
||||
CONFIG_SCHEMA = (
|
||||
water_heater.water_heater_schema(TemplateWaterHeater)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
|
||||
RESTORE_MODES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
|
||||
cv.Optional(CONF_MODE): cv.returning_lambda,
|
||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||
water_heater.validate_water_heater_mode
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await water_heater.register_water_heater(var, config)
|
||||
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
@@ -10,7 +10,7 @@ TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {}
|
||||
void TemplateWaterHeater::setup() {
|
||||
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
|
||||
this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) {
|
||||
auto restore = this->restore_state();
|
||||
auto restore = this->restore_state_();
|
||||
|
||||
if (restore.has_value()) {
|
||||
restore->perform();
|
||||
|
||||
@@ -13,7 +13,7 @@ enum TemplateWaterHeaterRestoreMode {
|
||||
WATER_HEATER_RESTORE_AND_CALL,
|
||||
};
|
||||
|
||||
class TemplateWaterHeater : public water_heater::WaterHeater {
|
||||
class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
||||
public:
|
||||
TemplateWaterHeater();
|
||||
|
||||
|
||||
@@ -1060,11 +1060,11 @@ bool ThermostatClimate::cooling_required_() {
|
||||
auto temperature = this->supports_two_points_ ? this->target_temperature_high : this->target_temperature;
|
||||
|
||||
if (this->supports_cool_) {
|
||||
if (this->current_temperature > temperature + this->cooling_deadband_) {
|
||||
// if the current temperature exceeds the target + deadband, cooling is required
|
||||
if (this->current_temperature >= temperature + this->cooling_deadband_) {
|
||||
// if the current temperature reaches or exceeds the target + deadband, cooling is required
|
||||
return true;
|
||||
} else if (this->current_temperature < temperature - this->cooling_overrun_) {
|
||||
// if the current temperature is less than the target - overrun, cooling should stop
|
||||
} else if (this->current_temperature <= temperature - this->cooling_overrun_) {
|
||||
// if the current temperature is less than or equal to the target - overrun, cooling should stop
|
||||
return false;
|
||||
} else {
|
||||
// if we get here, the current temperature is between target + deadband and target - overrun,
|
||||
@@ -1081,11 +1081,11 @@ bool ThermostatClimate::fanning_required_() {
|
||||
|
||||
if (this->supports_fan_only_) {
|
||||
if (this->supports_fan_only_cooling_) {
|
||||
if (this->current_temperature > temperature + this->cooling_deadband_) {
|
||||
// if the current temperature exceeds the target + deadband, fanning is required
|
||||
if (this->current_temperature >= temperature + this->cooling_deadband_) {
|
||||
// if the current temperature reaches or exceeds the target + deadband, fanning is required
|
||||
return true;
|
||||
} else if (this->current_temperature < temperature - this->cooling_overrun_) {
|
||||
// if the current temperature is less than the target - overrun, fanning should stop
|
||||
} else if (this->current_temperature <= temperature - this->cooling_overrun_) {
|
||||
// if the current temperature is less than or equal to the target - overrun, fanning should stop
|
||||
return false;
|
||||
} else {
|
||||
// if we get here, the current temperature is between target + deadband and target - overrun,
|
||||
@@ -1103,11 +1103,12 @@ bool ThermostatClimate::heating_required_() {
|
||||
auto temperature = this->supports_two_points_ ? this->target_temperature_low : this->target_temperature;
|
||||
|
||||
if (this->supports_heat_) {
|
||||
if (this->current_temperature < temperature - this->heating_deadband_) {
|
||||
// if the current temperature is below the target - deadband, heating is required
|
||||
if (this->current_temperature <= temperature - this->heating_deadband_) {
|
||||
// if the current temperature is below or equal to the target - deadband, heating is required
|
||||
return true;
|
||||
} else if (this->current_temperature > temperature + this->heating_overrun_) {
|
||||
// if the current temperature is above the target + overrun, heating should stop
|
||||
} else if (this->current_temperature >= temperature + this->heating_overrun_) {
|
||||
// if the current temperature is above or equal to the target + overrun, heating should stop
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// if we get here, the current temperature is between target - deadband and target + overrun,
|
||||
|
||||
@@ -18,7 +18,7 @@ CODEOWNERS = ["@dhoeben"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
water_heater_ns = cg.esphome_ns.namespace("water_heater")
|
||||
WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase, cg.Component)
|
||||
WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase)
|
||||
WaterHeaterCall = water_heater_ns.class_("WaterHeaterCall")
|
||||
WaterHeaterTraits = water_heater_ns.class_("WaterHeaterTraits")
|
||||
|
||||
@@ -46,7 +46,7 @@ _WATER_HEATER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
_WATER_HEATER_SCHEMA.add_extra(entity_duplicate_validator("water_heater"))
|
||||
|
||||
@@ -91,8 +91,6 @@ async def register_water_heater(var: cg.Pvariable, config: ConfigType) -> cg.Pva
|
||||
|
||||
cg.add_define("USE_WATER_HEATER")
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(cg.App.register_water_heater(var))
|
||||
|
||||
CORE.register_platform_component("water_heater", var)
|
||||
|
||||
@@ -146,10 +146,6 @@ void WaterHeaterCall::validate_() {
|
||||
}
|
||||
}
|
||||
|
||||
void WaterHeater::setup() {
|
||||
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash());
|
||||
}
|
||||
|
||||
void WaterHeater::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
ESP_LOGD(TAG,
|
||||
@@ -188,7 +184,8 @@ void WaterHeater::publish_state() {
|
||||
this->pref_.save(&saved);
|
||||
}
|
||||
|
||||
optional<WaterHeaterCall> WaterHeater::restore_state() {
|
||||
optional<WaterHeaterCall> WaterHeater::restore_state_() {
|
||||
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash());
|
||||
SavedWaterHeaterState recovered{};
|
||||
if (!this->pref_.load(&recovered))
|
||||
return {};
|
||||
|
||||
@@ -177,7 +177,7 @@ class WaterHeaterTraits {
|
||||
WaterHeaterModeMask supported_modes_;
|
||||
};
|
||||
|
||||
class WaterHeater : public EntityBase, public Component {
|
||||
class WaterHeater : public EntityBase {
|
||||
public:
|
||||
WaterHeaterMode get_mode() const { return this->mode_; }
|
||||
float get_current_temperature() const { return this->current_temperature_; }
|
||||
@@ -204,16 +204,15 @@ class WaterHeater : public EntityBase, public Component {
|
||||
#endif
|
||||
virtual void control(const WaterHeaterCall &call) = 0;
|
||||
|
||||
void setup() override;
|
||||
|
||||
optional<WaterHeaterCall> restore_state();
|
||||
|
||||
protected:
|
||||
virtual WaterHeaterTraits traits() = 0;
|
||||
|
||||
/// Log the traits of this water heater for dump_config().
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
/// Restore the state of the water heater, call this from your setup() method.
|
||||
optional<WaterHeaterCall> restore_state_();
|
||||
|
||||
/// Set the mode of the water heater. Should only be called from control().
|
||||
void set_mode_(WaterHeaterMode mode) { this->mode_ = mode; }
|
||||
/// Set the target temperature of the water heater. Should only be called from control().
|
||||
|
||||
@@ -746,32 +746,16 @@ void WiFiComponent::setup_ap_config_() {
|
||||
return;
|
||||
|
||||
if (this->ap_.get_ssid().empty()) {
|
||||
// Build AP SSID from app name without heap allocation
|
||||
// WiFi SSID max is 32 bytes, with MAC suffix we keep first 25 + last 7
|
||||
static constexpr size_t AP_SSID_MAX_LEN = 32;
|
||||
static constexpr size_t AP_SSID_PREFIX_LEN = 25;
|
||||
static constexpr size_t AP_SSID_SUFFIX_LEN = 7;
|
||||
|
||||
const std::string &app_name = App.get_name();
|
||||
const char *name_ptr = app_name.c_str();
|
||||
size_t name_len = app_name.length();
|
||||
|
||||
if (name_len <= AP_SSID_MAX_LEN) {
|
||||
// Name fits, use directly
|
||||
this->ap_.set_ssid(name_ptr);
|
||||
} else {
|
||||
// Name too long, need to truncate into stack buffer
|
||||
char ssid_buf[AP_SSID_MAX_LEN + 1];
|
||||
std::string name = App.get_name();
|
||||
if (name.length() > 32) {
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
// Keep first 25 chars and last 7 chars (MAC suffix), remove middle
|
||||
memcpy(ssid_buf, name_ptr, AP_SSID_PREFIX_LEN);
|
||||
memcpy(ssid_buf + AP_SSID_PREFIX_LEN, name_ptr + name_len - AP_SSID_SUFFIX_LEN, AP_SSID_SUFFIX_LEN);
|
||||
name.erase(25, name.length() - 32);
|
||||
} else {
|
||||
memcpy(ssid_buf, name_ptr, AP_SSID_MAX_LEN);
|
||||
name.resize(32);
|
||||
}
|
||||
ssid_buf[AP_SSID_MAX_LEN] = '\0';
|
||||
this->ap_.set_ssid(ssid_buf);
|
||||
}
|
||||
this->ap_.set_ssid(name);
|
||||
}
|
||||
this->ap_setup_ = this->wifi_start_ap_(this->ap_);
|
||||
|
||||
|
||||
@@ -698,6 +698,10 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
|
||||
// Reset scan_done_ before starting new scan to prevent stale flag from previous scan
|
||||
// (e.g., roaming scan completed just before unexpected disconnect)
|
||||
this->scan_done_ = false;
|
||||
|
||||
struct scan_config config {};
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.ssid = nullptr;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
#if (ESP_IDF_VERSION_MAJOR >= 5) && (ESP_IDF_VERSION_MINOR >= 1)
|
||||
@@ -828,16 +829,29 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
|
||||
uint16_t number = it.number;
|
||||
scan_result_.init(number);
|
||||
|
||||
// Process one record at a time to avoid large buffer allocation
|
||||
wifi_ap_record_t record;
|
||||
#ifdef USE_ESP32_HOSTED
|
||||
// getting records one at a time fails on P4 with hosted esp32 WiFi coprocessor
|
||||
// Presumably an upstream bug, work-around by getting all records at once
|
||||
auto records = std::make_unique<wifi_ap_record_t[]>(number);
|
||||
err = esp_wifi_scan_get_ap_records(&number, records.get());
|
||||
if (err != ESP_OK) {
|
||||
esp_wifi_clear_ap_list();
|
||||
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
for (uint16_t i = 0; i < number; i++) {
|
||||
wifi_ap_record_t &record = records[i];
|
||||
#else
|
||||
// Process one record at a time to avoid large buffer allocation
|
||||
for (uint16_t i = 0; i < number; i++) {
|
||||
wifi_ap_record_t record;
|
||||
err = esp_wifi_scan_get_ap_record(&record);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_record failed: %s", esp_err_to_name(err));
|
||||
esp_wifi_clear_ap_list(); // Free remaining records not yet retrieved
|
||||
break;
|
||||
}
|
||||
#endif // USE_ESP32_HOSTED
|
||||
bssid_t bssid;
|
||||
std::copy(record.bssid, record.bssid + 6, bssid.begin());
|
||||
std::string ssid(reinterpret_cast<const char *>(record.ssid));
|
||||
|
||||
@@ -649,6 +649,10 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
|
||||
// Reset scan_done_ before starting new scan to prevent stale flag from previous scan
|
||||
// (e.g., roaming scan completed just before unexpected disconnect)
|
||||
this->scan_done_ = false;
|
||||
|
||||
// need to use WiFi because of WiFiScanClass allocations :(
|
||||
int16_t err = WiFi.scanNetworks(true, true, passive, 200);
|
||||
if (err != WIFI_SCAN_RUNNING) {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#define USE_DEVICES
|
||||
#define USE_DISPLAY
|
||||
#define USE_ENTITY_ICON
|
||||
#define USE_ESP32_HOSTED
|
||||
#define USE_ESP32_IMPROV_STATE_CALLBACK
|
||||
#define USE_EVENT
|
||||
#define USE_FAN
|
||||
|
||||
@@ -212,7 +212,7 @@ build_unflags =
|
||||
; This are common settings for the LibreTiny (all variants) using Arduino.
|
||||
[common:libretiny-arduino]
|
||||
extends = common:arduino
|
||||
platform = libretiny@1.9.2
|
||||
platform = https://github.com/libretiny-eu/libretiny.git#v1.11.0
|
||||
framework = arduino
|
||||
lib_compat_mode = soft
|
||||
lib_deps =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pylint==4.0.4
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.13 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.14 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- platform: bmp581
|
||||
- platform: bmp581_i2c
|
||||
i2c_id: i2c_bus
|
||||
temperature:
|
||||
name: BMP581 Temperature
|
||||
@@ -197,6 +197,9 @@ lvgl:
|
||||
- lvgl.label.update:
|
||||
id: msgbox_label
|
||||
text: Unloaded
|
||||
- lvgl.label.update:
|
||||
id: msgbox_label
|
||||
text: "" # Empty text
|
||||
on_all_events:
|
||||
logger.log:
|
||||
format: "Event %s"
|
||||
|
||||
57
tests/components/sy6970/common.yaml
Normal file
57
tests/components/sy6970/common.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
sy6970:
|
||||
id: sy6970_component
|
||||
i2c_id: i2c_bus
|
||||
address: 0x6A
|
||||
enable_status_led: true
|
||||
input_current_limit: 1000
|
||||
charge_voltage: 4200
|
||||
charge_current: 500
|
||||
precharge_current: 128
|
||||
charge_enabled: true
|
||||
enable_adc: true
|
||||
update_interval: 5s
|
||||
|
||||
sensor:
|
||||
- platform: sy6970
|
||||
sy6970_id: sy6970_component
|
||||
vbus_voltage:
|
||||
name: "VBUS Voltage"
|
||||
id: vbus_voltage_sensor
|
||||
battery_voltage:
|
||||
name: "Battery Voltage"
|
||||
id: battery_voltage_sensor
|
||||
system_voltage:
|
||||
name: "System Voltage"
|
||||
id: system_voltage_sensor
|
||||
charge_current:
|
||||
name: "Charge Current"
|
||||
id: charge_current_sensor
|
||||
precharge_current:
|
||||
name: "Precharge Current"
|
||||
id: precharge_current_sensor
|
||||
|
||||
binary_sensor:
|
||||
- platform: sy6970
|
||||
sy6970_id: sy6970_component
|
||||
vbus_connected:
|
||||
name: "VBUS Connected"
|
||||
id: vbus_connected_binary
|
||||
charging:
|
||||
name: "Charging"
|
||||
id: charging_binary
|
||||
charge_done:
|
||||
name: "Charge Done"
|
||||
id: charge_done_binary
|
||||
|
||||
text_sensor:
|
||||
- platform: sy6970
|
||||
sy6970_id: sy6970_component
|
||||
bus_status:
|
||||
name: "Bus Status"
|
||||
id: bus_status_text
|
||||
charge_status:
|
||||
name: "Charge Status"
|
||||
id: charge_status_text
|
||||
ntc_status:
|
||||
name: "NTC Status"
|
||||
id: ntc_status_text
|
||||
4
tests/components/sy6970/test.esp32-idf.yaml
Normal file
4
tests/components/sy6970/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user