Problem extracting data in custom component Python

actually seeing a bunch of errors…

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 408, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 275, in async_update_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 309, in _async_write_ha_state
    state = self.state
  File "/config/custom_components/bom_forecast/sensor.py", line 290, in state
    self._condition, self._index)
  File "/config/custom_components/bom_forecast/sensor.py", line 401, in get_reading
    uv_alert = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert')).text
AttributeError: 'NoneType' object has no attribute 'text'

Even though it’s working… hmm…

also with the path change I get this…
SyntaxError: cannot use absolute path on element

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 408, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 275, in async_update_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 309, in _async_write_ha_state
    state = self.state
  File "/config/custom_components/bom_forecast/sensor.py", line 290, in state
    self._condition, self._index)
  File "/config/custom_components/bom_forecast/sensor.py", line 401, in get_reading
    uv_alert = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert')).text
AttributeError: 'NoneType' object has no attribute 'text'
2019-11-16 17:28:19 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 408, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 275, in async_update_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 309, in _async_write_ha_state
    state = self.state
  File "/config/custom_components/bom_forecast/sensor.py", line 290, in state
    self._condition, self._index)
  File "/config/custom_components/bom_forecast/sensor.py", line 404, in get_reading
    fire_danger =  self._data.find(_FIND_QUERY_4.format(index, 'fire_danger')).text
AttributeError: 'NoneType' object has no attribute 'text'
2019-11-16 17:28:19 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 408, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 275, in async_update_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 309, in _async_write_ha_state
    state = self.state
  File "/config/custom_components/bom_forecast/sensor.py", line 290, in state
    self._condition, self._index)
  File "/config/custom_components/bom_forecast/sensor.py", line 401, in get_reading
    uv_alert = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert')).text

I think the index can only be 0 or 1 (depends on if peak UV has been reached for the day)

You have to cater for the case where the XPath does not yield any result under that index:

    uv_alert_data = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert'))
    if uv_alert_data:
      return uv_alert_data.text

In general, I’d recommend to use more defensive programming with external sources because the format and details are out of your control, i.e. you cannot rely on the format to be always correct and always the same as it was on the day you developed your component.

So that stopped the error but I am now getting 6 sensors for 6 days and the state for all of them is n/a whereas before _1 was giving me the uv and fire for tomorrow… so the test not quite right.

Can I use:

    uv_alert_data = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert'))
    if uv_alert_data:
      uv_alert = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert')) ,text     
      return uv_alert

Seems not… still n/a for everything

Before the ‘if’ I was getting errors but it was only creating 1 up and one fire sensor (and they were the correct string)… but for some reason the condition is returning n/a for everything.

So close I can taste it!

Tried this too…

        if condition == 'uv_alert':
            uv_alert_data = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert'))
            if uv_alert_data:
                uv_alert = self._data.find(_FIND_QUERY_3.format(index, 'uv_alert')).text
            else:
                uv_alert = 'none'
            return uv_alert

        find_query = (_FIND_QUERY.format(index, SENSOR_TYPES[condition][0]))
        state = self._data.find(find_query)

So I moved this up above the last 2 lines shown…

Now all my sensors are showing ‘none’ as the state so it’s not reading the dats (as above I guess) Not sure how to get back to it reading the data without the errors I was getting before.

OK, so I don’t exactly know how the sensor state should work in your component. But maybe it’s time to add some debug log statements into your code, for example in BOMForecastSensorFriendly#state or BOMForecastData#get_reading to better see what happens in your code.

So I have no idea now…

I have tried to emulate the forecast which looks the same in my eyes.
Error:

2019-11-19 17:38:49 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 408, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 275, in async_update_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 309, in _async_write_ha_state
    state = self.state
  File "/config/custom_components/bom_forecast/sensor.py", line 290, in state
    self._condition, self._index)
  File "/config/custom_components/bom_forecast/sensor.py", line 397, in get_reading
    uv_alert_data = self._data.find(_FIND_QUERY_3.format(index)).text
AttributeError: 'NoneType' object has no attribute 'text'

This log error just keeps on repeating.

It is producing the desired result…

Because it’s after 4:40pm it has switched from _0 to _1 and I only get that one alert.

In the py file I have:
lines 25++

_FIND_QUERY = "./forecast/area[@type='location']/forecast-period[@index='{}']/*[@type='{}']"
_FIND_QUERY_2 = "./forecast/area[@type='metropolitan']/forecast-period[@index='{}']/text[@type='forecast']"
_FIND_QUERY_3 = "./forecast/area[@type='metropolitan']/forecast-period[@index='{}']/*[@type='uv_alert']"
_FIND_QUERY_4 = "./forecast/area[@type='metropolitan']/forecast-period[@index='{}']/*[@type='fire_danger']"

I’m just concentrating on uv_alert for now.
Here is where I am trying to read it directly after the forecast.

    def get_reading(self, condition, index):
        """Return the value for the given condition."""
        if condition == 'detailed_summary':
            if PRODUCT_ID_LAT_LON_LOCATION[self._product_id][3] == 'City':
                detailed_summary = self._data.find(_FIND_QUERY_2.format(index)).text
            else:
                detailed_summary = self._data.find(_FIND_QUERY.format(index, 'forecast')).text
            return (detailed_summary[:251] + '...') if len(detailed_summary) > 251 else detailed_summary
        
        if condition == 'uv_alert':
            if PRODUCT_ID_LAT_LON_LOCATION[self._product_id][3] == 'City':
                uv_alert_data = self._data.find(_FIND_QUERY_3.format(index)).text
                if uv_alert_data:
                    uv_alert = self._data.find(_FIND_QUERY_3.format(index)).text
            else:
                uv_alert_data = self._data.find(_FIND_QUERY.format(index, 'uv_alert')).text
                if uv_alert_data:
                    uv_alert = self._data.find(_FIND_QUERY.format(index, 'uv_alert')).text
            return uv_alert

For day_1 the xml file looks like:

    <forecast>
        <area aac="NSW_FA001" description="New South Wales" type="region">
            <forecast-period start-time-local="2019-11-16T16:25:11+11:00" end-time-local="2019-11-16T16:25:11+11:00" start-time-utc="2019-11-16T05:25:11Z" end-time-utc="2019-11-16T05:25:11Z">
                <text type="warning_summary_footer">Details of warnings are available on the Bureau's website www.bom.gov.au, by telephone 1300-659-218* or through some TV and radio broadcasts.</text>
                <text type="product_footer">* Calls to 1300 numbers cost around 27.5c incl. GST, higher from mobiles or public phones.</text>
            </forecast-period>
        </area>
        <area aac="NSW_ME004" description="Central Coast" type="metropolitan" parent-aac="NSW_FA001">
            <forecast-period index="0" start-time-local="2019-11-16T17:00:00+11:00" end-time-local="2019-11-17T00:00:00+11:00" start-time-utc="2019-11-16T06:00:00Z" end-time-utc="2019-11-16T13:00:00Z">
                <text type="forecast">Partly cloudy. Slight (20%) chance of a shower later tonight. Winds easterly 20 to 25 km/h tending northeasterly 25 to 35 km/h in the evening.</text>
            </forecast-period>
            <forecast-period index="1" start-time-local="2019-11-17T00:00:00+11:00" end-time-local="2019-11-18T00:00:00+11:00" start-time-utc="2019-11-16T13:00:00Z" end-time-utc="2019-11-17T13:00:00Z">
                <text type="forecast">Partly cloudy. Medium (60%) chance of showers, most likely in the morning and afternoon. The chance of a thunderstorm in the morning and early afternoon. Winds northeasterly 15 to 20 km/h shifting southerly 25 to 35 km/h in the morning then tending south to southeasterly 30 to 45 km/h in the early afternoon.</text>
                <text type="fire_danger">Very High</text>
                <text type="uv_alert">Sun protection 8:50am to 3:50pm, UV Index predicted to reach 8 [Very High]</text>
            </forecast-period>

for day_0

    <forecast>
        <area aac="NSW_FA001" description="New South Wales" type="region">
            <forecast-period start-time-local="2019-06-24T04:45:16+10:00" end-time-local="2019-06-24T04:45:16+10:00" start-time-utc="2019-06-23T18:45:16Z" end-time-utc="2019-06-23T18:45:16Z">
                <text type="warning_summary_footer">Details of warnings are available on the Bureau's website www.bom.gov.au, by telephone 1300-659-218* or through some TV and radio broadcasts.</text>
                <text type="product_footer">* Calls to 1300 numbers cost around 27.5c incl. GST, higher from mobiles or public phones.</text>
            </forecast-period>
        </area>
        <area aac="NSW_ME004" description="Central Coast" type="metropolitan" parent-aac="NSW_FA001">
            <forecast-period index="0" start-time-local="2019-06-24T00:00:00+10:00" end-time-local="2019-06-25T00:00:00+10:00" start-time-utc="2019-06-23T14:00:00Z" end-time-utc="2019-06-24T14:00:00Z">
                <text type="forecast">Partly cloudy. Very high (95%) chance of showers, becoming less likely this evening. Winds south to southwesterly 20 to 30 km/h turning southeasterly 25 to 35 km/h in the middle of the day then decreasing to 15 to 25 km/h in the late afternoon.</text>
                <text type="uv_alert">Sun protection not recommended, UV Index predicted to reach 2 [Low]</text>
            </forecast-period>

The full py file is here:

Can anyone not help with this?
@pnbruckner maybe or @exxamalte or anyone???
I’m just trying to hack this py component to get some extra info - I don’t know python and am struggling. When I forked that repo I made only minor cosmetic changes and this is just beyond my skills right now although to me it seems I’m just missing something simple.

First things first:
You have to cater for the case the the XPath yields an empty (=None) result, i.e. the XML you are looking for simply doesn’t exist. So you should first check if the XPath yields a result, i.e. remove the .text from the first line. Then in the if statement you check if there was a result, and if so, then you retrieve the text from that result.
The following is just one occurrence, and you should follow the same pattern in the rest of the code.

                uv_alert_data = self._data.find(_FIND_QUERY_3.format(index))
                if uv_alert_data:
                    uv_alert = uv_alert_data.text

Now, looking at this XPath: From the two XML snippets you posted, I cannot see how this XPath would match with type='location'. There are only two areas - one with type “region” and one with type “metropolitan”.

        <area aac="NSW_FA001" description="New South Wales" type="region">
        <area aac="NSW_ME004" description="Central Coast" type="metropolitan" parent-aac="NSW_FA001">

The difference between the two XML snippets appears to be that “day_1” has the uv_alert under index="1" and “day_0” has the uv_alert under index="0".
My understanding from the XML structure is that the index indicates for which day that forecast is, i.e. 0=today, 1=tomorrow, etc.

So, maybe you need to rethink what your sensors represent? Is sensor _0 always today regardless of the time? So, maybe if it’s after 4:40pm, and there is no uv_alert available anymore for index="0", you need to set the sensor’s state to None/Unknown?

Thing is other areas of Australia will have that key there… the forecast one works.

If I take the text off the end I get pages of errors continually… the test is not working at all…

Sorry, can’t really help here. I did take a quick look at the code (at least the diffs between sensor.py & sensor.py.work), but this seems to have more to do with parsing XML than Python, and that’s not really “in my wheelhouse.”

Alright, hang in there. I got this running on my dev machine. I’ll add a few more fixes and share the code tonight.

1 Like

@DavidFW1960: I just sent you the code directly. Please let us know if that’s getting you closer to what you want to achieve, and please share the final code/component, maybe others find that useful, too.

1 Like

Will do. It will be on HACS as soon as I get a chance to test it. There’s a few people who want it.

Thanks a lot for helping sort this out!

1 Like

Got someone who is in Perth and he’s not getting the fire_danger. Looking at the XML file, it looks like this:

    <forecast>
        <area aac="WA_FA001" description="Western Australia" type="region">
            <forecast-period start-time-local="2019-11-23T04:40:07+08:00" end-time-local="2019-11-23T04:40:07+08:00" start-time-utc="2019-11-22T20:40:07Z" end-time-utc="2019-11-22T20:40:07Z">
                <text type="warning_summary_footer">For latest warnings go to www.bom.gov.au, subscribe to RSS feeds, call 1300 659 210* or listen for warnings on relevant TV and radio broadcasts.</text>
                <text type="product_footer">* Calls to 1300 numbers cost around 27.5c incl. GST, higher from mobiles or public phones.</text>
            </forecast-period>
        </area>
        <area aac="WA_MW009" description="Perth Coast: Two Rocks to Dawesville" type="coast" parent-aac="WA_FA001">
            <warning-summary type="marine_forecast" start-time-local="2019-11-23T05:00:00+08:00" end-time-local="2019-11-25T00:00:00+08:00" start-time-utc="2019-11-22T21:00:00Z" end-time-utc="2019-11-24T16:00:00Z">Strong Wind Warning for Sunday for Perth Coast</warning-summary>
        </area>
        <area aac="WA_ME001" description="Perth" type="metropolitan" parent-aac="WA_FA001">
            <forecast-period index="0" start-time-local="2019-11-23T00:00:00+08:00" end-time-local="2019-11-24T00:00:00+08:00" start-time-utc="2019-11-22T16:00:00Z" end-time-utc="2019-11-23T16:00:00Z">
                <text type="forecast">Sunny. Winds easterly 15 to 25 km/h shifting south to southwesterly 20 to 30 km/h in the middle of the day. Winds gusting up to 60 km/h about the hills this morning.</text>
                <text type="fire_danger">
                    <p>Perth Coastal Plain: High</p>
                    <p>Perth Hills: High</p>
                </text>
                <text type="uv_alert">Sun protection 7:50am to 4:10pm, UV Index predicted to reach 12 [Extreme]</text>
            </forecast-period>
            <forecast-period index="1" start-time-local="2019-11-24T00:00:00+08:00" end-time-local="2019-11-25T00:00:00+08:00" start-time-utc="2019-11-23T16:00:00Z" end-time-utc="2019-11-24T16:00:00Z">
                <text type="forecast">Sunny. Winds south to southeasterly 15 to 20 km/h tending south to southwesterly 25 to 35 km/h in the morning then tending south to southeasterly 15 to 25 km/h in the late evening.</text>
            </forecast-period>

So there’s 2 regions for the fire_danger. It is coming into HA as blank. Is there a way to read the 2 values in? Then the user can use a Template to split and extract what he wants. It’s IDW12300

TIA.

Ha! can’t post it. Doh!

OK, so the XML either provides plain text inside the text tag, or there could be multiple p tags inside the text tag.

In the following, I’ve added .strip() to the the fire danger reading to ensure all the whitespace is removed. Then I’m checking if the remaining string is empty, and if so use an XPath expression to find all the p sub-tags, and then concatenate the into a comma-separated list.

        if condition == 'fire_danger':
            if PRODUCT_ID_LAT_LON_LOCATION[self._product_id][3] == 'City':
                _LOGGER.debug("City")
                fire_danger_data = self._data.find(_FIND_QUERY_4.format(index))
                _LOGGER.debug("fire_danger_data = %s", fire_danger_data)
                if fire_danger_data is not None:
                    fire_danger = fire_danger_data.text.strip()
                    if fire_danger == '':
                        # Check if there are sub-tags.
                        fire_danger_data_paragraphs = fire_danger_data.findall("./p")
                        if fire_danger_data_paragraphs is not None:
                            paragraphs = [paragraph.text for paragraph in fire_danger_data_paragraphs]
                            return ", ".join(paragraphs)
                    return fire_danger
            else:

That seems to work fine. Thanks again!

Ha! Me again… lol…
A user has reported an error here: Australian Weather Forecast using BOM Public FTP
He is using IDV10703 which has a slightly different format.
He says he sees the error for uv_alert and fire_danger and icon but I suspect icon is ok… (Although having said that, the original dev had avoided documenting the icon monitored condition so maybe there is a problem with that and he knew it…)
Are you able to take a look? It seems it has different area assignments to mine (IDN11052)
I did try to add an extra condition and search for ‘Location’ but caused more trouble than I solved lol.
Anyway if you can take a look I’d appreciate it.

The code apparently already caters for distinguishing between ‘City’ and ‘Town’. You just had to apply the same error handling for the ‘Town’ part.

OLD (lines 422-431)

            else:
                _LOGGER.debug("not City")
                uv_alert_data = self._data.find(
                    _FIND_QUERY.format(index, 'uv_alert')).text
                _LOGGER.debug("uv_alert_data = %s", uv_alert_data)
                if uv_alert_data is not None:
                    uv_alert = self._data.find(
                        _FIND_QUERY.format(index, 'uv_alert')).text
                    _LOGGER.debug("uv_alert = %s", uv_alert)
                    return uv_alert

NEW

            else:
                _LOGGER.debug("not City")
                uv_alert_data = self._data.find(_FIND_QUERY.format(index, 'uv_alert'))
                _LOGGER.debug("uv_alert_data = %s", uv_alert_data)
                if uv_alert_data is not None:
                    uv_alert = uv_alert_data.text
                    _LOGGER.debug("uv_alert = %s", uv_alert)
                    return uv_alert

OLD (lines 447-456):

            else:
                _LOGGER.debug("not City")
                fire_danger_data = self._data.find(
                    _FIND_QUERY.format(index, 'fire_danger')).text
                _LOGGER.debug("fire_danger_data = %s", fire_danger_data)
                if fire_danger_data is not None:
                    fire_danger = self._data.find(
                        _FIND_QUERY.format(index, 'fire_danger')).text
                    _LOGGER.debug("fire_danger = %s", fire_danger)
                    return fire_danger

NEW

            else:
                _LOGGER.debug("not City")
                fire_danger_data = self._data.find(
                    _FIND_QUERY.format(index, 'fire_danger'))
                _LOGGER.debug("fire_danger_data = %s", fire_danger_data)
                if fire_danger_data is not None:
                    fire_danger = fire_danger_data.text
                    _LOGGER.debug("fire_danger = %s", fire_danger)
                    return fire_danger
1 Like

Yeah that was what I tried but couldn’t make that work… I’ll try the change. Thanks again.

1 Like