UDP component

I’m planning to use the newly introduced UDP component on some of my switches and lights

Now in the documentation the sensors for devices 1 and 2 are different.
would it work if I use the same sensor on both ends?

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
    id: relay1_sensor
    on_press:
      switch.turn_on: relay1
    on_release:
      switch.turn_off: relay1

or will this create an infinite loop?
The issue is that sometimes I want to combine 3 or 4 switches, and needing a sensor for them all and a listener would make everything really complex.

I can’t really figure this out from the docs.

also I don’t get why there are on_press and on_release events for a binary_sensor

  1. You can’t have two sensors in the same device with the same ID.
  2. What you want to achieve will work, if done correctly. There is no infinite loop because the UDP component propogates state, not events.

Doing it for more than 2 switches does get complex, but that’s just the nature of the beast - it’s a complex problem. You would probably want to use one switch as a master and have each other switch only aware of itself and the master. Then the complexity only increases linearly with the number of switches, rather than factorially.

What part don’t you get? Is it that you don’t like the names, or you don’t understand what they do?

Because the most common use is for connecting to a GPIO button and on_on or on_off would be very confusing. on_true or on_false isn’t much better.

Can you come up with better names for the events?

I’m not a fan of any of these:

on_close/open
on_high/low
on_active/inactive

that one i Missed, maybe I could use a broadcast_id for that one.

I will start with 2 switches, And check if I can get away using the same broadcast_id on both devices. then the sensors can have different ids.

but unfortunately today I don’t have the time to test.

there is no press and release on a binary session. technically only to_on and to_off
or something. probably the names of the actions are confusing for me.

then that would be called a switch.

but a different naming would be better.
of if press equals true and release equals false
than at least is is clear only a little confusing.

No. A switch is bistable. A push button is not.

sensor:
  - platform: udp
    provider: some-device-name
    id: local_sensor_id
    remote_id: some_sensor_id

Here is as described in doc - remote sensor id some_sensor_id to be re-mapped to local local_sensor_id.

As well to receive data from remote device need to declare provider as here:

udp:
  providers:
    - name: some-device-name
      encryption: "REPLACEME with some key"

I’m not sure how providers work directly under the udp component.

At this moment In my test with 2 switches I have this configuration

udp:
  binary_sensors:
    - id: relay1_sensor
      broadcast_id: udp_Garage
      
binary_sensor:
  - platform: template
    id: relay1_sensor
    lambda: "return id(switch_1).state;"
  - platform: udp
    id: relay1_remote
    remote_id: udp_Garage
    provider: differnt_per_device
    on_press:
      switch.turn_on: switch_1
    on_release:
      switch.turn_off: switch_1

I here have a different provider per device,
Meaning, with 3 devices I need 2 udp binary_sensors, and then just multicast the same id.

next to that there is a setting update_interval which syncs the states every X settings, But on a state change I would prefer to send the message multiple times. so if the 1ste is missed I don’t have to wait the interval time,

You are right - it is used to define encryption key once in configuration.

The update interval is a time, not a number of state changes. When a state changes it will be broadcast immediately, then at the update interval all sensors will be transmitted. Because UDP is unreliable there is no guarantee that any broadcast will be received.

If you want to minimise the delay if one is missed, use a short update interval. But there is no way of guaranteeing reception within any given period, and using too short an update interval can be counterproductive as it increases traffic, and therefore the likelihood of a missed transmission. If you need guarantees of reception, UDP is not the right choice.

I know,
But I need it to be fast and reliable.
an automation is HA usually is just to slow.

The previous custom component I used sends the udp package multiple times on a status change, but not on a time interval, this way the udp sync actually never failed.
but there was no esp-idf support, so That’s why I think this new component can work out better.
I’m afraid if I set the interval to 1s, that I’m flooding the network.

1s should not be a problem unless you have many nodes. You can also reduce load on receivers (rather than the network itself) by sending to specific IP addresses, that way other nodes won’t have to process the packet.

thnx,

in the end, my full configuration will have about 20 devices which will sync using UDP. So I don’t know if that is considered many.
Specifying IP addresses would be a good idea, however my IP addresses are dynamic,
working with mDNS names would work better for me,
of maybe this is already possible but it’s not in the docs.

I think maybe this would work for me. where the value 5 would be configurable.
but I don’t know c++ well enough to know if this is valid.

void UDPComponent::send_data_(bool all) {
  if (!this->should_send_)
    return;
  for (int i = 0; i < 5; i++) {
    if (!network::is_connected()) {
      sleep(10);
      continue;
    }
    this->init_data_();
    #ifdef USE_SENSOR
      for (auto &sensor : this->sensors_) {
        if (all || sensor.updated) {
          sensor.updated = false;
          this->add_data_(SENSOR_KEY, sensor.id, sensor.sensor->get_state());
        }
      }
    #endif
    #ifdef USE_BINARY_SENSOR
      for (auto &sensor : this->binary_sensors_) {
        if (all || sensor.updated) {
          sensor.updated = false;
          this->add_binary_data_(BINARY_SENSOR_KEY, sensor.id, sensor.sensor->state);
        }
      }
    #endif
    this->flush_();
  }
  this->updated_ = false;
  this->resend_data_ = false;
}

This way sending always tries 5 times, and even if the network is failing for a bit the device retries.
The only issue Is that I’m not sure about the sleep time, this might be to long for a device.

1 Like

Hi, do you think you could sync lights/color temp/brightness with UDP?

Usually I’d just use device group to do that

But I can’t use that for devices with esp: idf framework

I tried my sollution above,
without the sleep command because that one is not valid,
So think I can revert the is_connected check all together.
and there are 2 things to mention.

  1. syncing now works perfect on my testdevices, no issues at all during all my tests.
    before I had some issues sometimes.
    2 I now get this error is a loop.
[20:34:09][W][udp:602]: udp.write() error

So I think I’m getting somewhere, but it’s not good enough.
also I think the retry count should be a setting which defaults 1 for current behaviar.
@ferbulous I don’t know, using the Cossid component I only configured all my bulbs as switches.

Edit: the performance update probably was there because I set update_interval to 0s,
not because of my changes.
setting if to never makes the sync never apply also not on status change.

Using example from cossid, i added virtual light and some button actions to dim and change the color temperature

globals:
  - id: counter
    type: int
    restore_value: False
    initial_value: "0"

  - id: bool_dim_or_bright #false = dim, true = brighten
    type: bool
    restore_value: no
    initial_value: "false"

external_components:
  - source: github://cossid/tasmotadevicegroupsforesphome@main
    components: [device_groups]
    refresh: 10 min

device_groups:
  - group_name: "cornerlights" # Tasmota device group name
    lights:
      - internal_light

output:
  - platform: template
    id: dummy_output
    type: float
    write_action:
      - lambda: return;

light:
  - platform: rgbww
    id: internal_light
    color_interlock: true
    cold_white_color_temperature: 6500 K
    warm_white_color_temperature: 2700 K
    red: dummy_output
    green: dummy_output
    blue: dummy_output
    cold_white: dummy_output
    warm_white: dummy_output

  - platform: gpio
    pin:
      number: GPIO26
      mode: INPUT_PULLUP
      inverted: True
    name: $devicename Button 2
    id: button2
    on_multi_click:
      # single click
      - timing:
          - ON for at most 1s
          - OFF for at least 0.5s
        then:
          - if:
              condition:
                  - wifi.connected:
              # toggle light when wifi is conncected
              then:
                - light.toggle: internal_light
              # toggle relay in case  wifi is not connected
              else:
                - switch.toggle: relay_2
      # double click to switch to cold or warm light
      - timing:
          - ON for at most 1s
          - OFF for at most 1s
          - ON for at most 1s
          - OFF for at least 0.2s
        then:
          - lambda: |-
              auto call = id(internal_light).turn_on();

              if (id(counter) == 0) { //Cold white with 100% brightness
                call.set_cold_white(1.0);
                call.set_brightness(1.0);
              }

              if (id(counter) == 1) { //Warm white with 50% brightness
                call.set_cold_white(0.0);
                call.set_warm_white(1.0);
                call.set_brightness(0.5);
              }

              if (id(counter) < 1) { //Or your maximum effects
                id(counter) += 1;
              } else {
                id(counter) = 0;
              }

              call.perform();

    #long press for dimming
    on_press:
      then:
        - if:
            condition:
              lambda: |-
                return id(bool_dim_or_bright);
            # When above condition evaluates to true - brighter function else dimmer
            then:
              - delay: 0.5s
              - while:
                  condition:
                    binary_sensor.is_on: button2
                  then:
                    - light.dim_relative:
                        id: internal_light
                        relative_brightness: 5%
                        transition_length: 0.1s
                    - delay: 0.1s
              - lambda: |-
                  id(bool_dim_or_bright) = (false);
            else:
              - delay: 0.5s
              - while:
                  condition:
                    and:
                      - binary_sensor.is_on: button2
                  then:
                    - light.dim_relative:
                        id: internal_light
                        relative_brightness: -5%
                        transition_length: 0.1s
                    - delay: 0.1s
              - lambda: |-
                  id(bool_dim_or_bright) = (true);

I was wondering if I could retrieve those light state from lambda and just send them thru udp