Extract sensors from a string text sensor using templating

I have an esphome setup that returns this string as a sensor value (103.0 53.61 14.82 07.65 07.17 0798 +044 43.39 -002 0000 11000000).

The bolded 53.61 represents a voltage reading.

How can I use template withing ESPHOME yaml to return 53.61 as a sensor.

I have this done using Home Assistant template. However, I wish I can just get ESPHOME to output it without having to mess with Home Assistant Templating.

Thanks in Advance.

This is the esphome yaml that produce the sensor if it helps.


  - platform: pipsolar
    pipsolar_id: fcce1
      id: fcce1_qpigs
      name: fcce1_qpigs

1 Like

I think you’ll need to use a lambda filter to step through each character of the “before” sensor value (x in the lambda), discard everything up to the first space, build a string from the subsequent characters until you hit the second space then stop and return what you’ve found.

Alternatively, try using strtok() like in this topic.

Personally, I’d stick with HA’s rather simpler solution:

value_template: "{{ value.split()[1] }}"
1 Like
  • lambda: |-
    std::string str0 = id(my text_sensor).state;
    std::string voltage = str0.substr(7,5);

What if the string is (99.3 57.1 …) ?

It’s interesting, I actually developed what seemed to be a fully working and tested solution to this based on @Troon s suggested approach and with help from They Who Shall Not Be Named (who has been helping me write some pretty advanced and quite similar Lambda’s lately) , but then double checked the forum rules and it was a no-go.

You are absolutely allowed to show tested and working code, no matter which AI abomination helped you with it.

1 Like

Please show it if it is working. I think what the moderators frown upon is dumping untested AI generated codes that may not work.

And yes, I also turned to ChatGPT for help and after several hours of failed suggestions, it pointed me to a working solution and I now have the yaml working perfectly.

For anyone interested, this is the code that worked.

  - platform: template
    name: "fcc1 battvoltage"
    update_interval: 1s
    unit_of_measurement: V
    icon: "mdi:sine-wave"
    device_class: "voltage"
    state_class: "measurement"
    accuracy_decimals: 2
    lambda: |-
      auto str = id(fcce1_qpigs).state.c_str();
      int startPos = strcspn(str, " ") + 1; // Find the position of the first space and add 1
      int endPos = strcspn(str + startPos, " "); // Find the position of the next space
      if (endPos > 0) {
        std::string figureStr(str + startPos, endPos);
        float fcc1battvoltage = std::stof(figureStr);
        return fcc1battvoltage;
      } else {
        return NAN;

I was able to modify this for other sensors from the string.


That’s pretty similar to where I landed.

Like your experience I usually find I have to re-prompt and iterate a bit to get to a working solution. But I get there and the speed of the iterations you can do means you can get there independently quite fast.

Often I ask it for smaller steps/chunks of logic, and build it up (like you would with your own code builds)

Sometimes I request a review of the end code from someone on Discord who knows what they’re doing, and they often suggest tweeks.

1 Like

Might not be worth it if you’ve got a working solution, but if you need to parse out all values, you can actually do it all in one lambda in a single parse, loop over each split/delimiter, and publish the new values to the relevant sensor.

My example below does something a bit similar (but you’d need to adjust). It does a “double split” of the string. First it splits on ‘,’ and then it splits again on ‘M’ or ‘m’.

  - platform: template
    name: "test String Splits"
    id: test_string_split
    icon: mdi:format-text-rotation-none
    update_interval: never
    #x is like "1M-10,2M-700,3M-100,4M-10,3M70,8M170"
        - lambda: |-
            ESP_LOGD("custom", "text sensor received new value");
            std::string item;
            std::size_t start = 0, end; // Initialize "start" variable to 0
            // Loop until no ',' character is found in "x"
            while ((end = x.find(",", start)) != std::string::npos) 
              // Extract substring from "x" starting at "start" and ending at "end"
              item = x.substr(start, end - start);
              // Split item by "M" to get motor number and movement amount
              std::size_t motor_number_pos = item.find_first_of("Mm");
              std::string motor_number_str = item.substr(0, motor_number_pos);
              std::string movement_amount_str = item.substr(motor_number_pos + 1);
              // If motor number and movement amount are not empty, process them
              if (!motor_number_str.empty() && !movement_amount_str.empty()) 
                int motor_number = std::stoi(motor_number_str);
                int movement_amount = std::stoi(movement_amount_str);
                ESP_LOGD("custom", "Motor Number: %d, Movement Amount: %d", motor_number, movement_amount);
                // Publish movement amount to corresponding template based on motor number
                switch (motor_number) 
                  case 1: id(m1_last_movement_amount).publish_state(movement_amount); break;
                  case 2: id(m2_last_movement_amount).publish_state(movement_amount); break;
                  case 3: id(m3_last_movement_amount).publish_state(movement_amount); break;
                  case 4: id(m4_last_movement_amount).publish_state(movement_amount); break;
                  case 5: id(m5_last_movement_amount).publish_state(movement_amount); break;
                  case 6: id(m6_last_movement_amount).publish_state(movement_amount); break;
                  case 7: id(m7_last_movement_amount).publish_state(movement_amount); break;
                  case 8: id(m8_last_movement_amount).publish_state(movement_amount); break;
                  default: break;
              // Set "start" to position after ',' character found
              start = end + 1;
1 Like