esphome
esphome copied to clipboard
[nrf52] Add openthread support
What does this implement/fix?
Supporting openthread in nrf52 devices, enabling ipv6 connectivity through openthread border router which unlocks ip based functionality such as mdns discoverability, api component, mqtt etc.
It requires:
- https://github.com/esphome/esphome/pull/7049
- https://github.com/esphome/esphome/pull/6075
TODO
- [x] Openthread basic support for nrf52
- [x] Internet connectivity with ipv6
- [x] Support esphome api component
- [x] OTA with mcumgr udp
- [x] Logs in dashboard over thread
- [x] OTA glue into esphome dashboard
- [x] Factory reset pin
- [x] Move MCUMGR transport restart logic to mcumgr module
- [ ] Board configurations to allow e.g. external flash
- [ ] Add RCP support
- [x] Support api encryption
- [ ] Allow moving settings to external storage
- [ ] Allow specifying custom pm_static
- [ ] Cleanup code
- [ ] Tests
Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Other
Related issue or feature (if applicable):
- https://github.com/esphome/esphome/pull/7506
Test Environment
- [ ] ESP32
- [ ] ESP32 IDF
- [ ] ESP8266
- [ ] RP2040
- [ ] BK72xx
- [ ] RTL87xx
- [x] nrf52840
Example entry for config.yaml:
# Example config.yaml
external_components:
- source: github://pr#8410
components: [ logger, nrf52, time, zephyr, zephyr_openthread, mdns, network ]
refresh: always
nrf52:
board: adafruit_itsybitsy_nrf52840
esphome:
name: nrf52-ot
logger:
level: DEBUG
logs:
switch: NONE
switch:
- platform: gpio
name: "Led Light"
pin:
number: 15
inverted: true
id: gpio_15
output:
- platform: gpio
pin:
number: 14
inverted: true
mode:
output: true
id: rest_gpio
# Enable IPv6 networking
network:
enable_ipv6: True
# Enable OpenThread
zephyr_openthread:
channel: 15
panid: 0x1234
network_name: "ha-thread-228c"
xpanid: "1111111122222222"
network_key: "00112233445566778899aabbccddeeff"
pskc: "00112233445566778899aabbccddeeff"
radio_tx_power: 0
force_dataset: true
Checklist:
- [x] The code change is tested and works locally.
- [ ] Tests have been added to verify that the new code works (under
tests/folder).
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in esphome-docs.
Codecov Report
Attention: Patch coverage is 27.27273% with 8 lines in your changes missing coverage. Please review.
Project coverage is 54.90%. Comparing base (
4d8b5ed) to head (1c8d5b6). Report is 2285 commits behind head on dev.
| Files with missing lines | Patch % | Lines |
|---|---|---|
| esphome/config_validation.py | 0.00% | 7 Missing :warning: |
| esphome/core/__init__.py | 66.66% | 1 Missing :warning: |
Additional details and impacted files
@@ Coverage Diff @@
## dev #8410 +/- ##
==========================================
+ Coverage 53.70% 54.90% +1.19%
==========================================
Files 50 50
Lines 9408 9925 +517
Branches 1654 1357 -297
==========================================
+ Hits 5053 5449 +396
- Misses 4056 4133 +77
- Partials 299 343 +44
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
It works :+1:
external_components:
- source: github://pr#7049
components: [ logger, nrf52, zephyr, time ]
refresh: always
- source: github://pr#8410
components: [ socket, zephyr_openthread, mdns, network ]
refresh: always
nrf52:
board: adafruit_itsybitsy_nrf52840
bootloader: adafruit_nrf52_sd140_v6 # for nice nano
esphome:
name: nrf52-test-nrf
logger:
level: DEBUG
switch:
- platform: gpio
pin:
number: 15
mode:
output: true
id: gpio_15
interval:
- interval: 500ms
then:
- switch.toggle: gpio_15
socket:
network:
enable_ipv6: True
# Enable OpenThread
zephyr_openthread:
channel: 15
panid: 0x1234
network_name: "OpenThread-ESP"
xpanid: ""
network_key: ""
pskc: ""
radio_tx_power: 0
force_dataset: true
once #8412 is merged and development gets easier by being able to build in the dev web interface, I will continue development
once #8412 is merged and development gets easier by being able to build in the dev web interface, I will continue development
You could hardcode those temporarily. I did similar thing before other changes were merged.
did it! now possible to build from the dev device builder. next I will take a look into OTA so that its easier to iterate and afterwards, fixing API encryption
e.g.
external_components:
- source: github://pr#8410
components: [ logger, nrf52, time, zephyr, zephyr_openthread, mdns, network, socket, api ]
refresh: always
nrf52:
board: xiao_ble
bootloader: mcuboot
esphome:
name: nrf52-ot
api:
logger:
level: DEBUG
logs:
switch: NONE
switch:
- platform: gpio
name: "Led Light"
icon: "mdi:restart"
pin:
number: P0.06
inverted: true
id: gpio_15
# Enable IPv6 networking
network:
enable_ipv6: true
# Enable OpenThread
zephyr_openthread:
channel: 25
panid: 0xe064
network_name: "MyHome5"
xpanid: "1db74a0a3797454b"
network_key: "00112233445566778899AABBCCDDEEFF"
pskc: "00112233445566778899AABBCCDDEEFF"
force_dataset: true
shell: false
# Add a text sensor to display the IPv6 address
text_sensor:
- platform: template
name: "Thread IPv6 Address"
id: thread_ipv6
update_interval: 60s
lambda: |-
if (zephyr_openthread::global_openthread_component->has_ipv6_address()) {
return zephyr_openthread::global_openthread_component->get_ipv6_address();
}
return {"No IPv6 Address"};
This is independent of PR https://github.com/esphome/esphome/pull/7506 ?
This is independent of PR #7506 ?
As stated in the first comment, PR #7506 is related, but not a dependency.
Related issue or feature (if applicable):
* [Add OpenThread support on ESP-IDF #7506](https://github.com/esphome/esphome/pull/7506)
made good progress with ota. managed to get it working with mcumgr using udp transport over thread. rebased the work in #6075 ota works already by calling mcumgr manually but will work on the glue with esphome dashboard
ota works (needs to run esphome dashboard from this pr/branch) e.g.
external_components:
- source: github://pr#8410
components: [ logger, nrf52, time, zephyr, zephyr_openthread, mdns, network, socket, api, openthread_info, ota, zephyr_mcumgr ]
refresh: always
nrf52:
board: xiao_ble
bootloader: mcuboot
esphome:
name: nrf52-ot
api:
logger:
level: DEBUG
logs:
switch: NONE
hardware_uart: uart0
switch:
- platform: gpio
name: "Led Light"
icon: "mdi:restart"
pin:
number: P0.06
inverted: true
id: gpio_15
# Enable IPv6 networking
network:
enable_ipv6: true
# Enable OpenThread
zephyr_openthread:
channel: 25
panid: 0xf060
network_name: "Network"
xpanid: "1122334455667788"
network_key: "112233445566778899aabbccddeeff00"
pskc: "112233445566778899aabbccddeeff00"
force_dataset: true
shell: false
ota:
- platform: zephyr_mcumgr
transport: udp
on_begin:
then:
- logger.log: "OTA start"
on_progress:
then:
- logger.log:
format: "OTA progress %0.1f%%"
args: ["x"]
on_end:
then:
- logger.log: "OTA end"
on_error:
then:
- logger.log:
format: "OTA update error %d"
args: ["x"]
on_state_change:
then:
lambda: >-
ESP_LOGD("ota", "State %d", state);
# Then add the text sensor for IPv6 addresses
text_sensor:
- platform: openthread_info
ip_address:
name: "Thread IPv6 Addresses"
update_interval: 30s
role:
name: "Thread Role"
update_interval: 30s
channel:
name: "Thread Channel"
update_interval: 30s
network_name:
name: "Thread Network Name"
update_interval: 30s
panid:
name: "Thread PanID"
update_interval: 30s
- [ ] Allow specifying custom pm_static It seems to be already implemented https://github.com/tomaszduda23/esphome/blob/fd8d7956af3b5d1923f37b85716a9ed83a96009e/esphome/components/nrf52/init.py#L50
- [ ] Allow specifying custom pm_static It seems to be already implemented https://github.com/tomaszduda23/esphome/blob/fd8d7956af3b5d1923f37b85716a9ed83a96009e/esphome/components/nrf52/init.py#L50
I mean receive it from the esphome's device config yaml so the user can resize partitions if they want
@tomaszduda23 maybe you can help me with something, when I try to compile with api encryption, platformio includes noise-c as a dependency, here: https://github.com/esphome/esphome/blob/dev/esphome/components/api/init.py#L154-L160 And then when I'm compiling, the linker is not able to find methods that should be provided by the lib, which is weird cause the generated platformio.conf file did insert the lib as a dependency:
; ========== AUTO GENERATED CODE BEGIN ===========
[common]
lib_deps =
build_flags =
upload_flags =
; ========== AUTO GENERATED CODE BEGIN ===========
[platformio]
description = ESPHome 2025.4.0-dev
[env:nrf52-ot]
board = xiao_ble
boards_dir = /home/felipe/dev/esphome/config/.esphome/build/nrf52-ot/boards
build_flags =
-DCONFIG_LWIP_IPV6
-DCONFIG_LWIP_IPV6_AUTOCONFIG
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_DEBUG
-DUSE_IPV6
-DUSE_NRF52
-DUSE_OPENTHREAD
-DUSE_ZEPHYR
-DUSE_ZEPHYR_NETWORKING
-DUSE_ZEPHYR_OPENTHREAD
-Wno-sign-compare
-Wno-unused-but-set-variable
-Wno-unused-variable
-fno-exceptions
extra_scripts =
pre:/home/felipe/dev/esphome/esphome/components/zephyr/pre_build.py.script
framework = zephyr
lib_deps =
esphome/[email protected]
${common.lib_deps}
platform = https://github.com/tomaszduda23/platform-nordicnrf52/archive/refs/tags/v10.3.0-1.zip
platform_packages =
platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-4.zip
platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.16.1-1.zip
; =========== AUTO GENERATED CODE END ============
shouldn't the link happen automatically?
Zephyr uses west build system. Esphome uses platformio. I take all complication flags, source files etc from platformio and generate cmake file. Than use west to build it. After that return result to platformio. Probably I missed something for lib_deps.
It should be handled somewhere in https://github.com/tomaszduda23/framework-sdk-nrf/blob/0c8827425a7955fbcfff4796c45d82cb396a2d55/scripts/platformio/platformio-build.py#L170
You can try https://github.com/tomaszduda23/framework-sdk-nrf/releases/tag/v2.6.1-6. It fix the linking issue. All source file are in single target. It mess up with flags a little bit.
Thx for that! I'm on vacations now. Will test as soon as I come back
=== EDIT
Tested and link now works! thx @tomaszduda23
It seems there is a conflict to resolve here.
For reference, ESPHome 2025.6 merged PR by @mrene with OpenThread component supporting ESP-IDF:
-
https://github.com/esphome/esphome/pull/7506
-
https://esphome.io/changelog/2025.6.0.html#openthread
- https://esphome.io/components/openthread
-
PS: Off-topic but I think that with the introduction of native OpenThread in ESPHome there will a huge new interest in making ultra low-power battery-operated devices combining the Thread protocol and ESPHome, so wondering if you and/or others also have a roadmap for extending ESPHome built-in power-management funtionality for having ability to make sleepy end devices using generic sleeping technics (low-power sleep functionality) and/or even deep sleep features (practical power-off with wake-up timer) on all platforms supported by ESPHome?
@felipejfc core part was just merged. I will rebase other PRs soon. That way most of the code in this PR won't be needed anymore.
Any updates on this?
Could you indicate which files are in conflict? There is an error building this:
INFO ESPHome 2025.11.0-dev
INFO Reading configuration /config/esphome/xiao-nrf52-ot.yaml...
INFO Updating https://github.com/esphome/esphome.git@pull/8410/head
ERROR Unable to import component zephyr_openthread:
Traceback (most recent call last):
File "/esphome/esphome/loader.py", line 199, in _lookup_module
module = importlib.import_module(f"esphome.components.{domain}")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/importlib/__init__.py", line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 999, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "/data/external_components/aa491595/esphome/components/zephyr_openthread/__init__.py", line 10, in <module>
from esphome.const import CONF_ID, CONF_ENABLE_IPV6, CONF_OTA, CONF_TRANSPORT, CONF_UDP
ImportError: cannot import name 'CONF_TRANSPORT' from 'esphome.const' (/esphome/esphome/const.py)
Failed config
zephyr_openthread: [source /config/esphome/xiao-nrf52-ot.yaml:51]
Component not found: zephyr_openthread.
channel: 15
panid: 4660
network_name: ha-thread-228c
xpanid: 1111111122222222
network_key: 00112233445566778899aabbccddeeff
pskc: 00112233445566778899aabbccddeeff
radio_tx_power: 0
force_dataset: True
This PR has a lot of old stuff which is not related to OT. Probably it would be easier to open new one and take OT/network related things only.
zephyr_openthread
Do we have to use the existing openthread componant or keep the zephyr_openthread componant?
Having the same config for all is better.