Conversion between brightness_pct (1-100), Z-Wave (1-99), and brightness (1-255)

I’ve been working on calibrated presets (dim, normal, bright) for each of my Z-Wave dimmers. My sliders are sometimes off by one and my comparisons can fail for the same reason. It’s my understanding that brightness_pct is 1-100 while Z-Wave uses 1-99. The native brightness is 1-255. I created a spreadsheet of values by using light.turn_on with brightness_pct, logging the actual levels sent/received over Z-Wave and then reading the resulting brightness of the light. I would like to know the exact formulas being used for conversion. I suspect that HA is doing some intermediate rounding. My only solution at this point is to use lookup tables to correct what I consider errors. The yellow highlight is not necessarily an error, but shows where percent and Z-Wave diverge. If the formula were linear, I would expect an evenly-spaced pattern instead of clumps.

It will go through two conversions, percentage to byte (0-255) then byte to z-wave (0-99). The Z-Wave integration never sees the percentage value.

If you want to bypass the conversions and set the Z-Wave value yourself, you can always use the low-level zwave_js.set_value service call with the Multilevel Switch value targetValue.

I was doing some more analysis while you were writing your reply. We essentially came to the same conclusion. Because there are multiple conversions, the intermediate rounding can introduce an error. Here’s my summary. I will also update the original spreadsheet link.

Now that I understand what’s happening, I’ll have to figure out the best solution. Thanks for your help! Fortunately, I already have the dimmers templated as fans (with presets), so I can easily make adjustments.

PS: If you drag a Z-Wave slider to any of the levels in my image, you’ll see the percentage jump forward or back. The same happens with a voice integration.

This is so much cleaner. I associated an input_number helper with each dimmer. The helper gets set based on incoming Z-Wave level events. I can then use the helper in the template for both value and percentage. There’s no math. I just clip 100% to 99, so the slider may back up 1 in that special case. The Z-Wave set_value needed different endpoints based on the dimmer model, but fortunately I had encoded enough information in my entity_id’s. I’ve got 18 dimmers using the same core scripts.

kitchen:
  unique_id: kitchen
  friendly_name: KITCHEN
  value_template: "{{ int(states('input_number.kitchen47d')) > 0 }}"
  percentage_template: "{{ int(states('input_number.kitchen47d')) }}"
  preset_mode_template: "{{ states('input_text.kitchen47d') }}"
  preset_modes: !include light_preset_list.yaml
  turn_on:
  - service: script.turn_on
    data:
      entity_id: script.control_ui
      variables:
        switch: kitchen47d
        state: normal        
  turn_off:
  - service: script.turn_on
    data:
      entity_id: script.control_ui
      variables:
        switch: kitchen47d
  set_percentage:
  - service: script.turn_on
    data:
      entity_id: script.control_ui
      variables:
        switch: kitchen47d
        state: "{{ percentage }}"
  set_preset_mode:
  - service: script.turn_on
    data:
      entity_id: script.control_ui
      variables:
        switch: kitchen47d
        state: "{{ preset_mode }}"


- service: zwave_js.set_value
  target:
    entity_id: light.{{ switch }}
  data:
    command_class: 38
    property: targetValue
    endpoint: "{{ 1 if switch[-2] == 'm' else 0 }}"
    value: "{{ level }}"

If your level coincides with one of the presets, then that preset is marked as active. I have additional logic to allow a single or double-tap to cycle through the presets. You can also pick a preset from the list below the slider (it’s actually a fan template). Before today’s changes, all my comparisons had to include +/- 1 to account for the slop introduced by HA internal rounding.

1 Like