Files
hassos_config/custom_components/afvalinfo/sensor.py
2023-02-26 11:43:30 +01:00

414 lines
15 KiB
Python

#!/usr/bin/env python3
"""
Sensor component for Afvalinfo
Author: Johnny Visser
"""
import voluptuous as vol
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
import urllib.error
from babel import Locale
from babel.dates import format_date, format_datetime, format_time
import re
from .const.const import (
MIN_TIME_BETWEEN_UPDATES,
_LOGGER,
CONF_CITY,
CONF_DISTRICT,
CONF_LOCATION,
CONF_POSTCODE,
CONF_STREET_NUMBER,
CONF_STREET_NUMBER_SUFFIX,
CONF_GET_WHOLE_YEAR,
CONF_DATE_FORMAT,
CONF_TIMESPAN_IN_DAYS,
CONF_NO_TRASH_TEXT,
CONF_DIFTAR_CODE,
CONF_LOCALE,
CONF_ID,
SENSOR_PREFIX,
ATTR_ERROR,
ATTR_LAST_UPDATE,
ATTR_HIDDEN,
ATTR_DAYS_UNTIL_COLLECTION_DATE,
ATTR_IS_COLLECTION_DATE_TODAY,
ATTR_YEAR_MONTH_DAY_DATE,
ATTR_FRIENDLY_NAME,
ATTR_LAST_COLLECTION_DATE,
ATTR_TOTAL_COLLECTIONS_THIS_YEAR,
ATTR_WHOLE_YEAR_DATES,
SENSOR_TYPES,
)
from .location.trashapi import TrashApiAfval
from .sensortomorrow import AfvalInfoTomorrowSensor
from .sensortoday import AfvalInfoTodaySensor
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_RESOURCES
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_RESOURCES, default=[]): vol.All(cv.ensure_list),
vol.Optional(CONF_CITY, default=""): cv.string,
vol.Optional(CONF_LOCATION, default="sliedrecht"): cv.string,
vol.Required(CONF_POSTCODE, default="3361AB"): cv.string,
vol.Required(CONF_STREET_NUMBER, default="1"): cv.string,
vol.Optional(CONF_STREET_NUMBER_SUFFIX, default=""): cv.string,
vol.Optional(CONF_DISTRICT, default=""): cv.string,
vol.Optional(CONF_DATE_FORMAT, default="%d-%m-%Y"): cv.string,
vol.Optional(CONF_TIMESPAN_IN_DAYS, default="365"): cv.string,
vol.Optional(CONF_LOCALE, default="en"): cv.string,
vol.Optional(CONF_ID, default=""): cv.string,
vol.Optional(CONF_NO_TRASH_TEXT, default="none"): cv.string,
vol.Optional(CONF_DIFTAR_CODE, default=""): cv.string,
vol.Optional(CONF_GET_WHOLE_YEAR, default="false"): cv.string,
}
)
def setup_platform(hass, config, add_entities, discovery_info=None):
_LOGGER.debug("Setup Afvalinfo sensor")
location = config.get(CONF_CITY).lower().strip()
if len(location) == 0:
location = config.get(CONF_LOCATION).lower().strip()
postcode = config.get(CONF_POSTCODE).strip()
street_number = config.get(CONF_STREET_NUMBER)
street_number_suffix = config.get(CONF_STREET_NUMBER_SUFFIX)
district = config.get(CONF_DISTRICT)
date_format = config.get(CONF_DATE_FORMAT).strip()
timespan_in_days = config.get(CONF_TIMESPAN_IN_DAYS)
locale = config.get(CONF_LOCALE)
id_name = config.get(CONF_ID)
no_trash_text = config.get(CONF_NO_TRASH_TEXT)
diftar_code = config.get(CONF_DIFTAR_CODE)
get_whole_year = config.get(CONF_GET_WHOLE_YEAR)
try:
resources = config[CONF_RESOURCES].copy()
# filter the types from the dict if it's a dictionary
if isinstance(resources[0], dict):
resourcesMinusTodayAndTomorrow = [obj["type"] for obj in resources]
else:
resourcesMinusTodayAndTomorrow = resources
if "trash_type_today" in resourcesMinusTodayAndTomorrow:
resourcesMinusTodayAndTomorrow.remove("trash_type_today")
if "trash_type_tomorrow" in resourcesMinusTodayAndTomorrow:
resourcesMinusTodayAndTomorrow.remove("trash_type_tomorrow")
data = AfvalinfoData(
location,
postcode,
street_number,
street_number_suffix,
district,
diftar_code,
get_whole_year,
resourcesMinusTodayAndTomorrow,
)
except urllib.error.HTTPError as error:
_LOGGER.error(error.reason)
return False
entities = []
for resource in config[CONF_RESOURCES]:
# old way, before 20220204
if type(resource) == str:
sensor_type = resource.lower()
sensor_friendly_name = sensor_type
# new way
else:
sensor_type = resource["type"].lower()
if "friendly_name" in resource.keys():
sensor_friendly_name = resource["friendly_name"]
else:
# If no friendly name is provided, use the sensor_type as friendly name
sensor_friendly_name = sensor_type
# if sensor_type not in SENSOR_TYPES:
if (
sensor_type.title().lower() != "trash_type_today"
and sensor_type.title().lower() != "trash_type_tomorrow"
):
entities.append(
AfvalinfoSensor(
data,
sensor_type,
sensor_friendly_name,
date_format,
timespan_in_days,
locale,
id_name,
get_whole_year,
)
)
# Add sensor -trash_type_today
if sensor_type.title().lower() == "trash_type_today":
today = AfvalInfoTodaySensor(
data,
sensor_type,
sensor_friendly_name,
entities,
id_name,
no_trash_text,
)
entities.append(today)
# Add sensor -trash_type_tomorrow
if sensor_type.title().lower() == "trash_type_tomorrow":
tomorrow = AfvalInfoTomorrowSensor(
data,
sensor_type,
sensor_friendly_name,
entities,
id_name,
no_trash_text,
)
entities.append(tomorrow)
add_entities(entities)
class AfvalinfoData(object):
def __init__(
self,
location,
postcode,
street_number,
street_number_suffix,
district,
diftar_code,
get_whole_year,
resources,
):
self.data = None
self.location = location
self.postcode = postcode
self.street_number = street_number
self.street_number_suffix = street_number_suffix
self.district = district
self.diftar_code = diftar_code
self.get_whole_year = get_whole_year
self.resources = resources
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
_LOGGER.debug("Updating Waste collection dates")
self.data = TrashApiAfval().get_data(
self.location,
self.postcode,
self.street_number,
self.street_number_suffix,
self.district,
self.diftar_code,
self.get_whole_year,
self.resources,
)
class AfvalinfoSensor(Entity):
def __init__(
self,
data,
sensor_type,
sensor_friendly_name,
date_format,
timespan_in_days,
locale,
id_name,
get_whole_year,
):
self.data = data
self.type = sensor_type
self.friendly_name = sensor_friendly_name
self.date_format = date_format
self.timespan_in_days = timespan_in_days
self.locale = locale
self._name = sensor_friendly_name
self._get_whole_year = get_whole_year
self.entity_id = "sensor." + (
(
SENSOR_PREFIX
+ (id_name + " " if len(id_name) > 0 else "")
+ sensor_friendly_name
)
.lower()
.replace(" ", "_")
)
self._attr_unique_id = (
SENSOR_PREFIX
+ (id_name + " " if len(id_name) > 0 else "")
+ sensor_friendly_name
)
self._icon = SENSOR_TYPES[sensor_type][1]
self._hidden = False
self._error = False
self._state = None
self._last_update = None
self._days_until_collection_date = None
self._is_collection_date_today = False
self._year_month_day_date = None
self._last_collection_date = None
self._total_collections_this_year = None
self._whole_year_dates = None
@property
def name(self):
return self._name
@property
def icon(self):
return self._icon
@property
def state(self):
return self._state
@property
def extra_state_attributes(self):
return {
ATTR_ERROR: self._error,
ATTR_FRIENDLY_NAME: self.friendly_name,
ATTR_YEAR_MONTH_DAY_DATE: self._year_month_day_date,
ATTR_LAST_UPDATE: self._last_update,
ATTR_HIDDEN: self._hidden,
ATTR_DAYS_UNTIL_COLLECTION_DATE: self._days_until_collection_date,
ATTR_IS_COLLECTION_DATE_TODAY: self._is_collection_date_today,
ATTR_LAST_COLLECTION_DATE: self._last_collection_date,
ATTR_TOTAL_COLLECTIONS_THIS_YEAR: self._total_collections_this_year,
ATTR_WHOLE_YEAR_DATES: self._whole_year_dates,
}
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
self.data.update()
waste_array = self.data.data
self._error = False
# Loop through all the dates to put the dates in the whole_year_dates attribute
if self._get_whole_year == "True":
whole_year_dates = []
for waste_data in waste_array:
if self.type in waste_data:
whole_year_dates.append(
datetime.strptime(waste_data[self.type], "%Y-%m-%d").date()
)
self._whole_year_dates = whole_year_dates
try:
if waste_array:
for waste_data in waste_array:
if self.type in waste_data:
collection_date = datetime.strptime(
waste_data[self.type], "%Y-%m-%d"
).date()
# Date in date format "%Y-%m-%d"
self._year_month_day_date = str(collection_date)
if collection_date:
# Set the values of the sensor
self._last_update = datetime.today().strftime(
"%d-%m-%Y %H:%M"
)
# Is the collection date today?
self._is_collection_date_today = (
date.today() == collection_date
)
if (
self.type == "restafval"
and "restafvaldiftardate" in waste_data
):
self._last_collection_date = str(
datetime.strptime(
waste_data["restafvaldiftardate"], "%Y-%m-%d"
).date()
)
self._total_collections_this_year = waste_data[
"restafvaldiftarcollections"
]
# Days until collection date
delta = collection_date - date.today()
self._days_until_collection_date = delta.days
# Only show the value if the date is lesser than or equal to (today + timespan_in_days)
if collection_date <= date.today() + relativedelta(
days=int(self.timespan_in_days)
):
# if the date does not contain a named day or month, return the date as normal
if (
self.date_format.find("a") == -1
and self.date_format.find("A") == -1
and self.date_format.find("b") == -1
and self.date_format.find("B") == -1
):
self._state = collection_date.strftime(
self.date_format
)
# else convert the named values to the locale names
else:
edited_date_format = self.date_format.replace(
"%a", "EEE"
)
edited_date_format = edited_date_format.replace(
"%A", "EEEE"
)
edited_date_format = edited_date_format.replace(
"%b", "MMM"
)
edited_date_format = edited_date_format.replace(
"%B", "MMMM"
)
# half babel, half date string... something like EEEE 04-MMMM-2020
half_babel_half_date = collection_date.strftime(
edited_date_format
)
# replace the digits with qquoted digits 01 --> '01'
half_babel_half_date = re.sub(
r"(\d+)", r"'\1'", half_babel_half_date
)
# transform the EEE, EEEE etc... to a real locale date, with babel
locale_date = format_date(
collection_date,
half_babel_half_date,
locale=self.locale,
)
self._state = locale_date
break # we have a result, break the loop
else:
self._hidden = True
else:
# collection_date empty
raise ValueError()
# else:
# No matching result data for current waste type, no problem
else:
raise ValueError()
except ValueError:
self._error = True
# self._state = None
# self._hidden = True
# self._days_until_collection_date = None
# self._year_month_day_date = None
# self._is_collection_date_today = False
# self._last_collection_date = None
# self._total_collections_this_year = None
# self._whole_year_dates = None
self._last_update = datetime.today().strftime("%d-%m-%Y %H:%M")