Cannot parse @ in value template sensor

Hi there,

I’m trying to read the XML data from my solar inverter to be able to get the live yield from it. After retrieving the XML, HA converts it to JSON but the attributes are prefixed with an @ character. When trying to parse that into a value, the template development tool throws an error:

Error rendering template: TemplateSyntaxError: unexpected char ‘@’ at 520

I did everything I can think of using my own knowledge (escaping and so) but it ends here and I need help from the community.

value template

value_template: ‘{{ states.sensor.solar_inverter.attributes[“root”].Device.Measurements.Measurement[2].@Value }}’

HA converted XML to JSON

{
“root”: {
“Device”: {
“Measurements”: {
“Measurement”: [
{
@Value”: “236.4”,
@Unit”: “V”,
@Type”: “AC_Voltage”
},
{
@Value”: “4.960”,
@Unit”: “A”,
@Type”: “AC_Current”
},
{
@Value”: “1163.9”,
@Unit”: “W”,
@Type”: “AC_Power”
}
]
},
“_DateTime”: “2020-04-08T08:53:32”
}
}
}

Hope someone can help me :slight_smile:

change your template to

value_template: '{{ states.sensor.solar_inverter.attributes["root"].Device.Measurements.Measurement[2]["@Value"] }}'

for extra safety (incase things aren’t working on your solar inverter), use this

value_template: >
  {% set root = state_attr('sensor.solar_inverter', 'root') %}
  {% if root.Device is defined %}
    {{ root.Device.Measurements.Measurement[2]["@Value"] }}
  {% else %}
    {{ states('sensor.this_sensor') }}
  {% endif %}

Just replace ‘sensor.this_sensor’ with your template sensor that you are defining.

EDIT: I forgot to mention, your original issue is because the field you are trying to access starts with a special character, the @ symbol. You can’t use dot notation (this.is.dot.notation) with special characters.

Hi Petro, you’re the best! I have implemented the ‘extra safety’ solution for now as an extra sensor to see if it works after sunset when the ‘value’ attribute disappears from the XML and automatically, the JSON (no sun no glory). The value won’t go to 0 so maybe have to do something there.

For now you made my day after being grumpy for half the day :+1:

For the record, this works for me with the StecaGrid Coolcept inverter.

2 Likes

wooo, I’m not the only one!

1 Like

Newbie here with my first post.

I never got it to work in the way you’re describing it in this thread, so I went down the route of using a command_line sensor instead.

Here’s my solarpower.py script:

import json
import urllib
import xmltodict

url = 'http://a.b.c.d/measurements.xml'
xmlfeed = urllib.request.urlopen(url).read()
content = xmltodict.parse(str(xmlfeed, 'utf-8'))
data = json.dumps(content)
json_data = json.loads(data)

power=json_data['root']['Device']['Measurements']['Measurement'][2]['@Value']
if power == '-nan':
        power = 0
print(power)

I should add code to handle the error that it might through if the inverter isn’t delivering the xml file. It’s on my list. And if you want to contribute, you’ll find the project here:

And here’s my configuration.yaml:

sensor:
  - platform: command_line
    name: Solar Power
    command: 'python3 solarpower.py'
    unit_of_measurement: W
    value_template: '{{ value|float }}'

I hope the Python script doesn’t put too much extra load on my little RPi.

Btw, I also have a StecaGrid CoolCept inverter :slight_smile:

1 Like

First off, a little grumpiness :frowning: : Damm the people at Steca-support. I have asked them if I was able to do some API calls to have some numbers from my CoolCept-1500, but the answer was “Not possible”
So the easy solution was to have the PNG file, from the web-interface, being displayed on my HA dashboard.

And then the sun rised :wink:
Going home early from work, straight home and try to implement - Yahhhh.

Hi @thinx

Can you post your entire “inverter sensor section” - How does it look now (after the new template)?

Thanks
Niels

Thanks @PaleSkinnySwede.
Works GREAT here with my CoolCept-1500 inverter, Just have to remember the full path for the pyton script:

[snip from my /config/configuration.yaml]

  - platform: command_line
    name: Solar Power
    command: 'python3 /config/custom_components/sensor/solarpower.py'
    unit_of_measurement: W
    value_template: '{{ value|float }}'

SolarPower

1 Like

Hi Niels,

Sure, did some tweaking to get more nifty values out of the inverter.

Rest Sensor

  - platform: rest
    resource: http://[INVERTER_IP]/measurements.xml
    json_attributes:
      - root
      - Measurements
    name: solar_inverter
    scan_interval: 10
    value_template: 'OK'

Template sensors

  - platform: template
    sensors:
      solar_live_output:
        value_template: >
            {% set root = state_attr('sensor.solar_inverter', 'root') %}
            {% if root.Device.Measurements.Measurement[2]["@Value"] is defined %}
                {{ root.Device.Measurements.Measurement[2]["@Value"] | round(0)}}
            {% else %}
                0
            {% endif %}
        device_class: power
        unit_of_measurement: 'Watt'
        friendly_name: Live power

      solar_live_temp:
        value_template: >
            {% set root = state_attr('sensor.solar_inverter', 'root') %}
            {% if root.Device.Measurements.Measurement[6]["@Value"] is defined %}
                {{ root.Device.Measurements.Measurement[6]["@Value"] }}
            {% else %}
                0
            {% endif %}
        device_class: temperature
        unit_of_measurement: '°C'
        friendly_name: Inverter temperature

I push all template sensor values to influxDB to be able to graph them neatly:

You can get many more values by just adding more template sensors, just look at the XML that the inverter spits out:


<Measurement Value="233.3" Unit="V" Type="AC_Voltage"/>
<Measurement Value="0.836" Unit="A" Type="AC_Current"/>
<Measurement Value="177.3" Unit="W" Type="AC_Power"/>
<Measurement Value="49.996" Unit="Hz" Type="AC_Frequency"/>
<Measurement Value="403.7" Unit="V" Type="DC_Voltage"/>
<Measurement Value="0.450" Unit="A" Type="DC_Current"/>
<Measurement Value="34.0" Unit="°C" Type="Temp"/>
<Measurement Unit="V" Type="LINK_Voltage"/>
<Measurement Unit="W" Type="GridPower"/>
<Measurement Unit="W" Type="GridConsumedPower"/>
<Measurement Unit="W" Type="GridInjectedPower"/>
<Measurement Unit="W" Type="OwnConsumedPower"/>
<Measurement Value="100.0" Unit="%" Type="Derating"/>
2 Likes

@thinx WOW… Looks real good…
And the rest-call make it more “pretty” :wink:

Hi.
Maybe someone will need the solutions I found for Steca solar inverters.
I struggled a bit with modbus access to the Steca inverter. I did not find a ready integration. Below is a description of the working configuration.

  1. Integration needed: solaredge_modbus
  2. Registers number found on the basis of the description from: https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf
    The register addresses in the steca inverter are higher by 2 than in the above-mentioned implementation.
    The following configuration allows access to the basic parameters of a Steca inverter. Tested on hardware: StecaGrid5513.

configuration.yaml:

modbus:
  - type: tcp
    host: IP # for example (192.168.x.x)
    port: 1502 #from Your inverter modbus configuration. 1502 is default in my inverter
    name: steca

sensor:
  - platform: integration
    source: sensor.current_ac_power_steca
    name: steca_energy_production
    round: 2

  - platform: template
    sensors:
      daily_energy_production_steca:
        friendly_name: Daily Energy Production Steca
        unit_of_measurement: kWh
        value_template: "{{ states('sensor.daily_energy_steca')|float }}"
      monthly_energy_production_steca:
        friendly_name: Monthly Energy Production Steca
        unit_of_measurement: kWh
        value_template: "{{ states('sensor.monthly_energy_steca')|float }}"
      yearly_energy_production_steca:
        friendly_name: Yearly Energy Production Steca
        unit_of_measurement: kWh
        value_template: "{{ states('sensor.yearly_energy_steca')|float }}"


  - platform: modbus
    scan_interval: 10
    registers:
      - name: "DC Power (steca)"
        hub: steca
        unit_of_measurement: "kW"
        scale: 0.001
        precision: 3
        slave: 71
        register: 40102

      - name: "Total production (steca)"
        hub: steca
        unit_of_measurement: "kWh"
        slave: 71
        register: 40095
        data_type: uint
        count: 2
        scale: 0.001
        precision: 1
      - name: "L1 Voltage (steca)"
        hub: steca
        unit_of_measurement: "V"
        slave: 71
        register: 40081
        scale: 0.1
        precision: 2
      - name: "L2 Voltage (steca)"
        hub: steca
        unit_of_measurement: "V"
        slave: 71
        register: 40082
        scale: 0.1
        precision: 2
      - name: "L3 Voltage (steca)"
        hub: steca
        unit_of_measurement: "V"
        slave: 71
        register: 40083
        scale: 0.1
        precision: 2
      - name: "Apparent Power (steca)"
        hub: steca
        unit_of_measurement: "kVA"
        scale: 0.001
        precision: 3
        slave: 71
        register: 40089
      - name: "Current AC Power (steca)"
        hub: steca
        unit_of_measurement: "kW"
        scale: 0.001
        precision: 3
        slave: 71
        register: 40085
      - name: "Temp. (steca)"
        hub: steca
        unit_of_measurement: "°C"
        slave: 71
        register: 40104
        scale: 0.1
        precision: 2


utility_meter:
  daily_energy_steca:
    source: sensor.steca_energy_production
    cycle: daily
  monthly_energy_steca:
    source: sensor.steca_energy_production
    cycle: monthly
  yearly_energy_steca:
    source: sensor.steca_energy_production
    cycle: yearly

As people from helpdesks say: “It works for me”.
Greetings from Poland.
Michał

1 Like

Michal, when posting code blocks, use 3 backticks before and after your code block to format it correctly. Currently, no one can use your code because the format is incorrect.

Correct:

my:
  code:
    block:
    - something

Incorrect:

my:
code:
block:
- something

Thanks for Your advice.
I hope its ok now.

1 Like

Yep, looks good!

Hi again René
I used your example, and added the remaining sensors, and it has been working flawless ever since :slight_smile: - Resource light, and very stable.

But after upgrading to Home Assistant 2021.6 I get an error:

  • Error while processing template: Template("{% set root = state_attr(‘sensor.solar_inverter’, ‘root’) %} {% if root.Device.Measurements.Measurement[2]["@Value"] is defined %} {{ root.Device.Measurements.Measurement[2]["@Value"] | round(0)}} {% else %} 0 {% endif %}")

I think it has to do with the “REST-wannabe-answer” from the inverter, but I’m not able to pinpoint where I have to correct it.
Do you got the same problem after update?

k.r. Niels

Hi Niels,

Indeed I’m seeing the same at my installation, I didn’t notice.
I’ll get to work and find out why this doesn’t work anymore, I’ll keep you posted :slight_smile:

Regards,
René

1 Like

@petro Can you help us out here?

There’s nothing changed on the code, but still it doesn’t work and can’t figure out why. This is the converted XML to JSON coming back from the inverter and put it in the template editor. It throws the exact same error as I see in the log files:

UndefinedError: ‘None’ has no attribute 'Device’

{## Imitate available variables: ##}
{% set my_test_json = {
"root": {
		"Device": {
			"Measurements": {
				"Measurement": [
					{
						"_Value": "228.8",
						"_Unit": "V",
						"_Type": "AC_Voltage"
					},
					{
						"_Value": "1.832",
						"_Unit": "A",
						"_Type": "AC_Current"
					},
					{
						"_Value": "403.4",
						"_Unit": "W",
						"_Type": "AC_Power"
					},
					{
						"_Value": "49.977",
						"_Unit": "Hz",
						"_Type": "AC_Frequency"
					},
					{
						"_Value": "415.2",
						"_Unit": "V",
						"_Type": "DC_Voltage"
					},
					{
						"_Value": "0.975",
						"_Unit": "A",
						"_Type": "DC_Current"
					},
					{
						"_Value": "39.1",
						"_Unit": "°C",
						"_Type": "Temp"
					},
					{
						"_Unit": "V",
						"_Type": "LINK_Voltage"
					},
					{
						"_Unit": "W",
						"_Type": "GridPower"
					},
					{
						"_Unit": "W",
						"_Type": "GridConsumedPower"
					},
					{
						"_Unit": "W",
						"_Type": "GridInjectedPower"
					},
					{
						"_Unit": "W",
						"_Type": "OwnConsumedPower"
					},
					{
						"_Value": "100.0",
						"_Unit": "%",
						"_Type": "Derating"
					}
				]
			},
			"_Name": "StecaGrid 3600x",
			"_NominalPower": "3680",
			"_Type": "Inverter",
			"_Serial": "xxxxx",
			"_BusAddress": "1",
			"_NetBiosName": "xxxxx",
			"_IpAddress": "xxxxx",
			"_DateTime": "2021-06-21T18:23:19"
		}
	}
} %}

{% set root = state_attr('my_test_json', 'root') %}
        {{ (root.Device.Measurements.Measurement[2]["@Value"] | float / 1000) }}

And this is the logging from homeassistant logbook:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 389, in async_render
    render_result = _render_with_context(self.template, compiled, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1358, in _render_with_context
    return template.render(**kwargs)
  File "/usr/local/lib/python3.8/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/usr/local/lib/python3.8/site-packages/jinja2/environment.py", line 925, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
  File "/usr/local/lib/python3.8/site-packages/jinja2/sandbox.py", line 326, in getattr
    value = getattr(obj, attribute)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1382, in _fail_with_undefined_error
    raise ex
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1374, in _fail_with_undefined_error
    return super()._fail_with_undefined_error(*args, **kwargs)
jinja2.exceptions.UndefinedError: 'None' has no attribute 'Device'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 505, in async_render_to_info
    render_info._result = self.async_render(variables, strict=strict, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 391, in async_render
    raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: UndefinedError: 'None' has no attribute 'Device'

Not sure if it will solve the actual problem, but the template should be:

{% set root = my_test_json['root'] %}
{{ (root.Device.Measurements.Measurement[2]["_Value"] | float / 1000) }}

Exactly what I was going to observe, but you beat me to it. It looks as though the XML has changed from @Value to _Value which is why the original sensor isn’t working — and you cannot use state_attr on a variable you’ve declared in the template editor, which is why you’re getting that error.

If there’s no guarantee that the measurements will be in the same order each type, you can loop through to find the correct _Type, as I have with my printer.

If that is your template, root is None at startup. Therefore accessing anything in it will result in an error. So, make a check. There’s plenty of ways to do this:

            {% set root = state_attr('sensor.solar_inverter', 'root') %}
            {{ root.Device.Measurements.Measurement[2]["@Value"] | float / 1000 if root is not none else 0 }}
            {% set root = state_attr('sensor.solar_inverter', 'root') %}
            {% if root %}
            {{ root.Device.Measurements.Measurement[2]["@Value"] | float / 1000 }}
            {% else %}
            0
            {% endif %}