Beispiel #1
0
def receive_fmi_temperature(
) -> Tuple[Optional[Decimal], Optional[arrow.Arrow]]:
    temp, ts = None, None

    try:
        starttime = arrow.now().shift(
            hours=-1).to('UTC').format('YYYY-MM-DDTHH:mm:ss') + 'Z'
        result = get_url(
            'https://opendata.fmi.fi/wfs?request=getFeature&storedquery_id=fmi::observations::weather'
            '::simple&place={place}&parameters=temperature&starttime={starttime}'
            .format(place=config.FMI_LOCATION, starttime=starttime))
    except Exception as e:
        logger.exception(e)
    else:
        if result.status_code != 200:
            logger.error('%d: %s' % (result.status_code, result.content))
        else:
            try:
                wfs_member = xmltodict.parse(result.content).get(
                    'wfs:FeatureCollection', {}).get('wfs:member')
                temp_data = wfs_member[-1].get('BsWfs:BsWfsElement')
                if temp_data and 'BsWfs:Time' in temp_data and 'BsWfs:ParameterValue' in temp_data:
                    ts = arrow.get(temp_data['BsWfs:Time']).to(config.TIMEZONE)
                    temp = Decimal(temp_data['BsWfs:ParameterValue'])
                    if not temp.is_finite():
                        raise TypeError()
            except (KeyError, TypeError):
                temp, ts = None, None

    logger.info('temp:%s ts:%s', temp, ts)
    return temp, ts
Beispiel #2
0
def send_command(persistent_data, next_command, error: Optional[Decimal], extra_info, **kwargs):
    now = time.time()

    heating_start_time = persistent_data.get('heating_start_time', now)
    last_command = persistent_data.get('last_command')

    seconds_since_heating_start = now - heating_start_time

    if last_command is not None and last_command != Commands.off:
        logger.debug('Heating started %d hours ago', seconds_since_heating_start / 3600.0)

    min_time_heating = 60 * 45
    from_off_to_heating = (last_command is None or last_command == Commands.off) and next_command != Commands.off
    from_heating_to_off = (last_command is None or last_command != Commands.off) and next_command == Commands.off
    from_heating_to_heating = (last_command is None or last_command != Commands.off) and next_command != Commands.off
    is_min_time_heating_elapsed = seconds_since_heating_start > min_time_heating

    if (
        last_command is None
        or last_command != next_command
        and (
            from_off_to_heating and (error is None or error > 0)
            or from_heating_to_off and (error is None or error < 0) and is_min_time_heating_elapsed
            or from_heating_to_heating
        )
    ):
        send_command_email = from_off_to_heating or from_heating_to_off
        send_ir_signal(next_command, extra_info=extra_info, send_command_email=send_command_email)
        last_command = next_command

    extra_info.append('Actual last command: %s' % last_command)
    logger.info('Actual last command: %s' % last_command)

    return {'extra_info': extra_info}, {'last_command': last_command, 'heating_start_time': heating_start_time}
Beispiel #3
0
def get_temp(functions: list, max_ts_diff=None, **kwargs):

    MAX_TS_DIFF_MINUTES = 60

    if max_ts_diff is None:
        max_ts_diff = MAX_TS_DIFF_MINUTES

    temperatures = []

    for func in functions:
        result = func(**kwargs)
        if result:
            temp, ts = result
            if temp is not None:
                if ts is None:
                    temperatures.append((temp, ts))
                else:
                    seconds = (arrow.now() - ts).total_seconds()
                    if abs(seconds) < 60 * max_ts_diff:
                        temperatures.append((temp, ts))
                    else:
                        logger.info(
                            'Discarding temperature %s, temp: %s, temp time: %s',
                            func_name(func), temp, ts)

    return median(temperatures)
Beispiel #4
0
def run():
    have_valid_time(5 * 60)

    state_klass = ReadLastMessageFromDB
    payload = None

    while True:
        state = state_klass()
        payload = state.run(payload)
        last_state_klass = state_klass
        state_klass = state.nex(payload)
        logger.info('State %s -> %s. Payload: %s', last_state_klass.__name__, state_klass.__name__, payload)
Beispiel #5
0
    def run(self, payload):

        pipeline = [
            general.get_controller,
            general.handle_payload,
            lambda **kwargs: {
                'have_valid_time': have_valid_time()
            },
            general.get_add_extra_info,
            get_forecast,
            get_outside,
            target_inside_temp,
            adjust_target_with_rh,
            general.hysteresis,
            get_inside,
            get_error,
            general.update_controller,
            get_next_command,
            send_status_mail,
            general.send_command,
            general.send_to_lambda,
            general.write_log,
            general.save_controller_state,
        ]

        data = {'payload': payload}

        for pipe in pipeline:
            logger.info('Calling %s', pipe)
            result = pipe(persistent_data=AutoPipeline.persistent_data, **data)
            logger.info('Call result %s: %s', pipe, result)

            if result:
                if isinstance(result, tuple):
                    new_data, new_persistent_data = result
                else:
                    new_data, new_persistent_data = result, {}

                data.update(new_data)
                AutoPipeline.persistent_data.update(new_persistent_data)

        return get_most_recent_message(once=True)
Beispiel #6
0
def receive_open_weather_map_temperature(
) -> Tuple[Optional[Decimal], Optional[arrow.Arrow]]:
    temp, ts = None, None

    try:
        result = get_url(
            'http://api.openweathermap.org/data/2.5/weather?q={place}&units=metric&appid={key}'
            .format(key=config.OPEN_WEATHER_MAP_KEY,
                    place=config.OPEN_WEATHER_MAP_LOCATION))
    except Exception as e:
        logger.exception(e)
    else:
        if result.status_code != 200:
            logger.error('%d: %s' % (result.status_code, result.content))
        else:
            result_json = result.json()
            temp = decimal_round(result_json['main']['temp'])
            ts = arrow.get(result_json['dt']).to(config.TIMEZONE)

    logger.info('temp:%s ts:%s', temp, ts)
    return temp, ts
Beispiel #7
0
def receive_fmi_dew_point() -> Tuple[Optional[Decimal], Optional[arrow.Arrow]]:
    dew_points = []
    ts = None

    try:
        starttime = arrow.now().shift(
            hours=-3).to('UTC').format('YYYY-MM-DDTHH:mm:ss') + 'Z'
        result = get_url(
            'https://opendata.fmi.fi/wfs?request=getFeature&storedquery_id=fmi::observations::weather'
            '::simple&place={place}&parameters=td&starttime={starttime}'.
            format(place=config.FMI_LOCATION, starttime=starttime))
    except Exception as e:
        logger.exception(e)
    else:
        if result.status_code != 200:
            logger.error('%d: %s' % (result.status_code, result.content))
        else:
            try:
                wfs_member = xmltodict.parse(result.content).get(
                    'wfs:FeatureCollection', {}).get('wfs:member')

                for member in wfs_member:
                    temp_data = member.get('BsWfs:BsWfsElement')
                    if temp_data and 'BsWfs:Time' in temp_data and 'BsWfs:ParameterValue' in temp_data:
                        ts = arrow.get(temp_data['BsWfs:Time']).to(
                            config.TIMEZONE)
                        dew_points.append(
                            Decimal(temp_data['BsWfs:ParameterValue']))
            except (KeyError, TypeError):
                pass

    if dew_points:
        dew_point = sum(dew_points) / len(dew_points)
    else:
        dew_point = None
        ts = None

    logger.info('dew_point:%s ts:%s', dew_point, ts)
    return dew_point, ts
Beispiel #8
0
 def add_extra_info(message):
     logger.info(message)
     extra_info.append(message)
Beispiel #9
0
    def update(
            self, error: Optional[Decimal],
            error_without_hysteresis: Optional[Decimal]
    ) -> Tuple[Decimal, str]:
        if error is None:
            error = Decimal(0)
        else:
            self._update_past_errors(error_without_hysteresis)

        logger.debug('controller error %.4f', error)

        p_term = self.kp * error

        new_time = time.time()

        error_slope_per_second = self._past_error_slope_per_second()
        error_slope_per_hour = error_slope_per_second * Decimal(3600)

        error_slope_per_hour = min(error_slope_per_hour, Decimal('0.5'))
        error_slope_per_hour = max(error_slope_per_hour, Decimal('-0.5'))

        if self.current_time is not None:
            delta_time = Decimal(new_time - self.current_time)
            logger.debug('controller delta_time %.4f', delta_time)

            if error > 0 and error_slope_per_hour >= Decimal(
                    '-0.05') or error < 0 and error_slope_per_hour <= 0:
                integral_update_value = self.ki * error * delta_time / Decimal(
                    3600)
                logger.info('Updating integral with %.4f',
                            integral_update_value)
                self.integral += integral_update_value
            else:
                logger.info('Not updating integral')

        self.current_time = new_time

        if self.integral > self.i_high_limit:
            self.integral = self.i_high_limit
            logger.debug('controller integral high limit %.4f',
                         self.i_high_limit)
        elif self.integral < self.i_low_limit:
            self.set_integral_to_lower_limit()

        i_term = self.integral

        d_term = self.kd * error_slope_per_hour

        logger.debug('controller p_term %.4f', p_term)
        logger.debug('controller i_term %.4f', i_term)
        logger.debug('controller d_term %.4f', d_term)
        past_errors_for_log = [(decimal_round(p[0]), decimal_round(p[1]))
                               for p in self.past_errors]
        logger.debug('controller past errors %s', past_errors_for_log)

        output = p_term + i_term + d_term

        logger.debug('controller output %.4f', output)
        return output, self.log(error, p_term, i_term, d_term,
                                error_slope_per_hour, self.i_low_limit,
                                self.i_high_limit, output)