UDP Component

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

# 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, ID): Manually specify the ID used for code generation.

  • update_interval (Optional, 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, 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, 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, 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 [1]. 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.

# 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.

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:

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");

See Also