mirror of
https://github.com/esphome/esphome.git
synced 2026-01-28 07:22:11 -07:00
Compare commits
79 Commits
dashboard_
...
2025.11.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42811edeb4 | ||
|
|
8f20abebf6 | ||
|
|
7077488dc7 | ||
|
|
ef34239064 | ||
|
|
44148c0c6b | ||
|
|
1b53fcf634 | ||
|
|
b18e3d943a | ||
|
|
f0673f6304 | ||
|
|
320ba30d50 | ||
|
|
cfd88376b9 | ||
|
|
577a6b2941 | ||
|
|
de68b56c4a | ||
|
|
ccd23e692b | ||
|
|
1f5a44be3d | ||
|
|
1d1e47c757 | ||
| 3fbed1fa79 | |||
|
|
5c71520635 | ||
|
|
9d6c81ec23 | ||
|
|
73fa9230e6 | ||
|
|
48caff13c9 | ||
|
|
71bb94524e | ||
|
|
a3199792c6 | ||
|
|
50c1720c16 | ||
|
|
4115dd7222 | ||
|
|
d5e2543751 | ||
|
|
b4b34aee13 | ||
|
|
6645994700 | ||
|
|
ae140f52e3 | ||
|
|
46ae6d35a2 | ||
|
|
278f12fb99 | ||
|
|
acdcd56395 | ||
|
|
9289fc36f7 | ||
|
|
3775b54554 | ||
|
|
9186144dcd | ||
|
|
25bcd0ea25 | ||
|
|
50d08a2eba | ||
|
|
3a7a0c66ab | ||
|
|
83525b7a92 | ||
|
|
f31f023c89 | ||
|
|
f8efefffaa | ||
|
|
d698083ede | ||
|
|
11ba6440d7 | ||
|
|
89ee37a2d5 | ||
|
|
45b8c1e267 | ||
|
|
fbe091f167 | ||
|
|
625172e07d | ||
|
|
1e9c7d3c6d | ||
|
|
c2bc7b3cdc | ||
|
|
c75abfb894 | ||
|
|
1157b4aee8 | ||
|
|
71dc2d374d | ||
|
|
0a224f919b | ||
|
|
7ef4b4f3d9 | ||
|
|
13b875c763 | ||
|
|
dfd614c00c | ||
|
|
2681a14d05 | ||
|
|
f436f6ee2e | ||
|
|
f18bc62690 | ||
|
|
6db73df649 | ||
|
|
93215f1737 | ||
|
|
70aa94b8a4 | ||
|
|
e8998a79c7 | ||
|
|
3b25fdbc5f | ||
|
|
6c8577678c | ||
|
|
70366d2124 | ||
|
|
a38c4e0c6e | ||
|
|
6c6b03bda0 | ||
|
|
9e02e31917 | ||
|
|
3fd58f1a91 | ||
|
|
9151489481 | ||
|
|
f19296ac7f | ||
|
|
36868ee7b1 | ||
|
|
d559f9f52e | ||
|
|
6440b5fbf5 | ||
|
|
97c4914573 | ||
|
|
7ce94c27fe | ||
|
|
eb54c0026d | ||
|
|
fe00e209ff | ||
|
|
aed80732f9 |
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@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
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@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
53
.github/workflows/release.yml
vendored
53
.github/workflows/release.yml
vendored
@@ -219,10 +219,19 @@ jobs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: home-assistant-addon
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
let description = "ESPHome";
|
||||
if (context.eventName == "release") {
|
||||
@@ -245,10 +254,19 @@ jobs:
|
||||
needs: [init]
|
||||
environment: ${{ needs.init.outputs.deploy_env }}
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: esphome-schema
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
@@ -259,3 +277,34 @@ jobs:
|
||||
version: "${{ needs.init.outputs.tag }}",
|
||||
}
|
||||
})
|
||||
|
||||
version-notifier:
|
||||
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
owner: esphome
|
||||
repositories: version-notifier
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "version-notifier",
|
||||
workflow_id: "notify.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ needs.init.outputs.tag }}",
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.5
|
||||
rev: v0.14.4
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.12.0-dev
|
||||
PROJECT_NUMBER = 2025.11.4
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -741,13 +741,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
|
||||
|
||||
|
||||
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
# Set memory analysis options in config
|
||||
if args.analyze_memory:
|
||||
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
|
||||
|
||||
if args.memory_report:
|
||||
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
|
||||
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
@@ -1209,17 +1202,6 @@ def parse_args(argv):
|
||||
help="Only generate source code, do not compile.",
|
||||
action="store_true",
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--analyze-memory",
|
||||
help="Analyze and display memory usage by component after compilation.",
|
||||
action="store_true",
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--memory-report",
|
||||
help="Save memory analysis report to a file (supports .json or .txt).",
|
||||
type=str,
|
||||
metavar="FILE",
|
||||
)
|
||||
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload",
|
||||
@@ -1337,7 +1319,7 @@ def parse_args(argv):
|
||||
"clean-all", help="Clean all build and platform files."
|
||||
)
|
||||
parser_clean_all.add_argument(
|
||||
"configuration", help="Your YAML configuration directory.", nargs="*"
|
||||
"configuration", help="Your YAML file or configuration directory.", nargs="*"
|
||||
)
|
||||
|
||||
parser_dashboard = subparsers.add_parser(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""CLI interface for memory analysis with report generation."""
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import sys
|
||||
|
||||
from . import (
|
||||
@@ -298,28 +297,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Export analysis results as JSON."""
|
||||
data = {
|
||||
"components": {
|
||||
name: {
|
||||
"text": mem.text_size,
|
||||
"rodata": mem.rodata_size,
|
||||
"data": mem.data_size,
|
||||
"bss": mem.bss_size,
|
||||
"flash_total": mem.flash_total,
|
||||
"ram_total": mem.ram_total,
|
||||
"symbol_count": mem.symbol_count,
|
||||
}
|
||||
for name, mem in self.components.items()
|
||||
},
|
||||
"totals": {
|
||||
"flash": sum(c.flash_total for c in self.components.values()),
|
||||
"ram": sum(c.ram_total for c in self.components.values()),
|
||||
},
|
||||
}
|
||||
return json.dumps(data, indent=2)
|
||||
|
||||
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
|
||||
"""Dump uncategorized symbols for analysis."""
|
||||
# Sort by size descending
|
||||
|
||||
@@ -25,7 +25,8 @@ void ADE7953::setup() {
|
||||
this->ade_write_8(PGA_V_8, pga_v_);
|
||||
this->ade_write_8(PGA_IA_8, pga_ia_);
|
||||
this->ade_write_8(PGA_IB_8, pga_ib_);
|
||||
this->ade_write_32(AVGAIN_32, vgain_);
|
||||
this->ade_write_32(AVGAIN_32, avgain_);
|
||||
this->ade_write_32(BVGAIN_32, bvgain_);
|
||||
this->ade_write_32(AIGAIN_32, aigain_);
|
||||
this->ade_write_32(BIGAIN_32, bigain_);
|
||||
this->ade_write_32(AWGAIN_32, awgain_);
|
||||
@@ -34,7 +35,8 @@ void ADE7953::setup() {
|
||||
this->ade_read_8(PGA_V_8, &pga_v_);
|
||||
this->ade_read_8(PGA_IA_8, &pga_ia_);
|
||||
this->ade_read_8(PGA_IB_8, &pga_ib_);
|
||||
this->ade_read_32(AVGAIN_32, &vgain_);
|
||||
this->ade_read_32(AVGAIN_32, &avgain_);
|
||||
this->ade_read_32(BVGAIN_32, &bvgain_);
|
||||
this->ade_read_32(AIGAIN_32, &aigain_);
|
||||
this->ade_read_32(BIGAIN_32, &bigain_);
|
||||
this->ade_read_32(AWGAIN_32, &awgain_);
|
||||
@@ -63,13 +65,14 @@ void ADE7953::dump_config() {
|
||||
" PGA_V_8: 0x%X\n"
|
||||
" PGA_IA_8: 0x%X\n"
|
||||
" PGA_IB_8: 0x%X\n"
|
||||
" VGAIN_32: 0x%08jX\n"
|
||||
" AVGAIN_32: 0x%08jX\n"
|
||||
" BVGAIN_32: 0x%08jX\n"
|
||||
" AIGAIN_32: 0x%08jX\n"
|
||||
" BIGAIN_32: 0x%08jX\n"
|
||||
" AWGAIN_32: 0x%08jX\n"
|
||||
" BWGAIN_32: 0x%08jX",
|
||||
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) vgain_, (uintmax_t) aigain_,
|
||||
(uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
|
||||
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) avgain_, (uintmax_t) bvgain_,
|
||||
(uintmax_t) aigain_, (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
|
||||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, val, factor) \
|
||||
|
||||
@@ -46,7 +46,12 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
||||
void set_pga_ib(uint8_t pga_ib) { pga_ib_ = pga_ib; }
|
||||
|
||||
// Set input gains
|
||||
void set_vgain(uint32_t vgain) { vgain_ = vgain; }
|
||||
void set_vgain(uint32_t vgain) {
|
||||
// Datasheet says: "to avoid discrepancies in other registers,
|
||||
// if AVGAIN is set then BVGAIN should be set to the same value."
|
||||
avgain_ = vgain;
|
||||
bvgain_ = vgain;
|
||||
}
|
||||
void set_aigain(uint32_t aigain) { aigain_ = aigain; }
|
||||
void set_bigain(uint32_t bigain) { bigain_ = bigain; }
|
||||
void set_awgain(uint32_t awgain) { awgain_ = awgain; }
|
||||
@@ -100,7 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
|
||||
uint8_t pga_v_;
|
||||
uint8_t pga_ia_;
|
||||
uint8_t pga_ib_;
|
||||
uint32_t vgain_;
|
||||
uint32_t avgain_;
|
||||
uint32_t bvgain_;
|
||||
uint32_t aigain_;
|
||||
uint32_t bigain_;
|
||||
uint32_t awgain_;
|
||||
|
||||
@@ -12,10 +12,11 @@ void AnalogThresholdBinarySensor::setup() {
|
||||
// TRUE state is defined to be when sensor is >= threshold
|
||||
// so when undefined sensor value initialize to FALSE
|
||||
if (std::isnan(sensor_value)) {
|
||||
this->raw_state_ = false;
|
||||
this->publish_initial_state(false);
|
||||
} else {
|
||||
this->publish_initial_state(sensor_value >=
|
||||
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
|
||||
this->raw_state_ = sensor_value >= (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f;
|
||||
this->publish_initial_state(this->raw_state_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +26,10 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
|
||||
this->sensor_->add_on_state_callback([this](float sensor_value) {
|
||||
// if there is an invalid sensor reading, ignore the change and keep the current state
|
||||
if (!std::isnan(sensor_value)) {
|
||||
this->publish_state(sensor_value >=
|
||||
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
|
||||
// Use raw_state_ for hysteresis logic, not this->state which is post-filter
|
||||
this->raw_state_ =
|
||||
sensor_value >= (this->raw_state_ ? this->lower_threshold_.value() : this->upper_threshold_.value());
|
||||
this->publish_state(this->raw_state_);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
TemplatableValue<float> upper_threshold_{};
|
||||
TemplatableValue<float> lower_threshold_{};
|
||||
bool raw_state_{false}; // Pre-filter state for hysteresis logic
|
||||
};
|
||||
|
||||
} // namespace analog_threshold
|
||||
|
||||
@@ -51,14 +51,13 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
return false;
|
||||
if (req.args.size() != sizeof...(Ts))
|
||||
return false;
|
||||
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<typename ArgsContainer, size_t... S>
|
||||
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
|
||||
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
@@ -96,14 +95,13 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
||||
return false;
|
||||
if (req.args.size() != sizeof...(Ts))
|
||||
return false;
|
||||
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<typename ArgsContainer, size_t... S>
|
||||
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
|
||||
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "bh1750.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome::bh1750 {
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
|
||||
static const char *const TAG = "bh1750.sensor";
|
||||
|
||||
@@ -13,31 +13,6 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
|
||||
|
||||
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
|
||||
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
|
||||
|
||||
// Measurement time constants (datasheet values)
|
||||
static constexpr uint16_t MTREG_DEFAULT = 69;
|
||||
static constexpr uint16_t MTREG_MIN = 31;
|
||||
static constexpr uint16_t MTREG_MAX = 254;
|
||||
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
|
||||
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
|
||||
|
||||
// Conversion constants (datasheet formulas)
|
||||
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
|
||||
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
|
||||
|
||||
// MTreg calculation constants
|
||||
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
|
||||
static constexpr int COUNTS_NUMERATOR = 10;
|
||||
static constexpr int COUNTS_DENOMINATOR = 12;
|
||||
|
||||
// MTreg register bit manipulation constants
|
||||
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
|
||||
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
|
||||
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
|
||||
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
|
||||
|
||||
/*
|
||||
bh1750 properties:
|
||||
|
||||
@@ -68,7 +43,74 @@ void BH1750Sensor::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
|
||||
// turn on (after one-shot sensor automatically powers down)
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_mtreg_ != mtreg) {
|
||||
// set mtreg
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
active_mtreg_ = 0;
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = 24 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
default:
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
// probably not needed, but adjust for rounding
|
||||
meas_time++;
|
||||
|
||||
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / mtreg;
|
||||
if (mode == BH1750_MODE_H2)
|
||||
lx /= 2.0f;
|
||||
|
||||
f(lx);
|
||||
});
|
||||
}
|
||||
|
||||
void BH1750Sensor::dump_config() {
|
||||
@@ -82,188 +124,45 @@ void BH1750Sensor::dump_config() {
|
||||
}
|
||||
|
||||
void BH1750Sensor::update() {
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Start coarse measurement to determine optimal mode/mtreg
|
||||
if (this->state_ != IDLE) {
|
||||
// Safety timeout: reset if stuck
|
||||
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
|
||||
ESP_LOGW(TAG, "Measurement timeout, resetting state");
|
||||
this->state_ = IDLE;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
|
||||
// first do a quick measurement in L-mode with full range
|
||||
// to find right range
|
||||
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
this->state_ = WAITING_COARSE_MEASUREMENT;
|
||||
this->enable_loop(); // Enable loop while measurement in progress
|
||||
}
|
||||
|
||||
void BH1750Sensor::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
switch (this->state_) {
|
||||
case IDLE:
|
||||
// Disable loop when idle to save cycles
|
||||
this->disable_loop();
|
||||
break;
|
||||
|
||||
case WAITING_COARSE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_COARSE_RESULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case READING_COARSE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->process_coarse_result_(lx);
|
||||
|
||||
// Start fine measurement with optimal settings
|
||||
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, now)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->state_ = WAITING_FINE_MEASUREMENT;
|
||||
break;
|
||||
BH1750Mode use_mode;
|
||||
uint8_t use_mtreg;
|
||||
if (val <= 7000) {
|
||||
use_mode = BH1750_MODE_H2;
|
||||
use_mtreg = 254;
|
||||
} else {
|
||||
use_mode = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
|
||||
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
|
||||
}
|
||||
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
|
||||
|
||||
case WAITING_FINE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_FINE_RESULT;
|
||||
this->read_lx_(use_mode, use_mtreg, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case READING_FINE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(lx);
|
||||
this->state_ = IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
|
||||
// Power on
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set MTreg if changed
|
||||
if (this->active_mtreg_ != mtreg) {
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
this->active_mtreg_ = 0;
|
||||
return false;
|
||||
}
|
||||
this->active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
// Start measurement
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store current measurement parameters
|
||||
this->current_mode_ = mode;
|
||||
this->current_mtreg_ = mtreg;
|
||||
this->measurement_start_time_ = now;
|
||||
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BH1750Sensor::read_measurement_(float &lx_out) {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
return false;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / RESOLUTION_DIVISOR;
|
||||
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
|
||||
if (this->current_mode_ == BH1750_MODE_H2) {
|
||||
lx /= MODE_H2_DIVISOR;
|
||||
}
|
||||
|
||||
lx_out = lx;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BH1750Sensor::process_coarse_result_(float lx) {
|
||||
if (std::isnan(lx)) {
|
||||
// Use defaults if coarse measurement failed
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
} else {
|
||||
this->fine_mode_ = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
|
||||
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
|
||||
}
|
||||
|
||||
void BH1750Sensor::fail_and_reset_() {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
this->state_ = IDLE;
|
||||
this->publish_state(val);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace esphome::bh1750
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::bh1750 {
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
|
||||
enum BH1750Mode : uint8_t {
|
||||
enum BH1750Mode {
|
||||
BH1750_MODE_L,
|
||||
BH1750_MODE_H,
|
||||
BH1750_MODE_H2,
|
||||
@@ -20,36 +21,13 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
// State machine states
|
||||
enum State : uint8_t {
|
||||
IDLE,
|
||||
WAITING_COARSE_MEASUREMENT,
|
||||
READING_COARSE_RESULT,
|
||||
WAITING_FINE_MEASUREMENT,
|
||||
READING_FINE_RESULT,
|
||||
};
|
||||
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
|
||||
|
||||
// 4-byte aligned members
|
||||
uint32_t measurement_start_time_{0};
|
||||
uint32_t measurement_duration_{0};
|
||||
|
||||
// 1-byte members grouped together to minimize padding
|
||||
State state_{IDLE};
|
||||
BH1750Mode current_mode_{BH1750_MODE_L};
|
||||
uint8_t current_mtreg_{31};
|
||||
BH1750Mode fine_mode_{BH1750_MODE_H2};
|
||||
uint8_t fine_mtreg_{254};
|
||||
uint8_t active_mtreg_{0};
|
||||
|
||||
// Helper methods
|
||||
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
|
||||
bool read_measurement_(float &lx_out);
|
||||
void process_coarse_result_(float lx);
|
||||
void fail_and_reset_();
|
||||
};
|
||||
|
||||
} // namespace esphome::bh1750
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::binary_sensor {
|
||||
namespace esphome {
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor.automation";
|
||||
|
||||
void MultiClickTrigger::on_state_(bool state) {
|
||||
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
|
||||
// Handle duplicate events
|
||||
if (state == this->last_state_) {
|
||||
return;
|
||||
@@ -66,7 +67,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
}
|
||||
void MultiClickTrigger::schedule_cooldown_() {
|
||||
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||
this->is_in_cooldown_ = true;
|
||||
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
|
||||
@@ -78,7 +79,7 @@ void MultiClickTrigger::schedule_cooldown_() {
|
||||
this->cancel_timeout("is_valid");
|
||||
this->cancel_timeout("is_not_valid");
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
if (min_length == 0) {
|
||||
this->is_valid_ = true;
|
||||
return;
|
||||
@@ -89,19 +90,19 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
this->is_valid_ = true;
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
this->set_timeout("is_not_valid", max_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::cancel() {
|
||||
void binary_sensor::MultiClickTrigger::cancel() {
|
||||
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
}
|
||||
void MultiClickTrigger::trigger_() {
|
||||
void binary_sensor::MultiClickTrigger::trigger_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout("trigger");
|
||||
@@ -117,4 +118,5 @@ bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
|
||||
return length >= min_length && length <= max_length;
|
||||
}
|
||||
}
|
||||
} // namespace esphome::binary_sensor
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome::binary_sensor {
|
||||
namespace esphome {
|
||||
namespace binary_sensor {
|
||||
|
||||
struct MultiClickTriggerEvent {
|
||||
bool state;
|
||||
@@ -171,4 +172,5 @@ template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts..
|
||||
BinarySensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::binary_sensor {
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor";
|
||||
|
||||
@@ -61,4 +63,6 @@ void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
|
||||
}
|
||||
bool BinarySensor::is_status_binary_sensor() const { return false; }
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace esphome::binary_sensor {
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
class BinarySensor;
|
||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
||||
@@ -68,4 +70,5 @@ class BinarySensorInitiallyOff : public BinarySensor {
|
||||
bool has_state() const override { return true; }
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include "binary_sensor.h"
|
||||
|
||||
namespace esphome::binary_sensor {
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
@@ -130,4 +132,6 @@ optional<bool> SettleFilter::new_value(bool value) {
|
||||
|
||||
float SettleFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::binary_sensor {
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
class BinarySensor;
|
||||
|
||||
@@ -137,4 +139,6 @@ class SettleFilter : public Filter, public Component {
|
||||
bool steady_{true};
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -122,19 +122,16 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
void play_complex(const Ts &...x) override {
|
||||
this->num_running_++;
|
||||
this->var_ = std::make_tuple(x...);
|
||||
|
||||
bool result;
|
||||
std::vector<uint8_t> value;
|
||||
if (this->len_ >= 0) {
|
||||
// Static mode: write directly from flash pointer
|
||||
result = this->write(this->value_.data, this->len_);
|
||||
// Static mode: copy from flash to vector
|
||||
value.assign(this->value_.data, this->value_.data + this->len_);
|
||||
} else {
|
||||
// Template mode: call function and write the vector
|
||||
std::vector<uint8_t> value = this->value_.func(x...);
|
||||
result = this->write(value);
|
||||
// Template mode: call function
|
||||
value = this->value_.func(x...);
|
||||
}
|
||||
|
||||
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
||||
if (!result)
|
||||
if (!write(value))
|
||||
this->play_next_(x...);
|
||||
}
|
||||
|
||||
@@ -147,15 +144,15 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
* errors.
|
||||
*/
|
||||
// initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
|
||||
bool write(const uint8_t *data, size_t len) {
|
||||
bool write(const std::vector<uint8_t> &value) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
|
||||
return false;
|
||||
}
|
||||
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
|
||||
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
|
||||
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
|
||||
esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
|
||||
this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
|
||||
this->write_type_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ESP_OK) {
|
||||
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
|
||||
return false;
|
||||
@@ -163,8 +160,6 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
switch (event) {
|
||||
|
||||
@@ -72,6 +72,16 @@ def _final_validate(config: ConfigType) -> ConfigType:
|
||||
"Add 'ap:' to your WiFi configuration to enable the captive portal."
|
||||
)
|
||||
|
||||
# Register socket needs for DNS server and additional HTTP connections
|
||||
# - 1 UDP socket for DNS server
|
||||
# - 3 additional TCP sockets for captive portal detection probes + configuration requests
|
||||
# OS captive portal detection makes multiple probe requests that stay in TIME_WAIT.
|
||||
# Need headroom for actual user configuration requests.
|
||||
# LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts.
|
||||
from esphome.components import socket
|
||||
|
||||
socket.consume_sockets(4, "captive_portal")(config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||
ESP_LOGI(TAG, "Requested WiFi Settings Change:");
|
||||
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
|
||||
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
// Defer save to main loop thread to avoid NVS operations from HTTP thread
|
||||
this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
|
||||
request->redirect(ESPHOME_F("/?save"));
|
||||
}
|
||||
|
||||
@@ -63,6 +63,12 @@ void CaptivePortal::start() {
|
||||
this->base_->init();
|
||||
if (!this->initialized_) {
|
||||
this->base_->add_handler(this);
|
||||
#ifdef USE_ESP32
|
||||
// Enable LRU socket purging to handle captive portal detection probe bursts
|
||||
// OS captive portal detection makes many simultaneous HTTP requests which can
|
||||
// exhaust sockets. LRU purging automatically closes oldest idle connections.
|
||||
this->base_->get_server()->set_lru_purge_enable(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
|
||||
@@ -40,6 +40,10 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
void end() {
|
||||
this->active_ = false;
|
||||
this->disable_loop(); // Stop processing DNS requests
|
||||
#ifdef USE_ESP32
|
||||
// Disable LRU socket purging now that captive portal is done
|
||||
this->base_->get_server()->set_lru_purge_enable(false);
|
||||
#endif
|
||||
this->base_->deinit();
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->stop();
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "cover.h"
|
||||
|
||||
namespace esphome::cover {
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
@@ -130,4 +131,5 @@ class CoverClosedTrigger : public Trigger<> {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::cover
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::cover {
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
static const char *const TAG = "cover";
|
||||
|
||||
@@ -211,4 +212,5 @@ void CoverRestoreState::apply(Cover *cover) {
|
||||
cover->publish_state();
|
||||
}
|
||||
|
||||
} // namespace esphome::cover
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
#include "cover_traits.h"
|
||||
|
||||
namespace esphome::cover {
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
const extern float COVER_OPEN;
|
||||
const extern float COVER_CLOSED;
|
||||
@@ -156,4 +157,5 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace esphome::cover
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome::cover {
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
class CoverTraits {
|
||||
public:
|
||||
@@ -25,4 +26,5 @@ class CoverTraits {
|
||||
bool supports_stop_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::cover
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
|
||||
@@ -19,8 +19,9 @@ void CST816Touchscreen::continue_setup_() {
|
||||
case CST816T_CHIP_ID:
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
|
||||
this->status_set_error("Unknown chip ID");
|
||||
this->mark_failed();
|
||||
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
|
||||
return;
|
||||
}
|
||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
static const char *g_package_import_url = ""; // NOLINT
|
||||
static std::string g_package_import_url; // NOLINT
|
||||
|
||||
const char *get_package_import_url() { return g_package_import_url; }
|
||||
void set_package_import_url(const char *url) { g_package_import_url = url; }
|
||||
const std::string &get_package_import_url() { return g_package_import_url; }
|
||||
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
const char *get_package_import_url();
|
||||
void set_package_import_url(const char *url);
|
||||
const std::string &get_package_import_url();
|
||||
void set_package_import_url(std::string url);
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
||||
|
||||
@@ -102,7 +102,7 @@ def customise_schema(config):
|
||||
"""
|
||||
config = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"),
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
|
||||
@@ -32,11 +32,15 @@ class SpectraE6(EpaperModel):
|
||||
|
||||
spectra_e6 = SpectraE6("spectra-e6")
|
||||
|
||||
spectra_e6.extend(
|
||||
"Seeed-reTerminal-E1002",
|
||||
spectra_e6_7p3 = spectra_e6.extend(
|
||||
"7.3in-Spectra-E6",
|
||||
width=800,
|
||||
height=480,
|
||||
data_rate="20MHz",
|
||||
)
|
||||
|
||||
spectra_e6_7p3.extend(
|
||||
"Seeed-reTerminal-E1002",
|
||||
cs_pin=10,
|
||||
dc_pin=11,
|
||||
reset_pin=12,
|
||||
|
||||
@@ -22,7 +22,6 @@ ES8311_BITS_PER_SAMPLE_ENUM = {
|
||||
|
||||
es8311_mic_gain = es8311_ns.enum("ES8311MicGain")
|
||||
ES8311_MIC_GAIN_ENUM = {
|
||||
"MIN": es8311_mic_gain.ES8311_MIC_GAIN_MIN,
|
||||
"0DB": es8311_mic_gain.ES8311_MIC_GAIN_0DB,
|
||||
"6DB": es8311_mic_gain.ES8311_MIC_GAIN_6DB,
|
||||
"12DB": es8311_mic_gain.ES8311_MIC_GAIN_12DB,
|
||||
@@ -31,7 +30,6 @@ ES8311_MIC_GAIN_ENUM = {
|
||||
"30DB": es8311_mic_gain.ES8311_MIC_GAIN_30DB,
|
||||
"36DB": es8311_mic_gain.ES8311_MIC_GAIN_36DB,
|
||||
"42DB": es8311_mic_gain.ES8311_MIC_GAIN_42DB,
|
||||
"MAX": es8311_mic_gain.ES8311_MIC_GAIN_MAX,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -854,8 +854,13 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
|
||||
async def to_code(config):
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
|
||||
cg.add_platformio_option(
|
||||
"board_upload.maximum_size",
|
||||
int(config[CONF_FLASH_SIZE].removesuffix("MB")) * 1024 * 1024,
|
||||
)
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
cg.add_build_flag("-Wl,-z,noexecstack")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
variant = config[CONF_VARIANT]
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
|
||||
@@ -883,6 +888,12 @@ async def to_code(config):
|
||||
CORE.relative_internal_path(".espressif")
|
||||
)
|
||||
|
||||
add_extra_script(
|
||||
"pre",
|
||||
"pre_build.py",
|
||||
Path(__file__).parent / "pre_build.py.script",
|
||||
)
|
||||
|
||||
add_extra_script(
|
||||
"post",
|
||||
"post_build.py",
|
||||
@@ -931,6 +942,12 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
|
||||
|
||||
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
|
||||
if get_esp32_variant() == VARIANT_ESP32S2:
|
||||
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
|
||||
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0")
|
||||
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0")
|
||||
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
|
||||
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
||||
|
||||
9
esphome/components/esp32/pre_build.py.script
Normal file
9
esphome/components/esp32/pre_build.py.script
Normal file
@@ -0,0 +1,9 @@
|
||||
Import("env") # noqa: F821
|
||||
|
||||
# Remove custom_sdkconfig from the board config as it causes
|
||||
# pioarduino to enable some strange hybrid build mode that breaks IDF
|
||||
board = env.BoardConfig()
|
||||
if "espidf.custom_sdkconfig" in board:
|
||||
del board._manifest["espidf"]["custom_sdkconfig"]
|
||||
if not board._manifest["espidf"]:
|
||||
del board._manifest["espidf"]
|
||||
@@ -96,10 +96,6 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
|
||||
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
|
||||
}
|
||||
|
||||
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
|
||||
this->advertising_init_();
|
||||
this->advertising_->set_manufacturer_data(data);
|
||||
this->advertising_start();
|
||||
@@ -638,13 +634,11 @@ void ESP32BLE::dump_config() {
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
char mac_s[18];
|
||||
format_mac_addr_upper(mac_address, mac_s);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BLE:\n"
|
||||
" MAC address: %s\n"
|
||||
" IO Capability: %s",
|
||||
mac_s, io_capability_s);
|
||||
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
|
||||
}
|
||||
|
||||
@@ -118,7 +118,6 @@ class ESP32BLE : public Component {
|
||||
void advertising_start();
|
||||
void advertising_set_service_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
|
||||
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
|
||||
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
|
||||
void advertising_add_service_uuid(ESPBTUUID uuid);
|
||||
|
||||
@@ -59,10 +59,6 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
|
||||
this->set_manufacturer_data(std::span<const uint8_t>(data));
|
||||
}
|
||||
|
||||
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
|
||||
delete[] this->advertising_data_.p_manufacturer_data;
|
||||
this->advertising_data_.p_manufacturer_data = nullptr;
|
||||
this->advertising_data_.manufacturer_len = data.size();
|
||||
|
||||
@@ -37,7 +37,6 @@ class BLEAdvertising {
|
||||
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
|
||||
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
|
||||
void set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
void set_manufacturer_data(std::span<const uint8_t> data);
|
||||
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
|
||||
void set_service_data(const std::vector<uint8_t> &data);
|
||||
void set_service_data(std::span<const uint8_t> data);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "esp32_ble_beacon.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
|
||||
@@ -15,10 +15,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
|
||||
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
new Trigger<std::vector<uint8_t>, uint16_t>();
|
||||
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
|
||||
// Convert span to vector for trigger - copy is necessary because:
|
||||
// 1. Trigger stores the data for use in automation actions that execute later
|
||||
// 2. The span is only valid during this callback (points to temporary BLE stack data)
|
||||
// 3. User lambdas in automations need persistent data they can access asynchronously
|
||||
// Convert span to vector for trigger
|
||||
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
|
||||
});
|
||||
return on_write_trigger;
|
||||
@@ -30,10 +27,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
|
||||
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
new Trigger<std::vector<uint8_t>, uint16_t>();
|
||||
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
|
||||
// Convert span to vector for trigger - copy is necessary because:
|
||||
// 1. Trigger stores the data for use in automation actions that execute later
|
||||
// 2. The span is only valid during this callback (points to temporary BLE stack data)
|
||||
// 3. User lambdas in automations need persistent data they can access asynchronously
|
||||
// Convert span to vector for trigger
|
||||
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
|
||||
});
|
||||
return on_write_trigger;
|
||||
|
||||
@@ -93,9 +93,9 @@ async def to_code(config):
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}"
|
||||
if framework_ver >= cv.Version(5, 5, 0):
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.1.5")
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.6.1")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0")
|
||||
else:
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0")
|
||||
|
||||
@@ -22,6 +22,11 @@ constexpr size_t CHUNK_SIZE = 1500;
|
||||
void Esp32HostedUpdate::setup() {
|
||||
this->update_info_.title = "ESP32 Hosted Coprocessor";
|
||||
|
||||
// if wifi is not present, connect to the coprocessor
|
||||
#ifndef USE_WIFI
|
||||
esp_hosted_connect_to_slave(); // NOLINT
|
||||
#endif
|
||||
|
||||
// get coprocessor version
|
||||
esp_hosted_coprocessor_fwver_t ver_info;
|
||||
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||
|
||||
@@ -20,6 +20,10 @@ CONF_ON_STOP = "on_stop"
|
||||
CONF_STATUS_INDICATOR = "status_indicator"
|
||||
CONF_WIFI_TIMEOUT = "wifi_timeout"
|
||||
|
||||
# Default WiFi timeout - aligned with WiFi component ap_timeout
|
||||
# Allows sufficient time to try all BSSIDs before starting provisioning mode
|
||||
DEFAULT_WIFI_TIMEOUT = "90s"
|
||||
|
||||
|
||||
improv_ns = cg.esphome_ns.namespace("improv")
|
||||
Error = improv_ns.enum("Error")
|
||||
@@ -59,7 +63,7 @@ CONFIG_SCHEMA = (
|
||||
CONF_AUTHORIZED_DURATION, default="1min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_WIFI_TIMEOUT, default="1min"
|
||||
CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
|
||||
{
|
||||
|
||||
@@ -127,6 +127,7 @@ void ESP32ImprovComponent::loop() {
|
||||
// Set initial state based on whether we have an authorizer
|
||||
this->set_state_(this->get_initial_state_(), false);
|
||||
this->set_error_(improv::ERROR_NONE);
|
||||
this->should_start_ = false; // Clear flag after starting
|
||||
ESP_LOGD(TAG, "Service started!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
|
||||
void start();
|
||||
void stop();
|
||||
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
|
||||
bool should_start() const { return this->should_start_; }
|
||||
|
||||
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||
void add_on_state_callback(std::function<void(improv::State, improv::Error)> &&callback) {
|
||||
|
||||
@@ -14,8 +14,8 @@ void EspLdo::setup() {
|
||||
config.flags.adjustable = this->adjustable_;
|
||||
auto err = esp_ldo_acquire_channel(&config, &this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
auto msg = str_sprintf("Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
|
||||
this->mark_failed(msg.c_str());
|
||||
ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
|
||||
this->mark_failed("Failed to acquire LDO channel");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_now.h>
|
||||
#include <esp_random.h>
|
||||
#include <esp_wifi.h>
|
||||
@@ -157,6 +158,12 @@ bool ESPNowComponent::is_wifi_enabled() {
|
||||
}
|
||||
|
||||
void ESPNowComponent::setup() {
|
||||
#ifndef USE_WIFI
|
||||
// Initialize LwIP stack for wake_loop_threadsafe() socket support
|
||||
// When WiFi component is present, it handles esp_netif_init()
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
#endif
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
this->enable_();
|
||||
} else {
|
||||
|
||||
@@ -383,7 +383,6 @@ async def to_code(config):
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add_define("USE_ETHERNET_MANUAL_IP")
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
# Add compile-time define for PHY types with specific code
|
||||
|
||||
@@ -553,14 +553,11 @@ void EthernetComponent::start_connect_() {
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
info.ip = this->manual_ip_->static_ip;
|
||||
info.gw = this->manual_ip_->gateway;
|
||||
info.netmask = this->manual_ip_->subnet;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
info.ip.addr = 0;
|
||||
info.gw.addr = 0;
|
||||
info.netmask.addr = 0;
|
||||
@@ -581,7 +578,6 @@ void EthernetComponent::start_connect_() {
|
||||
err = esp_netif_set_ip_info(this->eth_netif_, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
@@ -594,9 +590,7 @@ void EthernetComponent::start_connect_() {
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
err = esp_netif_dhcpc_start(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
@@ -694,9 +688,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
|
||||
@@ -82,9 +82,7 @@ class EthernetComponent : public Component {
|
||||
void add_phy_register(PHYRegister register_value);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
#endif
|
||||
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
@@ -139,9 +137,7 @@ class EthernetComponent : public Component {
|
||||
uint8_t mdc_pin_{23};
|
||||
uint8_t mdio_pin_{18};
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_{};
|
||||
#endif
|
||||
uint32_t connect_begin_;
|
||||
|
||||
// Group all uint8_t types together (enums and bools)
|
||||
|
||||
@@ -36,6 +36,7 @@ from esphome.const import (
|
||||
CONF_WEIGHT,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.helpers import cpp_string_escape
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -49,6 +50,7 @@ font_ns = cg.esphome_ns.namespace("font")
|
||||
|
||||
Font = font_ns.class_("Font")
|
||||
Glyph = font_ns.class_("Glyph")
|
||||
GlyphData = font_ns.struct("GlyphData")
|
||||
|
||||
CONF_BPP = "bpp"
|
||||
CONF_EXTRAS = "extras"
|
||||
@@ -461,7 +463,7 @@ FONT_SCHEMA = cv.Schema(
|
||||
)
|
||||
),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(Glyph),
|
||||
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -486,8 +488,6 @@ class GlyphInfo:
|
||||
|
||||
|
||||
def glyph_to_glyphinfo(glyph, font, size, bpp):
|
||||
# Convert to 32 bit unicode codepoint
|
||||
glyph = ord(glyph)
|
||||
scale = 256 // (1 << bpp)
|
||||
if not font.is_scalable:
|
||||
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||
@@ -583,15 +583,22 @@ async def to_code(config):
|
||||
|
||||
# Create the glyph table that points to data in the above array.
|
||||
glyph_initializer = [
|
||||
[
|
||||
x.glyph,
|
||||
prog_arr + (y - len(x.bitmap_data)),
|
||||
x.advance,
|
||||
x.offset_x,
|
||||
x.offset_y,
|
||||
x.width,
|
||||
x.height,
|
||||
]
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
|
||||
),
|
||||
("advance", x.advance),
|
||||
("offset_x", x.offset_x),
|
||||
("offset_y", x.offset_y),
|
||||
("width", x.width),
|
||||
("height", x.height),
|
||||
)
|
||||
for (x, y) in zip(
|
||||
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
|
||||
)
|
||||
|
||||
@@ -6,245 +6,133 @@
|
||||
|
||||
namespace esphome {
|
||||
namespace font {
|
||||
|
||||
static const char *const TAG = "font";
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
|
||||
auto *fe = (Font *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data_(unicode_letter);
|
||||
if (gd == nullptr) {
|
||||
return nullptr;
|
||||
const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
||||
// Compare the char at the string position with this char.
|
||||
// Return true if this char is less than or equal the other.
|
||||
bool Glyph::compare_to(const uint8_t *str) const {
|
||||
// 1 -> this->char_
|
||||
// 2 -> str
|
||||
for (uint32_t i = 0;; i++) {
|
||||
if (this->glyph_data_->a_char[i] == '\0')
|
||||
return true;
|
||||
if (str[i] == '\0')
|
||||
return false;
|
||||
if (this->glyph_data_->a_char[i] > str[i])
|
||||
return false;
|
||||
if (this->glyph_data_->a_char[i] < str[i])
|
||||
return true;
|
||||
}
|
||||
return gd->data;
|
||||
// this should not happen
|
||||
return false;
|
||||
}
|
||||
int Glyph::match_length(const uint8_t *str) const {
|
||||
for (uint32_t i = 0;; i++) {
|
||||
if (this->glyph_data_->a_char[i] == '\0')
|
||||
return i;
|
||||
if (str[i] != this->glyph_data_->a_char[i])
|
||||
return 0;
|
||||
}
|
||||
// this should not happen
|
||||
return 0;
|
||||
}
|
||||
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
||||
*x1 = this->glyph_data_->offset_x;
|
||||
*y1 = this->glyph_data_->offset_y;
|
||||
*width = this->glyph_data_->width;
|
||||
*height = this->glyph_data_->height;
|
||||
}
|
||||
|
||||
bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
|
||||
auto *fe = (Font *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data_(unicode_letter);
|
||||
if (gd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
dsc->adv_w = gd->advance;
|
||||
dsc->ofs_x = gd->offset_x;
|
||||
dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line;
|
||||
dsc->box_w = gd->width;
|
||||
dsc->box_h = gd->height;
|
||||
dsc->is_placeholder = 0;
|
||||
dsc->bpp = fe->get_bpp();
|
||||
return true;
|
||||
}
|
||||
|
||||
const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) {
|
||||
if (unicode_letter == this->last_letter_ && this->last_letter_ != 0)
|
||||
return this->last_data_;
|
||||
auto *glyph = this->find_glyph(unicode_letter);
|
||||
if (glyph == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
this->last_data_ = glyph;
|
||||
this->last_letter_ = unicode_letter;
|
||||
return glyph;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string.
|
||||
* If successful, return the codepoint and set the length to the number of bytes read.
|
||||
* If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to
|
||||
* 0.
|
||||
*
|
||||
* @param utf8_str The input string
|
||||
* @param length Pointer to length storage
|
||||
* @return The extracted code point
|
||||
*/
|
||||
static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) {
|
||||
// Safely cast to uint8_t* for correct bitwise operations on bytes
|
||||
const uint8_t *current = reinterpret_cast<const uint8_t *>(utf8_str);
|
||||
uint32_t code_point = 0;
|
||||
uint8_t c1 = *current++;
|
||||
|
||||
// check for end of string
|
||||
if (c1 == 0) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- 1-Byte Sequence: 0xxxxxxx (ASCII) ---
|
||||
if (c1 < 0x80) {
|
||||
// Valid ASCII byte.
|
||||
code_point = c1;
|
||||
// Optimization: No need to check for continuation bytes.
|
||||
}
|
||||
// --- 2-Byte Sequence: 110xxxxx 10xxxxxx ---
|
||||
else if ((c1 & 0xE0) == 0xC0) {
|
||||
uint8_t c2 = *current++;
|
||||
|
||||
// Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx)
|
||||
if ((c2 & 0xC0) != 0x80) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_point = (c1 & 0x1F) << 6;
|
||||
code_point |= (c2 & 0x3F);
|
||||
|
||||
// Error Check 2: Overlong check (2-byte must be > 0x7F)
|
||||
if (code_point <= 0x7F) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx ---
|
||||
else if ((c1 & 0xF0) == 0xE0) {
|
||||
uint8_t c2 = *current++;
|
||||
uint8_t c3 = *current++;
|
||||
|
||||
// Error Check 1: Check continuation bytes
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_point = (c1 & 0x0F) << 12;
|
||||
code_point |= (c2 & 0x3F) << 6;
|
||||
code_point |= (c3 & 0x3F);
|
||||
|
||||
// Error Check 2: Overlong check (3-byte must be > 0x7FF)
|
||||
// Also check for surrogates (0xD800-0xDFFF)
|
||||
if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ---
|
||||
else if ((c1 & 0xF8) == 0xF0) {
|
||||
uint8_t c2 = *current++;
|
||||
uint8_t c3 = *current++;
|
||||
uint8_t c4 = *current++;
|
||||
|
||||
// Error Check 1: Check continuation bytes
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_point = (c1 & 0x07) << 18;
|
||||
code_point |= (c2 & 0x3F) << 12;
|
||||
code_point |= (c3 & 0x3F) << 6;
|
||||
code_point |= (c4 & 0x3F);
|
||||
|
||||
// Error Check 2: Overlong check (4-byte must be > 0xFFFF)
|
||||
// Also check for valid Unicode range (must be <= 0x10FFFF)
|
||||
if (code_point <= 0xFFFF || code_point > 0x10FFFF) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) ---
|
||||
else {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
*length = current - reinterpret_cast<const uint8_t *>(utf8_str);
|
||||
return code_point;
|
||||
}
|
||||
|
||||
Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp)
|
||||
: glyphs_(ConstVector(data, data_nr)),
|
||||
baseline_(baseline),
|
||||
: baseline_(baseline),
|
||||
height_(height),
|
||||
descender_(descender),
|
||||
linegap_(height - baseline - descender),
|
||||
xheight_(xheight),
|
||||
capheight_(capheight),
|
||||
bpp_(bpp) {
|
||||
#ifdef USE_LVGL_FONT
|
||||
this->lv_font_.dsc = this;
|
||||
this->lv_font_.line_height = this->get_height();
|
||||
this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline();
|
||||
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
|
||||
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
|
||||
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
|
||||
this->lv_font_.underline_position = -1;
|
||||
this->lv_font_.underline_thickness = 1;
|
||||
#endif
|
||||
glyphs_.reserve(data_nr);
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(&data[i]);
|
||||
}
|
||||
|
||||
const Glyph *Font::find_glyph(uint32_t codepoint) const {
|
||||
int Font::match_next_glyph(const uint8_t *str, int *match_length) {
|
||||
int lo = 0;
|
||||
int hi = this->glyphs_.size() - 1;
|
||||
while (lo != hi) {
|
||||
int mid = (lo + hi + 1) / 2;
|
||||
if (this->glyphs_[mid].is_less_or_equal(codepoint)) {
|
||||
if (this->glyphs_[mid].compare_to(str)) {
|
||||
lo = mid;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
auto *result = &this->glyphs_[lo];
|
||||
if (result->code_point == codepoint)
|
||||
return result;
|
||||
return nullptr;
|
||||
*match_length = this->glyphs_[lo].match_length(str);
|
||||
if (*match_length <= 0)
|
||||
return -1;
|
||||
return lo;
|
||||
}
|
||||
|
||||
#ifdef USE_DISPLAY
|
||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
||||
*baseline = this->baseline_;
|
||||
*height = this->height_;
|
||||
int i = 0;
|
||||
int min_x = 0;
|
||||
bool has_char = false;
|
||||
int x = 0;
|
||||
for (;;) {
|
||||
size_t length;
|
||||
auto code_point = extract_unicode_codepoint(str, &length);
|
||||
if (length == 0)
|
||||
break;
|
||||
str += length;
|
||||
auto *glyph = this->find_glyph(code_point);
|
||||
if (glyph == nullptr) {
|
||||
while (str[i] != '\0') {
|
||||
int match_length;
|
||||
int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length);
|
||||
if (glyph_n < 0) {
|
||||
// Unknown char, skip
|
||||
if (!this->glyphs_.empty())
|
||||
x += this->glyphs_[0].advance;
|
||||
if (!this->get_glyphs().empty())
|
||||
x += this->get_glyphs()[0].glyph_data_->advance;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Glyph &glyph = this->glyphs_[glyph_n];
|
||||
if (!has_char) {
|
||||
min_x = glyph->offset_x;
|
||||
min_x = glyph.glyph_data_->offset_x;
|
||||
} else {
|
||||
min_x = std::min(min_x, x + glyph->offset_x);
|
||||
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
||||
}
|
||||
x += glyph->advance;
|
||||
x += glyph.glyph_data_->advance;
|
||||
|
||||
i += match_length;
|
||||
has_char = true;
|
||||
}
|
||||
*x_offset = min_x;
|
||||
*width = x - min_x;
|
||||
}
|
||||
|
||||
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
|
||||
int i = 0;
|
||||
int x_at = x_start;
|
||||
for (;;) {
|
||||
size_t length;
|
||||
auto code_point = extract_unicode_codepoint(text, &length);
|
||||
if (length == 0)
|
||||
break;
|
||||
text += length;
|
||||
auto *glyph = this->find_glyph(code_point);
|
||||
if (glyph == nullptr) {
|
||||
int scan_x1, scan_y1, scan_width, scan_height;
|
||||
while (text[i] != '\0') {
|
||||
int match_length;
|
||||
int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length);
|
||||
if (glyph_n < 0) {
|
||||
// Unknown char, skip
|
||||
ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
|
||||
if (!this->glyphs_.empty()) {
|
||||
uint8_t glyph_width = this->glyphs_[0].advance;
|
||||
display->rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
||||
if (!this->get_glyphs().empty()) {
|
||||
uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance;
|
||||
display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
x_at += glyph_width;
|
||||
}
|
||||
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t *data = glyph->data;
|
||||
const int max_x = x_at + glyph->offset_x + glyph->width;
|
||||
const int max_y = y_start + glyph->offset_y + glyph->height;
|
||||
const Glyph &glyph = this->get_glyphs()[glyph_n];
|
||||
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
|
||||
|
||||
const uint8_t *data = glyph.glyph_data_->data;
|
||||
const int max_x = x_at + scan_x1 + scan_width;
|
||||
const int max_y = y_start + scan_y1 + scan_height;
|
||||
|
||||
uint8_t bitmask = 0;
|
||||
uint8_t pixel_data = 0;
|
||||
@@ -257,10 +145,10 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
auto b_g = (float) background.g;
|
||||
auto b_b = (float) background.b;
|
||||
auto b_w = (float) background.w;
|
||||
for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) {
|
||||
for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) {
|
||||
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
|
||||
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
|
||||
uint8_t pixel = 0;
|
||||
for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) {
|
||||
for (int bit_num = 0; bit_num != this->bpp_; bit_num++) {
|
||||
if (bitmask == 0) {
|
||||
pixel_data = progmem_read_byte(data++);
|
||||
bitmask = 0x80;
|
||||
@@ -280,9 +168,12 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
}
|
||||
}
|
||||
}
|
||||
x_at += glyph->advance;
|
||||
x_at += glyph.glyph_data_->advance;
|
||||
|
||||
i += match_length;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace font
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,30 +6,14 @@
|
||||
#ifdef USE_DISPLAY
|
||||
#include "esphome/components/display/display.h"
|
||||
#endif
|
||||
#ifdef USE_LVGL_FONT
|
||||
#include <lvgl.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace font {
|
||||
|
||||
class Font;
|
||||
|
||||
class Glyph {
|
||||
public:
|
||||
constexpr Glyph(uint32_t code_point, const uint8_t *data, int advance, int offset_x, int offset_y, int width,
|
||||
int height)
|
||||
: code_point(code_point),
|
||||
data(data),
|
||||
advance(advance),
|
||||
offset_x(offset_x),
|
||||
offset_y(offset_y),
|
||||
width(width),
|
||||
height(height) {}
|
||||
|
||||
bool is_less_or_equal(uint32_t other) const { return this->code_point <= other; }
|
||||
|
||||
const uint32_t code_point;
|
||||
struct GlyphData {
|
||||
const uint8_t *a_char;
|
||||
const uint8_t *data;
|
||||
int advance;
|
||||
int offset_x;
|
||||
@@ -38,6 +22,26 @@ class Glyph {
|
||||
int height;
|
||||
};
|
||||
|
||||
class Glyph {
|
||||
public:
|
||||
Glyph(const GlyphData *data) : glyph_data_(data) {}
|
||||
|
||||
const uint8_t *get_char() const;
|
||||
|
||||
bool compare_to(const uint8_t *str) const;
|
||||
|
||||
int match_length(const uint8_t *str) const;
|
||||
|
||||
void scan_area(int *x1, int *y1, int *width, int *height) const;
|
||||
|
||||
const GlyphData *get_glyph_data() const { return this->glyph_data_; }
|
||||
|
||||
protected:
|
||||
friend Font;
|
||||
|
||||
const GlyphData *glyph_data_;
|
||||
};
|
||||
|
||||
class Font
|
||||
#ifdef USE_DISPLAY
|
||||
: public display::BaseFont
|
||||
@@ -46,8 +50,8 @@ class Font
|
||||
public:
|
||||
/** Construct the font with the given glyphs.
|
||||
*
|
||||
* @param data A list of glyphs, must be sorted lexicographically.
|
||||
* @param data_nr The number of glyphs
|
||||
* @param data A vector of glyphs, must be sorted lexicographically.
|
||||
* @param data_nr The number of glyphs in data.
|
||||
* @param baseline The y-offset from the top of the text to the baseline.
|
||||
* @param height The y-offset from the top of the text to the bottom.
|
||||
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
|
||||
@@ -55,10 +59,10 @@ class Font
|
||||
* @param capheight The height of capital letters, usually measured at the "X" glyph.
|
||||
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
|
||||
*/
|
||||
Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp = 1);
|
||||
|
||||
const Glyph *find_glyph(uint32_t codepoint) const;
|
||||
int match_next_glyph(const uint8_t *str, int *match_length);
|
||||
|
||||
#ifdef USE_DISPLAY
|
||||
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
|
||||
@@ -73,14 +77,11 @@ class Font
|
||||
inline int get_xheight() { return this->xheight_; }
|
||||
inline int get_capheight() { return this->capheight_; }
|
||||
inline int get_bpp() { return this->bpp_; }
|
||||
#ifdef USE_LVGL_FONT
|
||||
const lv_font_t *get_lv_font() const { return &this->lv_font_; }
|
||||
#endif
|
||||
|
||||
const ConstVector<Glyph> &get_glyphs() const { return glyphs_; }
|
||||
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||
|
||||
protected:
|
||||
ConstVector<Glyph> glyphs_;
|
||||
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
|
||||
int baseline_;
|
||||
int height_;
|
||||
int descender_;
|
||||
@@ -88,14 +89,6 @@ class Font
|
||||
int xheight_;
|
||||
int capheight_;
|
||||
uint8_t bpp_; // bits per pixel
|
||||
#ifdef USE_LVGL_FONT
|
||||
lv_font_t lv_font_{};
|
||||
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter);
|
||||
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next);
|
||||
const Glyph *get_glyph_data_(uint32_t unicode_letter);
|
||||
uint32_t last_letter_{};
|
||||
const Glyph *last_data_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace font
|
||||
|
||||
@@ -337,7 +337,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
|
||||
return;
|
||||
|
||||
/// Plot border
|
||||
if (this->border_) {
|
||||
if (legend_->border_) {
|
||||
int w = legend_->width_;
|
||||
int h = legend_->height_;
|
||||
buff->horizontal_line(x_offset, y_offset, w, color);
|
||||
|
||||
@@ -189,7 +189,7 @@ template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public P
|
||||
TEMPLATABLE_VALUE(std::string, name)
|
||||
TEMPLATABLE_VALUE(uint8_t, direction)
|
||||
|
||||
void play(Ts... x) override {
|
||||
void play(const Ts &...x) override {
|
||||
auto name = this->name_.value(x...);
|
||||
auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...);
|
||||
this->parent_->enroll_face(name, direction);
|
||||
@@ -200,7 +200,7 @@ template<typename... Ts> class DeleteAction : public Action<Ts...>, public Paren
|
||||
public:
|
||||
TEMPLATABLE_VALUE(int16_t, face_id)
|
||||
|
||||
void play(Ts... x) override {
|
||||
void play(const Ts &...x) override {
|
||||
auto face_id = this->face_id_.value(x...);
|
||||
this->parent_->delete_face(face_id);
|
||||
}
|
||||
@@ -208,17 +208,17 @@ template<typename... Ts> class DeleteAction : public Action<Ts...>, public Paren
|
||||
|
||||
template<typename... Ts> class DeleteAllAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->delete_all_faces(); }
|
||||
void play(const Ts &...x) override { this->parent_->delete_all_faces(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ScanAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->scan_face(); }
|
||||
void play(const Ts &...x) override { this->parent_->scan_face(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->reset(); }
|
||||
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hlk_fm22x
|
||||
|
||||
@@ -49,18 +49,18 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
auto container = this_update->request_parent_->get(this_update->source_url_);
|
||||
|
||||
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
|
||||
std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
|
||||
ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str());
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error("Failed to fetch manifest"); });
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
uint8_t *data = allocator.allocate(container->content_length);
|
||||
if (data == nullptr) {
|
||||
std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length);
|
||||
ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length);
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error("Failed to allocate memory for manifest"); });
|
||||
container->end();
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
@@ -121,9 +121,9 @@ void HttpRequestUpdate::update_task(void *params) {
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||
// Defer to main loop to avoid race condition on component_state_ read-modify-write
|
||||
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
|
||||
this_update->defer([this_update]() { this_update->status_set_error("Failed to parse manifest JSON"); });
|
||||
UPDATE_RETURN;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ void IDFI2CBus::dump_config() {
|
||||
if (s.second) {
|
||||
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace jsn_sr04t {
|
||||
static const char *const TAG = "jsn_sr04t.sensor";
|
||||
|
||||
void Jsnsr04tComponent::update() {
|
||||
this->write_byte(0x55);
|
||||
this->write_byte((this->model_ == AJ_SR04M) ? 0x01 : 0x55);
|
||||
ESP_LOGV(TAG, "Request read out from sensor");
|
||||
}
|
||||
|
||||
@@ -31,19 +31,10 @@ void Jsnsr04tComponent::loop() {
|
||||
}
|
||||
|
||||
void Jsnsr04tComponent::check_buffer_() {
|
||||
uint8_t checksum = 0;
|
||||
switch (this->model_) {
|
||||
case JSN_SR04T:
|
||||
checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
|
||||
break;
|
||||
case AJ_SR04M:
|
||||
checksum = this->buffer_[1] + this->buffer_[2];
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
|
||||
if (this->buffer_[3] == checksum) {
|
||||
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
|
||||
if (distance > 250) {
|
||||
if (distance > ((this->model_ == AJ_SR04M) ? 200 : 250)) {
|
||||
float meters = distance / 1000.0f;
|
||||
ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters);
|
||||
this->publish_state(meters);
|
||||
|
||||
@@ -205,8 +205,10 @@ void LD2420Component::dump_config() {
|
||||
LOG_BUTTON(" ", "Factory Reset:", this->factory_reset_button_);
|
||||
LOG_BUTTON(" ", "Restart Module:", this->restart_module_button_);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
ESP_LOGCONFIG(TAG, "Select:");
|
||||
LOG_SELECT(" ", "Operating Mode", this->operating_selector_);
|
||||
#endif
|
||||
if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
|
||||
}
|
||||
@@ -238,12 +240,20 @@ void LD2420Component::setup() {
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
}
|
||||
#endif
|
||||
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
|
||||
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
|
||||
} else {
|
||||
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
|
||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
@@ -383,8 +393,12 @@ void LD2420Component::set_operating_mode(const char *state) {
|
||||
// If unsupported firmware ignore mode select
|
||||
if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
||||
this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state);
|
||||
// Entering Auto Calibrate we need to clear the privoiuos data collection
|
||||
this->operating_selector_->publish_state(state);
|
||||
// Entering Auto Calibrate we need to clear the previous data collection
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(state);
|
||||
}
|
||||
#endif
|
||||
if (current_operating_mode == OP_CALIBRATE_MODE) {
|
||||
this->set_calibration_(true);
|
||||
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
|
||||
@@ -404,7 +418,11 @@ void LD2420Component::set_operating_mode(const char *state) {
|
||||
}
|
||||
} else {
|
||||
this->current_operating_mode = OP_SIMPLE_MODE;
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
#ifdef USE_SELECT
|
||||
if (this->operating_selector_ != nullptr) {
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@ void LightState::setup() {
|
||||
effect->init_internal(this);
|
||||
}
|
||||
|
||||
// Start with loop disabled if idle - respects any effects/transitions set up during initialization
|
||||
this->disable_loop_if_idle_();
|
||||
|
||||
// When supported color temperature range is known, initialize color temperature setting within bounds.
|
||||
auto traits = this->get_traits();
|
||||
float min_mireds = traits.get_min_mireds();
|
||||
@@ -129,9 +126,6 @@ void LightState::loop() {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +133,6 @@ void LightState::loop() {
|
||||
if (this->next_write_) {
|
||||
this->next_write_ = false;
|
||||
this->output_->write_state(this);
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +228,6 @@ void LightState::start_effect_(uint32_t effect_index) {
|
||||
this->active_effect_index_ = effect_index;
|
||||
auto *effect = this->get_active_effect_();
|
||||
effect->start_internal();
|
||||
// Enable loop while effect is active
|
||||
this->enable_loop();
|
||||
}
|
||||
LightEffect *LightState::get_active_effect_() {
|
||||
if (this->active_effect_index_ == 0) {
|
||||
@@ -252,8 +242,6 @@ void LightState::stop_effect_() {
|
||||
effect->stop();
|
||||
}
|
||||
this->active_effect_index_ = 0;
|
||||
// Disable loop if idle (no effect and no transformer)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
|
||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
@@ -263,8 +251,6 @@ void LightState::start_transition_(const LightColorValues &target, uint32_t leng
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
// Enable loop while transition is active
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
@@ -280,8 +266,6 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
};
|
||||
// Enable loop while flash is active
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
@@ -293,14 +277,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
|
||||
}
|
||||
this->output_->update_state(this);
|
||||
this->next_write_ = true;
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::disable_loop_if_idle_() {
|
||||
// Only disable loop if both transformer and effect are inactive, and no pending writes
|
||||
if (this->transformer_ == nullptr && this->get_active_effect_() == nullptr && !this->next_write_) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
|
||||
@@ -256,9 +256,6 @@ class LightState : public EntityBase, public Component {
|
||||
/// Internal method to save the current remote_values to the preferences
|
||||
void save_remote_values_();
|
||||
|
||||
/// Disable loop if neither transformer nor effect is active
|
||||
void disable_loop_if_idle_();
|
||||
|
||||
/// Store the output to allow effects to have more access.
|
||||
LightOutput *output_;
|
||||
/// The currently active transformer for this light (transition/flash).
|
||||
|
||||
@@ -365,10 +365,8 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG")
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")
|
||||
|
||||
@@ -65,9 +65,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
uint16_t buffer_at = 0; // Initialize buffer position
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
|
||||
MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
|
||||
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
this->write_msg_(console_buffer, buffer_at);
|
||||
this->write_msg_(console_buffer);
|
||||
}
|
||||
|
||||
// Reset the recursion guard for this task
|
||||
@@ -133,19 +131,18 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
|
||||
// Save the offset before calling format_log_to_buffer_with_terminator_
|
||||
// since it will increment tx_buffer_at_ to the end of the formatted string
|
||||
uint16_t msg_start = this->tx_buffer_at_;
|
||||
uint32_t msg_start = this->tx_buffer_at_;
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
|
||||
&this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
|
||||
uint16_t msg_length =
|
||||
// Write to console and send callback starting at the msg_start
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->write_msg_(this->tx_buffer_ + msg_start);
|
||||
}
|
||||
size_t msg_length =
|
||||
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||
|
||||
// Callbacks get message first (before console write)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
|
||||
global_recursion_guard_ = false;
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
@@ -212,7 +209,9 @@ void Logger::process_messages_() {
|
||||
// This ensures all log messages appear on the console in a clean, serialized manner
|
||||
// Note: Messages may appear slightly out of order due to async processing, but
|
||||
// this is preferred over corrupted/interleaved console output
|
||||
this->write_tx_buffer_to_console_();
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->write_msg_(this->tx_buffer_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No messages to process, disable loop if appropriate
|
||||
|
||||
@@ -71,17 +71,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
|
||||
// "0x" + 2 hex digits per byte + '\0'
|
||||
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
|
||||
|
||||
// Platform-specific: does write_msg_ add its own newline?
|
||||
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny)
|
||||
// Allows single write call with newline included for efficiency
|
||||
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
|
||||
// Newline should NOT be added to buffer
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
|
||||
#else
|
||||
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
@@ -184,7 +173,7 @@ class Logger : public Component {
|
||||
|
||||
protected:
|
||||
void process_messages_();
|
||||
void write_msg_(const char *msg, size_t len);
|
||||
void write_msg_(const char *msg);
|
||||
|
||||
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
||||
// It's the caller's responsibility to initialize buffer_at (typically to 0)
|
||||
@@ -211,35 +200,6 @@ class Logger : public Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to add newline to buffer for platforms that need it
|
||||
// Modifies buffer_at to include the newline
|
||||
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
|
||||
// Add newline - don't need to maintain null termination
|
||||
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
|
||||
// This is safe because:
|
||||
// 1. Callbacks already received the message (before we add newline)
|
||||
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
|
||||
if (*buffer_at < buffer_size) {
|
||||
buffer[(*buffer_at)++] = '\n';
|
||||
} else if (buffer_size > 0) {
|
||||
// Buffer was full - replace last char with newline to ensure it's visible
|
||||
buffer[buffer_size - 1] = '\n';
|
||||
*buffer_at = buffer_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to write tx_buffer_ to console if logging is enabled
|
||||
// INTERNAL USE ONLY - offset > 0 requires length parameter to be non-null
|
||||
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
|
||||
if (this->baud_rate_ > 0) {
|
||||
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
|
||||
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
|
||||
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to format and send a log message to both console and callbacks
|
||||
inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
|
||||
va_list args) {
|
||||
@@ -248,11 +208,10 @@ class Logger : public Component {
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
|
||||
this->tx_buffer_size_);
|
||||
|
||||
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
|
||||
}
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
|
||||
// Console gets message WITH newline (if platform needs it)
|
||||
this->write_tx_buffer_to_console_();
|
||||
}
|
||||
|
||||
// Write the body of the log message to the buffer
|
||||
@@ -466,9 +425,7 @@ class Logger : public Component {
|
||||
}
|
||||
|
||||
// Update buffer_at with the formatted length (handle truncation)
|
||||
// When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
|
||||
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
|
||||
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
|
||||
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
|
||||
*buffer_at += formatted_len;
|
||||
|
||||
// Remove all trailing newlines right after formatting
|
||||
|
||||
@@ -121,23 +121,25 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
// Length is now always passed explicitly - no strlen() fallback needed
|
||||
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
// USB CDC/JTAG - single write including newline (already in buffer)
|
||||
// Use fwrite to stdout which goes through VFS to USB console
|
||||
//
|
||||
// Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG).
|
||||
// They are ONLY defined when the user explicitly selects USB as the logger output in their config.
|
||||
// This is compile-time selection, not runtime detection - if USB is configured, it's always used.
|
||||
// There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility
|
||||
// to configure correctly for their hardware. This approach eliminates runtime overhead.
|
||||
fwrite(msg, 1, len, stdout);
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
if (
|
||||
#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_LOGGER_USB_SERIAL_JTAG) && !defined(USE_LOGGER_USB_CDC)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_LOGGER_USB_CDC) && defined(USE_LOGGER_USB_SERIAL_JTAG)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
// Regular UART - single write including newline (already in buffer)
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
#endif
|
||||
) {
|
||||
puts(msg);
|
||||
} else {
|
||||
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen
|
||||
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
|
||||
@@ -33,10 +33,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
this->hw_serial_->write(msg, len);
|
||||
}
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) {
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
@@ -49,7 +49,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) { this->hw_serial_->write(msg, len); }
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -27,7 +27,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -62,7 +62,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) {
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("%s\n", msg);
|
||||
#endif
|
||||
|
||||
@@ -174,7 +174,7 @@ void LTRAlsPs501Component::loop() {
|
||||
break;
|
||||
|
||||
case State::WAITING_FOR_DATA:
|
||||
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
|
||||
if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) {
|
||||
tries = 0;
|
||||
ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms",
|
||||
get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time));
|
||||
@@ -379,18 +379,18 @@ void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time)
|
||||
}
|
||||
}
|
||||
|
||||
DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
|
||||
LtrDataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
|
||||
AlsPsStatusRegister als_status{0};
|
||||
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
|
||||
if (!als_status.als_new_data)
|
||||
return DataAvail::NO_DATA;
|
||||
return LtrDataAvail::LTR_NO_DATA;
|
||||
ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain));
|
||||
if (data.gain != als_status.gain) {
|
||||
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
|
||||
return DataAvail::BAD_DATA;
|
||||
return LtrDataAvail::LTR_BAD_DATA;
|
||||
}
|
||||
data.gain = als_status.gain;
|
||||
return DataAvail::DATA_OK;
|
||||
return LtrDataAvail::LTR_DATA_OK;
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace esphome {
|
||||
namespace ltr501 {
|
||||
|
||||
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
|
||||
enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK };
|
||||
|
||||
enum LtrType : uint8_t {
|
||||
LTR_TYPE_UNKNOWN = 0,
|
||||
@@ -106,7 +106,7 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void configure_als_();
|
||||
void configure_integration_time_(IntegrationTime501 time);
|
||||
void configure_gain_(AlsGain501 gain);
|
||||
DataAvail is_als_data_ready_(AlsReadings &data);
|
||||
LtrDataAvail is_als_data_ready_(AlsReadings &data);
|
||||
void read_sensor_data_(AlsReadings &data);
|
||||
bool are_adjustments_required_(AlsReadings &data);
|
||||
void apply_lux_calculation_(AlsReadings &data);
|
||||
|
||||
@@ -165,7 +165,7 @@ void LTRAlsPsComponent::loop() {
|
||||
break;
|
||||
|
||||
case State::WAITING_FOR_DATA:
|
||||
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
|
||||
if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) {
|
||||
tries = 0;
|
||||
ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
|
||||
get_itime_ms(this->als_readings_.integration_time));
|
||||
@@ -376,23 +376,23 @@ void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) {
|
||||
}
|
||||
}
|
||||
|
||||
DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
|
||||
LtrDataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
|
||||
AlsPsStatusRegister als_status{0};
|
||||
|
||||
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
|
||||
if (!als_status.als_new_data)
|
||||
return DataAvail::NO_DATA;
|
||||
return LtrDataAvail::LTR_NO_DATA;
|
||||
|
||||
if (als_status.data_invalid) {
|
||||
ESP_LOGW(TAG, "Data available but not valid");
|
||||
return DataAvail::BAD_DATA;
|
||||
return LtrDataAvail::LTR_BAD_DATA;
|
||||
}
|
||||
ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain));
|
||||
if (data.gain != als_status.gain) {
|
||||
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
|
||||
return DataAvail::BAD_DATA;
|
||||
return LtrDataAvail::LTR_BAD_DATA;
|
||||
}
|
||||
return DataAvail::DATA_OK;
|
||||
return LtrDataAvail::LTR_DATA_OK;
|
||||
}
|
||||
|
||||
void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace esphome {
|
||||
namespace ltr_als_ps {
|
||||
|
||||
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
|
||||
enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK };
|
||||
|
||||
enum LtrType : uint8_t {
|
||||
LTR_TYPE_UNKNOWN = 0,
|
||||
@@ -106,7 +106,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
void configure_als_();
|
||||
void configure_integration_time_(IntegrationTime time);
|
||||
void configure_gain_(AlsGain gain);
|
||||
DataAvail is_als_data_ready_(AlsReadings &data);
|
||||
LtrDataAvail is_als_data_ready_(AlsReadings &data);
|
||||
void read_sensor_data_(AlsReadings &data);
|
||||
bool are_adjustments_required_(AlsReadings &data);
|
||||
void apply_lux_calculation_(AlsReadings &data);
|
||||
|
||||
@@ -52,7 +52,15 @@ from .schemas import (
|
||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||
from .trigger import add_on_boot_triggers, generate_triggers
|
||||
from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns
|
||||
from .types import (
|
||||
FontEngine,
|
||||
IdleTrigger,
|
||||
PlainTrigger,
|
||||
lv_font_t,
|
||||
lv_group_t,
|
||||
lv_style_t,
|
||||
lvgl_ns,
|
||||
)
|
||||
from .widgets import (
|
||||
LvScrActType,
|
||||
Widget,
|
||||
@@ -236,6 +244,7 @@ async def to_code(configs):
|
||||
cg.add_global(lvgl_ns.using)
|
||||
for font in helpers.esphome_fonts_used:
|
||||
await cg.get_variable(font)
|
||||
cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font))
|
||||
default_font = config_0[df.CONF_DEFAULT_FONT]
|
||||
if not lvalid.is_lv_font(default_font):
|
||||
add_define(
|
||||
@@ -247,8 +256,7 @@ async def to_code(configs):
|
||||
type=lv_font_t.operator("ptr").operator("const"),
|
||||
)
|
||||
cg.new_variable(
|
||||
globfont_id,
|
||||
MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(),
|
||||
globfont_id, MockObj(await lvalid.lv_font.process(default_font))
|
||||
)
|
||||
add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
|
||||
else:
|
||||
|
||||
76
esphome/components/lvgl/font.cpp
Normal file
76
esphome/components/lvgl/font.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "lvgl_esphome.h"
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
|
||||
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
|
||||
auto *fe = (FontEngine *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data(unicode_letter);
|
||||
if (gd == nullptr)
|
||||
return nullptr;
|
||||
// esph_log_d(TAG, "Returning bitmap @ %X", (uint32_t)gd->data);
|
||||
|
||||
return gd->data;
|
||||
}
|
||||
|
||||
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
|
||||
auto *fe = (FontEngine *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data(unicode_letter);
|
||||
if (gd == nullptr)
|
||||
return false;
|
||||
dsc->adv_w = gd->advance;
|
||||
dsc->ofs_x = gd->offset_x;
|
||||
dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline;
|
||||
dsc->box_w = gd->width;
|
||||
dsc->box_h = gd->height;
|
||||
dsc->is_placeholder = 0;
|
||||
dsc->bpp = fe->bpp;
|
||||
return true;
|
||||
}
|
||||
|
||||
FontEngine::FontEngine(font::Font *esp_font) : font_(esp_font) {
|
||||
this->bpp = esp_font->get_bpp();
|
||||
this->lv_font_.dsc = this;
|
||||
this->lv_font_.line_height = this->height = esp_font->get_height();
|
||||
this->lv_font_.base_line = this->baseline = this->lv_font_.line_height - esp_font->get_baseline();
|
||||
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
|
||||
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
|
||||
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
|
||||
this->lv_font_.underline_position = -1;
|
||||
this->lv_font_.underline_thickness = 1;
|
||||
}
|
||||
|
||||
const lv_font_t *FontEngine::get_lv_font() { return &this->lv_font_; }
|
||||
|
||||
const font::GlyphData *FontEngine::get_glyph_data(uint32_t unicode_letter) {
|
||||
if (unicode_letter == last_letter_)
|
||||
return this->last_data_;
|
||||
uint8_t unicode[5];
|
||||
memset(unicode, 0, sizeof unicode);
|
||||
if (unicode_letter > 0xFFFF) {
|
||||
unicode[0] = 0xF0 + ((unicode_letter >> 18) & 0x7);
|
||||
unicode[1] = 0x80 + ((unicode_letter >> 12) & 0x3F);
|
||||
unicode[2] = 0x80 + ((unicode_letter >> 6) & 0x3F);
|
||||
unicode[3] = 0x80 + (unicode_letter & 0x3F);
|
||||
} else if (unicode_letter > 0x7FF) {
|
||||
unicode[0] = 0xE0 + ((unicode_letter >> 12) & 0xF);
|
||||
unicode[1] = 0x80 + ((unicode_letter >> 6) & 0x3F);
|
||||
unicode[2] = 0x80 + (unicode_letter & 0x3F);
|
||||
} else if (unicode_letter > 0x7F) {
|
||||
unicode[0] = 0xC0 + ((unicode_letter >> 6) & 0x1F);
|
||||
unicode[1] = 0x80 + (unicode_letter & 0x3F);
|
||||
} else {
|
||||
unicode[0] = unicode_letter;
|
||||
}
|
||||
int match_length;
|
||||
int glyph_n = this->font_->match_next_glyph(unicode, &match_length);
|
||||
if (glyph_n < 0)
|
||||
return nullptr;
|
||||
this->last_data_ = this->font_->get_glyphs()[glyph_n].get_glyph_data();
|
||||
this->last_letter_ = unicode_letter;
|
||||
return this->last_data_;
|
||||
}
|
||||
} // namespace lvgl
|
||||
} // namespace esphome
|
||||
#endif // USES_LVGL_FONT
|
||||
@@ -36,6 +36,8 @@ from .defines import (
|
||||
)
|
||||
from .lv_validation import padding, size
|
||||
|
||||
CONF_MULTIPLE_WIDGETS_PER_CELL = "multiple_widgets_per_cell"
|
||||
|
||||
cell_alignments = LV_CELL_ALIGNMENTS.one_of
|
||||
grid_alignments = LV_GRID_ALIGNMENTS.one_of
|
||||
flex_alignments = LV_FLEX_ALIGNMENTS.one_of
|
||||
@@ -220,6 +222,7 @@ class GridLayout(Layout):
|
||||
cv.Optional(CONF_GRID_ROW_ALIGN): grid_alignments,
|
||||
cv.Optional(CONF_PAD_ROW): padding,
|
||||
cv.Optional(CONF_PAD_COLUMN): padding,
|
||||
cv.Optional(CONF_MULTIPLE_WIDGETS_PER_CELL, default=False): cv.boolean,
|
||||
},
|
||||
{
|
||||
cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||
@@ -263,6 +266,7 @@ class GridLayout(Layout):
|
||||
# should be guaranteed to be a dict at this point
|
||||
assert isinstance(layout, dict)
|
||||
assert layout.get(CONF_TYPE).lower() == TYPE_GRID
|
||||
allow_multiple = layout.get(CONF_MULTIPLE_WIDGETS_PER_CELL, False)
|
||||
rows = len(layout[CONF_GRID_ROWS])
|
||||
columns = len(layout[CONF_GRID_COLUMNS])
|
||||
used_cells = [[None] * columns for _ in range(rows)]
|
||||
@@ -299,7 +303,10 @@ class GridLayout(Layout):
|
||||
f"exceeds grid size {rows}x{columns}",
|
||||
[CONF_WIDGETS, index],
|
||||
)
|
||||
if used_cells[row + i][column + j] is not None:
|
||||
if (
|
||||
not allow_multiple
|
||||
and used_cells[row + i][column + j] is not None
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}",
|
||||
[CONF_WIDGETS, index],
|
||||
|
||||
@@ -493,7 +493,6 @@ class LvFont(LValidator):
|
||||
return LV_FONTS
|
||||
if is_lv_font(value):
|
||||
return lv_builtin_font(value)
|
||||
add_lv_use("font")
|
||||
fontval = cv.use_id(Font)(value)
|
||||
esphome_fonts_used.add(fontval)
|
||||
return requires_component("font")(fontval)
|
||||
@@ -503,9 +502,7 @@ class LvFont(LValidator):
|
||||
async def process(self, value, args=()):
|
||||
if is_lv_font(value):
|
||||
return literal(f"&lv_font_{value}")
|
||||
if isinstance(value, str):
|
||||
return literal(f"{value}")
|
||||
return await super().process(value, args)
|
||||
return literal(f"{value}_engine->get_lv_font()")
|
||||
|
||||
|
||||
lv_font = LvFont()
|
||||
|
||||
@@ -50,14 +50,6 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
|
||||
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332;
|
||||
#endif // LV_COLOR_DEPTH
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
inline void lv_obj_set_style_text_font(lv_obj_t *obj, const font::Font *font, lv_style_selector_t part) {
|
||||
lv_obj_set_style_text_font(obj, font->get_lv_font(), part);
|
||||
}
|
||||
inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
|
||||
lv_style_set_text_font(style, font->get_lv_font());
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
// Shortcut / overload, so that the source of an image can easily be updated
|
||||
// from within a lambda.
|
||||
@@ -142,6 +134,24 @@ template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
|
||||
protected:
|
||||
std::function<void(Ts...)> lamb_;
|
||||
};
|
||||
#ifdef USE_LVGL_FONT
|
||||
class FontEngine {
|
||||
public:
|
||||
FontEngine(font::Font *esp_font);
|
||||
const lv_font_t *get_lv_font();
|
||||
|
||||
const font::GlyphData *get_glyph_data(uint32_t unicode_letter);
|
||||
uint16_t baseline{};
|
||||
uint16_t height{};
|
||||
uint8_t bpp{};
|
||||
|
||||
protected:
|
||||
font::Font *font_{};
|
||||
uint32_t last_letter_{};
|
||||
const font::GlyphData *last_data_{};
|
||||
lv_font_t lv_font_{};
|
||||
};
|
||||
#endif // USE_LVGL_FONT
|
||||
#ifdef USE_LVGL_ANIMIMG
|
||||
void lv_animimg_stop(lv_obj_t *obj);
|
||||
#endif // USE_LVGL_ANIMIMG
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from esphome import config_validation as cv
|
||||
from esphome.automation import Trigger, validate_automation
|
||||
from esphome.components.time import RealTimeClock
|
||||
from esphome.config_validation import prepend_path
|
||||
from esphome.const import (
|
||||
CONF_ARGS,
|
||||
CONF_FORMAT,
|
||||
@@ -422,7 +423,10 @@ def any_widget_schema(extras=None):
|
||||
def validator(value):
|
||||
if isinstance(value, dict):
|
||||
# Convert to list
|
||||
is_dict = True
|
||||
value = [{k: v} for k, v in value.items()]
|
||||
else:
|
||||
is_dict = False
|
||||
if not isinstance(value, list):
|
||||
raise cv.Invalid("Expected a list of widgets")
|
||||
result = []
|
||||
@@ -443,7 +447,9 @@ def any_widget_schema(extras=None):
|
||||
)
|
||||
# Apply custom validation
|
||||
value = widget_type.validate(value or {})
|
||||
result.append({key: container_validator(value)})
|
||||
path = [key] if is_dict else [index, key]
|
||||
with prepend_path(path):
|
||||
result.append({key: container_validator(value)})
|
||||
return result
|
||||
|
||||
return validator
|
||||
|
||||
@@ -45,6 +45,7 @@ lv_coord_t = cg.global_ns.namespace("lv_coord_t")
|
||||
lv_event_code_t = cg.global_ns.enum("lv_event_code_t")
|
||||
lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t")
|
||||
lv_key_t = cg.global_ns.enum("lv_key_t")
|
||||
FontEngine = lvgl_ns.class_("FontEngine")
|
||||
PlainTrigger = esphome_ns.class_("Trigger<>", automation.Trigger.template())
|
||||
DrawEndTrigger = esphome_ns.class_(
|
||||
"Trigger<uint32_t, uint32_t>", automation.Trigger.template(cg.uint32, cg.uint32)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..automation import action_to_code
|
||||
from ..defines import (
|
||||
@@ -114,7 +115,9 @@ class SpinboxType(WidgetType):
|
||||
w.obj, digits, digits - config[CONF_DECIMAL_PLACES]
|
||||
)
|
||||
if (value := config.get(CONF_VALUE)) is not None:
|
||||
lv.spinbox_set_value(w.obj, await lv_float.process(value))
|
||||
lv.spinbox_set_value(
|
||||
w.obj, MockObj(await lv_float.process(value)) * w.get_scale()
|
||||
)
|
||||
|
||||
def get_scale(self, config):
|
||||
return 10 ** config[CONF_DECIMAL_PLACES]
|
||||
|
||||
@@ -56,7 +56,7 @@ void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
this->update_reg_(pin, false, iodir);
|
||||
}
|
||||
}
|
||||
float MCP23016::get_setup_priority() const { return setup_priority::IO; }
|
||||
float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
bool MCP23016::read_reg_(uint8_t reg, uint8_t *value) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
@@ -135,7 +135,8 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
|
||||
#ifdef USE_DASHBOARD_IMPORT
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
|
||||
txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url())});
|
||||
txt_records.push_back(
|
||||
{MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url().c_str())});
|
||||
#endif
|
||||
}
|
||||
#endif // USE_API
|
||||
|
||||
@@ -11,6 +11,12 @@ static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel
|
||||
xSemaphoreGiveFromISR(sem, &need_yield);
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
void MIPI_DSI::smark_failed(const char *message, esp_err_t err) {
|
||||
ESP_LOGE(TAG, "%s: %s", message, esp_err_to_name(err));
|
||||
this->mark_failed(message);
|
||||
}
|
||||
|
||||
void MIPI_DSI::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running Setup");
|
||||
|
||||
|
||||
@@ -62,10 +62,7 @@ class MIPI_DSI : public display::Display {
|
||||
void set_lanes(uint8_t lanes) { this->lanes_ = lanes; }
|
||||
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
|
||||
|
||||
void smark_failed(const char *message, esp_err_t err) {
|
||||
auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err));
|
||||
this->mark_failed(str.c_str());
|
||||
}
|
||||
void smark_failed(const char *message, esp_err_t err);
|
||||
|
||||
void update() override;
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ void MipiRgb::common_setup_() {
|
||||
if (err == ESP_OK)
|
||||
err = esp_lcd_panel_init(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed(msg.c_str());
|
||||
ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed("lcd setup failed");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
|
||||
}
|
||||
@@ -350,6 +350,7 @@ void MipiRgb::dump_config() {
|
||||
"\n Width: %u"
|
||||
"\n Height: %u"
|
||||
"\n Rotation: %d degrees"
|
||||
"\n PCLK Inverted: %s"
|
||||
"\n HSync Pulse Width: %u"
|
||||
"\n HSync Back Porch: %u"
|
||||
"\n HSync Front Porch: %u"
|
||||
@@ -357,18 +358,18 @@ void MipiRgb::dump_config() {
|
||||
"\n VSync Back Porch: %u"
|
||||
"\n VSync Front Porch: %u"
|
||||
"\n Invert Colors: %s"
|
||||
"\n Pixel Clock: %dMHz"
|
||||
"\n Pixel Clock: %uMHz"
|
||||
"\n Reset Pin: %s"
|
||||
"\n DE Pin: %s"
|
||||
"\n PCLK Pin: %s"
|
||||
"\n HSYNC Pin: %s"
|
||||
"\n VSYNC Pin: %s",
|
||||
this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_,
|
||||
this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_,
|
||||
this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000,
|
||||
get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(),
|
||||
get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(),
|
||||
get_pin_name(this->vsync_pin_).c_str());
|
||||
this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
|
||||
this->hsync_pulse_width_, this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_,
|
||||
this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
|
||||
(unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_).c_str(),
|
||||
get_pin_name(this->de_pin_).c_str(), get_pin_name(this->pclk_pin_).c_str(),
|
||||
get_pin_name(this->hsync_pin_).c_str(), get_pin_name(this->vsync_pin_).c_str());
|
||||
|
||||
if (this->madctl_ & MADCTL_BGR) {
|
||||
this->dump_pins_(8, 13, "Blue", 0);
|
||||
|
||||
@@ -11,6 +11,7 @@ st7701s.extend(
|
||||
vsync_pin=17,
|
||||
pclk_pin=21,
|
||||
pclk_frequency="12MHz",
|
||||
pclk_inverted=False,
|
||||
pixel_mode="18bit",
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
|
||||
@@ -116,7 +116,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
||||
|
||||
// Get temperature of sensor
|
||||
if (this->temperature_ != nullptr) {
|
||||
uint8_t temp_in_c = this->parse_temperature_(manu_data.data);
|
||||
int8_t temp_in_c = this->parse_temperature_(manu_data.data);
|
||||
this->temperature_->publish_state(temp_in_c);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector<uint8_t> &message) {
|
||||
(MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t));
|
||||
}
|
||||
|
||||
uint8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; }
|
||||
int8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; }
|
||||
|
||||
SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector<uint8_t> &message) {
|
||||
// Since a 8 bit value is being shifted and truncated to 2 bits all possible values are defined as enumeration
|
||||
|
||||
@@ -61,7 +61,7 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi
|
||||
|
||||
uint8_t parse_battery_level_(const std::vector<uint8_t> &message);
|
||||
uint32_t parse_distance_(const std::vector<uint8_t> &message);
|
||||
uint8_t parse_temperature_(const std::vector<uint8_t> &message);
|
||||
int8_t parse_temperature_(const std::vector<uint8_t> &message);
|
||||
SensorReadQuality parse_read_quality_(const std::vector<uint8_t> &message);
|
||||
};
|
||||
|
||||
|
||||
@@ -81,7 +81,12 @@ struct IPAddress {
|
||||
ip_addr_.type = IPADDR_TYPE_V6;
|
||||
}
|
||||
#endif /* LWIP_IPV6 */
|
||||
IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); }
|
||||
IPAddress(esp_ip4_addr_t *other_ip) {
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t));
|
||||
#if LWIP_IPV6
|
||||
ip_addr_.type = IPADDR_TYPE_V4;
|
||||
#endif
|
||||
}
|
||||
IPAddress(esp_ip_addr_t *other_ip) {
|
||||
#if LWIP_IPV6
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_));
|
||||
@@ -118,10 +123,10 @@ struct IPAddress {
|
||||
operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
|
||||
#endif
|
||||
|
||||
bool is_set() const { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() { return ip_addr_ismulticast(&ip_addr_); }
|
||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
|
||||
@@ -174,6 +174,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
|
||||
// Check if baud rate is supported
|
||||
this->original_baud_rate_ = this->parent_->get_baud_rate();
|
||||
if (baud_rate <= 0) {
|
||||
baud_rate = this->original_baud_rate_;
|
||||
}
|
||||
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
|
||||
|
||||
// Define the configuration for the HTTP client
|
||||
|
||||
@@ -177,6 +177,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
|
||||
// Check if baud rate is supported
|
||||
this->original_baud_rate_ = this->parent_->get_baud_rate();
|
||||
if (baud_rate <= 0) {
|
||||
baud_rate = this->original_baud_rate_;
|
||||
}
|
||||
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
|
||||
|
||||
// Define the configuration for the HTTP client
|
||||
|
||||
@@ -103,7 +103,6 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52")
|
||||
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
|
||||
|
||||
CONF_DFU = "dfu"
|
||||
CONF_DCDC = "dcdc"
|
||||
CONF_REG0 = "reg0"
|
||||
CONF_UICR_ERASE = "uicr_erase"
|
||||
|
||||
@@ -122,7 +121,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DCDC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_REG0): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): cv.All(
|
||||
@@ -198,7 +196,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if dfu_config := config.get(CONF_DFU):
|
||||
CORE.add_job(_dfu_to_code, dfu_config)
|
||||
zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC])
|
||||
|
||||
if reg0_config := config.get(CONF_REG0):
|
||||
value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE])
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::number {
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
||||
static const char *const TAG = "number.automation";
|
||||
|
||||
@@ -51,4 +52,5 @@ void ValueRangeTrigger::on_state_(float state) {
|
||||
this->rtc_.save(&in_range);
|
||||
}
|
||||
|
||||
} // namespace esphome::number
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::number {
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
||||
class NumberStateTrigger : public Trigger<float> {
|
||||
public:
|
||||
@@ -90,4 +91,5 @@ template<typename... Ts> class NumberInRangeCondition : public Condition<Ts...>
|
||||
float max_{NAN};
|
||||
};
|
||||
|
||||
} // namespace esphome::number
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::number {
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
||||
static const char *const TAG = "number";
|
||||
|
||||
@@ -42,4 +43,5 @@ void Number::add_on_state_callback(std::function<void(float)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace esphome::number
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#include "number_call.h"
|
||||
#include "number_traits.h"
|
||||
|
||||
namespace esphome::number {
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
||||
class Number;
|
||||
void log_number(const char *tag, const char *prefix, const char *type, Number *obj);
|
||||
@@ -52,4 +53,5 @@ class Number : public EntityBase {
|
||||
CallbackManager<void(float)> state_callback_;
|
||||
};
|
||||
|
||||
} // namespace esphome::number
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
#include "number.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::number {
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
||||
static const char *const TAG = "number";
|
||||
|
||||
@@ -124,4 +125,5 @@ void NumberCall::perform() {
|
||||
this->parent_->control(target_value);
|
||||
}
|
||||
|
||||
} // namespace esphome::number
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user