Can I set the output to text if the value is 0 or 100 in a template sensor?

For a rudder position sensor in my boat I would like the value to be set to the numbers I have when it’s between 5 and 95, to “bottom” if it’s less than 5 (which in my setup will be 0) and “top” if it’s more than 5 (which here is 100). Is this possible? Here’s the template sensor code:

  - platform: template
    name: "Motorheisposisjon"
    lambda: |-
      return ((id(motorheisspenning).state / (id(referansespenning).state)) * 100);
    update_interval: 100ms
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10
      - calibrate_linear:
            - 17.3 -> 0
            - 48.5 -> 100
      - lambda: return 5*round(x/5);
      - clamp:
          min_value: 0
          max_value: 100
      - lambda: |
          if (x < 0.1) return {0}; 
          else return x;

You can define more than two datapoints for the calibrate_linear filter, so this might do what you want (not tested):

- platform: template
    name: "Motorheisposisjon"
    lambda: |-
      return ((id(motorheisspenning).state / (id(referansespenning).state)) * 100);
    update_interval: 100ms
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10
      - calibrate_linear:
          method: exact
          datapoints:
            - 17.3 -> 0
            - 18.8 -> 0
            - 18.9 -> 5
            - 46.8 -> 95
            - 46.9 -> 100
            - 48.5 -> 100
      - lambda: return 5*round(x/5);
      - clamp:
          min_value: 0
          max_value: 100
      - lambda: |
          if (x < 0.1) return {0}; 
          else return x;

Thanks for answering! But either there’s something I don’t understand, or you misunderstood me. I don’t think that code can show “Bottom” and “Top” (I set it in quotation marks to show that it was the text words I meant).

Sorry, I misunderstood your issue. I thought that you meant that the bottom value should be 0 and the top value should be 100, but you want it the other way around.

However, my proposal might still be of interest, because I think that with the original code the rounded output value will be 95 with an input value between 95 and 97 ( 5 * round( 97 / 5 ) = 95 ).
Only with an input value of 98 to 100 the output value will be 100.
And similar an input value between 3 and 5 will output 5.
So you will get Top with values between 98 and 100 and Bottom with values between 0 and 2.
Or is this what you want?

I don’t think it is possible to have a sensor with a conditional data type output, so sometimes an int like 20 or 80 and sometimes a string like Bottom or Top.
What are you doing with the output of the sensor? Is it only printed on a display?
So would it function if the output would be a string always, so also when the output is for instance 50?
If so, then something like this might work (again not tested):

- platform: template
    name: "Motorheisposisjon"
    id: motorheisposisjon
    lambda: |-
      return ((id(motorheisspenning).state / (id(referansespenning).state)) * 100);
    update_interval: 100ms
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10
      - calibrate_linear:
            - 17.3 -> 0
            - 48.5 -> 100
      - lambda: return 5*round(x/5);
      - clamp:
          min_value: 0
          max_value: 100
      - lambda: |
          if (x < 0.1) return {0}; 
          else return x;
    on_value:
      then:
        - text_sensor.template.publish:
            id: motorheisposisjon_string
            state: !lambda 'return id(motorheisposisjon).state;'

And then add a Text Sensor with a mapping filter like this:

text_sensor:
  - platform: template
    name: "Motorheisposisjon as String"
    id: motorheisposisjon_string
    filters:
      - map:
        - 0 -> Bottom
        - 100 -> Top

Like this you can use the Text Sensor as output instead of the Template Sensor.

I think that’s what I want, thanks a lot! I will experiment tonight after work! :+1:

I think the approach you could use is create a new Template Text sensor.

Then use an on_value action on your existing sensor and some if statements to publish the text values to the text sensor.

For the non top/bottom values, you’ll need to convert the x to a string before publishing it.

I’ll see if I can dig up some coffee snippets but I’m on my mobile for a while.

Edit: Just read @thusassistint last post. His way is probably cleaner.

Yeah, I think his approach will work for me. :smiley: I’ll mark that as the solution when I have tested it.

1 Like

@thusassistint It won’t compile. I get this conversion error, and I have no idea how to convert the float numbers to a string:

motorheis-oppsett-tekstsensor.yaml: In lambda function:
motorheis-oppsett-tekstsensor.yaml:91:33: error: could not convert 'motorheisposisjon->esphome::template_::TemplateSensor::<anonymous>.esphome::sensor::Sensor::state' from 'float' to 'std::__cxx11::string' {aka 'std::__cxx11::basic_string<char>'}
*** [.pioenvs\motorheis\src\main.cpp.o] Error 1

Edit: I wonder if this can be done with an if - then in the text sensor, so if the value is 0 then bottom, if the value is 100 then top else the value that comes in?

One more try:

    on_value:
      then:
        - text_sensor.template.publish:
            id: motorheisposisjon_string
            state: !lambda 'return (to_string(id(motorheisposisjon).state).c_str());'

This should convert the float to string.

Thanks, it did! That converted to string the unfiltered version, but when I changed it like this it worked better:

    on_value:
      then:
        - text_sensor.template.publish:
            id: motorheisposisjon_tekst
            state: !lambda 'return (to_string(x).c_str());'

It now takes into accounts the filters, but with six decimals, so it’s 0.000000 and 100.000000 instead of 0 and 100. I tried to put in accuracy_decimals: 0
before the filters, but that didn’t help either. Kind of weird that there are no filters that works this way. Maybe because as it says about accuracy decimals:

This does not impact the actual value reported to Home Assistant, it just sets the number of decimals to use when displaying it.

Is there another way to do this?

OK, one more try:

on_value:
      then:
        - text_sensor.template.publish:
            id: motorheisposisjon_string
            state: !lambda 'return (to_string(int(id(motorheisposisjon).state)).c_str());'

Or maybe:

on_value:
      then:
        - text_sensor.template.publish:
            id: motorheisposisjon_tekst
            state: !lambda 'return (to_string(int(x)).c_str());'

I actually managed to think myself into the second version by myself, from the first, before I saw your edit! And now it works as it should, so thank you very much! :+1:

Full code with adc sensors and everything:

sensor:
  - platform: adc
    pin: 35
    attenuation: auto
    name: "Motorheisspenning"
    id: motorheisspenning
    update_interval: 5ms
    filters:
      - sliding_window_moving_average:
          window_size: 200
          send_every: 100

  - platform: adc
    pin: 32
    attenuation: auto
    name: "Referansespenning"
    id: referansespenning
    update_interval: 5ms
    filters:
      - sliding_window_moving_average:
          window_size: 100
          send_every: 100

  - platform: template
    name: "Motorheis-forholdstall"
    id: motorheisposisjon
    lambda: |-
      return ((id(motorheisspenning).state / (id(referansespenning).state)) * 100);
    update_interval: 100ms

  - platform: template
    name: "Motorheisposisjon"
    lambda: |-
      return ((id(motorheisspenning).state / (id(referansespenning).state)) * 100);
    update_interval: 100ms
    accuracy_decimals: 0
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10
      - calibrate_linear:
            - 17.3 -> 0
            - 48.5 -> 100
      - lambda: return 5*round(x/5);
      - clamp:
          min_value: 0
          max_value: 100
      - lambda: |
          if (x < 1) return {0}; 
          else return x;
      - lambda: |
          if (x > 99) return {100}; 
          else return x;

    on_value:
      then:
        - text_sensor.template.publish:
            id: motorheisposisjon_tekst
            state: !lambda 'return (to_string(int(x)).c_str());'
            
text_sensor:
  - platform: template
    name: "Motorheisposisjon som tekst"
    id: motorheisposisjon_tekst
    
    filters:
      - map:
          - 0 -> Bunn
          - 100 -> Topp
2 Likes

Well done! :+1: