diff --git a/components/binary_sensor/udp.rst b/components/binary_sensor/udp.rst new file mode 100644 index 000000000..a4122ed56 --- /dev/null +++ b/components/binary_sensor/udp.rst @@ -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 `. + +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` diff --git a/components/sensor/udp.rst b/components/sensor/udp.rst new file mode 100644 index 000000000..7d5d798c0 --- /dev/null +++ b/components/sensor/udp.rst @@ -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 `. + +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` diff --git a/components/udp.rst b/components/udp.rst new file mode 100644 index 000000000..59a92fd32 --- /dev/null +++ b/components/udp.rst @@ -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 `_ 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` diff --git a/images/udp.svg b/images/udp.svg new file mode 100644 index 000000000..d9e02adc1 --- /dev/null +++ b/images/udp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.rst b/index.rst index 14f70b012..0370be6c7 100644 --- a/index.rst +++ b/index.rst @@ -204,6 +204,7 @@ Network Protocols HTTP Request, components/http_request, connection.svg, dark-invert mDNS, components/mdns, radio-tower.svg, dark-invert WireGuard, components/wireguard, wireguard_custom_logo.svg + UDP, components/udp, udp.svg Bluetooth/BLE ------------- @@ -1156,7 +1157,6 @@ Cookbook Sonoff Fishpond Pump, cookbook/sonoff-fishpond-pump, cookbook-sonoff-fishpond-pump.jpg Arduino Port Extender, cookbook/arduino_port_extender, arduino_logo.svg 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 documentation for others to copy. See :doc:`Contributing `.