示例#1
0
def adjust_target_with_rh(add_extra_info, target_inside_temp, **kwargs):
    dew_point, ts = get_temp([receive_fmi_dew_point], max_ts_diff=6 * 60)
    add_extra_info('Dew point: %s' % decimal_round(dew_point))

    if dew_point is not None:
        min_temp_with_80_rh = estimate_temperature_with_rh(
            dew_point, Decimal('0.8'))
        add_extra_info('Temp with 80%% RH: %s' %
                       decimal_round(min_temp_with_80_rh, 1))

        target_inside_temp = max(target_inside_temp, min_temp_with_80_rh)

    add_extra_info('Target inside temperature: %s' %
                   decimal_round(target_inside_temp, 1))

    return {'target_inside_temp': target_inside_temp}
示例#2
0
def update_controller(add_extra_info, error, error_without_hysteresis, persistent_data, **kwargs):
    controller = persistent_data.get('controller')
    degrees_per_hour_slope = Decimal('0.05')

    lowest_heating_value = Decimal(0) - Decimal('0.01')
    highest_heating_value = Decimal(1) + Decimal('0.01')

    controller.set_i_low_limit(lowest_heating_value - degrees_per_hour_slope * controller.kd)
    controller.set_i_high_limit(highest_heating_value + degrees_per_hour_slope * controller.kd)

    controller_output, controller_log = controller.update(error, error_without_hysteresis)

    add_extra_info('Controller: %s (%s)' % (decimal_round(controller_output, 2), controller_log))

    return {'controller_output': controller_output}
示例#3
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
示例#4
0
def send_to_lambda(target_inside_temp: Decimal, inside_temp: Optional[Decimal], outside_temp_ts: TempTs,
                   persistent_data: Dict, **kwargs):
    data = {
        'sensorId': {'S': 'controller'},
        'ts': {'S': get_now_isoformat()},
        'temperatures': {
            'M': {
                'target_inside': {'S': decimal_round(target_inside_temp, decimals=2)},
                'outside': {'S': outside_temp_ts.temp},
            },
        },
    }

    if inside_temp is not None:
        data['temperatures']['M']['inside'] = {'S': inside_temp}

    last_command = persistent_data.get('last_command')

    if last_command is not None and last_command.temp is not None:
        data['temperatures']['M']['command'] = {'S': last_command.temp}

    post_url(url=config.STORAGE_ROOT_URL + 'addOne', data=data)
示例#5
0
def get_outside(add_extra_info, mean_forecast, **kwargs):
    outside_temp, outside_ts = get_temp([
        receive_ulkoilma_temperature, receive_fmi_temperature,
        receive_open_weather_map_temperature
    ])
    add_extra_info('Outside temperature: %s' % outside_temp)
    if outside_temp is None:
        valid_outside = False
        outside_ts = arrow.now()
        if mean_forecast is not None:
            outside_temp = mean_forecast
            add_extra_info('Using mean forecast as outside temp: %s' %
                           decimal_round(mean_forecast))
        else:
            outside_temp = PREDEFINED_OUTSIDE_TEMP
            add_extra_info('Using predefined outside temperature: %s' %
                           outside_temp)
    else:
        valid_outside = True

    return {
        'outside_temp_ts': TempTs(temp=outside_temp, ts=outside_ts),
        'valid_outside': valid_outside
    }
示例#6
0
def hysteresis(add_extra_info, target_inside_temp, **kwargs):
    hyst = Decimal('0.0')
    add_extra_info('Hysteresis: %s (%s)' % (decimal_round(hyst), decimal_round(target_inside_temp + hyst)))
    return {'hysteresis': hyst}
示例#7
0
def target_inside_temp(add_extra_info,
                       mean_forecast,
                       outside_temp_ts: TempTs,
                       forecast: Union[Forecast, None],
                       persistent_data,
                       **kwargs):

    minimum_inside_temp = persistent_data.get('minimum_inside_temp')
    allowed_min_inside_temp = config.ALLOWED_MINIMUM_INSIDE_TEMP
    cooling_time_buffer = config.COOLING_TIME_BUFFER

    if mean_forecast:
        outside_for_target_calc = TempTs(mean_forecast, arrow.now())
    else:
        outside_for_target_calc = outside_temp_ts

    # print('target_inside_temperature', '-' * 50)

    # from pprint import pprint
    # pprint(forecast)

    cooling_time_buffer_hours = cooling_time_buffer_resolved(cooling_time_buffer, outside_for_target_calc.temp, forecast)

    add_extra_info('Buffer is %s h at %s C' % (
        decimal_round(cooling_time_buffer_hours), decimal_round(outside_for_target_calc.temp)))

    valid_forecast = []

    if outside_for_target_calc:
        valid_forecast.append(outside_for_target_calc)

    if forecast and forecast.temps:
        for f in forecast.temps:
            if f.ts > valid_forecast[-1].ts:
                valid_forecast.append(f)

    # if valid_forecast:
    #     outside_after_forecast = mean(t.temp for t in valid_forecast)
    #     while len(valid_forecast) < config.COOLING_TIME_BUFFER:
    #         valid_forecast.append(TempTs(temp=outside_after_forecast, ts=valid_forecast[-1].ts.shift(hours=1)))

    reversed_forecast = list(reversed(valid_forecast))

    # pprint(reversed_forecast)
    # pprint(reversed_forecast[-1].ts)

    iteration_inside_temp = allowed_min_inside_temp
    iteration_ts = arrow.now().shift(hours=float(cooling_time_buffer_hours))
    # print('iteration_ts', iteration_ts)

    # if reversed_forecast[0].ts < iteration_ts:
    outside_after_forecast = mean(t.temp for t in reversed_forecast)
    # print('outside_after_forecast', outside_after_forecast)
    while iteration_ts > reversed_forecast[0].ts:
        hours_to_forecast_start = Decimal((iteration_ts - reversed_forecast[0].ts).total_seconds() / 3600.0)
        assert hours_to_forecast_start >= 0, hours_to_forecast_start
        this_iteration_hours = min([Decimal(1), hours_to_forecast_start])
        outside_inside_diff = outside_after_forecast - iteration_inside_temp
        temp_drop = config.COOLING_RATE_PER_HOUR_PER_TEMPERATURE_DIFF * outside_inside_diff * this_iteration_hours

        if outside_after_forecast <= -17:
            # When outside temp is about -17 or colder, then the pump heating power will decrease a lot
            logger.debug('Forecast temp <= -17: %.1f' % outside_after_forecast)
            temp_drop *= 2

        iteration_inside_temp -= temp_drop
        iteration_ts = iteration_ts.shift(hours=float(-this_iteration_hours))

        # from pprint import pprint
        # pprint({
        #     'iteration_ts': iteration_ts,
        #     'temp_drop': temp_drop,
        #     'iteration_inside_temp': iteration_inside_temp,
        #     'this_iteration_hours': this_iteration_hours,
        # })
        # print('-' * 50)

        if iteration_inside_temp < allowed_min_inside_temp:
            iteration_inside_temp = allowed_min_inside_temp
            # print('*' * 20)

    # print('-' * 10, 'start forecast', iteration_ts, iteration_inside_temp)

    for fc in filter(lambda x: x.ts <= iteration_ts, reversed_forecast):
        this_iteration_hours = Decimal((iteration_ts - fc.ts).total_seconds() / 3600.0)
        assert this_iteration_hours >= 0, this_iteration_hours
        outside_inside_diff = fc.temp - iteration_inside_temp
        temp_drop = config.COOLING_RATE_PER_HOUR_PER_TEMPERATURE_DIFF * outside_inside_diff * this_iteration_hours
        # if iteration_inside_temp - temp_drop > allowed_min_inside_temp:
        #     iteration_inside_temp -= temp_drop
        # else:
        #     break

        if fc.temp <= -17:
            # When outside temp is about -17 or colder, then the pump heating power will decrease a lot
            logger.debug('Forecast temp <= -17: %.1f' % fc.temp)
            temp_drop *= 2

        iteration_inside_temp -= temp_drop
        iteration_ts = fc.ts

        # from pprint import pprint
        # pprint({
        #     'fc': fc,
        #     'temp_drop': temp_drop,
        #     'iteration_inside_temp': iteration_inside_temp,
        #     'this_iteration_hours': this_iteration_hours,
        # })
        # print('-' * 50)

        if iteration_inside_temp < allowed_min_inside_temp:
            iteration_inside_temp = allowed_min_inside_temp
            # print('!' * 20)
            # assert False, iteration_inside_temp

    # print('iteration_ts', iteration_ts)
    # print('target_inside_temperature', iteration_inside_temp)
    return {'target_inside_temp': max(iteration_inside_temp, minimum_inside_temp)}
示例#8
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)