Example #1
0
def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Web scrape sensor."""
    name = config.get(CONF_NAME)
    resource = config.get(CONF_RESOURCE)
    method = 'GET'
    payload = None
    headers = config.get(CONF_HEADERS)
    verify_ssl = config.get(CONF_VERIFY_SSL)
    select = config.get(CONF_SELECT)
    attr = config.get(CONF_ATTR)
    index = config.get(CONF_INDEX)
    unit = config.get(CONF_UNIT_OF_MEASUREMENT)
    username = config.get(CONF_USERNAME)
    password = config.get(CONF_PASSWORD)
    value_template = config.get(CONF_VALUE_TEMPLATE)
    if value_template is not None:
        value_template.hass = hass

    if username and password:
        if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
            auth = HTTPDigestAuth(username, password)
        else:
            auth = HTTPBasicAuth(username, password)
    else:
        auth = None
    rest = RestData(method, resource, auth, headers, payload, verify_ssl)
    rest.update()

    if rest.data is None:
        raise PlatformNotReady

    add_entities([
        ScrapeSensor(rest, name, select, attr, index, value_template, unit)],
                 True)
Example #2
0
def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the PVOutput sensor."""
    name = config.get(CONF_NAME)
    api_key = config.get(CONF_API_KEY)
    system_id = config.get(CONF_SYSTEM_ID)
    method = 'GET'
    payload = auth = None
    verify_ssl = DEFAULT_VERIFY_SSL
    headers = {
        'X-Pvoutput-Apikey': api_key,
        'X-Pvoutput-SystemId': system_id,
    }

    rest = RestData(method, _ENDPOINT, auth, headers, payload, verify_ssl)
    rest.update()

    if rest.data is None:
        _LOGGER.error("Unable to fetch data from PVOutput")
        return False

    add_entities([PvoutputSensor(rest, name)], True)
Example #3
0
class DwdWeatherWarningsAPI:
    """Get the latest data and update the states."""

    def __init__(self, region_name):
        """Initialize the data object."""
        resource = "{}{}{}?{}".format(
            'https://',
            'www.dwd.de',
            '/DWD/warnungen/warnapp_landkreise/json/warnings.json',
            'jsonp=loadWarnings'
        )

        self._rest = RestData('GET', resource, None, None, None, True)
        self.region_name = region_name
        self.region_id = None
        self.region_state = None
        self.data = None
        self.available = True
        self.update()

    @Throttle(SCAN_INTERVAL)
    def update(self):
        """Get the latest data from the DWD-Weather-Warnings."""
        try:
            self._rest.update()

            json_string = self._rest.data[24:len(self._rest.data) - 2]
            json_obj = json.loads(json_string)

            data = {'time': json_obj['time']}

            for mykey, myvalue in {
                    'current': 'warnings',
                    'advance': 'vorabInformation'
            }.items():

                _LOGGER.debug("Found %d %s global DWD warnings",
                              len(json_obj[myvalue]), mykey)

                data['{}_warning_level'.format(mykey)] = 0
                my_warnings = []

                if self.region_id is not None:
                    # get a specific region_id
                    if self.region_id in json_obj[myvalue]:
                        my_warnings = json_obj[myvalue][self.region_id]

                else:
                    # loop through all items to find warnings, region_id
                    # and region_state for region_name
                    for key in json_obj[myvalue]:
                        my_region = json_obj[myvalue][key][0]['regionName']
                        if my_region != self.region_name:
                            continue
                        my_warnings = json_obj[myvalue][key]
                        my_state = json_obj[myvalue][key][0]['stateShort']
                        self.region_id = key
                        self.region_state = my_state
                        break

                # Get max warning level
                maxlevel = data['{}_warning_level'.format(mykey)]
                for event in my_warnings:
                    if event['level'] >= maxlevel:
                        data['{}_warning_level'.format(mykey)] = event['level']

                data['{}_warning_count'.format(mykey)] = len(my_warnings)
                data['{}_warnings'.format(mykey)] = my_warnings

                _LOGGER.debug("Found %d %s local DWD warnings",
                              len(my_warnings), mykey)

            self.data = data
            self.available = True
        except TypeError:
            _LOGGER.error("Unable to fetch data from DWD-Weather-Warnings")
            self.available = False
Example #4
0
class DwdWeatherWarningsAPI:
    """Get the latest data and update the states."""
    def __init__(self, region_name):
        """Initialize the data object."""
        resource = "{}{}{}?{}".format(
            "https://",
            "www.dwd.de",
            "/DWD/warnungen/warnapp_landkreise/json/warnings.json",
            "jsonp=loadWarnings",
        )

        # a User-Agent is necessary for this rest api endpoint (#29496)
        headers = {"User-Agent": HA_USER_AGENT}

        self._rest = RestData("GET", resource, None, headers, None, True)
        self.region_name = region_name
        self.region_id = None
        self.region_state = None
        self.data = None
        self.available = True
        self.update()

    @Throttle(SCAN_INTERVAL)
    def update(self):
        """Get the latest data from the DWD-Weather-Warnings."""
        try:
            self._rest.update()

            json_string = self._rest.data[24:len(self._rest.data) - 2]
            json_obj = json.loads(json_string)

            data = {"time": json_obj["time"]}

            for mykey, myvalue in {
                    "current": "warnings",
                    "advance": "vorabInformation",
            }.items():

                _LOGGER.debug("Found %d %s global DWD warnings",
                              len(json_obj[myvalue]), mykey)

                data[f"{mykey}_warning_level"] = 0
                my_warnings = []

                if self.region_id is not None:
                    # get a specific region_id
                    if self.region_id in json_obj[myvalue]:
                        my_warnings = json_obj[myvalue][self.region_id]

                else:
                    # loop through all items to find warnings, region_id
                    # and region_state for region_name
                    for key in json_obj[myvalue]:
                        my_region = json_obj[myvalue][key][0]["regionName"]
                        if my_region != self.region_name:
                            continue
                        my_warnings = json_obj[myvalue][key]
                        my_state = json_obj[myvalue][key][0]["stateShort"]
                        self.region_id = key
                        self.region_state = my_state
                        break

                # Get max warning level
                maxlevel = data[f"{mykey}_warning_level"]
                for event in my_warnings:
                    if event["level"] >= maxlevel:
                        data[f"{mykey}_warning_level"] = event["level"]

                data[f"{mykey}_warning_count"] = len(my_warnings)
                data[f"{mykey}_warnings"] = my_warnings

                _LOGGER.debug("Found %d %s local DWD warnings",
                              len(my_warnings), mykey)

            self.data = data
            self.available = True
        except TypeError:
            _LOGGER.error("Unable to fetch data from DWD-Weather-Warnings")
            self.available = False
Example #5
0
class PoolMathClient():
    def __init__(self, hass, config, add_sensors_callback):
        self._hass = hass
        self._sensors = {}
        self._add_sensors_callback = add_sensors_callback

        verify_ssl = True
        self._url = config.get(CONF_URL)
        self._rest = RestData('GET', self._url, '', '', '', verify_ssl)

        # query the latest data from Pool Math
        soup = self._fetch_latest_data()
        if soup is None:
            raise PlatformNotReady

        self._name = config.get(CONF_NAME)
        if self._name == None:
            self._name = DEFAULT_NAME

            # extract the pool name, if defined
            h1_span = soup.select('h1')
            if h1_span and h1_span[0]:
                pool_name = h1_span[0].string
                if pool_name != None:
                    self._name = f"{pool_name} {DEFAULT_NAME}"

        LOG.info(f"Creating Pool Math sensors for '{self._name}'")
        self._update_from_log_entries(soup)

    def _fetch_latest_data(self):
        """Fetch the latest log entries from the Pool Math service"""
        self._rest.update()
        result = self._rest.data
        if result is None:
            LOG.warn(f"Failed updating Pool Math data from {self._url}")
            return None
        soup = BeautifulSoup(result, 'html.parser')
        #LOG.debug("Raw data from %s: %s", self._url, soup)
        return soup

    def update(self):
        soup = self._fetch_latest_data()
        if not soup:
            return None
        return self._update_from_log_entries(soup)

    def get_sensor(self, sensor_type):
        sensor = self._sensors.get(sensor_type, None)
        if sensor:
            return sensor

        config = POOL_MATH_SENSOR_SETTINGS.get(sensor_type, None)
        if config is None:
            LOG.warning(
                f"Unknown Pool Math sensor '{sensor_type}' discovered at {self._url}"
            )
            return None

        name = self._name + ' ' + config['name']
        sensor = UpdatableSensor(self._hass, name, config)
        self._sensors[sensor_type] = sensor

        # register sensor with Home Assistant
        self._add_sensors_callback([sensor], True)
        return sensor

    def _update_from_log_entries(self, poolmath_soup):
        updated_sensors = {}
        latest_timestamp = None

        # Read back through all log entries and update any changed sensor states (since a given
        # log entry may only have a subset of sensor states)
        log_entries = poolmath_soup.find_all('div', class_='testLogCard')
        for log_entry in log_entries:
            log_fields = log_entry.select('.chiclet')
            LOG.debug("Pool Math log fields=%s", log_fields)

            if not latest_timestamp:
                # capture the timestamp for the most recent Pool Math log entry
                latest_timestamp = log_entry.find('time',
                                                  class_='timestamp timereal')

            # FIXME: improve parsing to be more robust to Pool Math changes
            for entry in log_fields:
                sensor_type = entry.contents[3].text.lower()
                if not sensor_type in updated_sensors:
                    state = entry.contents[1].text

                    sensor = self.get_sensor(sensor_type)
                    if sensor:
                        timestamp = log_entry.find(
                            'time', class_='timestamp timereal').text
                        if sensor.state != state:
                            LOG.info(
                                f"Pool Math returned updated {sensor_type}={state} (timestamp={timestamp})"
                            )
                        sensor.inject_state(state, timestamp)
                        updated_sensors[sensor_type] = sensor

        # record the most recent log entry's timestamp as the service's last updated timestamp
        self._timestamp = latest_timestamp
        return latest_timestamp

    @property
    def sensor_names(self):
        return self._sensors.keys()

    @property
    def latest_log_timestamp(self):
        return self._timestamp
Example #6
0
class DwdWeatherWarningsAPI:
    """Get the latest data and update the states."""
    def __init__(self, region_name):
        """Initialize the data object."""
        resource = "{}{}{}?{}".format(
            'https://', 'www.dwd.de',
            '/DWD/warnungen/warnapp_landkreise/json/warnings.json',
            'jsonp=loadWarnings')

        self._rest = RestData('GET', resource, None, None, None, True)
        self.region_name = region_name
        self.region_id = None
        self.region_state = None
        self.data = None
        self.available = True
        self.update()

    @Throttle(SCAN_INTERVAL)
    def update(self):
        """Get the latest data from the DWD-Weather-Warnings."""
        try:
            self._rest.update()

            json_string = self._rest.data[24:len(self._rest.data) - 2]
            json_obj = json.loads(json_string)

            data = {'time': json_obj['time']}

            for mykey, myvalue in {
                    'current': 'warnings',
                    'advance': 'vorabInformation'
            }.items():

                _LOGGER.debug("Found %d %s global DWD warnings",
                              len(json_obj[myvalue]), mykey)

                data['{}_warning_level'.format(mykey)] = 0
                my_warnings = []

                if self.region_id is not None:
                    # get a specific region_id
                    if self.region_id in json_obj[myvalue]:
                        my_warnings = json_obj[myvalue][self.region_id]

                else:
                    # loop through all items to find warnings, region_id
                    # and region_state for region_name
                    for key in json_obj[myvalue]:
                        my_region = json_obj[myvalue][key][0]['regionName']
                        if my_region != self.region_name:
                            continue
                        my_warnings = json_obj[myvalue][key]
                        my_state = json_obj[myvalue][key][0]['stateShort']
                        self.region_id = key
                        self.region_state = my_state
                        break

                # Get max warning level
                maxlevel = data['{}_warning_level'.format(mykey)]
                for event in my_warnings:
                    if event['level'] >= maxlevel:
                        data['{}_warning_level'.format(mykey)] = event['level']

                data['{}_warning_count'.format(mykey)] = len(my_warnings)
                data['{}_warnings'.format(mykey)] = my_warnings

                _LOGGER.debug("Found %d %s local DWD warnings",
                              len(my_warnings), mykey)

            self.data = data
            self.available = True
        except TypeError:
            _LOGGER.error("Unable to fetch data from DWD-Weather-Warnings")
            self.available = False
Example #7
0
class DwdPollenAPI:
    """
    Get the latest data and update the states.

    Format of map sensordata:
    sensordata[<region_id>]['region_name']
    sensordata[<region_id>]['partregion_name']
    sensordata[<region_id>]['pollendata'][<pollen_name>]['today'|'today_mapped'|'tomorrow'|'tomorrow_mapped'|'dayafter_tomorrow'|'dayafter_tomorrow_mapped']
    """
    def __init__(self, partregion_ids):
        """Initialize the data object."""
        resource = "https://opendata.dwd.de/climate_environment/health/alerts/s31fg.json"

        self._rest = RestData('GET', resource, None, None, None, True, None,
                              None)
        self._partregion_ids = partregion_ids
        self.last_update = None
        self.sensordata = {}
        self.api_id_to_descr = {}
        self.available = False
        self.update()

    @Throttle(SCAN_INTERVAL)
    def update(self):
        """Get the latest pollen_name data from DWD open data site."""
        try:
            # Update DWD weather data by calling rest service
            self._rest.update()
            # Retrieve REST data from correspionding object
            rest_api_result = json.loads(self._rest.data)

            self.last_update = datetime.strptime(
                rest_api_result[REST_API_KEY_LAST_UPDATE],
                '%Y-%m-%d %H:%M Uhr')

            self.generate_api_id_to_descr_map(rest_api_result)

            # Iterate over all supplied partregions
            for partregion_data in rest_api_result[REST_API_KEY_CONTENT]:

                current_partregion_id = partregion_data[
                    REST_API_KEY_PARTREGION_ID]

                # Is the current partregion_id included in the ones we should parse?
                if current_partregion_id in self._partregion_ids:
                    self.sensordata[current_partregion_id] = {}
                    self.sensordata[current_partregion_id][
                        SENSORDATA_REGION_NAME] = partregion_data[
                            REST_API_KEY_REGION_NAME]
                    self.sensordata[current_partregion_id][
                        SENSORDATA_PARTREGION_NAME] = partregion_data[
                            REST_API_KEY_PARTREGION_NAME]

                    self.sensordata[current_partregion_id]['data'] = {}
                    self.calculateit(current_partregion_id,
                                     partregion_data[REST_API_KEY_POLLEN],
                                     'today', self.last_update.date())
                    self.calculateit(
                        current_partregion_id,
                        partregion_data[REST_API_KEY_POLLEN], 'tomorrow',
                        self.last_update.date() + timedelta(days=1))
                    self.calculateit(
                        current_partregion_id,
                        partregion_data[REST_API_KEY_POLLEN], 'dayafter_to',
                        self.last_update.date() + timedelta(days=2))

            self.available = True
        except TypeError:
            _LOGGER.error(
                "Unable to fetch pollen_name data from DWD opendata server")
            self.available = False

    def calculateit(self, current_partregion_id, pollendata, day, pollen_date):
        """
        Format:
            {
                "41": {
                    "region_name": "<region name>",
                    "partregion_name": "<partregion name>",
                    "data": {
                        "<pollen_date1>": {
                            "pollendata": {
                                "<polle1>": {
                                    "id": "<api ID>",
                                    "descr": "<api description of api ID>",
                                    "value": "<sensor value>"
                                },
                                "<polle2>": {
                                    "id": "<api ID>",
                                    "descr": "<api description of api ID>",
                                    "value": "<sensor value>"
                                }
                                [...]
                            },
                            "stats": {
                                "min": <minimum value>,
                                "max": <maximum value>,
                                "avg": <average value>
                            }
                        },
                        [...]
                    }
                }
            }
        """
        minimum = None
        # minimum = 6
        maximum = None
        # maximum = 0
        total_count = None
        # total_count = 0
        total_sum = None
        # total_sum = 0

        self.sensordata[current_partregion_id]['data'][pollen_date] = {}
        self.sensordata[current_partregion_id]['data'][pollen_date][
            SENSORDATA_POLLENDATA] = {}

        for pollen_name in pollendata:
            retrieved_pollendata = pollendata[pollen_name]
            internal_pollen_id = str(pollen_name).lower()
            self.sensordata[current_partregion_id]['data'][pollen_date][
                SENSORDATA_POLLENDATA][internal_pollen_id] = {}

            pollen_amount_api_id = retrieved_pollendata[day]
            pollen_amount_descr = self.api_id_to_descr[pollen_amount_api_id]
            pollen_amount_value = API_TO_HOMEASSISTANT_MAP[
                pollen_amount_api_id]

            self.sensordata[current_partregion_id]['data'][pollen_date][
                SENSORDATA_POLLENDATA][internal_pollen_id][
                    'id'] = pollen_amount_api_id
            self.sensordata[current_partregion_id]['data'][pollen_date][
                SENSORDATA_POLLENDATA][internal_pollen_id][
                    'descr'] = pollen_amount_descr
            self.sensordata[current_partregion_id]['data'][pollen_date][
                SENSORDATA_POLLENDATA][internal_pollen_id][
                    'value'] = pollen_amount_value

            if pollen_amount_value is not None:
                minimum = pollen_amount_value if minimum is None else min(
                    minimum, pollen_amount_value)
                maximum = pollen_amount_value if maximum is None else max(
                    maximum, pollen_amount_value)
                total_count = 1 if total_count is None else total_count + 1
                total_sum = pollen_amount_value if total_sum is None else total_sum + pollen_amount_value

            _LOGGER.debug(
                "Successfully retrieved pollen_name data for %s (ID %d).",
                self.sensordata[current_partregion_id]
                [SENSORDATA_PARTREGION_NAME], current_partregion_id)

        self.sensordata[current_partregion_id]['data'][pollen_date][
            'stats'] = {}
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_MIN] = {}
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_MIN]['value'] = minimum
        minimum_api_id = HOMEASSISTANT_TO_API_MAP[minimum]
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_MIN]['descr'] = self.api_id_to_descr[minimum_api_id]

        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_MAX] = {}
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_MAX]['value'] = maximum
        maximum_api_id = HOMEASSISTANT_TO_API_MAP[maximum]
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_MAX]['descr'] = self.api_id_to_descr[maximum_api_id]

        if total_count is None or total_sum is None or total_count <= 0:
            average = None
        else:
            average = total_sum / total_count
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_AVG] = {}
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_AVG]['value'] = average
        average_api_id = None if average is None else HOMEASSISTANT_TO_API_MAP[
            round(average, 0)]
        self.sensordata[current_partregion_id]['data'][pollen_date]['stats'][
            STAT_AVG][
                'descr'] = None if average_api_id is None else self.api_id_to_descr[
                    average_api_id]

    def generate_api_id_to_descr_map(self, json_obj):
        legend_map = json_obj['legend']
        for key, value in legend_map.items():
            if not key.endswith('_desc'):
                self.api_id_to_descr[value] = legend_map["%s_desc" % key]

        self.api_id_to_descr['-1'] = 'n/a'

    def get_adjusted_day(self, day):
        today = datetime.now().date()
        last_update_day = self.last_update.date()
        offset = (today - last_update_day).days
        return DAY_ADJUSTMENTS[day][offset]

    def get_descr_for_value(self, value):
        if not value:
            return None
        api_id = HOMEASSISTANT_TO_API_MAP[value]
        if api_id in self.api_id_to_descr:
            return self.api_id_to_descr[api_id]
        else:
            return None