def test_translation(): fx = translation.translator_fx('de') assert fx('Fubar') == 'Fubar' assert fx('Error') == 'Fehler' fx = translation.translator_fx('en') assert fx('Fubar') == 'Fubar' assert fx('Error') == 'Error'
def service_log(num_lines: int, language: str = None) -> tuple: """Retrieves the service and system log from the web service.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) params = {'num-lines': num_lines} # Query heating service log rsp = _get_json(svc_urls.service_log, params=params, language=language) log_heating = None if rsp is None: log_heating = _('<b>Error:</b> Received no response from web service.') elif not rsp.success: log_heating = _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: log_heating = escape_html_entities(rsp.log) # Query system script log rsp = _get_json(svc_urls.ragnaroek_log, params=params, language=language) log_ragnaroek = None if rsp is None: log_ragnaroek = _( '<b>Error:</b> Received no response from web service.') elif not rsp.success: log_ragnaroek = _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: log_ragnaroek = escape_html_entities(rsp.log) return log_heating, log_ragnaroek
def query_details(language: str) -> str: """Retrieves the detailed heating system status from the web service.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) rsp = _get_json(svc_urls.status_details, language=language) txt = format_central_heating_status(rsp, language, True, False) txt += format_system_details(rsp, language) return txt
def reload_sensors(language: str) -> str: """Reload the ZigBee sensors via the web service.""" rsp = _get_json(svc_urls.temperature_reload_sensors, language=language) _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: return rsp.message
def toggle_pause(language: str) -> str: """Toggle heating program pause.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) rsp = _get_json(svc_urls.heating_jobs_pause, language=language) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: return rsp.message
def ping(language: str) -> str: """Pings the web service (to check if it's available and responsive).""" rsp = _get_json(svc_urls.service_ping) _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: return rsp.message
def format_weather_forecast(report: object, language: str) -> str: """Returns the current weather report and forecast.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) if report is None: return _('<b>Error:</b> Received no response from weather service.') else: # Current weather report/status txt = _('<b>Weather report:</b>') w = report.current # Alias txt += '\n\u2022 {:s}, {:s} {:s}, {:.1f}\u200a°C {:s}'.format( time_utils.format_time(w.reference_time.timetz()), w.detailed_status, weather_code_emoji(w.code, w.reference_time.timetz()), w.temperature_felt_or_raw, temperature_emoji(w.temperature_felt_or_raw)) if w.rain > 0: txt += '\n\u2022 ' + _('Rain:') + f' {w.rain:.1f}\u200amm' if w.snow > 0: txt += '\n\u2022 ' + _('Snow:') + f' {w.snow:.1f}\u200amm' if w.clouds > 0: txt += '\n\u2022 ' + _('Sky cover:') + f' {w.clouds}\u200a%' txt += '\n\u2022 ' + _('Humidity:') + f' {w.humidity}\u200a%' txt += '\n\u2022 ' + _( 'Atmospheric pressure:') + f' {w.atmospheric_pressure}\u200ahPa' if w.wind_speed > 0: txt += '\n\u2022 ' + _( 'Wind:') + f' {w.wind_speed:.1f}\u200akm/h ' + _( 'from') + f' {w.wind_direction}' txt += '\n\u2022 ' + _('Sunrise:') + ' ' + time_utils.format_time( w.sunrise_time) txt += '\n\u2022 ' + _('Sunset:') + ' ' + time_utils.format_time( w.sunset_time) if w.uv_index is not None and w.uv_index >= 1: txt += '\n\u2022 ' + _('UV index:') + f' {int(w.uv_index)} :sunny:' # Forecast txt += '\n\n' + _('<b>Forecast:</b>') tomorrow = time_utils.tomorrow_local() for idx in range(min(len(report.forecast), 25)): w = report.forecast[idx] # Alias if w.uv_index is not None and w.uv_index >= 1: uvi = f', :sunny: UVI: {int(w.uv_index)}' else: uvi = '' ref_time = time_utils.dt_as_local(w.reference_time) dstr = _('Tomorrow') if ref_time.date() == tomorrow else _('Today') txt += '\n\u2022 {:s} {:s}, {:s} {:s}, {:.1f}\u200a°C{:s}'.format( dstr, time_utils.format_time(ref_time.timetz()), w.detailed_status, weather_code_emoji(w.code, ref_time.timetz()), w.temperature_felt_or_raw, uvi) return txt
def turn_district_heating_on(inlet_temp: int, language: str) -> str: """Adjust the district heating system's inlet/supply temperature.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) rsp = _get_json(svc_urls.district_heating_turn_on, params={'inlet': inlet_temp}, language=language) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: return rsp.message + '\n\n' + _( 'Query its status via /district_heating')
def turn_heater_on(user: str, duration: datetime.timedelta, language: str) -> str: """Turn on heater via web service.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) params = {'user': user} if duration is not None: params['duration'] = time_utils.timedelta_hms_str(duration) rsp = _get_json(svc_urls.heating_turn_on, params=params, language=language) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: return rsp.message + '\n\n' + _('Query current /status')
def format_broadcast_message(message: dict, language: str) -> str: """Formats the given broadcasted message.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) icon = {'info': ':bulb:', 'warning': ':warning:', 'error': ':bangbang:'} # import json # print(json.dumps(message, indent=2, default=str)) if message['msg_type'] == 'image': return _( 'Bot received an image broadcast, which is not yet implemented!') dt = time_utils.dt_fromstr(message['timestamp']) dtstr = time_utils.format(dt, fmt=_('%%Y-%%m-%%d %%H:%%M')) return _( '<b>Broadcast</b> %(icon)s from %(sender)s, %(time)s:\n%(message)s', sender=message['source'], time='<code>' + dtstr + '</code>', icon=icon[message['msg_type']], message=message['message'])
def turn_heater_off(user: str, language: str) -> str: """ Turn off heater via web service. :user: str :language: str """ _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) rsp = _get_json(svc_urls.heating_turn_off, params={'user': user}, language=language) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: return rsp.message
def poll_broadcasts(language: str) -> str: _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) rsp = _get_json(svc_urls.broadcast_query, params={'receiver': 'bot'}, language=language) if rsp is None: return [ _('<b>Error:</b> Received no response from web service while polling broadcasts.' ) ] elif not rsp.success: return [ _('<b>Error while polling broadcasts:</b> %(message)s', message=escape_html_entities(rsp.message)) ] else: return [ format_broadcast_message(bc, language) for bc in rsp.broadcasts ]
def format_central_heating_status(rsp: object, language: str, detailed: bool, include_commands: bool) -> str: """ Formats the "central_heating_status" response of the web service as included in the /status and /details queries. """ _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: # Central heating overview txt = _('<b>Central heating</b> is on :thermometer:') if rsp.status.central_heating_status.is_heating_task_active else\ _('<b>Central heating</b> is off :snowman:') if rsp.status.central_heating_status.is_heating_task_active: if rsp.status.central_heating_status.residual_heating_time is None: txt += '\n' + _('Heater can only be stopped manually.') else: txt += '\n' + _('Remaining heating time: %(res)s', res=rsp.status.central_heating_status. residual_heating_time) if detailed: for lpddev in rsp.status.central_heating_status.power_socket_states: if lpddev['is_powered_on']: txt += '\n\u2022 ' + _('%(devname)s is on', devname=lpddev['display_name']) else: txt += '\n\u2022 ' + _('%(devname)s is off', devname=lpddev['display_name']) # Include commands to turn heater on/off if include_commands: if rsp.status.central_heating_status.is_heating_task_active: txt += '\n' + _('Turn heater /off ?') else: txt += '\n' + _('Turn heater /on ?') # Are the programs paused? if rsp.status.central_heating_status.is_paused: txt += '\n' + _('Heating programs are paused.') # Should we offer quicklinks to enable/disable heating programs? if include_commands: if rsp.status.central_heating_status.is_paused: txt += '\n' + _('Activate heating programs via /pause ?') else: txt += '\n' + _('Deactivate heating programs via /pause ?') # Temperature sensor overview (might be None if deCONZ gateway was not available) txt += '\n\n' + _('<b>Current temperature:</b>') if rsp.status.temperature_states is None: txt += '\n' + _("<b>Error:</b> Cannot query temperature sensors.") else: for skey in sorted( rsp.status.temperature_states, key=lambda x: rsp.status.temperature_states[x].alias): sstate = rsp.status.temperature_states[skey] txt += f'\n\u2022 {sstate.alias}: <code>{sstate.temperature:.1f}°</code>, <code>{sstate.humidity}%</code>' if sstate.battery_level is not None and ( detailed or sstate.battery_level < 30): txt += _(', %(icon)s Battery', icon=':warning:' if sstate.battery_level < 30 else '') + f' <code>{sstate.battery_level:d}%</code>' if not sstate.is_reachable: txt += _(', :bangbang: disconnected') return txt
def format_district_heating_status(rsp: object, language: str) -> str: """Formats the status query response of our district heating CMI.""" _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) if rsp is None: return _('<b>Error:</b> Received no response from web service.') elif not rsp.success: return _('<b>Error:</b> %(message)s', message=escape_html_entities(rsp.message)) else: dhs = rsp.district_heating_status txt = _('<b>District heating status:</b>') txt += '\n\u2022 ' + _('Energy source:') txt += '\n \u2022 ' + _( 'Solar panels:' ) + f' `{dhs.src_solar_power:.2f}kW`, `{dhs.src_solar_temperature:.1f}°C`' txt += '\n \u2022 ' + _( 'Heat network:' ) + f' `{dhs.src_tele_power:.2f}kW`, `{dhs.src_tele_temperature:.1f}°C`' txt += '\n\u2022 ' + _('Consumption:') if dhs.consumption_state: txt += '\n \u2022 ' + _('Consumption is turned on.') else: txt += '\n \u2022 ' + _('Consumption is turned off.') txt += '\n \u2022 ' + _( 'Central heating:' ) + f' `{dhs.consumption_heating_power:.2f}kW`, `{dhs.consumption_heating_temperature:.1f}°C`' txt += '\n \u2022 ' + _( 'Boiler:' ) + f' `{dhs.consumption_boiler_power:.2f}kW`, `{dhs.consumption_boiler_temperature:.1f}°C`' txt += '\n\u2022 ' + _('Button states:') if dhs.btn_eco_status: txt += '\n \u2022 ' + _('ECO is on.') else: txt += '\n \u2022 ' + _('ECO is off.') if dhs.btn_transition_status: txt += '\n \u2022 ' + _('Transition is on.') else: txt += '\n \u2022 ' + _('Transition is off.') if dhs.btn_medium_status: txt += '\n \u2022 ' + _( 'Medium is on, remaining period: %(rtime)s.', rtime=format_seconds(dhs.btn_medium_time)) else: txt += '\n \u2022 ' + _('Medium is off.') if dhs.btn_high_status: txt += '\n \u2022 ' + _( 'High is on, remaining period: %(rtime)s.', rtime=format_seconds(dhs.btn_high_time)) else: txt += '\n \u2022 ' + _('High is off.') if dhs.btn_very_high_status: txt += '\n \u2022 ' + _( 'Very high is on, remaining period: %(rtime)s.', rtime=format_seconds(dhs.btn_very_high_time)) else: txt += '\n \u2022 ' + _('Very high is off.') txt += '\n\n' + _('You can also adjust the /supply_temperature') return txt
def format_system_details(rsp: object, language: str) -> str: """ To be used in conjunction with format_central_heating_status Thus, it will always return a valid string (which may be empty because the error message would've already been set). """ _ = translation.translator_fx(language, HELHEIMR_BOT_LOCALE_DIR) if rsp is None or not rsp.success: return '' else: # Heating programs txt = '\n\n' + _('<b>Heating programs:</b>') if len(rsp.status.scheduled_jobs.heating_jobs) > 0: for job in rsp.status.scheduled_jobs.heating_jobs: txt += f"\n\u2022 {job['heating_string']}, {job['interval_string']} {job['at_string']}" else: txt += '\n' + _('There are no heating programs.') # Other scheduled tasks txt += '\n\n' + _('<b>Non-heating programs:</b>') if len(rsp.status.scheduled_jobs.non_heating_jobs) > 0: for job in rsp.status.scheduled_jobs.non_heating_jobs: txt += f"\n\u2022 {job['description']}, {job['interval_string']}" +\ _(', next run: ') + job['next_run'] else: txt += '\n' + _('There are no scheduled tasks.') # ZigBee if rsp.status.zigbee_state is None: txt += '\n\n' + _( '<b>Error:</b> Cannot query RaspBee/ZigBee status.') else: txt += '\n\n' + _('<b>RaspBee/ZigBee Gateway:</b>') txt += '\n\u2022 ' + _( 'deCONZ API version: <code>%(version)s</code>', version=rsp.status.zigbee_state.api_version) txt += '\n\u2022 ' + _( 'deCONZ SW version: <code>%(version)s</code>', version=rsp.status.zigbee_state.sw_version) txt += '\n\u2022 ' + _('ZigBee channel: <code>%(channel)d</code>', channel=rsp.status.zigbee_state.channel) # System status txt += '\n\n' + _('<b>System status:</b>') txt += '\n\u2022 ' + _( 'Process ID: ' ) + f"<code>{rsp.status.system_status.process['proc_id']}</code>" txt += '\n\u2022 ' + _( 'Memory usage: ' ) + f"<code>{rsp.status.system_status.process['mem_usage_mb']:.1f}MB</code>" # # CPU txt += '\n\u2022 ' + _( 'CPU load (1/5/15): ' ) + "<code>{}%</code>, <code>{}%</code>, <code>{}%</code>".format( int(rsp.status.system_status.cpu['load_avg_1']), int(rsp.status.system_status.cpu['load_avg_5']), int(rsp.status.system_status.cpu['load_avg_15'])) txt += '\n\u2022 ' + _( 'CPU frequency: ') + "<code>{}</code> (<code>{}-{}</code>)".format( int(rsp.status.system_status.cpu['freq_current']), int(rsp.status.system_status.cpu['freq_min']), int(rsp.status.system_status.cpu['freq_max'])) if rsp.status.system_status.cpu['temperature'] is not None: txt += '\n\u2022 ' + _( 'CPU temperature: ' ) + f" <code>{rsp.status.system_status.cpu['temperature']:.1f}°</code>" # # Storage txt += '\n\u2022 ' + _( 'Database size: ' ) + '<code>' + rsp.status.system_status.database_size.replace( ' ', '') + '</code>' txt += '\n\u2022 ' + _('Disk space:') for storage in rsp.status.system_status.storage: txt += f"\n \u2022 <code>{storage['mount_point']}</code>, <code>{storage['available']}</code> " +\ _('available') + f", <code>{storage['in_use']}%</code> " + _('in use') return txt
def translate(self, txt: str, **kwargs) -> str: _ = translation.translator_fx(self._language, commands.HELHEIMR_BOT_LOCALE_DIR) return _(txt, **kwargs)