1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-02 03:12:20 +01:00

Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston
2025-08-29 10:49:02 -05:00
29 changed files with 358 additions and 160 deletions

View File

@@ -11,7 +11,7 @@ ci:
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.12.10 rev: v0.12.11
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@@ -61,11 +61,10 @@ void AbsoluteHumidityComponent::loop() {
ESP_LOGW(TAG, "No valid state from temperature sensor!"); ESP_LOGW(TAG, "No valid state from temperature sensor!");
} }
if (no_humidity) { if (no_humidity) {
ESP_LOGW(TAG, "No valid state from temperature sensor!"); ESP_LOGW(TAG, "No valid state from humidity sensor!");
} }
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
this->publish_state(NAN); this->publish_state(NAN);
this->status_set_warning(); this->status_set_warning("Unable to calculate absolute humidity.");
return; return;
} }
@@ -87,9 +86,8 @@ void AbsoluteHumidityComponent::loop() {
es = es_wobus(temperature_c); es = es_wobus(temperature_c);
break; break;
default: default:
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
this->publish_state(NAN); this->publish_state(NAN);
this->status_set_error(); this->status_set_error("Invalid saturation vapor pressure equation selection!");
return; return;
} }
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es); ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

@@ -1712,6 +1712,7 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1; BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2; BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
} }
message BluetoothScannerSetModeRequest { message BluetoothScannerSetModeRequest {

View File

@@ -2153,10 +2153,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const { void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->state)); buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode)); buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
} }
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const { void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->state)); size.add_uint32(1, static_cast<uint32_t>(this->state));
size.add_uint32(1, static_cast<uint32_t>(this->mode)); size.add_uint32(1, static_cast<uint32_t>(this->mode));
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
} }
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {

View File

@@ -2214,12 +2214,13 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
class BluetoothScannerStateResponse final : public ProtoMessage { class BluetoothScannerStateResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 126; static constexpr uint8_t MESSAGE_TYPE = 126;
static constexpr uint8_t ESTIMATED_SIZE = 4; static constexpr uint8_t ESTIMATED_SIZE = 6;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_scanner_state_response"; } const char *message_name() const override { return "bluetooth_scanner_state_response"; }
#endif #endif
enums::BluetoothScannerState state{}; enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{}; enums::BluetoothScannerMode mode{};
enums::BluetoothScannerMode configured_mode{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -1704,6 +1704,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerStateResponse"); MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state)); dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode)); dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
} }
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const { void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest"); MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");

View File

@@ -24,6 +24,9 @@ void BluetoothProxy::setup() {
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS; this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS; this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
// Capture the configured scan mode from YAML before any API changes
this->configured_scan_active_ = this->parent_->get_scan_active();
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) { this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) { if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state); this->send_bluetooth_scanner_state_(state);
@@ -36,6 +39,9 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
resp.state = static_cast<api::enums::BluetoothScannerState>(state); resp.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
resp.configured_mode = this->configured_scan_active_
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE); this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
} }

View File

@@ -161,7 +161,8 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
// Group 4: 1-byte types grouped together // Group 4: 1-byte types grouped together
bool active_; bool active_;
uint8_t connection_count_{0}; uint8_t connection_count_{0};
// 2 bytes used, 2 bytes padding bool configured_scan_active_{false}; // Configured scan mode from YAML
// 3 bytes used, 1 byte padding
}; };
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -451,6 +451,7 @@ CONF_GRID_ROWS = "grid_rows"
CONF_HEADER_MODE = "header_mode" CONF_HEADER_MODE = "header_mode"
CONF_HOME = "home" CONF_HOME = "home"
CONF_INITIAL_FOCUS = "initial_focus" CONF_INITIAL_FOCUS = "initial_focus"
CONF_SELECTED_DIGIT = "selected_digit"
CONF_KEY_CODE = "key_code" CONF_KEY_CODE = "key_code"
CONF_KEYPADS = "keypads" CONF_KEYPADS = "keypads"
CONF_LAYOUT = "layout" CONF_LAYOUT = "layout"

View File

@@ -4,41 +4,31 @@ from esphome.yaml_util import parse_yaml
CONFIG = """ CONFIG = """
- obj: - obj:
radius: 0 id: hello_world_card_
pad_all: 12 pad_all: 12
bg_color: 0xFFFFFF bg_color: white
height: 100% height: 100%
width: 100% width: 100%
scrollable: false
widgets:
- obj:
align: top_mid
outline_width: 0
border_width: 0
pad_all: 4
scrollable: false
height: size_content
width: 100%
layout:
type: flex
flex_flow: row
flex_align_cross: center
flex_align_track: start
flex_align_main: space_between
widgets: widgets:
- spinner:
id: hello_world_spinner_
align: center
indicator:
arc_color: tomato
height: 100
width: 100
spin_time: 2s
arc_length: 60deg
- label:
id: hello_world_label_
text: "Hello World!"
align: center
on_click:
lvgl.spinner.update:
id: hello_world_spinner_
arc_color: springgreen
- checkbox:
pad_all: 8
text: Checkbox
align: top_right
on_click:
lvgl.label.update:
id: hello_world_label_
text: "Checked!"
- button: - button:
pad_all: 8
checkable: true checkable: true
align: top_left radius: 4
text_font: montserrat_20 text_font: montserrat_20
on_click: on_click:
lvgl.label.update: lvgl.label.update:
@@ -47,6 +37,79 @@ CONFIG = """
widgets: widgets:
- label: - label:
text: "Button" text: "Button"
- label:
id: hello_world_title_
text: ESPHome
text_font: montserrat_20
width: 100%
text_align: center
on_boot:
lvgl.widget.refresh: hello_world_title_
hidden: !lambda |-
return lv_obj_get_width(lv_scr_act()) < 400;
- checkbox:
text: Checkbox
id: hello_world_checkbox_
on_boot:
lvgl.widget.refresh: hello_world_checkbox_
hidden: !lambda |-
return lv_obj_get_width(lv_scr_act()) < 240;
on_click:
lvgl.label.update:
id: hello_world_label_
text: "Checked!"
- obj:
id: hello_world_container_
align: center
y: 14
pad_all: 0
outline_width: 0
border_width: 0
width: 100%
height: size_content
scrollable: false
on_click:
lvgl.spinner.update:
id: hello_world_spinner_
arc_color: springgreen
layout:
type: flex
flex_flow: row_wrap
flex_align_cross: center
flex_align_track: center
flex_align_main: space_evenly
widgets:
- spinner:
id: hello_world_spinner_
indicator:
arc_color: tomato
height: 100
width: 100
spin_time: 2s
arc_length: 60deg
widgets:
- label:
id: hello_world_label_
text: "Hello World!"
align: center
- obj:
id: hello_world_qrcode_
outline_width: 0
border_width: 0
hidden: !lambda |-
return lv_obj_get_width(lv_scr_act()) < 300 && lv_obj_get_height(lv_scr_act()) < 400;
widgets:
- label:
text_font: montserrat_14
text: esphome.io
align: top_mid
- qrcode:
text: "https://esphome.io"
size: 80
align: bottom_mid
on_boot:
lvgl.widget.refresh: hello_world_qrcode_
- slider: - slider:
width: 80% width: 80%
align: bottom_mid align: bottom_mid

View File

@@ -67,7 +67,6 @@ class Widget:
self.type = wtype self.type = wtype
self.config = config self.config = config
self.scale = 1.0 self.scale = 1.0
self.step = 1.0
self.range_from = -sys.maxsize self.range_from = -sys.maxsize
self.range_to = sys.maxsize self.range_to = sys.maxsize
if wtype.is_compound(): if wtype.is_compound():

View File

@@ -11,6 +11,7 @@ from ..defines import (
CONF_ROLLOVER, CONF_ROLLOVER,
CONF_SCROLLBAR, CONF_SCROLLBAR,
CONF_SELECTED, CONF_SELECTED,
CONF_SELECTED_DIGIT,
CONF_TEXTAREA_PLACEHOLDER, CONF_TEXTAREA_PLACEHOLDER,
) )
from ..lv_validation import lv_bool, lv_float from ..lv_validation import lv_bool, lv_float
@@ -38,18 +39,24 @@ def validate_spinbox(config):
min_val = -1 - max_val min_val = -1 - max_val
range_from = int(config[CONF_RANGE_FROM]) range_from = int(config[CONF_RANGE_FROM])
range_to = int(config[CONF_RANGE_TO]) range_to = int(config[CONF_RANGE_TO])
step = int(config[CONF_STEP]) step = config[CONF_SELECTED_DIGIT]
digits = config[CONF_DIGITS]
if ( if (
range_from > max_val range_from > max_val
or range_from < min_val or range_from < min_val
or range_to > max_val or range_to > max_val
or range_to < min_val or range_to < min_val
): ):
raise cv.Invalid("Range outside allowed limits") raise cv.Invalid("Range outside allowed limits", path=[CONF_RANGE_FROM])
if step <= 0 or step >= (range_to - range_from) / 2: if digits <= config[CONF_DECIMAL_PLACES]:
raise cv.Invalid("Invalid step value") raise cv.Invalid(
if config[CONF_DIGITS] <= config[CONF_DECIMAL_PLACES]: "Number of digits must exceed number of decimal places", path=[CONF_DIGITS]
raise cv.Invalid("Number of digits must exceed number of decimal places") )
if step >= digits:
raise cv.Invalid(
"Initial selected digit must be less than number of digits",
path=[CONF_SELECTED_DIGIT],
)
return config return config
@@ -59,7 +66,10 @@ SPINBOX_SCHEMA = cv.Schema(
cv.Optional(CONF_RANGE_FROM, default=0): cv.float_, cv.Optional(CONF_RANGE_FROM, default=0): cv.float_,
cv.Optional(CONF_RANGE_TO, default=100): cv.float_, cv.Optional(CONF_RANGE_TO, default=100): cv.float_,
cv.Optional(CONF_DIGITS, default=4): cv.int_range(1, 10), cv.Optional(CONF_DIGITS, default=4): cv.int_range(1, 10),
cv.Optional(CONF_STEP, default=1.0): cv.positive_float, cv.Optional(CONF_STEP): cv.invalid(
f"{CONF_STEP} has been replaced by {CONF_SELECTED_DIGIT}"
),
cv.Optional(CONF_SELECTED_DIGIT, default=0): cv.positive_int,
cv.Optional(CONF_DECIMAL_PLACES, default=0): cv.int_range(0, 6), cv.Optional(CONF_DECIMAL_PLACES, default=0): cv.int_range(0, 6),
cv.Optional(CONF_ROLLOVER, default=False): lv_bool, cv.Optional(CONF_ROLLOVER, default=False): lv_bool,
} }
@@ -93,13 +103,12 @@ class SpinboxType(WidgetType):
scale = 10 ** config[CONF_DECIMAL_PLACES] scale = 10 ** config[CONF_DECIMAL_PLACES]
range_from = int(config[CONF_RANGE_FROM]) * scale range_from = int(config[CONF_RANGE_FROM]) * scale
range_to = int(config[CONF_RANGE_TO]) * scale range_to = int(config[CONF_RANGE_TO]) * scale
step = int(config[CONF_STEP]) * scale step = config[CONF_SELECTED_DIGIT]
w.scale = scale w.scale = scale
w.step = step
w.range_to = range_to w.range_to = range_to
w.range_from = range_from w.range_from = range_from
lv.spinbox_set_range(w.obj, range_from, range_to) lv.spinbox_set_range(w.obj, range_from, range_to)
await w.set_property(CONF_STEP, step) await w.set_property("step", 10**step)
await w.set_property(CONF_ROLLOVER, config) await w.set_property(CONF_ROLLOVER, config)
lv.spinbox_set_digit_format( lv.spinbox_set_digit_format(
w.obj, digits, digits - config[CONF_DECIMAL_PLACES] w.obj, digits, digits - config[CONF_DECIMAL_PLACES]
@@ -120,7 +129,7 @@ class SpinboxType(WidgetType):
return config[CONF_RANGE_FROM] return config[CONF_RANGE_FROM]
def get_step(self, config: dict): def get_step(self, config: dict):
return config[CONF_STEP] return 10 ** config[CONF_SELECTED_DIGIT]
spinbox_spec = SpinboxType() spinbox_spec = SpinboxType()

View File

@@ -10,7 +10,8 @@ from esphome.loader import get_component
CODEOWNERS = ["@clydebarrow"] CODEOWNERS = ["@clydebarrow"]
MULTI_CONF = True MULTI_CONF = True
map_ = cg.std_ns.class_("map") mapping_ns = cg.esphome_ns.namespace("mapping")
mapping_class = mapping_ns.class_("Mapping")
CONF_ENTRIES = "entries" CONF_ENTRIES = "entries"
CONF_CLASS = "class" CONF_CLASS = "class"
@@ -29,7 +30,11 @@ class IndexType:
INDEX_TYPES = { INDEX_TYPES = {
"int": IndexType(cv.int_, cg.int_, int), "int": IndexType(cv.int_, cg.int_, int),
"string": IndexType(cv.string, cg.std_string, str), "string": IndexType(
cv.string,
cg.std_string,
str,
),
} }
@@ -47,7 +52,7 @@ def to_schema(value):
BASE_SCHEMA = cv.Schema( BASE_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_ID): cv.declare_id(map_), cv.Required(CONF_ID): cv.declare_id(mapping_class),
cv.Required(CONF_FROM): cv.one_of(*INDEX_TYPES, lower=True), cv.Required(CONF_FROM): cv.one_of(*INDEX_TYPES, lower=True),
cv.Required(CONF_TO): cv.string, cv.Required(CONF_TO): cv.string,
}, },
@@ -123,12 +128,15 @@ async def to_code(config):
if list(entries.values())[0].op != ".": if list(entries.values())[0].op != ".":
value_type = value_type.operator("ptr") value_type = value_type.operator("ptr")
varid = config[CONF_ID] varid = config[CONF_ID]
varid.type = map_.template(index_type, value_type) varid.type = mapping_class.template(
index_type,
value_type,
)
var = MockObj(varid, ".") var = MockObj(varid, ".")
decl = VariableDeclarationExpression(varid.type, "", varid) decl = VariableDeclarationExpression(varid.type, "", varid)
add_global(decl) add_global(decl)
CORE.register_variable(varid, var) CORE.register_variable(varid, var)
for key, value in entries.items(): for key, value in entries.items():
cg.add(var.insert((key, value))) cg.add(var.set(key, value))
return var return var

View File

@@ -0,0 +1,69 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <map>
#include <string>
namespace esphome::mapping {
using alloc_string_t = std::basic_string<char, std::char_traits<char>, RAMAllocator<char>>;
/**
*
* Mapping class with custom allocator.
* Additionally, when std::string is used as key or value, it will be replaced with a custom string type
* that uses RAMAllocator.
* @tparam K The type of the key in the mapping.
* @tparam V The type of the value in the mapping. Should be a basic type or pointer.
*/
static const char *const TAG = "mapping";
template<typename K, typename V> class Mapping {
public:
// Constructor
Mapping() = default;
using key_t = const std::conditional_t<std::is_same_v<K, std::string>,
alloc_string_t, // if K is std::string, custom string type
K>;
using value_t = std::conditional_t<std::is_same_v<V, std::string>,
alloc_string_t, // if V is std::string, custom string type
V>;
void set(const K &key, const V &value) { this->map_[key_t{key}] = value; }
V get(const K &key) const {
auto it = this->map_.find(key_t{key});
if (it != this->map_.end()) {
return V{it->second};
}
if constexpr (std::is_pointer_v<K>) {
esph_log_e(TAG, "Key '%p' not found in mapping", key);
} else if constexpr (std::is_same_v<K, std::string>) {
esph_log_e(TAG, "Key '%s' not found in mapping", key.c_str());
} else {
esph_log_e(TAG, "Key '%s' not found in mapping", to_string(key).c_str());
}
return {};
}
// index map overload
V operator[](K key) { return this->get(key); }
// convenience function for strings to get a C-style string
template<typename T = V, std::enable_if_t<std::is_same_v<T, std::string>, int> = 0>
const char *operator[](K key) const {
auto it = this->map_.find(key_t{key});
if (it != this->map_.end()) {
return it->second.c_str(); // safe since value remains in map
}
return "";
}
protected:
std::map<key_t, value_t, std::less<key_t>, RAMAllocator<std::pair<key_t, value_t>>> map_;
};
} // namespace esphome::mapping

View File

@@ -198,7 +198,7 @@ uint16_t Mcp4461Component::get_wiper_level_(Mcp4461WiperIdx wiper) {
uint16_t Mcp4461Component::read_wiper_level_(uint8_t wiper_idx) { uint16_t Mcp4461Component::read_wiper_level_(uint8_t wiper_idx) {
uint8_t addr = this->get_wiper_address_(wiper_idx); uint8_t addr = this->get_wiper_address_(wiper_idx);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::INCREMENT); uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::READ);
if (wiper_idx > 3) { if (wiper_idx > 3) {
if (!this->is_eeprom_ready_for_writing_(true)) { if (!this->is_eeprom_ready_for_writing_(true)) {
return 0; return 0;

View File

@@ -65,26 +65,47 @@ ACCELERATION_MODES = {
"high": RhtAccelerationMode.HIGH_ACCELERATION, "high": RhtAccelerationMode.HIGH_ACCELERATION,
} }
GAS_SENSOR = cv.Schema(
def _gas_sensor(
*,
index_offset: int,
learning_time_offset: int,
learning_time_gain: int,
gating_max_duration: int,
std_initial: int,
gain_factor: int,
) -> cv.Schema:
return sensor.sensor_schema(
icon=ICON_RADIATOR,
accuracy_decimals=0,
device_class=DEVICE_CLASS_AQI,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{ {
cv.Optional(CONF_ALGORITHM_TUNING): cv.Schema( cv.Optional(CONF_ALGORITHM_TUNING): cv.Schema(
{ {
cv.Optional(CONF_INDEX_OFFSET, default=100): cv.int_range(1, 250), cv.Optional(CONF_INDEX_OFFSET, default=index_offset): cv.int_range(
cv.Optional(CONF_LEARNING_TIME_OFFSET_HOURS, default=12): cv.int_range( 1, 250
1, 1000
),
cv.Optional(CONF_LEARNING_TIME_GAIN_HOURS, default=12): cv.int_range(
1, 1000
), ),
cv.Optional( cv.Optional(
CONF_GATING_MAX_DURATION_MINUTES, default=720 CONF_LEARNING_TIME_OFFSET_HOURS, default=learning_time_offset
): cv.int_range(1, 1000),
cv.Optional(
CONF_LEARNING_TIME_GAIN_HOURS, default=learning_time_gain
): cv.int_range(1, 1000),
cv.Optional(
CONF_GATING_MAX_DURATION_MINUTES, default=gating_max_duration
): cv.int_range(0, 3000), ): cv.int_range(0, 3000),
cv.Optional(CONF_STD_INITIAL, default=50): cv.int_, cv.Optional(CONF_STD_INITIAL, default=std_initial): cv.int_range(
cv.Optional(CONF_GAIN_FACTOR, default=230): cv.int_range(1, 1000), 10, 5000
),
cv.Optional(CONF_GAIN_FACTOR, default=gain_factor): cv.int_range(
1, 1000
),
} }
) )
} }
) )
def float_previously_pct(value): def float_previously_pct(value):
@@ -127,18 +148,22 @@ CONFIG_SCHEMA = (
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval, cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval,
cv.Optional(CONF_VOC): sensor.sensor_schema( cv.Optional(CONF_VOC): _gas_sensor(
icon=ICON_RADIATOR, index_offset=100,
accuracy_decimals=0, learning_time_offset=12,
device_class=DEVICE_CLASS_AQI, learning_time_gain=12,
state_class=STATE_CLASS_MEASUREMENT, gating_max_duration=180,
).extend(GAS_SENSOR), std_initial=50,
cv.Optional(CONF_NOX): sensor.sensor_schema( gain_factor=230,
icon=ICON_RADIATOR, ),
accuracy_decimals=0, cv.Optional(CONF_NOX): _gas_sensor(
device_class=DEVICE_CLASS_AQI, index_offset=1,
state_class=STATE_CLASS_MEASUREMENT, learning_time_offset=12,
).extend(GAS_SENSOR), learning_time_gain=12,
gating_max_duration=720,
std_initial=50,
gain_factor=230,
),
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
@@ -194,16 +219,15 @@ async def to_code(config):
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
for key, funcName in SETTING_MAP.items(): for key, funcName in SETTING_MAP.items():
if key in config: if cfg := config.get(key):
cg.add(getattr(var, funcName)(config[key])) cg.add(getattr(var, funcName)(cfg))
for key, funcName in SENSOR_MAP.items(): for key, funcName in SENSOR_MAP.items():
if key in config: if cfg := config.get(key):
sens = await sensor.new_sensor(config[key]) sens = await sensor.new_sensor(cfg)
cg.add(getattr(var, funcName)(sens)) cg.add(getattr(var, funcName)(sens))
if CONF_VOC in config and CONF_ALGORITHM_TUNING in config[CONF_VOC]: if cfg := config.get(CONF_VOC, {}).get(CONF_ALGORITHM_TUNING):
cfg = config[CONF_VOC][CONF_ALGORITHM_TUNING]
cg.add( cg.add(
var.set_voc_algorithm_tuning( var.set_voc_algorithm_tuning(
cfg[CONF_INDEX_OFFSET], cfg[CONF_INDEX_OFFSET],
@@ -214,8 +238,7 @@ async def to_code(config):
cfg[CONF_GAIN_FACTOR], cfg[CONF_GAIN_FACTOR],
) )
) )
if CONF_NOX in config and CONF_ALGORITHM_TUNING in config[CONF_NOX]: if cfg := config.get(CONF_NOX, {}).get(CONF_ALGORITHM_TUNING):
cfg = config[CONF_NOX][CONF_ALGORITHM_TUNING]
cg.add( cg.add(
var.set_nox_algorithm_tuning( var.set_nox_algorithm_tuning(
cfg[CONF_INDEX_OFFSET], cfg[CONF_INDEX_OFFSET],
@@ -225,12 +248,12 @@ async def to_code(config):
cfg[CONF_GAIN_FACTOR], cfg[CONF_GAIN_FACTOR],
) )
) )
if CONF_TEMPERATURE_COMPENSATION in config: if cfg := config.get(CONF_TEMPERATURE_COMPENSATION):
cg.add( cg.add(
var.set_temperature_compensation( var.set_temperature_compensation(
config[CONF_TEMPERATURE_COMPENSATION][CONF_OFFSET], cfg[CONF_OFFSET],
config[CONF_TEMPERATURE_COMPENSATION][CONF_NORMALIZED_OFFSET_SLOPE], cfg[CONF_NORMALIZED_OFFSET_SLOPE],
config[CONF_TEMPERATURE_COMPENSATION][CONF_TIME_CONSTANT], cfg[CONF_TIME_CONSTANT],
) )
) )

View File

@@ -11,7 +11,7 @@ pyserial==3.5
platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==5.0.2 esptool==5.0.2
click==8.1.7 click==8.1.7
esphome-dashboard==20250814.0 esphome-dashboard==20250828.0
aioesphomeapi==39.0.0 aioesphomeapi==39.0.0
zeroconf==0.147.0 zeroconf==0.147.0
puremagic==1.30 puremagic==1.30

View File

@@ -1,6 +1,6 @@
pylint==3.3.8 pylint==3.3.8
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
ruff==0.12.10 # also change in .pre-commit-config.yaml when updating ruff==0.12.11 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
pre-commit pre-commit

View File

@@ -684,7 +684,7 @@ lvgl:
width: 120 width: 120
range_from: -10 range_from: -10
range_to: 1000 range_to: 1000
step: 5.0 selected_digit: 2
rollover: false rollover: false
digits: 6 digits: 6
decimal_places: 2 decimal_places: 2

View File

@@ -0,0 +1 @@
*.ttf -text

View File

@@ -50,6 +50,14 @@ mapping:
red: red_id red: red_id
blue: blue_id blue: blue_id
green: green_id green: green_id
- id: string_map_2
from: string
to: string
entries:
one: "one"
two: "two"
three: "three"
seventy-seven: "seventy-seven"
color: color:
- id: red_id - id: red_id
@@ -65,7 +73,14 @@ color:
green: 0.0 green: 0.0
blue: 1.0 blue: 1.0
font:
- file: "$component_dir/helvetica.ttf"
id: font_id
size: 20
display: display:
lambda: |- lambda: |-
it.image(0, 0, id(weather_map)[0]); std::string value = id(int_map)[2];
it.image(0, 100, id(weather_map)[1]); it.print(0, 0, id(font_id), TextAlign::TOP_LEFT, value.c_str());
it.image(0, 0, id(weather_map)["clear-night"]);
it.image(0, 100, id(weather_map)["sunny"]);

Binary file not shown.

View File

@@ -4,14 +4,14 @@ spi:
mosi_pin: 17 mosi_pin: 17
miso_pin: 15 miso_pin: 15
packages:
map: !include common.yaml
display: display:
- platform: ili9xxx platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 12 cs_pin: 12
dc_pin: 13 dc_pin: 13
reset_pin: 21 reset_pin: 21
invert_colors: false invert_colors: false
packages:
map: !include common.yaml

View File

@@ -5,7 +5,7 @@ spi:
miso_pin: 5 miso_pin: 5
display: display:
- platform: ili9xxx platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 8 cs_pin: 8

View File

@@ -5,7 +5,7 @@ spi:
miso_pin: 5 miso_pin: 5
display: display:
- platform: ili9xxx platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 8 cs_pin: 8

View File

@@ -5,7 +5,7 @@ spi:
miso_pin: 15 miso_pin: 15
display: display:
- platform: ili9xxx platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 12 cs_pin: 12

View File

@@ -5,7 +5,7 @@ spi:
miso_pin: 12 miso_pin: 12
display: display:
- platform: ili9xxx platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 5 cs_pin: 5

View File

@@ -1,5 +1,5 @@
display: display:
- platform: sdl platform: sdl
id: sdl_display id: sdl_display
update_interval: 1s update_interval: 1s
auto_clear_enabled: false auto_clear_enabled: false

View File

@@ -5,7 +5,7 @@ spi:
miso_pin: 4 miso_pin: 4
display: display:
- platform: ili9xxx platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 20 cs_pin: 20