From ac76798d817baf2fbf3fd4e13f40ce8d4c5d0a8a Mon Sep 17 00:00:00 2001 From: Rolf Date: Wed, 4 Feb 2026 22:41:49 +0100 Subject: [PATCH] fixed --- .../__pycache__/__init__.cpython-313.pyc | Bin 1434 -> 0 bytes .../__pycache__/button.cpython-313.pyc | Bin 2451 -> 0 bytes .../__pycache__/config_flow.cpython-313.pyc | Bin 2053 -> 0 bytes .../__pycache__/const.cpython-313.pyc | Bin 466 -> 0 bytes .../__pycache__/sensor.cpython-313.pyc | Bin 10892 -> 10531 bytes custom_components/uster_waste/sensor.py | 107 +++++++++++++++--- 6 files changed, 92 insertions(+), 15 deletions(-) delete mode 100644 custom_components/uster_waste/__pycache__/__init__.cpython-313.pyc delete mode 100644 custom_components/uster_waste/__pycache__/button.cpython-313.pyc delete mode 100644 custom_components/uster_waste/__pycache__/config_flow.cpython-313.pyc delete mode 100644 custom_components/uster_waste/__pycache__/const.cpython-313.pyc diff --git a/custom_components/uster_waste/__pycache__/__init__.cpython-313.pyc b/custom_components/uster_waste/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index efc90ebe6608c013ce7bd2d26d06c05c3b73faa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1434 zcma)6L2MgE6rI_fUE6CrsnaHemQ)KhYAuMvA*e+yNQMRxyLKvXEDnvbTI_YQ$lkSP z)>QE+v1+3BKatd>S#$VkA>lmBxuhZtwJ;-(YO&=B1UvcGGa@KS=G?aqAv+ep3uev?Z`?Z zlk7}Zm~T`4a|?v-xW2tYE#L9n?7j$_!94zn=hmH-IoGFauuOjDHSHW@4)ZP72SxN| z!}9AMZSLd22KD+vF*jf0WYuDfE4J9kRciZf>Qovk zxdA;qWXG#|m`2b+5_C}NOjO*4XVqS6SVpF!ke^Qs%F_LM#?sfoAdD;xQ&7#+crTUirqaFC)o$wQ*4(2Hc2dP&ytqw@-FWdi6D+)wp2ol6 zcgO=}8kaJoJYt$PuWFk7ja7J?@aP6_Gkcse6sQWPIO#%dDyqUypES74r{aZj1>yh> z2&o!#C1at?)sHKSiwmXTs+@e`d5wS>!G$)_0c=BqTSu=oy;{3re?;E`o%lmA(YwBk zF@A#1@1ogV^zJUY^f!8aFFF35`K|fG>V5A=Z#$7)kM&grpW2x2BM@8hKs+YT#HxQv Y!X>=%VIK*(b#5RZpZOEc-zc|WF!~g&Q diff --git a/custom_components/uster_waste/__pycache__/button.cpython-313.pyc b/custom_components/uster_waste/__pycache__/button.cpython-313.pyc deleted file mode 100644 index c1be01dfe11cc20e59e38fdce5b44cdcb4a2f124..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 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 XytQm?=k;ZyliPZrz5CRJj^2C+M>dgW diff --git a/custom_components/uster_waste/__pycache__/sensor.cpython-313.pyc b/custom_components/uster_waste/__pycache__/sensor.cpython-313.pyc index 3702d83f9134ef973e04ed7566ad05fdd50f3d35..97468486cc9daf7a47fd59570dc2e1e436a477bd 100644 GIT binary patch delta 2100 zcmY*aZ%k8H6n~et@Y>SCpq28srIgY)Kw&}vod^>dWYTTcIu!6x+9y<>E#CJK*cLI< zWtx~py;H%Nh|@&pqMIR0%sy=&X7<4>)Gq1Lkxde}WXV3TiBtE*-SZv=xuL&z{@;7g zJ?C)Je`UM*k;!CW==%Meqv84D(`FkIZ&z$Zo?dHjg^tSWHK1h}?6EOU##dgz_}sn5 zWhpmv5*gCbf8>0|N2r6?*$t?Z>}J;^ANiQQpc|l`dq}0esmjmPAzwxTvya`U#d;sx zYp6@}=_#+pMxOy^`k74#n_LWM#9ic=e(l8DuxT5>TNHwDmek6i1RU$pF$&G-EYp+H zFgtX9rjOeCQI9qy^r4>gWjd`%>tMPa;)9C%v5MKdWcI;Y9&G3i(9~U#TQ(YdY%tbV~OV_ zxRV3c5!?)xyFl+De`VOoU4z-)LfNNBm_;+AH=oFev9?Le#nJ11_qt|TTa4X3GjFiy z(E;*r?!ml%YRVzJk$&)g01+TU4qNidBB0YHvm(Nn%m<^QCV(G09%$U0*l&^d3S8YY5zmh0P($U(amrIF{wKBUqba=Ii}rLEffrQkg|sT~v# zyV0_Ww~{J{KC@@dYGGd5I;K8NmG#(G>x7I{=$iBtX?2fUR1qc z$}pK9YxH`j7ZJ5pIyieJg<(nR?J++2`VvQTE)`3q&GS_)4g zsSnM$CkA6?E^e+&u$8lHhtIhxae&Z70& zb9oKuCaTxoN^d}4qXu%pHD*%Y93}?s!9G%4UXSL9uiShVW$m`>oK zfYQ$bzC~)?P3RA@-`!S!mHOk)0ImUC0k{kR(YaiF2518C3BXx^bL63WqfeO`O0UD< zP?Ca`%d^8cO6R*gp41LRqaywi)H47V06qn{Na4kq;RT_W9Ij|Lm~@86C0dPb{ANjfC$PiMF@fgp$cesMQJdZ;_iUkuDf_=@ju1Z zhuV}_^;S!4Yi(^}YZI#>O?_(hp=lqqak5Q@OntOX+m~Y07$2JSp4nXt5qgvP?z!jZ zo-=p8d*)JTddTt6ZnrRWoc!W=Y$t!(QHG+|YkQCro%s&8|Zsa*jQ3*v>E{U~ct<3o4k`jo2J!cUhYA!e)JC z4O_5n+$z{`KRIu1aBhP%-2huDL`>udZml@n7|&L~FvFekY+i;5Geb<=n5iPWGA&x= z=T_s4fChyE+({L9aSpth+_vyRlV+5q_}Ee&GHmLBfjg+Av7#7D#8RTDts=KeJfzoZ zw>MD3F=pPu6qiq%XJxC)jP{Tdu6?B=)F2?-MkhQ5Fb)tS&f?Ol7}!i&VM02X5+liY zT#BS($pqd9E)yBQ&BgWI7(`tuLj;d^f&z{~GbEA+G<*1^u0iqT!R<$Po#KWYor1Aw3;*B_YK*q2wPR$-H*=VdS$i#Po3j8z47N6OmU?;y- z>>79r?5|SLjL}#mg{MI*7DYKVnuX$nHPl z%x&>}o~1(#3!z2Jhmyg{%2L<=*bh>B$@2CWm#iACVR9FHq-^=%sU?H+b9FZ^NFHXq zpv5qT1op6NXd*hAl034MkdxR0bdVY{EPKY~48dt)e2fC66g~=zJ^-DL053i(be3zM6|s)gM&+LDk=; z_}f(f2F1VOrupZ!Y5!o_xAlbaSGIDFwW+M2u!71qDs1C@XZ2D+cROE6`$PAfTOcIO{zDjc!R2Uz2aSeqdo2IyXWL) zqAK5@@C_=zPT|+7e2c=j+%Vr9Bj{!ad_ypi10N6@cflq?`KENq}(*S44;hHw_EfAs6>Wx}&(fBf5 zYU~6~(vOikq#1W7lW}|%e69nW1G7DQo)IY?7e)FD`4?|v0aj66UNd17}vP#-l-Qu6rk0Vt6fB}58-4I08r;a{m=%oKR{sVLt^4S0Y diff --git a/custom_components/uster_waste/sensor.py b/custom_components/uster_waste/sensor.py index e5d22b1..45bb11d 100644 --- a/custom_components/uster_waste/sensor.py +++ b/custom_components/uster_waste/sensor.py @@ -63,18 +63,16 @@ async def async_setup_entry( waste_id = config["id"] name = config.get("name", "Uster Waste") - session = async_get_clientsession(hass) - coordinator = UsterWasteDataUpdateCoordinator(hass, session, token, waste_id) - entity = UsterWasteSensor( entry_id=entry.entry_id, - coordinator=coordinator, + token=token, + waste_id=waste_id, name=name ) - async_add_entities([entity], update_before_add=True) + async_add_entities([entity]) -class UsterWasteDataUpdateCoordinator: +class UsterWasteDataUpdateCoordinator(DataUpdateCoordinator[dict]): """Fetch data from Uster website.""" def __init__( @@ -84,7 +82,7 @@ class UsterWasteDataUpdateCoordinator: token: str, waste_id: str, ): - self.hass = hass + super().__init__(hass, _LOGGER, name="Uster Waste", update_interval=SCAN_INTERVAL) self.session = session self.token = token self.waste_id = waste_id @@ -190,20 +188,25 @@ class UsterWasteSensor(SensorEntity): def __init__( self, entry_id: str, - coordinator: UsterWasteDataUpdateCoordinator, + token: str, + waste_id: str, name: str ): self._entry_id = entry_id - self.coordinator = coordinator + self.token = token + self.waste_id = waste_id self._attr_name = f"{name} Schedule" self._attr_unique_id = f"uster_waste_{entry_id}" self._attr_extra_state_attributes = { ATTR_ENTRIES: [], } + self.data = None async def async_update(self): """Update sensor state.""" - data = self.coordinator.data if self.coordinator.data is not None else {} + # Fetch fresh data + data = await self._fetch_data() + self.data = data self._attr_native_value = len(data.get("entries", [])) self._attr_extra_state_attributes.update( { @@ -216,14 +219,88 @@ class UsterWasteSensor(SensorEntity): } ) + async def _fetch_data(self) -> dict: + """Fetch data from Uster website.""" + try: + session = async_get_clientsession(self.hass) + url = ( + "https://www.uster.ch/abfallstrassenabschnitt" + f"?strassenabschnitt%5B_token%5D={self.token}" + f"&strassenabschnitt%5BstrassenabschnittId%5D={self.waste_id}" + ) + + async with session.get(url, timeout=10) as response: + if response.status == 403 or response.status == 404: + raise Exception( + "Token expired or invalid. " + "Please get a fresh URL from https://www.uster.ch/abfallstrassenabschnitt" + ) + response.raise_for_status() + html = await response.text() + + # Parse HTML + soup = BeautifulSoup(html, "html.parser") + table = soup.find("table", class_="table table-striped") + if not table: + table = soup.find("table") + if not table: + raise ValueError("No table found on page.") + + rows = table.find_all("tr") + if len(rows) < 2: + raise ValueError("Table has no data rows.") + + entries = [] + now = datetime.now() + + for row in rows[1:4]: # Next 3 entries + cols = row.find_all("td") + if len(cols) < 2: + continue + + collection_type = cols[0].get_text(strip=True) + date_str = cols[1].get_text(strip=True).replace("  ", " ") # Clean no-break space + dt = _parse_date(date_str) + if not dt: + _LOGGER.warning(f"Skipping row with invalid date: {date_str}") + continue + + entries.append({ + "Sammlung": collection_type, + "Wann?": date_str, + "date_obj": dt, + "days_until": (dt - now).days + }) + + # Sort by date (ascending) + entries.sort(key=lambda x: x["date_obj"]) + + return { + "next_collection": entries[0]["Sammlung"] if entries else None, + "date": entries[0]["Wann?"] if entries else None, + "type": entries[0]["Sammlung"] if entries else None, + "days_until": entries[0]["days_until"] if entries else None, + "entries": [ + { + "type": e["Sammlung"], + "date": e["Wann?"], + "days_until": e["days_until"] + } + for e in entries[:3] + ] + } + except Exception as e: + _LOGGER.error("Error fetching Uster data: %s", e) + return { + ATTR_ERROR: str(e), + "next_collection": None, + "date": None, + "entries": [] + } + async def async_added_to_hass(self): """When entity is added to hass.""" await super().async_added_to_hass() - self.async_on_remove( - self.coordinator.async_add_listener( - self.async_write_ha_state, self.coordinator.last_updated - ) - ) # Force first update await self.async_update()