Compare commits

..

69 Commits

Author SHA1 Message Date
J. Nick Koston
85e5119ba2 fix scheduler heap churn with rapid timeouts 2025-11-25 14:57:02 -06:00
dependabot[bot]
ae60b5e6a1 Bump actions/setup-python from 6.0.0 to 6.1.0 in /.github/actions/restore-python (#12108)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-25 14:27:49 -06:00
dependabot[bot]
70df4ecaa9 Bump actions/setup-python from 6.0.0 to 6.1.0 (#12106)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-25 13:35:40 -06:00
Clyde Stubbs
b6be5e3eda [lvgl] Allow multiple widgets per grid cell (#12091) 2025-11-26 06:06:42 +11:00
Nikolai Ryzhkov
dec323e786 [sht4x] Read and store a serial number of SHT4x sensors (#12089)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-25 13:27:35 -05:00
J. Nick Koston
6ca0cd1e8b [ltr390] Simplify mode tracking with bitmask instead of vector/function (#12093) 2025-11-25 12:16:48 -06:00
J. Nick Koston
3106934678 [esp32_ble] Optimize name storage to reduce RAM and eliminate heap allocations (#12071) 2025-11-25 12:16:27 -06:00
J. Nick Koston
8c5985f68a [web_server] Consolidate turn_on/turn_off handlers to eliminate duplicate lambdas (#12094) 2025-11-25 12:16:02 -06:00
J. Nick Koston
cf8c205644 [core] Reduce flash size by combining set_name() and set_object_id() calls (#11941) 2025-11-25 12:15:45 -06:00
J. Nick Koston
a571033b43 [script] Fix script.wait hanging when triggered from on_boot (#12102) 2025-11-25 10:30:01 -06:00
Jonathan Swoboda
cdf27f1447 [esp32] Fix platformio flash size print (#12099)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-25 11:14:53 -05:00
Edward Firmo
c30b920193 [nextion] Do not set alternative baud rate when not specified or <= 0 (#12097) 2025-11-25 07:48:32 -05:00
J. Nick Koston
697c5f424e [api] Use const char* pointers for light effects to eliminate heap allocations (#12090) 2025-11-25 08:17:53 +00:00
J. Nick Koston
18c97a08c3 [esp8266] Use C++17 nested namespaces and constexpr (#12096) 2025-11-25 01:47:06 -06:00
bdm310
66a871840e Add more lvgl arc update parameters (#12066) 2025-11-25 17:14:23 +11:00
J. Nick Koston
46a26560fd [template.alarm_control_panel] Replace std::map with FixedVector for heap and flash savings (#11893) 2025-11-25 16:21:56 +13:00
J. Nick Koston
1c808a3375 [ble_client] Write static BLE data directly from flash without allocation (#11826) 2025-11-25 16:19:18 +13:00
Keith Burzinski
2bc8a4a779 [wifi_info] Use callbacks instead of polling (#10748)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-24 20:23:10 -06:00
dependabot[bot]
7f1a9a611f Bump aioesphomeapi from 42.7.0 to 42.8.0 (#12092)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-25 02:09:02 +00:00
Jonathan Swoboda
b51409ed5e Merge branch 'release' into dev 2025-11-24 17:30:08 -05:00
Jonathan Swoboda
3775b54554 Merge pull request #12086 from esphome/bump-2025.11.1
2025.11.1
2025-11-24 17:29:53 -05:00
Keith Burzinski
88b898458b [bluetooth_proxy] Fix crash due to null pointer (#12084)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-11-24 21:25:49 +00:00
Jonathan Swoboda
9186144dcd Bump version to 2025.11.1 2025-11-24 16:24:38 -05:00
Jesse Hills
25bcd0ea25 [online_image] Fix some large PNGs causing watchdog timeout (#12025)
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
2025-11-24 16:24:38 -05:00
J. Nick Koston
50d08a2eba [esp_ldo,mipi_dsi,mipi_rgb] Fix dangling pointer bugs in mark_failed() (#12077) 2025-11-24 16:24:38 -05:00
J. Nick Koston
3a7a0c66ab [script][wait_until] Fix FIFO ordering and reentrancy bugs (#12049)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 16:24:38 -05:00
Jonathan Swoboda
83525b7a92 [core] Add support for passing yaml files to clean-all (#12039) 2025-11-24 16:24:38 -05:00
Jonathan Swoboda
f31f023c89 [esp32] Fix C2 builds (#12050) 2025-11-24 16:24:37 -05:00
J. Nick Koston
f8efefffaa [cst816][http_request] Fix status_set_error() dangling pointer bugs (#12033) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
d698083ede [jsn_sr04t] Fix model AJ_SR04M (#11992) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
11ba6440d7 [cst816][packet_transport][udp][wake_on_lan] Fix error messages (#12019) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
89ee37a2d5 [ltr501][ltr_als_ps] Rename enum to avoid collision with lwip defines (#12017) 2025-11-24 16:24:37 -05:00
J. Nick Koston
45b8c1e267 [network] Fix IPAddress constructor causing comparison failures and garbage output (#12005) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
fbe091f167 [graph] Fix legend border (#12000) 2025-11-24 16:24:37 -05:00
dependabot[bot]
e09656f20e Bump bleak from 1.1.1 to 2.0.0 (#12083)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 15:21:03 -06:00
Jesse Hills
eeb373fca9 [online_image] Fix some large PNGs causing watchdog timeout (#12025)
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
2025-11-25 09:15:30 +13:00
J. Nick Koston
97ba67f4ee [core] Deprecate unsafe const char* APIs in mark_failed() and status_set_error(), add LogString* overloads (#12021) 2025-11-24 13:45:56 -06:00
J. Nick Koston
909baf5e7a [prometheus] Use current_option() instead of deprecated .state for select entities (#12079) 2025-11-24 13:45:29 -06:00
J. Nick Koston
a0440603b7 [wifi] Use ESP-IDF IP formatting macros directly to eliminate heap allocations (#12078) 2025-11-24 13:45:06 -06:00
dependabot[bot]
e2cd0ccd0e Bump actions/create-github-app-token from 2.1.4 to 2.2.0 (#12081)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 13:44:43 -06:00
dependabot[bot]
378fc4120a Bump peter-evans/create-pull-request from 7.0.8 to 7.0.9 (#12082)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 13:44:27 -06:00
dependabot[bot]
0dd842744a Bump github/codeql-action from 4.31.4 to 4.31.5 (#12080)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 13:44:09 -06:00
J. Nick Koston
7a73a524b9 [logger] Eliminate strlen overhead on LibreTiny (#11938) 2025-11-24 12:21:09 -06:00
Kevin Ahrendt
d1a1bb446b [wifi] Add runtime power saving mode control (#11478)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-24 17:55:04 +00:00
J. Nick Koston
c146d92425 [api] Remove redundant socket pointer from APIFrameHelper (#11985) 2025-11-25 06:53:42 +13:00
J. Nick Koston
c888becfa7 [api] Optimize APINoiseContext memory usage by removing shared_ptr overhead (#11981) 2025-11-25 06:52:15 +13:00
Flo
09f3f62194 [api] Connected Condition - state_subscription_only flag (#11906)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-24 11:49:16 -06:00
Jordan Zucker
b820e67616 [prometheus] Add event and text base components metrics (#10240)
Co-authored-by: Jordan Zucker <jordan@Jordans-MacBook-Pro.local>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-24 11:42:07 -06:00
Sascha Ittner
d7da559885 [thermopro_ble] Add thermopro ble support (#11835)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-24 11:31:26 -06:00
Jonathan Swoboda
d7a197b3a3 [esp32] Use the IDF I2C implementation on Arduino (#12076) 2025-11-24 12:27:09 -05:00
Flo
66cda04664 [wifi] ap_active condition (#11852)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-11-24 11:19:38 -06:00
J. Nick Koston
0764f4da86 [esp_ldo,mipi_dsi,mipi_rgb] Fix dangling pointer bugs in mark_failed() (#12077) 2025-11-24 11:02:24 -06:00
J. Nick Koston
06815fe177 [script][wait_until] Fix FIFO ordering and reentrancy bugs (#12049)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 10:41:24 -06:00
J. Nick Koston
04ec6a6999 [api] Use stack buffer for MAC address in Noise handshake (#12072) 2025-11-24 10:23:31 -06:00
J. Nick Koston
737f23a0bd [light] Dynamically disable loop when idle to reduce CPU overhead (#11881) 2025-11-24 10:23:11 -06:00
J. Nick Koston
3c48e13c9f [ethernet] Conditionally compile manual_ip to save 24 bytes RAM (#11832) 2025-11-24 10:22:13 -06:00
J. Nick Koston
426734beef [web_server_base] Replace shared_ptr with unique_ptr for AsyncWebServer (#11984) 2025-11-24 10:22:01 -06:00
J. Nick Koston
056b4375eb [api] Reduce heap allocations in DeviceInfoResponse (#11952) 2025-11-24 10:21:47 -06:00
J. Nick Koston
1f0a5e1eea [logger] Reduce UART overhead on ESP32/ESP8266 and fix buffer truncation (#11927) 2025-11-24 10:21:32 -06:00
Jonathan Swoboda
8607a0881d [core] Add support for passing yaml files to clean-all (#12039) 2025-11-24 10:10:24 -05:00
James
b4b98505ba [mipi_dsi] add guition JC4880P443 display (#12068) 2025-11-24 21:05:02 +11:00
Jonathan Swoboda
60d687c2c6 [esp32] Fix C2 builds (#12050) 2025-11-23 23:31:14 -05:00
Jonathan Swoboda
5750f7fccb [ci] Fix test grouping (#12067) 2025-11-23 21:25:24 -06:00
Jonathan Swoboda
c91a9495e6 [ci] Fix filename (#12065) 2025-11-23 16:19:26 -05:00
Javier Peletier
f42b806889 [core] Fix error on invalid id extend/remove (#12064) 2025-11-24 08:03:13 +11:00
Jesse Hills
a5751b294f [api] Rename USE_API_SERVICES to USE_API_USER_DEFINED_ACTIONS (#12029) 2025-11-24 08:13:23 +13:00
Abílio Costa
3f6f2d7d65 [bm8563] Add bm8563 component (#11616)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-21 15:28:42 -05:00
Marko Draca
782aee92a7 [mcp3204] differential mode support (#7436)
Co-authored-by: marko <marko@>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-21 14:50:07 -05:00
Thomas Rupprecht
972b7e84fe [tests] Fix mipi_spi test board (#12031)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-21 08:38:44 -05:00
185 changed files with 3381 additions and 765 deletions

View File

@@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment

View File

@@ -26,7 +26,7 @@ jobs:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}

View File

@@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.11"

View File

@@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.11"

View File

@@ -45,7 +45,7 @@ jobs:
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.11"
- name: Set up Docker Buildx

View File

@@ -42,7 +42,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@@ -240,7 +240,7 @@ jobs:
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Restore Python virtual environment

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
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@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
category: "/language:${{matrix.language}}"

View File

@@ -62,7 +62,7 @@ jobs:
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.x"
- name: Build
@@ -94,7 +94,7 @@ jobs:
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.11"

View File

@@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: 3.13
@@ -41,7 +41,7 @@ jobs:
python script/run-in-env.py pre-commit run --all-files
- name: Commit changes
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org>

View File

@@ -72,6 +72,7 @@ esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/ble_nus/* @tomaszduda23
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
esphome/components/bm8563/* @abmantis
esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth
@@ -483,6 +484,7 @@ esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat
esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse
esphome/components/thermopro_ble/* @sittner
esphome/components/thermostat/* @kbx81
esphome/components/time/* @esphome/core
esphome/components/tinyusb/* @kbx81

View File

@@ -1319,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(

View File

@@ -87,7 +87,7 @@ void AbsoluteHumidityComponent::loop() {
break;
default:
this->publish_state(NAN);
this->status_set_error("Invalid saturation vapor pressure equation selection!");
this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!"));
return;
}
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

@@ -83,7 +83,7 @@ void AHT10Component::setup() {
void AHT10Component::restart_read_() {
if (this->read_count_ == AHT10_ATTEMPTS) {
this->read_count_ = 0;
this->status_set_error("Reading timed out");
this->status_set_error(LOG_STR("Reading timed out"));
return;
}
this->read_count_++;

View File

@@ -85,6 +85,7 @@ CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
CONF_LISTEN_BACKLOG = "listen_backlog"
CONF_MAX_SEND_QUEUE = "max_send_queue"
CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only"
def validate_encryption_key(value):
@@ -260,9 +261,9 @@ async def to_code(config):
cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
# Set USE_API_SERVICES if any services are enabled
# Set USE_API_USER_DEFINED_ACTIONS if any services are enabled
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
cg.add_define("USE_API_USER_DEFINED_ACTIONS")
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
if config[CONF_CUSTOM_SERVICES]:
@@ -537,9 +538,24 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
return var
@automation.register_condition("api.connected", APIConnectedCondition, {})
API_CONNECTED_CONDITION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(APIServer),
cv.Optional(CONF_STATE_SUBSCRIPTION_ONLY, default=False): cv.templatable(
cv.boolean
),
}
)
@automation.register_condition(
"api.connected", APIConnectedCondition, API_CONNECTED_CONDITION_SCHEMA
)
async def api_connected_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg)
var = cg.new_Pvariable(condition_id, template_arg)
templ = await cg.templatable(config[CONF_STATE_SUBSCRIPTION_ONLY], args, cg.bool_)
cg.add(var.set_state_subscription_only(templ))
return var
def FILTER_SOURCE_FILES() -> list[str]:

View File

@@ -518,7 +518,7 @@ message ListEntitiesLightResponse {
bool legacy_supports_color_temperature = 8 [deprecated=true];
float min_mireds = 9;
float max_mireds = 10;
repeated string effects = 11;
repeated string effects = 11 [(container_pointer_no_template) = "FixedVector<const char *>"];
bool disabled_by_default = 13;
string icon = 14 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 15;
@@ -855,21 +855,21 @@ enum ServiceArgType {
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
}
message ListEntitiesServicesArgument {
option (ifdef) = "USE_API_SERVICES";
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
string name = 1;
ServiceArgType type = 2;
}
message ListEntitiesServicesResponse {
option (id) = 41;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_SERVICES";
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
string name = 1;
fixed32 key = 2;
repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true];
}
message ExecuteServiceArgument {
option (ifdef) = "USE_API_SERVICES";
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
bool bool_ = 1;
int32 legacy_int = 2;
float float_ = 3;
@@ -885,7 +885,7 @@ message ExecuteServiceRequest {
option (id) = 42;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
option (ifdef) = "USE_API_SERVICES";
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
fixed32 key = 1;
repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true];

View File

@@ -90,8 +90,8 @@ static const int CAMERA_STOP_STREAM = 5000;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto noise_ctx = parent->get_noise_ctx();
if (noise_ctx->has_psk()) {
auto &noise_ctx = parent->get_noise_ctx();
if (noise_ctx.has_psk()) {
this->helper_ =
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
} else {
@@ -484,12 +484,16 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
msg.min_mireds = traits.get_min_mireds();
msg.max_mireds = traits.get_max_mireds();
}
FixedVector<const char *> effects_list;
if (light->supports_effects()) {
msg.effects.emplace_back("None");
for (auto *effect : light->get_effects()) {
msg.effects.emplace_back(effect->get_name());
auto &light_effects = light->get_effects();
effects_list.init(light_effects.size() + 1);
effects_list.push_back("None");
for (auto *effect : light_effects) {
effects_list.push_back(effect->get_name());
}
}
msg.effects = &effects_list;
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1451,8 +1455,11 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_AREAS
resp.set_suggested_area(StringRef(App.get_area()));
#endif
// mac_address must store temporary string - will be valid during send_message call
std::string mac_address = get_mac_address_pretty();
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char mac_address[18];
uint8_t mac[6];
get_mac_address_raw(mac);
format_mac_addr_upper(mac, mac_address);
resp.set_mac_address(StringRef(mac_address));
resp.set_esphome_version(ESPHOME_VERSION_REF);
@@ -1493,8 +1500,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#endif
#ifdef USE_BLUETOOTH_PROXY
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
// bt_mac must store temporary string - will be valid during send_message call
std::string bluetooth_mac = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char bluetooth_mac[18];
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -1541,7 +1549,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
}
}
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;
for (auto *service : this->parent_->get_user_services()) {

View File

@@ -221,7 +221,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void execute_service(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE

View File

@@ -84,9 +84,7 @@ class APIFrameHelper {
public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: socket_owned_(std::move(socket)), client_info_(client_info) {
socket_ = socket_owned_.get();
}
: socket_(std::move(socket)), client_info_(client_info) {}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
@@ -149,9 +147,8 @@ class APIFrameHelper {
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
const std::string &info, StateEnum &state, StateEnum failed_state);
// Pointers first (4 bytes each)
socket::Socket *socket_{nullptr};
std::unique_ptr<socket::Socket> socket_owned_;
// Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
std::unique_ptr<socket::Socket> socket_;
// Common state enum for all frame helpers
// Note: Not all states are used by all implementations

View File

@@ -239,12 +239,13 @@ APIError APINoiseFrameHelper::state_action_() {
}
if (state_ == State::SERVER_HELLO) {
// send server hello
constexpr size_t mac_len = 13; // 12 hex chars + null terminator
const std::string &name = App.get_name();
const std::string &mac = get_mac_address();
char mac[mac_len];
get_mac_address_into_buffer(mac);
// Calculate positions and sizes
size_t name_len = name.size() + 1; // including null terminator
size_t mac_len = mac.size() + 1; // including null terminator
size_t name_offset = 1;
size_t mac_offset = name_offset + name_len;
size_t total_size = 1 + name_len + mac_len;
@@ -257,7 +258,7 @@ APIError APINoiseFrameHelper::state_action_() {
// node name, terminated by null byte
std::memcpy(msg.get() + name_offset, name.c_str(), name_len);
// node mac, terminated by null byte
std::memcpy(msg.get() + mac_offset, mac.c_str(), mac_len);
std::memcpy(msg.get() + mac_offset, mac, mac_len);
aerr = write_frame_(msg.get(), total_size);
if (aerr != APIError::OK)
@@ -527,7 +528,7 @@ APIError APINoiseFrameHelper::init_handshake_() {
if (aerr != APIError::OK)
return aerr;
const auto &psk = ctx_->get_psk();
const auto &psk = this->ctx_.get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
APIError::HANDSHAKESTATE_SETUP_FAILED);

View File

@@ -9,9 +9,8 @@ namespace esphome::api {
class APINoiseFrameHelper final : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(std::move(ctx)) {
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(ctx) {
// Noise header structure:
// Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian)
@@ -41,8 +40,8 @@ class APINoiseFrameHelper final : public APIFrameHelper {
NoiseCipherState *send_cipher_{nullptr};
NoiseCipherState *recv_cipher_{nullptr};
// Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer)
std::shared_ptr<APINoiseContext> ctx_;
// Reference to noise context (4 bytes on 32-bit)
APINoiseContext &ctx_;
// Vector (12 bytes on 32-bit)
std::vector<uint8_t> prologue_;

View File

@@ -476,8 +476,8 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_float(9, this->min_mireds);
buffer.encode_float(10, this->max_mireds);
for (auto &it : this->effects) {
buffer.encode_string(11, it, true);
for (const char *it : *this->effects) {
buffer.encode_string(11, it, strlen(it), true);
}
buffer.encode_bool(13, this->disabled_by_default);
#ifdef USE_ENTITY_ICON
@@ -499,9 +499,9 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const {
}
size.add_float(1, this->min_mireds);
size.add_float(1, this->max_mireds);
if (!this->effects.empty()) {
for (const auto &it : this->effects) {
size.add_length_force(1, it.size());
if (!this->effects->empty()) {
for (const char *it : *this->effects) {
size.add_length_force(1, strlen(it));
}
}
size.add_bool(1, this->disabled_by_default);
@@ -995,7 +995,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
return true;
}
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_);
buffer.encode_uint32(2, static_cast<uint32_t>(this->type));

View File

@@ -63,7 +63,7 @@ enum LogLevel : uint32_t {
LOG_LEVEL_VERBOSE = 6,
LOG_LEVEL_VERY_VERBOSE = 7,
};
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
enum ServiceArgType : uint32_t {
SERVICE_ARG_TYPE_BOOL = 0,
SERVICE_ARG_TYPE_INT = 1,
@@ -793,7 +793,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage {
const light::ColorModeMask *supported_color_modes{};
float min_mireds{0.0f};
float max_mireds{0.0f};
std::vector<std::string> effects{};
const FixedVector<const char *> *effects{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1239,7 +1239,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
class ListEntitiesServicesArgument final : public ProtoMessage {
public:
StringRef name_ref_{};

View File

@@ -206,7 +206,7 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
return "UNKNOWN";
}
}
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
switch (value) {
case enums::SERVICE_ARG_TYPE_BOOL:
@@ -924,7 +924,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
}
dump_field(out, "min_mireds", this->min_mireds);
dump_field(out, "max_mireds", this->max_mireds);
for (const auto &it : this->effects) {
for (const auto &it : *this->effects) {
dump_field(out, "effects", it, 4);
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1177,7 +1177,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("\n");
}
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
dump_field(out, "name", this->name_ref_);

View File

@@ -193,7 +193,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
case ExecuteServiceRequest::MESSAGE_TYPE: {
ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size);
@@ -670,7 +670,7 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
this->subscribe_home_assistant_states(msg);
}
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); }
#endif
#ifdef USE_API_NOISE

View File

@@ -79,7 +79,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#endif
@@ -239,7 +239,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
#ifdef USE_API_NOISE
@@ -368,7 +368,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE

View File

@@ -227,8 +227,8 @@ void APIServer::dump_config() {
" Max connections: %u",
network::get_use_address(), this->port_, this->listen_backlog_, this->max_connections_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) {
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_.has_psk()));
if (!this->noise_ctx_.has_psk()) {
ESP_LOGCONFIG(TAG, " Supports encryption: YES");
}
#else
@@ -493,7 +493,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
ESP_LOGW(TAG, "Key set in YAML");
return false;
#else
auto &old_psk = this->noise_ctx_->get_psk();
auto &old_psk = this->noise_ctx_.get_psk();
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
ESP_LOGW(TAG, "New PSK matches old");
return true;
@@ -528,7 +528,18 @@ void APIServer::request_time() {
}
#endif
bool APIServer::is_connected() const { return !this->clients_.empty(); }
bool APIServer::is_connected(bool state_subscription_only) const {
if (!state_subscription_only) {
return !this->clients_.empty();
}
for (const auto &client : this->clients_) {
if (client->flags_.state_subscription) {
return true;
}
}
return false;
}
void APIServer::on_shutdown() {
this->shutting_down_ = true;

View File

@@ -12,7 +12,7 @@
#include "esphome/core/log.h"
#include "list_entities.h"
#include "subscribe_state.h"
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
#include "user_services.h"
#endif
@@ -54,8 +54,8 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
bool clear_noise_psk(bool make_active = true);
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); }
APINoiseContext &get_noise_ctx() { return this->noise_ctx_; }
#endif // USE_API_NOISE
void handle_disconnect(APIConnection *conn);
@@ -124,7 +124,7 @@ class APIServer : public Component, public Controller {
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
this->user_services_.assign(services);
}
@@ -150,7 +150,7 @@ class APIServer : public Component, public Controller {
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
#endif
bool is_connected() const;
bool is_connected(bool state_subscription_only = false) const;
#ifdef USE_API_HOMEASSISTANT_STATES
struct HomeAssistantStateSubscription {
@@ -166,7 +166,7 @@ class APIServer : public Component, public Controller {
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
#endif
@@ -206,7 +206,7 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_HOMEASSISTANT_STATES
std::vector<HomeAssistantStateSubscription> state_subs_;
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
std::vector<UserServiceDescriptor *> user_services_;
#endif
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
@@ -228,7 +228,7 @@ class APIServer : public Component, public Controller {
// 7 bytes used, 1 byte padding
#ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
APINoiseContext noise_ctx_;
ESPPreferenceObject noise_pref_;
#endif // USE_API_NOISE
};
@@ -236,8 +236,11 @@ class APIServer : public Component, public Controller {
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
TEMPLATABLE_VALUE(bool, state_subscription_only)
public:
bool check(const Ts &...x) override { return global_api_server->is_connected(); }
bool check(const Ts &...x) override {
return global_api_server->is_connected(this->state_subscription_only_.value(x...));
}
};
} // namespace esphome::api

View File

@@ -3,12 +3,12 @@
#include <map>
#include "api_server.h"
#ifdef USE_API
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
#include "user_services.h"
#endif
namespace esphome::api {
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
@@ -21,7 +21,7 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
T *obj_;
void (T::*callback_)(Ts...);
};
#endif // USE_API_SERVICES
#endif // USE_API_USER_DEFINED_ACTIONS
class CustomAPIDevice {
public:
@@ -49,7 +49,7 @@ class CustomAPIDevice {
* @param name The name of the service to register.
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
*/
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
@@ -90,7 +90,7 @@ class CustomAPIDevice {
* @param callback The member function to call when the service is triggered.
* @param name The name of the arguments for the service, must match the arguments of the function.
*/
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT

View File

@@ -82,7 +82,7 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response();
return this->client_->send_message(resp, ListEntitiesServicesResponse::MESSAGE_TYPE);

View File

@@ -43,7 +43,7 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override;
#endif
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
bool on_service(UserServiceDescriptor *service) override;
#endif
#ifdef USE_CAMERA

View File

@@ -7,7 +7,7 @@
#include "esphome/core/automation.h"
#include "api_pb2.h"
#ifdef USE_API_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
namespace esphome::api {
class UserServiceDescriptor {
@@ -122,4 +122,4 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
};
} // namespace esphome::api
#endif // USE_API_SERVICES
#endif // USE_API_USER_DEFINED_ACTIONS

View File

@@ -23,7 +23,7 @@ void BH1900NUXSensor::setup() {
i2c::ErrorCode result_code =
this->write_register(SOFT_RESET_REG, &SOFT_RESET_PAYLOAD, 1); // Software Reset to check communication
if (result_code != i2c::ERROR_OK) {
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
}

View File

@@ -122,16 +122,19 @@ 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...);
std::vector<uint8_t> value;
bool result;
if (this->len_ >= 0) {
// Static mode: copy from flash to vector
value.assign(this->value_.data, this->value_.data + this->len_);
// Static mode: write directly from flash pointer
result = this->write(this->value_.data, this->len_);
} else {
// Template mode: call function
value = this->value_.func(x...);
// Template mode: call function and write the vector
std::vector<uint8_t> value = this->value_.func(x...);
result = this->write(value);
}
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!write(value))
if (!result)
this->play_next_(x...);
}
@@ -144,15 +147,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 std::vector<uint8_t> &value) {
bool write(const uint8_t *data, size_t len) {
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", 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);
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);
if (err != ESP_OK) {
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
return false;
@@ -160,6 +163,8 @@ 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) {

View File

@@ -130,11 +130,13 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
return flags;
}
std::string get_bluetooth_mac_address_pretty() {
void get_bluetooth_mac_address_pretty(std::span<char, 18> output) {
const uint8_t *mac = esp_bt_dev_get_address();
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
if (mac != nullptr) {
format_mac_addr_upper(mac, output.data());
} else {
output[0] = '\0';
}
}
protected:

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@abmantis"]

View File

@@ -0,0 +1,198 @@
#include "bm8563.h"
#include "esphome/core/log.h"
namespace esphome::bm8563 {
static const char *const TAG = "bm8563";
static constexpr uint8_t CONTROL_STATUS_1_REG = 0x00;
static constexpr uint8_t CONTROL_STATUS_2_REG = 0x01;
static constexpr uint8_t TIME_FIRST_REG = 0x02; // Time uses reg 2, 3, 4
static constexpr uint8_t DATE_FIRST_REG = 0x05; // Date uses reg 5, 6, 7, 8
static constexpr uint8_t TIMER_CONTROL_REG = 0x0E;
static constexpr uint8_t TIMER_VALUE_REG = 0x0F;
static constexpr uint8_t CLOCK_1_HZ = 0x82;
static constexpr uint8_t CLOCK_1_60_HZ = 0x83;
// Maximum duration: 255 minutes (at 1/60 Hz) = 15300 seconds
static constexpr uint32_t MAX_TIMER_DURATION_S = 255 * 60;
void BM8563::setup() {
if (!this->write_byte_16(CONTROL_STATUS_1_REG, 0)) {
this->mark_failed();
return;
}
}
void BM8563::update() { this->read_time(); }
void BM8563::dump_config() {
ESP_LOGCONFIG(TAG, "BM8563:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
}
void BM8563::start_timer(uint32_t duration_s) {
this->clear_irq_();
this->set_timer_irq_(duration_s);
}
void BM8563::write_time() {
auto now = time::RealTimeClock::utcnow();
if (!now.is_valid()) {
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
return;
}
ESP_LOGD(TAG, "Writing time: %i-%i-%i %i, %i:%i:%i", now.year, now.month, now.day_of_month, now.day_of_week, now.hour,
now.minute, now.second);
this->set_time_(now);
this->set_date_(now);
}
void BM8563::read_time() {
ESPTime rtc_time;
this->get_time_(rtc_time);
this->get_date_(rtc_time);
rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid
ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second);
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
}
uint8_t BM8563::bcd2_to_byte_(uint8_t value) {
const uint8_t tmp = ((value & 0xF0) >> 0x4) * 10;
return tmp + (value & 0x0F);
}
uint8_t BM8563::byte_to_bcd2_(uint8_t value) {
const uint8_t bcdhigh = value / 10;
value -= bcdhigh * 10;
return (bcdhigh << 4) | value;
}
void BM8563::get_time_(ESPTime &time) {
uint8_t buf[3] = {0};
this->read_register(TIME_FIRST_REG, buf, 3);
time.second = this->bcd2_to_byte_(buf[0] & 0x7f);
time.minute = this->bcd2_to_byte_(buf[1] & 0x7f);
time.hour = this->bcd2_to_byte_(buf[2] & 0x3f);
}
void BM8563::set_time_(const ESPTime &time) {
uint8_t buf[3] = {this->byte_to_bcd2_(time.second), this->byte_to_bcd2_(time.minute), this->byte_to_bcd2_(time.hour)};
this->write_register_(TIME_FIRST_REG, buf, 3);
}
void BM8563::get_date_(ESPTime &time) {
uint8_t buf[4] = {0};
this->read_register(DATE_FIRST_REG, buf, sizeof(buf));
time.day_of_month = this->bcd2_to_byte_(buf[0] & 0x3f);
time.day_of_week = this->bcd2_to_byte_(buf[1] & 0x07);
time.month = this->bcd2_to_byte_(buf[2] & 0x1f);
uint8_t year_byte = this->bcd2_to_byte_(buf[3] & 0xff);
if (buf[2] & 0x80) {
time.year = 1900 + year_byte;
} else {
time.year = 2000 + year_byte;
}
}
void BM8563::set_date_(const ESPTime &time) {
uint8_t buf[4] = {
this->byte_to_bcd2_(time.day_of_month),
this->byte_to_bcd2_(time.day_of_week),
this->byte_to_bcd2_(time.month),
this->byte_to_bcd2_(time.year % 100),
};
if (time.year < 2000) {
buf[2] = buf[2] | 0x80;
}
this->write_register_(DATE_FIRST_REG, buf, 4);
}
void BM8563::write_byte_(uint8_t reg, uint8_t value) {
if (!this->write_byte(reg, value)) {
ESP_LOGE(TAG, "Failed to write byte 0x%02X with value 0x%02X", reg, value);
}
}
void BM8563::write_register_(uint8_t reg, const uint8_t *data, size_t len) {
if (auto error = this->write_register(reg, data, len); error != i2c::ErrorCode::NO_ERROR) {
ESP_LOGE(TAG, "Failed to write register 0x%02X with %zu bytes", reg, len);
}
}
optional<uint8_t> BM8563::read_register_(uint8_t reg) {
uint8_t data;
if (auto error = this->read_register(reg, &data, 1); error != i2c::ErrorCode::NO_ERROR) {
ESP_LOGE(TAG, "Failed to read register 0x%02X", reg);
return {};
}
return data;
}
void BM8563::set_timer_irq_(uint32_t duration_s) {
ESP_LOGI(TAG, "Timer Duration: %u s", duration_s);
if (duration_s > MAX_TIMER_DURATION_S) {
ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S);
return;
}
if (duration_s > 255) {
uint8_t duration_minutes = duration_s / 60;
this->write_byte_(TIMER_VALUE_REG, duration_minutes);
this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_60_HZ);
} else {
this->write_byte_(TIMER_VALUE_REG, duration_s);
this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_HZ);
}
auto maybe_ctrl_status_2 = this->read_register_(CONTROL_STATUS_2_REG);
if (!maybe_ctrl_status_2.has_value()) {
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
return;
}
uint8_t ctrl_status_2_reg_value = maybe_ctrl_status_2.value();
ctrl_status_2_reg_value |= (1 << 0);
ctrl_status_2_reg_value &= ~(1 << 7);
this->write_byte_(CONTROL_STATUS_2_REG, ctrl_status_2_reg_value);
}
void BM8563::clear_irq_() {
auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG);
if (!maybe_data.has_value()) {
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
return;
}
uint8_t data = maybe_data.value();
this->write_byte_(CONTROL_STATUS_2_REG, data & 0xf3);
}
void BM8563::disable_irq_() {
this->clear_irq_();
auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG);
if (!maybe_data.has_value()) {
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
return;
}
uint8_t data = maybe_data.value();
this->write_byte_(CONTROL_STATUS_2_REG, data & 0xfc);
}
} // namespace esphome::bm8563

View File

@@ -0,0 +1,57 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome::bm8563 {
class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
void write_time();
void read_time();
void start_timer(uint32_t duration_s);
private:
void get_time_(ESPTime &time);
void get_date_(ESPTime &time);
void set_time_(const ESPTime &time);
void set_date_(const ESPTime &time);
void set_timer_irq_(uint32_t duration_s);
void clear_irq_();
void disable_irq_();
void write_byte_(uint8_t reg, uint8_t value);
void write_register_(uint8_t reg, const uint8_t *data, size_t len);
optional<uint8_t> read_register_(uint8_t reg);
uint8_t bcd2_to_byte_(uint8_t value);
uint8_t byte_to_bcd2_(uint8_t value);
};
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<BM8563> {
public:
void play(const Ts &...x) override { this->parent_->write_time(); }
};
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<BM8563> {
public:
void play(const Ts &...x) override { this->parent_->read_time(); }
};
template<typename... Ts> class TimerAction : public Action<Ts...>, public Parented<BM8563> {
public:
TEMPLATABLE_VALUE(uint32_t, duration)
void play(const Ts &...x) override {
auto duration = this->duration_.value(x...);
this->parent_->start_timer(duration);
}
};
} // namespace esphome::bm8563

View File

@@ -0,0 +1,80 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, time
import esphome.config_validation as cv
from esphome.const import CONF_DURATION, CONF_ID
DEPENDENCIES = ["i2c"]
I2C_ADDR = 0x51
bm8563_ns = cg.esphome_ns.namespace("bm8563")
BM8563 = bm8563_ns.class_("BM8563", time.RealTimeClock, i2c.I2CDevice)
WriteAction = bm8563_ns.class_("WriteAction", automation.Action)
ReadAction = bm8563_ns.class_("ReadAction", automation.Action)
TimerAction = bm8563_ns.class_("TimerAction", automation.Action)
CONFIG_SCHEMA = (
time.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BM8563),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(I2C_ADDR))
)
@automation.register_action(
"bm8563.write_time",
WriteAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(BM8563),
}
),
)
async def bm8563_write_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"bm8563.start_timer",
TimerAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(BM8563),
cv.Required(CONF_DURATION): cv.templatable(cv.positive_time_period_seconds),
}
),
)
async def bm8563_start_timer_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_DURATION], args, cg.uint32)
cg.add(var.set_duration(template_))
return var
@automation.register_action(
"bm8563.read_time",
ReadAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(BM8563),
}
),
)
async def bm8563_read_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await time.register_time(var, config)

View File

@@ -100,18 +100,18 @@ void BME280Component::setup() {
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
if (chip_id != 0x60) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
this->mark_failed(LOG_STR(BME280_ERROR_WRONG_CHIP_ID));
return;
}
// Send a soft reset.
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
this->mark_failed("Reset failed");
this->mark_failed(LOG_STR("Reset failed"));
return;
}
// Wait until the NVM data has finished loading.
@@ -120,12 +120,12 @@ void BME280Component::setup() {
do { // NOLINT
delay(2);
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register");
this->mark_failed(LOG_STR("Error reading status register"));
return;
}
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
if (status & BME280_STATUS_IM_UPDATE) {
this->mark_failed("Timeout loading NVM");
this->mark_failed(LOG_STR("Timeout loading NVM"));
return;
}
@@ -153,26 +153,26 @@ void BME280Component::setup() {
uint8_t humid_control_val = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
this->mark_failed("Read humidity control");
this->mark_failed(LOG_STR("Read humidity control"));
return;
}
humid_control_val &= ~0b00000111;
humid_control_val |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
this->mark_failed("Write humidity control");
this->mark_failed(LOG_STR("Write humidity control"));
return;
}
uint8_t config_register = 0;
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config");
this->mark_failed(LOG_STR("Read config"));
return;
}
config_register &= ~0b11111100;
config_register |= 0b101 << 5; // 1000 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config");
this->mark_failed(LOG_STR("Write config"));
return;
}
}

View File

@@ -65,23 +65,23 @@ void BMP280Component::setup() {
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
if (chip_id != 0x58) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
this->mark_failed(LOG_STR(BMP280_ERROR_WRONG_CHIP_ID));
return;
}
// Send a soft reset.
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed("Reset failed");
this->mark_failed(LOG_STR("Reset failed"));
return;
}
// Wait until the NVM data has finished loading.
@@ -90,12 +90,12 @@ void BMP280Component::setup() {
do {
delay(2);
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register");
this->mark_failed(LOG_STR("Error reading status register"));
return;
}
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
if (status & BMP280_STATUS_IM_UPDATE) {
this->mark_failed("Timeout loading NVM");
this->mark_failed(LOG_STR("Timeout loading NVM"));
return;
}
@@ -116,14 +116,14 @@ void BMP280Component::setup() {
uint8_t config_register = 0;
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config");
this->mark_failed(LOG_STR("Read config"));
return;
}
config_register &= ~0b11111100;
config_register |= 0b000 << 5; // 0.5 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config");
this->mark_failed(LOG_STR("Write config"));
return;
}
}

View File

@@ -8,7 +8,7 @@ Camera *Camera::global_camera = nullptr;
Camera::Camera() {
if (global_camera != nullptr) {
this->status_set_error("Multiple cameras are configured, but only one is supported.");
this->status_set_error(LOG_STR("Multiple cameras are configured, but only one is supported."));
this->mark_failed();
return;
}

View File

@@ -19,13 +19,14 @@ void CST816Touchscreen::continue_setup_() {
case CST816T_CHIP_ID:
break;
default:
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_));
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
this->status_set_error(LOG_STR("Unknown chip ID"));
this->mark_failed();
return;
}
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
} else if (!this->skip_probe_) {
this->status_set_error("Failed to read chip id");
this->status_set_error(LOG_STR("Failed to read chip id"));
this->mark_failed();
return;
}

View File

@@ -22,7 +22,7 @@ const char *EPaperBase::epaper_state_to_string_() {
void EPaperBase::setup() {
if (!this->init_buffer_(this->buffer_length_)) {
this->mark_failed("Failed to initialise buffer");
this->mark_failed(LOG_STR("Failed to initialise buffer"));
return;
}
this->setup_pins_();
@@ -246,7 +246,7 @@ void EPaperBase::initialise_() {
auto length = this->init_sequence_length_;
while (index != length) {
if (length - index < 2) {
this->mark_failed("Malformed init sequence");
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
const uint8_t cmd = sequence[index++];

View File

@@ -854,6 +854,10 @@ 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_define("ESPHOME_BOARD", config[CONF_BOARD])
@@ -883,6 +887,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",

View 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"]

View File

@@ -256,29 +256,38 @@ bool ESP32BLE::ble_setup_() {
}
#endif
std::string name;
if (this->name_.has_value()) {
name = this->name_.value();
const char *device_name;
std::string name_with_suffix;
if (this->name_ != nullptr) {
if (App.is_name_add_mac_suffix_enabled()) {
// MAC address length: 12 hex chars + null terminator
constexpr size_t mac_address_len = 13;
// MAC address suffix length (last 6 characters of 12-char MAC address string)
constexpr size_t mac_address_suffix_len = 6;
const std::string mac_addr = get_mac_address();
const char *mac_suffix_ptr = mac_addr.c_str() + mac_address_suffix_len;
name = make_name_with_suffix(name, '-', mac_suffix_ptr, mac_address_suffix_len);
char mac_addr[mac_address_len];
get_mac_address_into_buffer(mac_addr);
const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len;
name_with_suffix =
make_name_with_suffix(this->name_, strlen(this->name_), '-', mac_suffix_ptr, mac_address_suffix_len);
device_name = name_with_suffix.c_str();
} else {
device_name = this->name_;
}
} else {
name = App.get_name();
if (name.length() > 20) {
name_with_suffix = App.get_name();
if (name_with_suffix.length() > 20) {
if (App.is_name_add_mac_suffix_enabled()) {
// Keep first 13 chars and last 7 chars (MAC suffix), remove middle
name.erase(13, name.length() - 20);
name_with_suffix.erase(13, name_with_suffix.length() - 20);
} else {
name.resize(20);
name_with_suffix.resize(20);
}
}
device_name = name_with_suffix.c_str();
}
err = esp_ble_gap_set_device_name(name.c_str());
err = esp_ble_gap_set_device_name(device_name);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err);
return false;

View File

@@ -112,7 +112,7 @@ class ESP32BLE : public Component {
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
void set_name(const std::string &name) { this->name_ = name; }
void set_name(const char *name) { this->name_ = name; }
#ifdef USE_ESP32_BLE_ADVERTISING
void advertising_start();
@@ -191,13 +191,11 @@ class ESP32BLE : public Component {
esphome::LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
esphome::EventPool<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_event_pool_;
// optional<string> (typically 16+ bytes on 32-bit, aligned to 4 bytes)
optional<std::string> name_;
// 4-byte aligned members
#ifdef USE_ESP32_BLE_ADVERTISING
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
#endif
const char *name_{nullptr}; // 4 bytes (pointer to string literal in flash)
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
uint32_t advertising_cycle_time_{}; // 4 bytes

View File

@@ -88,7 +88,7 @@ void Esp32HostedUpdate::perform(bool force) {
hasher.add(this->firmware_data_, this->firmware_size_);
hasher.calculate();
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
this->status_set_error("SHA256 verification failed");
this->status_set_error(LOG_STR("SHA256 verification failed"));
this->publish_state();
return;
}
@@ -105,7 +105,7 @@ void Esp32HostedUpdate::perform(bool force) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
this->state_ = prev_state;
this->status_set_error("Failed to begin OTA");
this->status_set_error(LOG_STR("Failed to begin OTA"));
this->publish_state();
return;
}
@@ -121,7 +121,7 @@ void Esp32HostedUpdate::perform(bool force) {
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
esp_hosted_slave_ota_end(); // NOLINT
this->state_ = prev_state;
this->status_set_error("Failed to write OTA data");
this->status_set_error(LOG_STR("Failed to write OTA data"));
this->publish_state();
return;
}
@@ -134,7 +134,7 @@ void Esp32HostedUpdate::perform(bool force) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
this->state_ = prev_state;
this->status_set_error("Failed to end OTA");
this->status_set_error(LOG_STR("Failed to end OTA"));
this->publish_state();
return;
}
@@ -144,7 +144,7 @@ void Esp32HostedUpdate::perform(bool force) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err));
this->state_ = prev_state;
this->status_set_error("Failed to activate OTA");
this->status_set_error(LOG_STR("Failed to activate OTA"));
this->publish_state();
return;
}

View File

@@ -7,8 +7,6 @@
extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16];
extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16];
namespace esphome {
namespace esp8266 {} // namespace esp8266
} // namespace esphome
namespace esphome::esp8266 {} // namespace esphome::esp8266
#endif // USE_ESP8266

View File

@@ -3,8 +3,7 @@
#include "gpio.h"
#include "esphome/core/log.h"
namespace esphome {
namespace esp8266 {
namespace esphome::esp8266 {
static const char *const TAG = "esp8266";
@@ -110,9 +109,11 @@ void ESP8266GPIOPin::digital_write(bool value) {
}
void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
} // namespace esp8266
} // namespace esphome::esp8266
using namespace esp8266;
namespace esphome {
using esp8266::ISRPinArg;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);

View File

@@ -5,8 +5,7 @@
#include "esphome/core/hal.h"
#include <Arduino.h>
namespace esphome {
namespace esp8266 {
namespace esphome::esp8266 {
class ESP8266GPIOPin : public InternalGPIOPin {
public:
@@ -33,7 +32,6 @@ class ESP8266GPIOPin : public InternalGPIOPin {
gpio::Flags flags_{};
};
} // namespace esp8266
} // namespace esphome
} // namespace esphome::esp8266
#endif // USE_ESP8266

View File

@@ -15,24 +15,24 @@ extern "C" {
#include <cstring>
#include <memory>
namespace esphome {
namespace esp8266 {
namespace esphome::esp8266 {
static const char *const TAG = "esp8266.preferences";
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
#ifdef USE_ESP8266_PREFERENCES_FLASH
static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
#else
static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
#endif
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
@@ -284,10 +284,10 @@ void setup_preferences() {
}
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
} // namespace esp8266
} // namespace esphome::esp8266
namespace esphome {
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esphome
#endif // USE_ESP8266

View File

@@ -2,13 +2,11 @@
#ifdef USE_ESP8266
namespace esphome {
namespace esp8266 {
namespace esphome::esp8266 {
void setup_preferences();
void preferences_prevent_write(bool prevent);
} // namespace esp8266
} // namespace esphome
} // namespace esphome::esp8266
#endif // USE_ESP8266

View File

@@ -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(LOG_STR("Failed to acquire LDO channel"));
} else {
ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_);
}

View File

@@ -383,6 +383,7 @@ 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

View File

@@ -553,11 +553,14 @@ 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 {
} else
#endif
{
info.ip.addr = 0;
info.gw.addr = 0;
info.netmask.addr = 0;
@@ -578,6 +581,7 @@ 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()) {
@@ -590,7 +594,9 @@ void EthernetComponent::start_connect_() {
d = this->manual_ip_->dns2;
dns_setserver(1, &d);
}
} else {
} else
#endif
{
err = esp_netif_dhcpc_start(this->eth_netif_);
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error");
@@ -688,7 +694,9 @@ 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.

View File

@@ -82,7 +82,9 @@ 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();
@@ -137,7 +139,9 @@ 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)

View File

@@ -36,20 +36,20 @@ void GDK101Component::setup() {
uint8_t data[2];
// first, reset the sensor
if (!this->reset_sensor_(data)) {
this->status_set_error("Reset failed!");
this->status_set_error(LOG_STR("Reset failed!"));
this->mark_failed();
return;
}
// sensor should acknowledge success of the reset procedure
if (data[0] != 1) {
this->status_set_error("Reset not acknowledged!");
this->status_set_error(LOG_STR("Reset not acknowledged!"));
this->mark_failed();
return;
}
delay(10);
// read firmware version
if (!this->read_fw_version_(data)) {
this->status_set_error("Failed to read firmware version");
this->status_set_error(LOG_STR("Failed to read firmware version"));
this->mark_failed();
return;
}

View File

@@ -79,13 +79,13 @@ void GT911Touchscreen::setup_internal_() {
}
}
if (err != i2c::ERROR_OK) {
this->mark_failed("Calibration error");
this->mark_failed(LOG_STR("Calibration error"));
return;
}
}
if (err != i2c::ERROR_OK) {
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
this->setup_done_ = true;

View File

@@ -29,7 +29,7 @@ void HttpRequestUpdate::setup() {
this->publish_state();
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
this->state_ = update::UPDATE_STATE_AVAILABLE;
this->status_set_error("Failed to install firmware");
this->status_set_error(LOG_STR("Failed to install firmware"));
this->publish_state();
}
});
@@ -49,18 +49,19 @@ 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); });
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("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); });
this_update->defer(
[this_update]() { this_update->status_set_error(LOG_STR("Failed to allocate memory for manifest")); });
container->end();
UPDATE_RETURN;
}
@@ -121,9 +122,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); });
this_update->defer([this_update]() { this_update->status_set_error(LOG_STR("Failed to parse manifest JSON")); });
UPDATE_RETURN;
}

View File

@@ -47,18 +47,20 @@ MULTI_CONF = True
def _bus_declare_type(value):
if CORE.is_esp32:
return cv.declare_id(IDFI2CBus)(value)
if CORE.using_arduino:
return cv.declare_id(ArduinoI2CBus)(value)
if CORE.using_esp_idf:
return cv.declare_id(IDFI2CBus)(value)
if CORE.using_zephyr:
return cv.declare_id(ZephyrI2CBus)(value)
raise NotImplementedError
def validate_config(config):
if CORE.using_esp_idf:
return cv.require_framework_version(esp_idf=cv.Version(5, 4, 2))(config)
if CORE.is_esp32:
return cv.require_framework_version(
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
)(config)
return config
@@ -67,12 +69,12 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): _bus_declare_type,
cv.Optional(CONF_SDA, default="SDA"): pins.internal_gpio_pin_number,
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
cv.only_with_esp_idf, cv.boolean
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
cv.only_on_esp32, cv.boolean
),
cv.Optional(CONF_SCL, default="SCL"): pins.internal_gpio_pin_number,
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
cv.only_with_esp_idf, cv.boolean
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
cv.only_on_esp32, cv.boolean
),
cv.SplitDefault(
CONF_FREQUENCY,
@@ -151,7 +153,7 @@ async def to_code(config):
cg.add(var.set_scan(config[CONF_SCAN]))
if CONF_TIMEOUT in config:
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
if CORE.using_arduino:
if CORE.using_arduino and not CORE.is_esp32:
cg.add_library("Wire", None)
@@ -248,14 +250,16 @@ def final_validate_device_schema(
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"i2c_bus_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
"i2c_bus_esp_idf.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#include "i2c_bus_arduino.h"
#include <Arduino.h>
@@ -15,16 +15,7 @@ static const char *const TAG = "i2c.arduino";
void ArduinoI2CBus::setup() {
recover_();
#if defined(USE_ESP32)
static uint8_t next_bus_num = 0;
if (next_bus_num == 0) {
wire_ = &Wire;
} else {
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
}
this->port_ = next_bus_num;
next_bus_num++;
#elif defined(USE_ESP8266)
#if defined(USE_ESP8266)
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
#elif defined(USE_RP2040)
static bool first = true;
@@ -54,10 +45,7 @@ void ArduinoI2CBus::set_pins_and_clock_() {
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
#endif
if (timeout_ > 0) { // if timeout specified in yaml
#if defined(USE_ESP32)
// https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.cpp
wire_->setTimeOut(timeout_ / 1000); // unit: ms
#elif defined(USE_ESP8266)
#if defined(USE_ESP8266)
// https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h
wire_->setClockStretchLimit(timeout_); // unit: us
#elif defined(USE_RP2040)
@@ -76,9 +64,7 @@ void ArduinoI2CBus::dump_config() {
" Frequency: %u Hz",
this->sda_pin_, this->scl_pin_, this->frequency_);
if (timeout_ > 0) {
#if defined(USE_ESP32)
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
#elif defined(USE_ESP8266)
#if defined(USE_ESP8266)
ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_);
#elif defined(USE_RP2040)
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
@@ -275,4 +261,4 @@ void ArduinoI2CBus::recover_() {
} // namespace i2c
} // namespace esphome
#endif // USE_ESP_IDF
#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)

View File

@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#include <Wire.h>
#include "esphome/core/component.h"
@@ -29,7 +29,7 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
void set_frequency(uint32_t frequency) { frequency_ = frequency; }
void set_timeout(uint32_t timeout) { timeout_ = timeout; }
int get_port() const override { return this->port_; }
int get_port() const override { return 0; }
private:
void recover_();
@@ -37,7 +37,6 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
RecoveryCode recovery_result_;
protected:
int8_t port_{-1};
TwoWire *wire_;
uint8_t sda_pin_;
uint8_t scl_pin_;
@@ -49,4 +48,4 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
} // namespace i2c
} // namespace esphome
#endif // USE_ARDUINO
#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)

View File

@@ -1,4 +1,4 @@
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "i2c_bus_esp_idf.h"
@@ -299,4 +299,4 @@ void IDFI2CBus::recover_() {
} // namespace i2c
} // namespace esphome
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "i2c_bus.h"
@@ -53,4 +53,4 @@ class IDFI2CBus : public InternalI2CBus, public Component {
} // namespace i2c
} // namespace esphome
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -23,6 +23,9 @@ 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();
@@ -125,6 +128,9 @@ 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_();
}
}
@@ -132,6 +138,8 @@ 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_();
}
}
@@ -227,6 +235,8 @@ 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) {
@@ -241,6 +251,8 @@ 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) {
@@ -250,6 +262,8 @@ 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) {
@@ -265,6 +279,8 @@ 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) {
@@ -276,6 +292,14 @@ 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_() {

View File

@@ -255,6 +255,9 @@ 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).

View File

@@ -365,8 +365,10 @@ 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")

View File

@@ -65,7 +65,9 @@ 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);
this->write_msg_(console_buffer);
// 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);
}
// Reset the recursion guard for this task
@@ -131,18 +133,19 @@ 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
uint32_t msg_start = this->tx_buffer_at_;
uint16_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_);
// 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 =
uint16_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
@@ -209,9 +212,7 @@ 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
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_);
}
this->write_tx_buffer_to_console_();
}
} else {
// No messages to process, disable loop if appropriate

View File

@@ -71,6 +71,17 @@ 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
*
@@ -173,7 +184,7 @@ class Logger : public Component {
protected:
void process_messages_();
void write_msg_(const char *msg);
void write_msg_(const char *msg, size_t len);
// 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)
@@ -200,6 +211,35 @@ 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) {
@@ -208,10 +248,11 @@ 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_);
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
}
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
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
@@ -425,7 +466,9 @@ class Logger : public Component {
}
// Update buffer_at with the formatted length (handle truncation)
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
// 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;
*buffer_at += formatted_len;
// Remove all trailing newlines right after formatting

View File

@@ -121,25 +121,23 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
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
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);
#else
/* DISABLES CODE */ (false) // NOLINT
// Regular UART - single write including newline (already in buffer)
uart_write_bytes(this->uart_num_, msg, len);
#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_() {

View File

@@ -33,7 +33,10 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
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);
}
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -3,7 +3,7 @@
namespace esphome::logger {
void HOT Logger::write_msg_(const char *msg) {
void HOT Logger::write_msg_(const char *msg, size_t) {
time_t rawtime;
struct tm *timeinfo;
char buffer[80];

View File

@@ -49,7 +49,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
void HOT Logger::write_msg_(const char *msg, size_t len) { this->hw_serial_->write(msg, len); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -27,7 +27,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -62,7 +62,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) {
void HOT Logger::write_msg_(const char *msg, size_t) {
#ifdef CONFIG_PRINTK
printk("%s\n", msg);
#endif

View File

@@ -104,12 +104,17 @@ void LTR390Component::read_uvs_() {
}
}
void LTR390Component::read_mode_(int mode_index) {
// Set mode
LTR390MODE mode = std::get<0>(this->mode_funcs_[mode_index]);
void LTR390Component::standby_() {
std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get();
ctrl[LTR390_CTRL_MODE] = mode;
ctrl[LTR390_CTRL_EN] = false;
this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong();
this->reading_ = false;
}
void LTR390Component::read_mode_(LTR390MODE mode) {
// Set mode
std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get();
ctrl[LTR390_CTRL_MODE] = (mode == LTR390_MODE_UVS);
ctrl[LTR390_CTRL_EN] = true;
this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong();
@@ -129,21 +134,18 @@ void LTR390Component::read_mode_(int mode_index) {
}
// After the sensor integration time do the following
this->set_timeout(int_time + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME, [this, mode_index]() {
// Read from the sensor
std::get<1>(this->mode_funcs_[mode_index])();
// If there are more modes to read then begin the next
// otherwise stop
if (mode_index + 1 < (int) this->mode_funcs_.size()) {
this->read_mode_(mode_index + 1);
this->set_timeout(int_time + LTR390_WAKEUP_TIME + LTR390_SETTLE_TIME, [this, mode]() {
// Read from the sensor and continue to next mode or standby
if (mode == LTR390_MODE_ALS) {
this->read_als_();
if (this->enabled_modes_ & ENABLED_MODE_UVS) {
this->read_mode_(LTR390_MODE_UVS);
return;
}
} else {
// put sensor in standby
std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get();
ctrl[LTR390_CTRL_EN] = false;
this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong();
this->reading_ = false;
this->read_uvs_();
}
this->standby_();
});
}
@@ -172,14 +174,12 @@ void LTR390Component::setup() {
// Set sensor read state
this->reading_ = false;
// If we need the light sensor then add to the list
// Determine which modes are enabled based on configured sensors
if (this->light_sensor_ != nullptr || this->als_sensor_ != nullptr) {
this->mode_funcs_.emplace_back(LTR390_MODE_ALS, std::bind(&LTR390Component::read_als_, this));
this->enabled_modes_ |= ENABLED_MODE_ALS;
}
// If we need the UV sensor then add to the list
if (this->uvi_sensor_ != nullptr || this->uv_sensor_ != nullptr) {
this->mode_funcs_.emplace_back(LTR390_MODE_UVS, std::bind(&LTR390Component::read_uvs_, this));
this->enabled_modes_ |= ENABLED_MODE_UVS;
}
}
@@ -195,10 +195,11 @@ void LTR390Component::dump_config() {
}
void LTR390Component::update() {
if (!this->reading_ && !mode_funcs_.empty()) {
this->reading_ = true;
this->read_mode_(0);
}
if (this->reading_ || this->enabled_modes_ == 0)
return;
this->reading_ = true;
this->read_mode_((this->enabled_modes_ & ENABLED_MODE_ALS) ? LTR390_MODE_ALS : LTR390_MODE_UVS);
}
} // namespace ltr390

View File

@@ -1,7 +1,5 @@
#pragma once
#include <tuple>
#include <vector>
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
@@ -60,17 +58,19 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice {
void set_uv_sensor(sensor::Sensor *uv_sensor) { this->uv_sensor_ = uv_sensor; }
protected:
static constexpr uint8_t ENABLED_MODE_ALS = 1 << 0;
static constexpr uint8_t ENABLED_MODE_UVS = 1 << 1;
optional<uint32_t> read_sensor_data_(LTR390MODE mode);
void read_als_();
void read_uvs_();
void read_mode_(int mode_index);
void read_mode_(LTR390MODE mode);
void standby_();
bool reading_;
// a list of modes and corresponding read functions
std::vector<std::tuple<LTR390MODE, std::function<void()>>> mode_funcs_;
bool reading_{false};
uint8_t enabled_modes_{0};
LTR390GAIN gain_als_;
LTR390GAIN gain_uv_;

View File

@@ -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],

View File

@@ -466,7 +466,7 @@ void LvglComponent::setup() {
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
}
if (buffer == nullptr) {
this->status_set_error("Memory allocation failure");
this->status_set_error(LOG_STR("Memory allocation failure"));
this->mark_failed();
return;
}
@@ -479,7 +479,7 @@ void LvglComponent::setup() {
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
if (this->rotate_buf_ == nullptr) {
this->status_set_error("Memory allocation failure");
this->status_set_error(LOG_STR("Memory allocation failure"));
this->mark_failed();
return;
}

View File

@@ -20,7 +20,13 @@ from ..defines import (
CONF_START_ANGLE,
literal,
)
from ..lv_validation import get_start_value, lv_angle_degrees, lv_float, lv_int
from ..lv_validation import (
get_start_value,
lv_angle_degrees,
lv_float,
lv_int,
lv_positive_int,
)
from ..lvcode import lv, lv_expr, lv_obj
from ..types import LvNumber, NumberType
from . import Widget
@@ -36,13 +42,20 @@ ARC_SCHEMA = cv.Schema(
cv.Optional(CONF_ROTATION, default=0.0): lv_angle_degrees,
cv.Optional(CONF_ADJUSTABLE, default=False): bool,
cv.Optional(CONF_MODE, default="NORMAL"): ARC_MODES.one_of,
cv.Optional(CONF_CHANGE_RATE, default=720): cv.uint16_t,
cv.Optional(CONF_CHANGE_RATE, default=720): lv_positive_int,
}
)
ARC_MODIFY_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_MIN_VALUE): lv_int,
cv.Optional(CONF_MAX_VALUE): lv_int,
cv.Optional(CONF_START_ANGLE): lv_angle_degrees,
cv.Optional(CONF_END_ANGLE): lv_angle_degrees,
cv.Optional(CONF_ROTATION): lv_angle_degrees,
cv.Optional(CONF_MODE): ARC_MODES.one_of,
cv.Optional(CONF_CHANGE_RATE): lv_positive_int,
}
)
@@ -58,17 +71,34 @@ class ArcType(NumberType):
)
async def to_code(self, w: Widget, config):
if CONF_MIN_VALUE in config:
if CONF_MIN_VALUE in config and CONF_MAX_VALUE in config:
max_value = await lv_int.process(config[CONF_MAX_VALUE])
min_value = await lv_int.process(config[CONF_MIN_VALUE])
lv.arc_set_range(w.obj, min_value, max_value)
start = await lv_angle_degrees.process(config[CONF_START_ANGLE])
end = await lv_angle_degrees.process(config[CONF_END_ANGLE])
rotation = await lv_angle_degrees.process(config[CONF_ROTATION])
lv.arc_set_bg_angles(w.obj, start, end)
lv.arc_set_rotation(w.obj, rotation)
lv.arc_set_mode(w.obj, literal(config[CONF_MODE]))
lv.arc_set_change_rate(w.obj, config[CONF_CHANGE_RATE])
elif CONF_MIN_VALUE in config:
max_value = w.get_property(CONF_MAX_VALUE)
min_value = await lv_int.process(config[CONF_MIN_VALUE])
lv.arc_set_range(w.obj, min_value, max_value)
elif CONF_MAX_VALUE in config:
max_value = await lv_int.process(config[CONF_MAX_VALUE])
min_value = w.get_property(CONF_MIN_VALUE)
lv.arc_set_range(w.obj, min_value, max_value)
await w.set_property(
CONF_START_ANGLE,
await lv_angle_degrees.process(config.get(CONF_START_ANGLE)),
)
await w.set_property(
CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE))
)
await w.set_property(
CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION))
)
await w.set_property(CONF_MODE, config)
await w.set_property(
CONF_CHANGE_RATE,
await lv_positive_int.process(config.get(CONF_CHANGE_RATE)),
)
if CONF_ADJUSTABLE in config:
if not config[CONF_ADJUSTABLE]:
@@ -78,9 +108,7 @@ class ArcType(NumberType):
# For some reason arc does not get automatically added to the default group
lv.group_add_obj(lv_expr.group_get_default(), w.obj)
value = await get_start_value(config)
if value is not None:
lv.arc_set_value(w.obj, value)
await w.set_property(CONF_VALUE, await get_start_value(config))
arc_spec = ArcType()

View File

@@ -57,14 +57,14 @@ void MAX17043Component::setup() {
if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) {
ESP_LOGE(TAG, "Device does not appear to be a MAX17043");
this->status_set_error("unrecognised");
this->status_set_error(LOG_STR("unrecognised"));
this->mark_failed();
return;
}
// need to write back to config register to reset the sleep bit
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) {
this->status_set_error("sleep reset failed");
this->status_set_error(LOG_STR("sleep reset failed"));
this->mark_failed();
return;
}

View File

@@ -16,16 +16,21 @@ void MCP3204::dump_config() {
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
}
float MCP3204::read_data(uint8_t pin) {
uint8_t adc_primary_config = 0b00000110 | (pin >> 2);
uint8_t adc_secondary_config = pin << 6;
float MCP3204::read_data(uint8_t pin, bool differential) {
uint8_t command, b0, b1;
command = (1 << 6) | // start bit
((differential ? 0 : 1) << 5) | // single or differential bit
((pin & 0x07) << 2); // pin
this->enable();
this->transfer_byte(adc_primary_config);
uint8_t adc_primary_byte = this->transfer_byte(adc_secondary_config);
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
this->transfer_byte(command);
b0 = this->transfer_byte(0x00);
b1 = this->transfer_byte(0x00);
this->disable();
uint16_t digital_value = (adc_primary_byte << 8 | adc_secondary_byte) & 0b111111111111;
return float(digital_value) / 4096.000 * this->reference_voltage_;
uint16_t digital_value = encode_uint16(b0, b1) >> 4;
return float(digital_value) / 4096.000 * this->reference_voltage_; // in V
}
} // namespace mcp3204

View File

@@ -18,7 +18,7 @@ class MCP3204 : public Component,
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
float read_data(uint8_t pin);
float read_data(uint8_t pin, bool differential);
protected:
float reference_voltage_;

View File

@@ -13,6 +13,7 @@ MCP3204Sensor = mcp3204_ns.class_(
"MCP3204Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
)
CONF_MCP3204_ID = "mcp3204_id"
CONF_DIFF_MODE = "diff_mode"
CONFIG_SCHEMA = (
sensor.sensor_schema(MCP3204Sensor)
@@ -20,6 +21,7 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204),
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7),
cv.Optional(CONF_DIFF_MODE, default=False): cv.boolean,
}
)
.extend(cv.polling_component_schema("60s"))
@@ -30,6 +32,7 @@ async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_NUMBER],
config[CONF_DIFF_MODE],
)
await cg.register_parented(var, config[CONF_MCP3204_ID])
await cg.register_component(var, config)

View File

@@ -7,16 +7,15 @@ namespace mcp3204 {
static const char *const TAG = "mcp3204.sensor";
MCP3204Sensor::MCP3204Sensor(uint8_t pin) : pin_(pin) {}
float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; }
void MCP3204Sensor::dump_config() {
LOG_SENSOR("", "MCP3204 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
ESP_LOGCONFIG(TAG, " Differential Mode: %s", YESNO(this->differential_mode_));
LOG_UPDATE_INTERVAL(this);
}
float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_); }
float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_, this->differential_mode_); }
void MCP3204Sensor::update() { this->publish_state(this->sample()); }
} // namespace mcp3204

View File

@@ -15,7 +15,7 @@ class MCP3204Sensor : public PollingComponent,
public sensor::Sensor,
public voltage_sampler::VoltageSampler {
public:
MCP3204Sensor(uint8_t pin);
MCP3204Sensor(uint8_t pin, bool differential_mode) : pin_(pin), differential_mode_(differential_mode) {}
void update() override;
void dump_config() override;
@@ -24,6 +24,7 @@ class MCP3204Sensor : public PollingComponent,
protected:
uint8_t pin_;
bool differential_mode_;
};
} // namespace mcp3204

View File

@@ -118,7 +118,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
bool has_psk = api::global_api_server->get_noise_ctx()->has_psk();
bool has_psk = api::global_api_server->get_noise_ctx().has_psk();
const char *encryption_key = has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED;
txt_records.push_back({MDNS_STR(encryption_key), MDNS_STR(NOISE_ENCRYPTION)});
#endif

View File

@@ -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 LogString *message, esp_err_t err) {
ESP_LOGE(TAG, "%s: %s", LOG_STR_ARG(message), esp_err_to_name(err));
this->mark_failed(message);
}
void MIPI_DSI::setup() {
ESP_LOGCONFIG(TAG, "Running Setup");
@@ -31,7 +37,7 @@ void MIPI_DSI::setup() {
};
auto err = esp_lcd_new_dsi_bus(&bus_config, &this->bus_handle_);
if (err != ESP_OK) {
this->smark_failed("lcd_new_dsi_bus failed", err);
this->smark_failed(LOG_STR("lcd_new_dsi_bus failed"), err);
return;
}
esp_lcd_dbi_io_config_t dbi_config = {
@@ -41,7 +47,7 @@ void MIPI_DSI::setup() {
};
err = esp_lcd_new_panel_io_dbi(this->bus_handle_, &dbi_config, &this->io_handle_);
if (err != ESP_OK) {
this->smark_failed("new_panel_io_dbi failed", err);
this->smark_failed(LOG_STR("new_panel_io_dbi failed"), err);
return;
}
auto pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
@@ -69,7 +75,7 @@ void MIPI_DSI::setup() {
}};
err = esp_lcd_new_panel_dpi(this->bus_handle_, &dpi_config, &this->handle_);
if (err != ESP_OK) {
this->smark_failed("esp_lcd_new_panel_dpi failed", err);
this->smark_failed(LOG_STR("esp_lcd_new_panel_dpi failed"), err);
return;
}
if (this->reset_pin_ != nullptr) {
@@ -86,14 +92,14 @@ void MIPI_DSI::setup() {
auto when = millis() + 120;
err = esp_lcd_panel_init(this->handle_);
if (err != ESP_OK) {
this->smark_failed("esp_lcd_init failed", err);
this->smark_failed(LOG_STR("esp_lcd_init failed"), err);
return;
}
size_t index = 0;
auto &vec = this->init_sequence_;
while (index != vec.size()) {
if (vec.size() - index < 2) {
this->mark_failed("Malformed init sequence");
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
uint8_t cmd = vec[index++];
@@ -104,7 +110,7 @@ void MIPI_DSI::setup() {
} else {
uint8_t num_args = x & 0x7F;
if (vec.size() - index < num_args) {
this->mark_failed("Malformed init sequence");
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
if (cmd == SLEEP_OUT) {
@@ -119,7 +125,7 @@ void MIPI_DSI::setup() {
format_hex_pretty(ptr, num_args, '.', false).c_str());
err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args);
if (err != ESP_OK) {
this->smark_failed("lcd_panel_io_tx_param failed", err);
this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err);
return;
}
index += num_args;
@@ -134,7 +140,7 @@ void MIPI_DSI::setup() {
err = (esp_lcd_dpi_panel_register_event_callbacks(this->handle_, &cbs, this->io_lock_));
if (err != ESP_OK) {
this->smark_failed("Failed to register callbacks", err);
this->smark_failed(LOG_STR("Failed to register callbacks"), err);
return;
}
@@ -216,7 +222,7 @@ bool MIPI_DSI::check_buffer_() {
RAMAllocator<uint8_t> allocator;
this->buffer_ = allocator.allocate(this->height_ * this->width_ * bytes_per_pixel);
if (this->buffer_ == nullptr) {
this->mark_failed("Could not allocate buffer for display!");
this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
return false;
}
return true;

View File

@@ -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 LogString *message, esp_err_t err);
void update() override;

View File

@@ -35,3 +35,70 @@ DriverChip(
(0x10, 0x0C), (0x11, 0x0C), (0x12, 0x0C), (0x13, 0x0C), (0x30, 0x00),
],
)
# JC4880P443 Driver Configuration (ST7701)
# Using parameters from esp_lcd_st7701.h and the working full init sequence
# ----------------------------------------------------------------------------------------------------------------------
# * Resolution: 480x800
# * PCLK Frequency: 34 MHz
# * DSI Lane Bit Rate: 500 Mbps (using 2-Lane DSI configuration)
# * Horizontal Timing (hsync_pulse_width=12, hsync_back_porch=42, hsync_front_porch=42)
# * Vertical Timing (vsync_pulse_width=2, vsync_back_porch=8, vsync_front_porch=166)
# ----------------------------------------------------------------------------------------------------------------------
DriverChip(
"JC4880P443",
width=480,
height=800,
hsync_back_porch=42,
hsync_pulse_width=12,
hsync_front_porch=42,
vsync_back_porch=8,
vsync_pulse_width=2,
vsync_front_porch=166,
pclk_frequency="34MHz",
lane_bit_rate="500Mbps",
swap_xy=cv.UNDEFINED,
color_order="RGB",
reset_pin=5,
initsequence=[
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13),
(0xEF, 0x08),
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10),
(0xC0, 0x63, 0x00),
(0xC1, 0x0D, 0x02),
(0xC2, 0x10, 0x08),
(0xCC, 0x10),
(0xB0, 0x80, 0x09, 0x53, 0x0C, 0xD0, 0x07, 0x0C, 0x09, 0x09, 0x28, 0x06, 0xD4, 0x13, 0x69, 0x2B, 0x71),
(0xB1, 0x80, 0x94, 0x5A, 0x10, 0xD3, 0x06, 0x0A, 0x08, 0x08, 0x25, 0x03, 0xD3, 0x12, 0x66, 0x6A, 0x0D),
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11),
(0xB0, 0x5D),
(0xB1, 0x58),
(0xB2, 0x87),
(0xB3, 0x80),
(0xB5, 0x4E),
(0xB7, 0x85),
(0xB8, 0x21),
(0xB9, 0x10, 0x1F),
(0xBB, 0x03),
(0xBC, 0x00),
(0xC1, 0x78),
(0xC2, 0x78),
(0xD0, 0x88),
(0xE0, 0x00, 0x3A, 0x02),
(0xE1, 0x04, 0xA0, 0x00, 0xA0, 0x05, 0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40),
(0xE2, 0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00),
(0xE3, 0x00, 0x00, 0x33, 0x33),
(0xE4, 0x44, 0x44),
(0xE5, 0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0),
(0xE6, 0x00, 0x00, 0x33, 0x33),
(0xE7, 0x44, 0x44),
(0xE8, 0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0),
(0xEB, 0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00),
(0xEC, 0x08, 0x01),
(0xED, 0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B),
(0xEF, 0x08, 0x08, 0x08, 0x45, 0x3F, 0x54),
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x00),
]
)
# fmt: on

View File

@@ -73,7 +73,7 @@ void MipiRgbSpi::write_init_sequence_() {
auto &vec = this->init_sequence_;
while (index != vec.size()) {
if (vec.size() - index < 2) {
this->mark_failed("Malformed init sequence");
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
uint8_t cmd = vec[index++];
@@ -84,7 +84,7 @@ void MipiRgbSpi::write_init_sequence_() {
} else {
uint8_t num_args = x & 0x7F;
if (vec.size() - index < num_args) {
this->mark_failed("Malformed init sequence");
this->mark_failed(LOG_STR("Malformed init sequence"));
return;
}
if (cmd == SLEEP_OUT) {
@@ -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(LOG_STR("lcd setup failed"));
}
ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
}
@@ -249,7 +249,7 @@ bool MipiRgb::check_buffer_() {
RAMAllocator<uint16_t> allocator;
this->buffer_ = allocator.allocate(this->height_ * this->width_);
if (this->buffer_ == nullptr) {
this->mark_failed("Could not allocate buffer for display!");
this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
return false;
}
return true;

View File

@@ -478,7 +478,7 @@ class MipiSpiBuffer : public MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DIS
RAMAllocator<BUFFERTYPE> allocator{};
this->buffer_ = allocator.allocate(BUFFER_WIDTH * BUFFER_HEIGHT / FRACTION);
if (this->buffer_ == nullptr) {
this->mark_failed("Buffer allocation failed");
this->mark_failed(LOG_STR("Buffer allocation failed"));
}
}

View File

@@ -78,19 +78,20 @@ void SourceSpeaker::loop() {
} else {
switch (err) {
case ESP_ERR_NO_MEM:
this->status_set_error("Failed to start mixer: not enough memory");
this->status_set_error(LOG_STR("Failed to start mixer: not enough memory"));
break;
case ESP_ERR_NOT_SUPPORTED:
this->status_set_error("Failed to start mixer: unsupported bits per sample");
this->status_set_error(LOG_STR("Failed to start mixer: unsupported bits per sample"));
break;
case ESP_ERR_INVALID_ARG:
this->status_set_error("Failed to start mixer: audio stream isn't compatible with the other audio stream.");
this->status_set_error(
LOG_STR("Failed to start mixer: audio stream isn't compatible with the other audio stream."));
break;
case ESP_ERR_INVALID_STATE:
this->status_set_error("Failed to start mixer: mixer task failed to start");
this->status_set_error(LOG_STR("Failed to start mixer: mixer task failed to start"));
break;
default:
this->status_set_error("Failed to start mixer");
this->status_set_error(LOG_STR("Failed to start mixer"));
break;
}
@@ -317,7 +318,7 @@ void MixerSpeaker::loop() {
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STARTING);
}
if (event_group_bits & MixerEventGroupBits::ERR_ESP_NO_MEM) {
this->status_set_error("Failed to allocate the mixer's internal buffer");
this->status_set_error(LOG_STR("Failed to allocate the mixer's internal buffer"));
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ERR_ESP_NO_MEM);
}
if (event_group_bits & MixerEventGroupBits::STATE_RUNNING) {

View File

@@ -140,7 +140,7 @@ void MQTTClientComponent::send_device_info_() {
#endif
#ifdef USE_API_NOISE
root[api::global_api_server->get_noise_ctx()->has_psk() ? "api_encryption" : "api_encryption_supported"] =
root[api::global_api_server->get_noise_ctx().has_psk() ? "api_encryption" : "api_encryption_supported"] =
"Noise_NNpsk0_25519_ChaChaPoly_SHA256";
#endif
},

View File

@@ -278,7 +278,7 @@ void NAU7802Sensor::loop() {
this->set_calibration_failure_(true);
this->state_ = CalibrationState::INACTIVE;
ESP_LOGE(TAG, "Failed to calibrate sensor");
this->status_set_error("Calibration Failed");
this->status_set_error(LOG_STR("Calibration Failed"));
return;
}

View File

@@ -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

View File

@@ -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

View File

@@ -2,6 +2,7 @@
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -38,6 +39,14 @@ static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, ui
PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
Color color(rgba[0], rgba[1], rgba[2], rgba[3]);
decoder->draw(x, y, w, h, color);
// Feed watchdog periodically to avoid triggering during long decode operations.
// Feed every 1024 pixels to balance efficiency and responsiveness.
uint32_t pixels = w * h;
decoder->increment_pixels_decoded(pixels);
if ((decoder->get_pixels_decoded() % 1024) < pixels) {
App.feed_wdt();
}
}
PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) {

View File

@@ -25,9 +25,13 @@ class PngDecoder : public ImageDecoder {
int prepare(size_t download_size) override;
int HOT decode(uint8_t *buffer, size_t size) override;
void increment_pixels_decoded(uint32_t count) { this->pixels_decoded_ += count; }
uint32_t get_pixels_decoded() const { return this->pixels_decoded_; }
protected:
RAMAllocator<pngle_t> allocator_;
pngle_t *pngle_;
uint32_t pixels_decoded_{0};
};
} // namespace online_image

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