Python script - how to get sensor

I have a Python script which parses some data from Slovenian weather service to get forecast for tomorrow and a day after tomorrow. If I call /bin/python3 /config/python_scripts/ I get data in readable form:

Time/Day: 15.04.2024 17:00 CEST Ponedeljek CEST (popoldne)
Temperatura: 25 °C
Hitrost vetra: 12 m/s
Vremenska napoved: pretežno jasno ()

Time/Day: 16.04.2024 8:00 CEST Torek CEST (zjutraj)
Temperatura: 13 °C
Hitrost vetra: 0 m/s
Vremenska napoved: pretežno oblačno ()

Time/Day: 16.04.2024 17:00 CEST Torek CEST (popoldne)
Temperatura: 6 °C
Hitrost vetra: 8 m/s
Padavine: mod
Vremenska napoved: oblačno (plohe)

Which is OK for a start. My goal is to get this data to sensor, so I could use it in announcements or get info over VA…

If I create a sensor in configuration.yaml:

  - platform: command_line
    name: "Temp Arso Pojutrišnjem"
    unique_id: vreme_temp_pojutrisnjem
    command: "python3 /config/python_scripts/"
    unit_of_measurement: "°C"
    value_template: "{{ value_json.Temperatura }}"
      - Temperatura
      - Napoved
      - Hitrost vetra
      - Padavine
    scan_interval: 3600 

The sensor is not created…

If I call a service in dev tools:

service: python_script.arso_parse2
data: {}

I get this error: (translation: Could not call service python_script.arso_parse2.)

Storitve ni bilo mogoče poklicati python_script.arso_parse2. Error executing script (ImportError): __import__ not found

My log is set to error and couldn’t find any output in log.

Can someone help me with some advice? Thank you in advance :slight_smile:

That data is not JSON, so you can’t treat it like it is.

Best options are: see if you can get HA to talk directly to the weather service and remove the Python script from the picture; or see if you can get the script to output JSON.

You can work with the data format you have, but it’s not ideal.

Could you post the Python script here?

Sure. Python script is:

import requests
from bs4 import BeautifulSoup

# Fetch XML data
url = ""
response = requests.get(url)

# Check if request was successful
if response.status_code == 200:
    # Parse XML content
    soup = BeautifulSoup(response.content, 'xml')

    # Find all metData elements
    met_data_list = soup.find_all('metData')

    # Iterate over each metData element
    for met_data in met_data_list:
        # Extract time or day information
        valid_day = met_data.find('valid_day').text
        valid_daypart = met_data.find('valid_daypart').text
        valid_time = met_data.find('valid').text
        # Extract temperature information
        t_var_desc = met_data.find('t_var_desc').text
        t_var_unit = met_data.find('t_var_unit').text
        t = met_data.find('t').text
        # Extract wind speed information
        ff_var_desc = met_data.find('ff_var_desc').text
        ff_var_unit = met_data.find('ff_var_unit').text
        ff_value = met_data.find('ff_value').text
        # Extract precipitation information
        rr_decode_text = met_data.find('rr_decodeText').text
        # Extract weather forecast information
        nn_short_text = met_data.find('nn_shortText').text
        wwsyn_short_text = met_data.find('wwsyn_shortText').text

        # Print the information
        print(f"Time/Day: {valid_time} {valid_day} ({valid_daypart})")
        print(f"Temperatura: {t} {t_var_unit}")
        print(f"Hitrost vetra: {ff_value} {ff_var_unit}")
        print(f"Padavine: {rr_decode_text}")
        print(f"Vremenska napoved: {nn_short_text} ({wwsyn_short_text})")
        print()  # Print newline after each metData element

    print("Failed to fetch XML data from the URL.")

Well, the weather service provides RSS, XML and HTML formats

Use the rest integration to pull the data you want from the XML with a single call. Let me know if you need help with that, but it looks pretty simple. Which data do you want?


Having had a look at it, you can pull the whole lot in with a single rest sensor:

  - platform: rest
    name: ARSO forecast
    scan_interval: 86400
    value_template: "{{ now() }}"
    json_attributes_path: "$.data"
      - metData

That gives this:

then use an automation to update it at the recommended xx:25 time:

  - platform: time_pattern
    minutes: 25
  - service: homeassistant.update_entity
      entity_id: sensor.arso_forecast

and you can pull out the individual items into template sensors if needed. The equivalent of your Python script is:

{% for i in state_attr('sensor.arso_forecast','metData') %}
Time/Day: {{ i['valid'] }} {{ i['valid_day'] }} ({{ i['valid_daypart'] }})
Temperatura: {{ i['t'] }} {{ i['t_var_unit'] }}
Hitrost vetra: {{ i['ff_value'] }} {{ i['ff_var_unit'] }}
Padavine: {{ i['rr_decodeText'] }}
Vremenska napoved: {{ i['nn_shortText'] }} ({{ i['wwsyn_shortText'] }})
{% endfor %}

which gives:

1 Like

Uau, THANK YOU VERY MUCH @Troon for your solution! Really appreciate it!!

Been strugling with value_template and json_attributes_path and couln’t get it working :slight_smile: but your code works!!!

1 Like

I used this tool: Best XML to JSON Converter Online

I copied the XML from the URL, then pasted it in to that tool, converted to JSON and it became much easier to read:

json_attributes_path is the JSONPath of the element that the attributes you want to pull is stored in: here, it’s $.data and you then want the metData structure (list) as an attribute.

1 Like

Much easier to decypher :slight_smile: Thank you!

1 Like

I don’t know any better way to present data for tomorrow and a day after tomorrow forecast as with use of a list since the metData spits out 3 sets of forecast. So I made a template sensor separate for today [0], tomorrow morning [1] and tomorrow afternoon [2] using this:

  - platform: template
        friendly_name: "ARSO napoved vremena jutri popoldne"
        unique_id: arso_napoved_jutri_popoldne
        value_template: >
          {% set forecast_list = state_attr('sensor.arso_forecast', 'metData') %}
          {% if forecast_list and forecast_list|length >= 3 %}
            Jutri popoldne bo {{ forecast_list[2]['nn_shortText'] }}.
          {% else %}
            Za jutri popoldne ni vremenske napovedi.
          {% endif %}

As there is sometimes some additional shot text added to a primary forecast, I used this template to add this or omit if there is null being presented instead of forecast state. For example partly cloudly [and] showers. This ‘showers’ is that part and if the forecast is just partly cloudly and no second part, the second part has null. So I changed to this:

{% set forecast_list = state_attr('sensor.arso_forecast', 'metData') %}
          {% if forecast_list and forecast_list|length >= 2 %}
            {{ forecast_list[1]['nn_shortText'] }}
            {% if forecast_list[1]['wwsyn_shortText'] != 'null' %}
              {% if forecast_list[1]['wwsyn_shortText'] is not none %}
                ({{ forecast_list[1]['wwsyn_shortText'] }})
              {% endif %}
            {% endif %}
          {% else %}
            Za jutri zjutraj ni vremenske napovedi.
          {% endif %}

I havent tested it though because I’m waiting for fresh forecast :slight_smile: