mirror of
https://github.com/esphome/esphome-docs.git
synced 2024-09-21 03:21:20 +02:00
Document UDP component (#3918)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: H. Árkosi Róbert <robreg@zsurob.hu>
This commit is contained in:
parent
f1b0f2e58f
commit
31f188f262
49
components/binary_sensor/udp.rst
Normal file
49
components/binary_sensor/udp.rst
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
UDP Binary Sensor
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. seo::
|
||||||
|
:description: Instructions for setting up a UDP binary sensor.
|
||||||
|
:image: udp.svg
|
||||||
|
|
||||||
|
The ``udp`` binary sensor platform allows you to receive binary sensor data directly from another ESPHome node.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Example configuration entry
|
||||||
|
binary_sensor:
|
||||||
|
- platform: udp
|
||||||
|
id: switch_status
|
||||||
|
provider: light-switch
|
||||||
|
remote_id: light_switch
|
||||||
|
|
||||||
|
Configuration variables
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
|
||||||
|
- **provider** (**Required**, string): The name of the provider node.
|
||||||
|
- **remote_id** (*Optional*, :ref:`config-id`): The ID of the original binary sensor in the provider device. If not specified defaults to the ID configured with ``id:``.
|
||||||
|
- **name** (*Optional*, string): The name of the binary sensor.
|
||||||
|
- **internal** (*Optional*, boolean): Whether the sensor should be exposed via API (e.g. to Home Assistant.) Defaults to ``true`` if name is not set, required if name is provided.
|
||||||
|
- All other options from :ref:`Binary Sensor <config-binary_sensor>`.
|
||||||
|
|
||||||
|
At least one of ``id`` and ``remote_id`` must be configured.
|
||||||
|
|
||||||
|
Publishing to Home Assistant
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Typically this type of binary sensor would be used for internal automation purposes rather than having it published back to
|
||||||
|
Home Assistant, since it would be a duplicate of the original sensor.
|
||||||
|
|
||||||
|
If it *is* desired to expose the binary sensor to Home Assistant, then the ``internal:`` configuration setting needs to be explicitly
|
||||||
|
set to ``false`` and a name provided.
|
||||||
|
Only the state (i.e. binary value) of the remote sensor is received by the consumer, so any other attributes must be explicitly
|
||||||
|
configured.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
|
||||||
|
- :doc:`/components/udp`
|
||||||
|
- :doc:`/components/sensor/index`
|
||||||
|
- :ref:`automation`
|
||||||
|
- :apiref:`udp/udp_component.h`
|
||||||
|
- :ghedit:`Edit`
|
49
components/sensor/udp.rst
Normal file
49
components/sensor/udp.rst
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
UDP Sensor
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. seo::
|
||||||
|
:description: Instructions for setting up a UDP sensor.
|
||||||
|
:image: udp.svg
|
||||||
|
|
||||||
|
The ``udp`` sensor platform allows you to receive numeric sensor data directly from another ESPHome node.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Example configuration entry
|
||||||
|
sensor:
|
||||||
|
- platform: udp
|
||||||
|
id: temperature_id
|
||||||
|
provider: thermometer
|
||||||
|
remote_id: temp_id
|
||||||
|
|
||||||
|
Configuration variables
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
|
||||||
|
- **provider** (**Required**, string): The name of the provider node.
|
||||||
|
- **remote_id** (*Optional*, :ref:`config-id`): The ID of the original sensor in the provider node. If not specified defaults to the ID configured with ``id:``.
|
||||||
|
- **name** (*Optional*, string): The name of the sensor.
|
||||||
|
- **internal** (*Optional*, boolean): Whether the sensor should be exposed via API (e.g. to Home Assistant.) Defaults to ``true`` if name is not set, required if name is provided.
|
||||||
|
- All other options from :ref:`Sensor <config-sensor>`.
|
||||||
|
|
||||||
|
At least one of ``id`` and ``remote_id`` must be configured.
|
||||||
|
|
||||||
|
Publishing to Home Assistant
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Typically this type of sensor would be used for internal automation purposes rather than having it published back to
|
||||||
|
Home Assistant, since it would be a duplicate of the original sensor.
|
||||||
|
|
||||||
|
If it *is* desired to expose the sensor to Home Assistant, then the ``internal:`` configuration setting needs to be explicitly
|
||||||
|
set to ``false`` and a name provided.
|
||||||
|
Only the state (i.e. numeric value) of the remote sensor is received by the consumer, so any other attributes must be explicitly
|
||||||
|
configured.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
|
||||||
|
- :doc:`/components/udp`
|
||||||
|
- :doc:`/components/binary_sensor/index`
|
||||||
|
- :ref:`automation`
|
||||||
|
- :apiref:`udp/udp_component.h`
|
||||||
|
- :ghedit:`Edit`
|
300
components/udp.rst
Normal file
300
components/udp.rst
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
.. _udp:
|
||||||
|
|
||||||
|
UDP Component
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. seo::
|
||||||
|
:description: Instructions for setting up a UDP component on ESPHome
|
||||||
|
:image: udp.svg
|
||||||
|
:keywords: UDP
|
||||||
|
|
||||||
|
The purpose of this component is to allow ESPHome nodes to directly communicate with each over an IP network.
|
||||||
|
It permits the state of sensors and binary sensors to be broadcast via UDP packets
|
||||||
|
to other nodes on the same LAN, or to specific IP addresses (which may be in remote, but reachable networks).
|
||||||
|
|
||||||
|
Nodes may be *providers* which broadcast sensor data, or *consumers* which receive sensor data from one or more
|
||||||
|
providers. A node may be both a provider and a consumer. Optional security is provided by one or more of:
|
||||||
|
|
||||||
|
- encryption using a shared secret key
|
||||||
|
- a rolling code
|
||||||
|
- a challenge-response (ping-pong) key
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Example configuration entry
|
||||||
|
udp:
|
||||||
|
update_interval: 5s
|
||||||
|
encryption: "REPLACEME"
|
||||||
|
rolling_code_enable: true
|
||||||
|
binary_sensors:
|
||||||
|
- binary_sensor_id1
|
||||||
|
sensors:
|
||||||
|
- sensor_id1
|
||||||
|
- id: sensor_id2
|
||||||
|
broadcast_id: different_id
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: some-device-name
|
||||||
|
encryption: "REPLACEME with some key"
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: udp
|
||||||
|
provider: some-device-name
|
||||||
|
id: local_sensor_id
|
||||||
|
remote_id: some_sensor_id
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: udp
|
||||||
|
provider: unencrypted-device
|
||||||
|
id: other_binary_sensor_id # also used as remote_id
|
||||||
|
|
||||||
|
Configuration variables:
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
|
||||||
|
- **update_interval** (*Optional*, :ref:`config-time`): Interval between full broadcasts. Defaults to 15s.
|
||||||
|
- **port** (*Optional*, int): The destination UDP port number to use. Defaults to ``18511``.
|
||||||
|
- **addresses** (*Optional*, list of IPv4 addresses): One or more IP addresses to broadcast data to. Defaults to ``255.255.255.255``
|
||||||
|
which is the local network broadcast address.
|
||||||
|
- **sensors** (*Optional*, list): A list of sensor IDs to be broadcast. Each entry may be just the sensor id, or may set a different id to be broadcast.
|
||||||
|
|
||||||
|
- **id** (**Required**, :ref:`config-id`): The id of the sensor to be used
|
||||||
|
- **broadcast_id** (*Optional*, string): The id to be used for this sensor in the broadcast. Defaults to the same as the internal id.
|
||||||
|
|
||||||
|
- **binary_sensors** (*Optional*, list): A list of binary sensor IDs to be broadcast.
|
||||||
|
|
||||||
|
- **id** (**Required**, :ref:`config-id`): The id of the binary sensor to be used
|
||||||
|
- **broadcast_id** (*Optional*, string): The id to be used for this binary sensor in the broadcast. Defaults to the same as the internal id.
|
||||||
|
|
||||||
|
- **encryption** (*Optional*, string): The encryption key to use when broadcasting. Default is no encryption. This may be
|
||||||
|
any string, and will be hashed to form a 256 bit key.
|
||||||
|
- **rolling_code_enable** (*Optional*, boolean): Enables a rolling code to be included in all broadcasts. Requires ``encryption`` to be set. Defaults to ``false``. Can be set only on the provider side.
|
||||||
|
- **ping_pong_enable** (*Optional*, boolean): When set, requires encrypted providers to include a *nonce* generated by this device in broadcasts. Defaults to ``false``. Can be set only on the consumer side.
|
||||||
|
- **ping_pong_recycle_time** (*Optional*, :ref:`config-time`): Controls how often the ping-pong key is regenerated. Requires ``ping_pong_enable`` to be set. Defaults to 10 minutes. Can be set only on the consumer side.
|
||||||
|
- **providers** (*Optional*, list): A list of provider device names and optionally their secret encryption keys.
|
||||||
|
|
||||||
|
- **name** (**Required**, string): The device name of the provider.
|
||||||
|
- **encryption** (*Optional*, string): The provider's encryption key.
|
||||||
|
|
||||||
|
Wherever a provider name is required, this should be the node name configured in the ``esphome:`` block.
|
||||||
|
|
||||||
|
This component supports multiple configurations, making it possible to differentiate between consumers when providing data to them.
|
||||||
|
When receiving data in such a configuration, sensors need an ``udp_id`` configuration item to know where to expect data to come from.
|
||||||
|
|
||||||
|
Reliability
|
||||||
|
-----------
|
||||||
|
|
||||||
|
UDP, like any other network protocol, does not provide a guarantee that data will be delivered, but unlike TCP it does not
|
||||||
|
even provide any indication whether data has been successfully delivered or not. When any of the configured sensors changes state,
|
||||||
|
the component will broadcast that sensor's state, but since this may not be delivered to a consumer, the UDP component
|
||||||
|
also broadcasts *all* sensor data on a timed schedule, set by ``update_interval``. Even this does not guarantee
|
||||||
|
delivery, but in practice unless the network has failed, updates will eventually be delivered, albeit possibly after
|
||||||
|
some delay.
|
||||||
|
|
||||||
|
Security
|
||||||
|
--------
|
||||||
|
|
||||||
|
By default there is no security - all data is transmitted in clear text on the network. This would be appropriate
|
||||||
|
for non-sensitive sensor data or perhaps on a fully secured wired network. For other cases the data can be encrypted
|
||||||
|
by providing an encryption key, which is shared between the provider and consumer.
|
||||||
|
|
||||||
|
Encryption alone ensures that data cannot be read in transit and protects against spoofing of data, but does not protect
|
||||||
|
against replay attacks (where a threat actor records a transmission and replays it later, e.g. to repeat an action.)
|
||||||
|
|
||||||
|
A rolling code can be enabled which mitigates replay attacks - each transmission contains a 64 bit value which is
|
||||||
|
guaranteed to monotonically increase, so the consumer will reject any data which contains a rolling code
|
||||||
|
already seen. The rolling code also ensures that the data in every packet is different, which makes brute-force
|
||||||
|
attacks on the encryption much more difficult. This is enabled in the provider configuration and adds minor overhead.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The rolling code's upper 32 bit field is incremented and written to flash *once* at reboot on the provider node.
|
||||||
|
It's also incremented and written to flash when the lower 32 bit field overflows, which can only happen after
|
||||||
|
a very long time. The consumer side does not store the d rolling codes in flash.
|
||||||
|
|
||||||
|
For further protection a ``ping-pong`` (or challenge-response) facility is available, which can be enabled in the
|
||||||
|
consumer configuration. The consumer periodically generates a 32 bit random number (a *nonce* aka "Number used Once")
|
||||||
|
and broadcasts it as a *ping*. Any provider receiving this nonce will include it in any future encrypted broadcasts as
|
||||||
|
*pong*. The consumer expects to get back its most recently transmitted *ping* in any packets it receives, and will reject
|
||||||
|
any that do not contain it.
|
||||||
|
|
||||||
|
Use of the ping-pong feature will add to network traffic and the size of the transmitted packets (a single packet may
|
||||||
|
include up to 4 nonces from different devices) but provides a high level of protection against replay attacks. It does
|
||||||
|
require a 2-way network connection, and it only works on local networks because the consumer can only *broadcast* the
|
||||||
|
nonce to the providers.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Occasionally a ``Ping key not seen`` warning message may appear in the device log. This is expected, because it may
|
||||||
|
happen that while the consumer has regenerated the *ping* key, it subsequently received a *pong* with the previous key,
|
||||||
|
most likely because the messages crossed in transit. In such a case, the message will be rejected, but the next message
|
||||||
|
will contain the correct *pong*.
|
||||||
|
|
||||||
|
Because of this, ``ping-pong`` is only recommended to be used for state transmissions, which are updated periodically
|
||||||
|
at ``update_interval``.
|
||||||
|
|
||||||
|
**Security considerations**
|
||||||
|
|
||||||
|
The encryption used is `XXTEA <https://en.wikipedia.org/wiki/XXTEA>`_ which is fast and compact. Although XXTEA is known
|
||||||
|
to be susceptible to a chosen-plaintext attack, such an attack is not possible with this application, and it otherwise
|
||||||
|
has no published weaknesses [#f1]_. The implementation used here has been modified slightly to use a 256 bit key which
|
||||||
|
will strengthen security compared to the original 128 bit key.
|
||||||
|
|
||||||
|
When encryption is used, all data is encrypted except the sender node name, and the initial request for a ping-pong key.
|
||||||
|
Broadcasting names does not compromise security, since this information would already be available via mDNS.
|
||||||
|
Requesting a key in clear text does not reduce the security of the key, since it is the ability to encrypt this key
|
||||||
|
with the shared secret key that provides the security assurance.
|
||||||
|
|
||||||
|
This does mean however that there is a possible Denial of Service attack by a malicious node overwriting a valid
|
||||||
|
ping-pong key, which will result in packets being rejected by the legitimate consumer.
|
||||||
|
|
||||||
|
Configuration examples
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
This example couples two light switches in two different devices, so that switching either one on or off will cause
|
||||||
|
the other to follow suit. In each case a template binary_sensor is used to mirror the switch state.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# Device 1
|
||||||
|
esphome:
|
||||||
|
name: device-1
|
||||||
|
|
||||||
|
udp:
|
||||||
|
binary_sensors:
|
||||||
|
- relay1_sensor
|
||||||
|
|
||||||
|
switch:
|
||||||
|
- platform: gpio
|
||||||
|
pin: GPIO6
|
||||||
|
id: relay1
|
||||||
|
name: "Device 1 switch"
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: template
|
||||||
|
id: relay1_sensor
|
||||||
|
lambda: "return id(relay1).state;"
|
||||||
|
|
||||||
|
- platform: udp
|
||||||
|
provider: device-2
|
||||||
|
id: relay2_sensor
|
||||||
|
on_press:
|
||||||
|
switch.turn_on: relay1
|
||||||
|
on_release:
|
||||||
|
switch.turn_off: relay1
|
||||||
|
|
||||||
|
|
||||||
|
# Device 2
|
||||||
|
esphome:
|
||||||
|
name: device-2
|
||||||
|
|
||||||
|
udp:
|
||||||
|
binary_sensors:
|
||||||
|
- relay2_sensor
|
||||||
|
|
||||||
|
switch:
|
||||||
|
- platform: gpio
|
||||||
|
pin: GPIO6
|
||||||
|
id: relay2
|
||||||
|
name: "Device 2 switch"
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: template
|
||||||
|
id: relay2_sensor
|
||||||
|
lambda: "return id(relay2).state;"
|
||||||
|
|
||||||
|
- platform: udp
|
||||||
|
provider: device-1
|
||||||
|
id: relay1_sensor
|
||||||
|
on_press:
|
||||||
|
switch.turn_on: relay2
|
||||||
|
on_release:
|
||||||
|
switch.turn_off: relay2
|
||||||
|
|
||||||
|
The following example shows a device using encryption to read a sensor and two binary sensors from two different
|
||||||
|
devices, one with encryption and ping-pong and one without. It also rebroadcasts one of those binary sensors with its own
|
||||||
|
encryption and a rolling code to a remote host.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
udp:
|
||||||
|
update_interval: 60s
|
||||||
|
addresses: ["10.87.135.110"]
|
||||||
|
ping_pong_enable: true
|
||||||
|
rolling_code_enable: true
|
||||||
|
encryption: "Muddy Waters"
|
||||||
|
binary_sensors:
|
||||||
|
- tick_tock
|
||||||
|
providers:
|
||||||
|
- name: st7735s
|
||||||
|
encryption: "Blind Willie Johnson"
|
||||||
|
# - name: room-lights # Not required here since no encryption
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: udp
|
||||||
|
provider: st7735s
|
||||||
|
id: tick_tock
|
||||||
|
- platform: udp
|
||||||
|
provider: room-lights
|
||||||
|
id: relay1_sensor
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: udp
|
||||||
|
provider: st7735s
|
||||||
|
id: wifi_signal_sensor
|
||||||
|
|
||||||
|
The example below shows a provider device separating data sent to different consumers. There are two provider confgurations, with different IDs.
|
||||||
|
The ``udp_internal`` provider broadcasts the selected sensor states in plain every 10 seconds to all the network members, while the ``udp_external``
|
||||||
|
provider sends other sensors data to an external IP address and port, with encryption. The node also listens to data from a ``remote-node`` through
|
||||||
|
the port specified in the ``udp_external`` configuration:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
udp:
|
||||||
|
- id: udp_internal
|
||||||
|
update_interval: 10s
|
||||||
|
sensors:
|
||||||
|
- temp_outdoor
|
||||||
|
- temp_rooma
|
||||||
|
- temp_roomb
|
||||||
|
- temp_roomc
|
||||||
|
- temp_garage
|
||||||
|
- temp_water
|
||||||
|
- humi_rooma
|
||||||
|
- humi_roomb
|
||||||
|
- humi_roomc
|
||||||
|
|
||||||
|
- id: udp_external
|
||||||
|
update_interval: 60s
|
||||||
|
encryption: "Muddy Waters"
|
||||||
|
ping_pong_enable: true
|
||||||
|
rolling_code_enable: true
|
||||||
|
port: 38512
|
||||||
|
addresses:
|
||||||
|
- 10.87.135.110
|
||||||
|
binary_sensors:
|
||||||
|
- binary_sensor_door
|
||||||
|
sensors:
|
||||||
|
- temp_outdoor
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: udp
|
||||||
|
id: binary_sensor_unlock
|
||||||
|
udp_id: udp_external
|
||||||
|
provider: remote-node
|
||||||
|
remote_id: binary_sensor_unlock_me
|
||||||
|
on_press:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGI("main", "d command to binary_sensor_unlock");
|
||||||
|
|
||||||
|
|
||||||
|
.. [#f1] As known in 2024.06.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
|
||||||
|
- :doc:`/components/binary_sensor/udp`
|
||||||
|
- :doc:`/components/sensor/udp`
|
||||||
|
- :ref:`automation`
|
||||||
|
- :apiref:`udp/udp_component.h`
|
||||||
|
- :ghedit:`Edit`
|
1
images/udp.svg
Normal file
1
images/udp.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 69 25" id="svg5" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs9"/><path d="M5 0H64a5 5 0 015 5v15a5 5 0 01-5 5H5a5 5 0 01-5-5V5a5 5 0 015-5z" style="fill:#000" id="path2"/><g aria-label="UDP" id="component-text" style="font-weight:900;font-size:25px;font-family:Montserrat;letter-spacing:1.1px;fill:#fffffc"><path d="m14.8 21.4q-4.025.0-6.275-2.175t-2.25-6.1V3.5h5.9v9.45q0 2 .725 2.85.725.825 1.95.825 1.25.0 1.95-.825.725-.85.725-2.85V3.5h5.8v9.625q0 3.925-2.25 6.1Q18.825 21.4 14.8 21.4z" id="path11"/><path d="M27.12501 21V3.5h8.625q2.925.0 5.15 1.075 2.225 1.05 3.475 3t1.25 4.65q0 2.725-1.25 4.7-1.25 1.95-3.475 3.025-2.225 1.05-5.15 1.05zm5.9-4.6h2.475q1.25.0 2.175-.475.95-.475 1.475-1.4.525-.95.525-2.3.0-1.325-.525-2.25t-1.475-1.4q-.925-.475-2.175-.475h-2.475z" id="path13"/><path d="M48.875016 21V3.5h8.425q2.45.0 4.225.8 1.8.8 2.775 2.3.975 1.475.975 3.5t-.975 3.5q-.975 1.475-2.775 2.3-1.775.8-4.225.8h-5.15l2.625-2.525V21zm5.9-6.175-2.625-2.675h4.775q1.225.0 1.8-.55.6-.55.6-1.5t-.6-1.5q-.575-.55-1.8-.55h-4.775l2.625-2.675z" id="path15"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -204,6 +204,7 @@ Network Protocols
|
|||||||
HTTP Request, components/http_request, connection.svg, dark-invert
|
HTTP Request, components/http_request, connection.svg, dark-invert
|
||||||
mDNS, components/mdns, radio-tower.svg, dark-invert
|
mDNS, components/mdns, radio-tower.svg, dark-invert
|
||||||
WireGuard, components/wireguard, wireguard_custom_logo.svg
|
WireGuard, components/wireguard, wireguard_custom_logo.svg
|
||||||
|
UDP, components/udp, udp.svg
|
||||||
|
|
||||||
Bluetooth/BLE
|
Bluetooth/BLE
|
||||||
-------------
|
-------------
|
||||||
@ -1156,7 +1157,6 @@ Cookbook
|
|||||||
Sonoff Fishpond Pump, cookbook/sonoff-fishpond-pump, cookbook-sonoff-fishpond-pump.jpg
|
Sonoff Fishpond Pump, cookbook/sonoff-fishpond-pump, cookbook-sonoff-fishpond-pump.jpg
|
||||||
Arduino Port Extender, cookbook/arduino_port_extender, arduino_logo.svg
|
Arduino Port Extender, cookbook/arduino_port_extender, arduino_logo.svg
|
||||||
EHMTX a matrix status/text display, cookbook/ehmtx, ehmtx.jpg
|
EHMTX a matrix status/text display, cookbook/ehmtx, ehmtx.jpg
|
||||||
Share data directly between ESPHome nodes, cookbook/http_request_sensor, connection.svg, dark-invert
|
|
||||||
|
|
||||||
Do you have other awesome automations or cool setups? Please feel free to add them to the
|
Do you have other awesome automations or cool setups? Please feel free to add them to the
|
||||||
documentation for others to copy. See :doc:`Contributing </guides/contributing>`.
|
documentation for others to copy. See :doc:`Contributing </guides/contributing>`.
|
||||||
|
Loading…
Reference in New Issue
Block a user