Esphome script on_value_range

Hello,

I’m just starting out with ESPhome programming.

I’m trying to pass a parameter to a script.

This script, “check_heaterLevel_upDown,” is supposed to compare the passed value “levelNow” with the global variable “heat_level_now” to see if the value is above or below the current level.

I was thinking of using on_value_range.

script:
  - id: check_heaterLevel_upDown
    parameters:
      levelNow: int
    then:
      - on_value_range:
        above: !lambda id(heat_level_now);
          script.execute: calc_minRunTimeInHeatLevel
          script.execute: heaterLevel_minus
        below: !lambda id(heat_level_now);
          script.execute: calc_minRunTimeInHeatLevel
          script.execute: heaterLevel_plus

This code also seems to be incorrect.

script:
  - id: check_heaterLevel_upDown
    parameters:
      levelNow: int
    then:
      - on_value_range:
        above: !lambda id(heat_level_now);
          - script.execute: calc_minRunTimeInHeatLevel
          - script.execute: heaterLevel_minus
        below: !lambda id(heat_level_now);
          - script.execute: calc_minRunTimeInHeatLevel
          - script.execute: heaterLevel_plus

But no matter what I do, I get an error message saying my syntax is incorrect.

mapping values are not allowed here
  in "/config/esphome/test1.yaml", line 184, column 25

Can anyone help me with this?

Post your full yaml - that will help us diagnose the issue…

---
substitutions: 
  devicename: esp32-s2-mini-saola
  board: esp32-s2-saola-1
  upper_devicename: ESP32_S2_TestBoard
  device_location: Schreibtisch
  device_description:  Testboard using ESPhome firmware
  ipaddress: !secret ESP32_S2_TESTBOX_IP
  last_update: "2025-10-23 22:54:12"


esphome:
  name: esp32-s2-mini-saola
  comment: Testboard
  includes: []
  libraries: []
  build_path: ESP32_S2_TestBoard
  platformio_options: {} 

esp32:
  board: esp32-s2-saola-1
  framework:
    type: esp-idf

api:
  encryption:
    key: !secret ESPHOME_API_KEY

wifi:
  power_save_mode: none # none (default for esp8266), light (default for esp32), high
  ssid: !secret WLAN_ASUS_SSID
  password: !secret WLAN_ASUS_PASSWORD
  manual_ip:
    static_ip: !secret ESP32_S2_TESTBOX_IP
    gateway: !secret WLAN_ASUS_GATEWAY_IP
    subnet: !secret WLAN_ASUS_SUBNET
    dns1: !secret WLAN_ASUS_DNS1

  ap:
    ssid: "ESP32-S2-MINI-Testboard"
    password: !secret WIFI_AP_PASSWORD

ota:
  - platform: esphome
    #safe_mode: true
    password: !secret HA_OTA_PASSWORD

web_server:
  port: 80
  auth:
    username: admin
    password: admin

globals:
   # global int variable
  - id: heater_run
    type: int
    restore_value: no
    initial_value: '0'

  - id: heat_level
    type: int
    restore_value: no
    initial_value: '10'

  - id: heat_level_now
    type: int
    restore_value: no
    initial_value: '0'

  - id: set_heat_level_to
    type: int
    restore_value: no
    initial_value: '0'
  # global string variable

  - id: string_heater_run_yes
    type: std::string
    restore_value: yes
    max_restore_data_length: 24
    initial_value: '"Heizung ist An"'

  - id: string_heater_run_no
    type: std::string
    restore_value: yes
    max_restore_data_length: 24
    initial_value: '"Heizung ist Aus"'

  - id: heater_minRunTimeInHeatLevel
    type: int
    restore_value: no
    initial_value: '0'

  - id: calc_startTime
    type: int
    restore_value: no
    # initial_value: '0'
    initial_value: '0'

  - id: calc_endTime
    type: int
    restore_value: no
    initial_value: '0'

remote_receiver:
  pin: GPIO18
  #dump: all # rc_switch
  dump: # dump: all
    - rc_switch 
  # Settings to optimize recognition of RF devices
  tolerance: 50%
  idle: 4ms

remote_transmitter:
  pin: GPIO17
  carrier_duty_percent: 100% # change from 100%  to 50%

sensor:
  - platform: dht
    pin: GPIO11
    model: AM2302
    temperature:
      name: "Garage Temperatur DHT_1"
      id: TEMP_DHT_1
      unit_of_measurement: "°C"
      accuracy_decimals: 2
    humidity:
      name: "Garage Luftfeuchtigkeit DHT_1"
      id: HUMIDITY_DHT_1
      unit_of_measurement: "%"
      accuracy_decimals: 2
    update_interval: 10s
  - platform: template
    name: "Temp Garage delta"
    id: tempGarage_delta
    unit_of_measurement: "°C"
    accuracy_decimals: 2
    update_interval: 60s
    lambda: |-
      return id(TEMP_DHT_1).state - id(garage_thermostat).target_temperature;
    on_value:
      then:
        - script.execute: update_vevor_heaster_power

# Example configuration entry
climate:
  - platform: thermostat
    name: "Garage Vevor Heizung"
    id: garage_thermostat
    sensor: TEMP_DHT_1
    heat_deadband: 0.0
    heat_overrun: 0.0
    visual:
      min_temperature: 15
      max_temperature: 30
      temperature_step: 0.1
    min_heating_off_time: 2s
    min_heating_run_time: 2s
    min_idle_time: 2s
    heat_action: []
    idle_action: []
    preset:
      - name: Kodus
        default_target_temperature_low: 20 °C
      - name: Eemal
        default_target_temperature_low: 18 °C

     
script:
  - id: check_heaterLevel_upDown
    parameters:
      levelNow: int
    then:
      on_value_range:
        above: !lambda id(heat_level_now);
          - script.execute: calc_minRunTimeInHeatLevel
          - script.execute: heaterLevel_minus
        below: !lambda id(heat_level_now);
          script.execute: calc_minRunTimeInHeatLevel
          script.execute: heaterLevel_plus

  - id: heaterLevel_plus
    parameters:
      heater: int
    then:
      - remote_transmitter.transmit_rc_switch_raw: 
        #code Plus
          code: '00011100010111100000100011110000'
          protocol: 
            pulse_length: 255
          repeat:
            times: 5
            wait_time: 0s
      - logger.log: Heizung Plus!

  - id: heaterLevel_minus
    parameters:
      heater: int
    then:
      - remote_transmitter.transmit_rc_switch_raw: 
        #code Plus
          code: '00011100010111100000001010001000'
          protocol: 
            pulse_length: 255
          repeat:
            times: 5
            wait_time: 0s
      - logger.log: Heizung Minus!

  - id: calc_minRunTimeInHeatLevel
    parameters:
      delta_degress: float
    then: 
      - lambda: |-
          // id(calc_startTime) = millis();
          int tempStartTime = millis();
          int tempCalc_startTime = calc_startTime;
          int tempCalc_endTime = calc_endTime;
          // heat_level
          if (delta_degress < 0.5) {
            id(tempCalc_endTime) = (id(calc_tempCalc_startTime) + (1 * 60000));
            if (temp_calcStartTime > tempCalc_endTime) && (heat_level > set_heat_level_to){
              id(calc_endTime) = (id(calc_startTime) + (1 * 60000));
            }
          }
          if (delta_degress < 1) {
            id(calc_endTime) = id(calc_startTime) + (2 * 600000);
          }
          if (delta_degress < 1.5) {
            id(calc_endTime) = id(calc_startTime) + (3 * 600000);
          }
          if (delta_degress < 2) {
            id(calc_endTime) = id(calc_startTime) + (4 * 600000);
          }
          if (delta_degress < 3) {
            id(calc_endTime) = id(calc_startTime) + (6 * 600000);
          }
          if (delta_degress < 4) {
            id(calc_endTime) = id(calc_startTime) + (8 * 600000);
          }
          if (delta_degress < 10) {
            id(calc_endTime) = id(calc_startTime) + (10 * 600000);
          }else{
             id(calc_endTime) = id(calc_startTime) + (15 * 600000);
          }

  - id: update_vevor_heaster_power
    mode: restart
    then:
      - if:
          # Heizstufe 10
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -3.0
          then:
            - script.execute:
                id: heaterLevel_plus
                heater: 3
      - if:
          # Heizstufe 9
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -2.5
              above: -3.0
          then:
            - script.execute:
                id: heaterLevel_minus
                heater: 5

      - if:
          # Heizstufe 8
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -2.0
              above: -2.49
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 3 x Minus

      - if:
          # Heizstufe 7
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -1.6
              above: -1.99
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 4 x Minus

      - if:
          # Heizstufe 6
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -1.2
              above: -1.59
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 2 x Minus
      - if:
          # Heizstufe 5
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -0.9
              above: -1.19
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 222 x Minus
      - if:
          # Heizstufe 4
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -0.6
              above: -0.89
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 3333 x Minus
      - if:
          # Heizstufe 3
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -0.4
              above: -0.59
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 2333 x Minus
      - if:
          # Heizstufe 2
          condition:
            sensor.in_range:
              id: tempGarage_delta
              below: -0.1
              above: -0.39
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 255555 x Minus
      - if:
          # Heizstufe 1
          condition:
            sensor.in_range:
              id: tempGarage_delta
              above: 0
          then:
            - lambda: |-
                id(set_heat_level_to) = 5;
                id(heat_level_now) = (id(heat_level) - id(set_heat_level_to));
            - logger.log: Heizung 266666 x Minus

switch:
  - platform: gpio
    pin: GPIO10
    name: "Heizung Strom An/Aus"
    id: RelayHeater

button:
  - platform: template
    name: Heizung An
    id: heaterOn
    on_press:
      - remote_transmitter.transmit_rc_switch_raw:   
          code: '00011100010111100001000110110000'
          protocol: 
            pulse_length: 255
          repeat:
            times: 5
            wait_time: 0s
      - logger.log: Heizung ANgeschaltet!
  - platform: template
    name: Heizung Aus
    id: heaterOff
    on_press:
      - remote_transmitter.transmit_rc_switch_raw:   
          code: '00011100010111100000010100001000'
          protocol: 
            pulse_length: 255
          repeat:
            times: 5
            wait_time: 0s
      - logger.log: Heizung AUSgeschaltet!
  - platform: template
    name: Heizleistung Plus
    id: heaterPlus
    on_press:
      - remote_transmitter.transmit_rc_switch_raw:   
          code: '00011100010111100000100011110000'
          protocol: 
            pulse_length: 255
          repeat:
            times: 5
            wait_time: 0s
  - platform: template
    name: Heizleistung Minus
    id: heaterMinus
    on_press:
      #- script.call: heizleistung_1
      - remote_transmitter.transmit_rc_switch_raw:   
          code: '00011100010111100000001010001000'
          protocol: 
            pulse_length: 255
          repeat:
            times: 5
            wait_time: 0s
improv_serial:

## Enable logging
logger:
  level: DEBUG
  baud_rate: 9600

Attempt at an explanation of the code:

What I want to achieve with the code:

It’s about controlling a slow-reacting diesel garage heater that often has a temperature difference of about 15°C between the actual and target temperatures when first switched on.

The heater has exactly 10 heating levels and starts at the highest level, 10, after every restart.

So, before I can regulate the heater using temperature measurements and try to maintain the target temperature, the heater first needs to run for a specific, predetermined time at a particular heating level.

If I don’t do this, the heater would just keep switching back and forth.

Therefore, I need temperature differences and switching times.

Actually - I didn’t need your yaml - Sunday arvo has fuzzed my brain and I didn’t notice that on_value_range: isn’t valid for scripts.

You need to test the parameter value in lambda - for example:

script:
  - id: check_heaterLevel_upDown
    parameters:
      levelNow: int
    then:
      - if:
        condition:
          lambda: 'return levelNow >= id(heat_level_now);'
        then:
          script.execute: calc_minRunTimeInHeatLevel
          script.execute: heaterLevel_minus

etc...
1 Like

Thank you,
yes,
i was just trying it with lambdas, and the code worked (better :slight_smile: ).

I just wasn’t sure if I had the wrong syntax or if on_value_range simply doesn’t work in the script, since I’ve only ever seen on_value_range used in Sensor and, I think, Number functions as examples.

I’ve now solved it with lambdas and if statements.

However, the syntax and notation you have to use in ESPHome is really quite confusing at times.

For example, the compiler doesn’t throw an error during the initial code check when you call execute under lambda, but after it’s almost finished compiling, it finally throws the error that it doesn’t like the execute command.

code snipped:

script:
  - id: check_heaterLevel_upDown
    parameters:
      levelNow: int
    then: 
      lambda: |-
        if(id(heat_level_now) > levelNow){
          //id(calc_minRunTimeInHeatLevel).execute();
          if(id(heat_level_now) > levelNow){
            id(heaterLevel_minus).execute();
          }
        };
        if(id(heat_level_now) < levelNow){
          //id(calc_minRunTimeInHeatLevel).execute();
          if(id(heat_level_now) > levelNow){
            id(heaterLevel_plus).execute();
          }
        };
        

Compiling .pioenvs/esp32-s2-mini-saola/src/main.cpp.o
/config/esphome/test1.yaml: In lambda function:
/config/esphome/test1.yaml:192:37: error: no matching function for call to 'esphome::script::SingleScript<int>::execute()'
  192 |             id(heaterLevel_minus).execute();
      |             ~~~~~~~~~~~~~~~~~~~~~~~~^~
In file included from src/esphome.h:88,
                 from src/main.cpp:3:
src/esphome/components/script/script.h:68:8: note: candidate: 'void esphome::script::SingleScript<Ts>::execute(Ts ...) [with Ts = {int}]'
   68 |   void execute(Ts... x) override {
      |        ^~~~~~~

but i try and try and hope i find out my misstakes

edit:
i find now more and more the target
this code now run… looks now much better
step by step

script:
  - id: check_heaterLevel_upDown
    parameters:
      levelNow: int
    then: 
      lambda: |-
        if(id(heat_level_now) > levelNow){
            id(heaterLevel_minus)->execute(levelNow);
        };
        if(id(heat_level_now) < levelNow){
            id(heaterLevel_plus)->execute(levelNow);
        };