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}¶meters=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
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}
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)
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)
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)
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
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}¶meters=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
def add_extra_info(message): logger.info(message) extra_info.append(message)
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)