예제 #1
0
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'
예제 #2
0
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
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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
예제 #6
0
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
예제 #7
0
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
예제 #8
0
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')
예제 #9
0
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')
예제 #10
0
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'])
예제 #11
0
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
예제 #12
0
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
        ]
예제 #13
0
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
예제 #14
0
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
예제 #15
0
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
예제 #16
0
 def translate(self, txt: str, **kwargs) -> str:
     _ = translation.translator_fx(self._language, commands.HELHEIMR_BOT_LOCALE_DIR)
     return _(txt, **kwargs)