Exemplo n.º 1
0
def psychrochart_config():
    if request.method == 'GET':
        if not has_var(redis, 'chart_style'):
            task = celery.send_task(TASK_CLEAN_CACHE_DATA)
            return json_error(404000,
                              error_msg=f"No chart config available!, "
                              f"resetting all (task: {task})")
        styles = get_var(redis, 'chart_style')
        styles['zones'] = get_var(redis, 'chart_zones')['zones']
        return json_response(styles)
    elif isinstance(request.json, dict) and request.json:
        new_data = request.json
        logging.warning(f"Set new chart style: {new_data}")

        styles = get_var(redis, 'chart_style')
        zones = get_var(redis, 'chart_zones')

        _update_dict(styles, new_data, CHART_STYLE_KEYS)
        _update_dict(zones, new_data, CHART_STYLE_KEYS)

        set_var(redis, 'chart_style', styles)
        set_var(redis, 'chart_zones', zones)
        set_var(redis, 'chart_config_changed', True)

        logging.debug('Make psychrochart now!')
        celery.send_task(TASK_CREATE_PSYCHROCHART)
        styles['zones'] = get_var(redis, 'chart_zones')['zones']
        return json_response({"new_config": new_data, "result": styles})
    return json_error(400,
                      error_msg="Bad request! json: %s; args: %s",
                      msg_args=[request.json, request.args])
Exemplo n.º 2
0
def get_homeassistant_sensors_evolution():
    ha_evolution = get_var(redis, 'ha_evolution')
    if ha_evolution:
        # Without response schema (direct use with HA REST sensor)
        return jsonify(ha_evolution)
    # Do something!
    return json_error(500002, error_msg="No history data available!")
Exemplo n.º 3
0
def homeassistant_states():
    if not has_var(redis, 'ha_states'):
        return json_error(404002,
                          error_msg="No Home Assistant states available!")

    ha_states = get_var(redis, 'ha_states', unpickle_object=True)
    for s in ha_states:
        ha_states[s]['last_updated'] = ha_states[s]['last_updated'].isoformat()
        ha_states[s]['last_changed'] = ha_states[s]['last_changed'].isoformat()
    return json_response(ha_states)
Exemplo n.º 4
0
def homeassistant_config():
    if request.method == 'GET':
        if not has_var(redis, 'ha_yaml_config'):
            return json_error(404001,
                              error_msg="No Home Assistant config available!, "
                              "please POST one")

        return json_response(get_var(redis, 'ha_yaml_config'))
    elif isinstance(request.json, dict) and request.json:
        new_data = request.json
        logging.warning(f"Set new HA config: {new_data}")
        ha_config = get_var(redis, 'ha_yaml_config')
        _update_dict(ha_config, new_data, HA_CONFIG_KEYS)
        set_var(redis, 'ha_yaml_changed', True)
        set_var(redis, 'ha_yaml_config', ha_config)
        celery.send_task(TASK_RELOAD_HA_CONFIG)
        return json_response(ha_config)
    return json_error(400,
                      error_msg="Bad request! json: %s; args: %s",
                      msg_args=[request.json, request.args])
Exemplo n.º 5
0
def get_ha_states(redis):
    api = get_var(redis, 'ha_api', unpickle_object=True)
    if not api:
        logging.error(f"No HA API loaded, aborting get_states")
        if has_var(redis, 'ha_states'):
            remove_var(redis, 'ha_states')
        return {}

    sensors = get_var(redis, 'ha_sensors')
    logging.debug(f"Sensors: {sensors}")
    entities = []
    if "pressure_sensor" in sensors:
        entities.append(sensors["pressure_sensor"])
    # if "sun" in sensors:
    #     entities.append(sensors["sun"])

    if "interior" in sensors:
        entities += [
            s for sensor in sensors["interior"].values()
            for k, s in sensor.items() if k in ['temperature', 'humidity']
        ]
    if "exterior" in sensors:
        entities += [
            s for sensor in sensors["exterior"].values()
            for k, s in sensor.items() if k in ['temperature', 'humidity']
        ]
    # print(entities)
    try:
        states = {
            s.entity_id: s.as_dict()
            for s in filter(lambda x: x.entity_id in entities, get_states(api))
        }
        set_var(redis, 'ha_states', states, pickle_object=True)
    except (ReadTimeoutError, ConnectionRefusedError, HomeAssistantError):
        states = {}

    return states
Exemplo n.º 6
0
def periodic_get_ha_states():
    """Background task to update the HA sensors states."""
    making_chart_now = get_var(redis, 'making_chart_now', default=0)
    if making_chart_now:
        logging.warning('last periodic_get_ha_states is not finished. '
                        'Aborting this try...')
        return

    set_var(redis, 'making_chart_now', 1)
    _log_task_init("periodic_get_ha_states")

    _load_homeassistant_config()
    if not has_var(redis, 'ha_api'):
        get_ha_api(redis)
    logging.debug('loading states...')
    states = get_ha_states(redis)
    if not states:
        logging.error(f"Can't load HA states!")
        set_var(redis, 'making_chart_now', 0)
        return

    logging.debug('making points...')
    make_points_from_states(redis, states)
    logging.debug('making chart...')
    ok = make_psychrochart(redis)
    logging.debug('chart DONE')
    set_var(redis, 'making_chart_now', 0)

    if ok and get_var(redis, 'ha_yaml_changed'):
        # HA Configuration changed, and the result is OK, saving it now
        logging.warning('Saving HA config to disk '
                        '(after producing successfully one chart)')
        save_homeassistant_config(get_var(redis, 'ha_yaml_config'))
        remove_var(redis, 'ha_yaml_changed')
        remove_var(redis, 'ha_yaml_config')
        _load_homeassistant_config()

    if ok and get_var(redis, 'chart_config_changed'):
        # HA Configuration changed, and the result is OK, saving it now
        logging.warning('Saving PsychroChart config to disk '
                        '(after producing successfully one chart)')
        save_chart_style(get_var(redis, 'chart_style'))
        save_chart_zones(get_var(redis, 'chart_zones'))
        remove_var(redis, 'chart_config_changed')
        remove_var(redis, 'chart_style')
        remove_var(redis, 'chart_zones')
        _load_chart_config()

    return True
Exemplo n.º 7
0
    def init_chart_config(sender, **kwargs):
        # from psychrochartmaker import TASK_PERIODIC_GET_HA_STATES
        from psychrochartmaker import TASK_CLEAN_CACHE_DATA
        from psychrochartmaker.tasks import periodic_get_ha_states

        logging.warning(f"On INIT_CHART_CONFIG")
        task = celery.send_task(TASK_CLEAN_CACHE_DATA)
        task.get()

        # Program HA polling schedule
        ha_history = get_var(redis, 'ha_history')
        scheduler = sender.add_periodic_task(ha_history['scan_interval'],
                                             periodic_get_ha_states.s(),
                                             name='HA sensor update')
        logging.info(f'DEBUG scheduler: {scheduler}')
        set_var(redis, 'scheduler', scheduler)

        # Make first psychrochart
        celery.send_task('create_psychrochart')
        return True
Exemplo n.º 8
0
def get_ha_api(redis):
    ha_config = get_var(redis, 'ha_config')
    logging.debug(f"HA API config: {ha_config}")

    # Get HA API
    api_params = dict(host=ha_config.get('host', '127.0.0.1'),
                      api_password=ha_config.get('api_password', None),
                      port=ha_config.get('port', 8123),
                      use_ssl=ha_config.get('use_ssl', False))
    try:
        api = API(**api_params)
        try:
            assert api.validate_api(force_validate=True)
            set_var(redis, 'ha_api', api, pickle_object=True)
        except AssertionError:
            logging.error(f"No HA API found. Removing config from cache")
            if has_var(redis, 'ha_api'):
                remove_var(redis, 'ha_api')
    except (HomeAssistantError, ConnectionError, NewConnectionError,
            MaxRetryError) as exc:
        logging.error(f"{exc.__class__}: {str(exc)}")
        return
Exemplo n.º 9
0
def get_svg_chart():
    svg = get_var(redis, 'svg_chart')
    if svg:
        return image_response(svg, image_type='svg')
    # Do something!
    return json_error(500001, error_msg="No SVG image available!")
Exemplo n.º 10
0
def make_psychrochart(redis,
                      altitude=None,
                      pressure_kpa=None,
                      points=None,
                      connectors=None,
                      arrows=None,
                      interior_zones=None):
    """Create the PsychroChart SVG file and save it to disk."""
    # Load chart style:
    chart_style = load_config(get_var(redis, 'chart_style'))
    zones = get_var(redis, 'chart_zones')

    if altitude is None:  # Try redis key
        altitude = get_var(redis, 'altitude')
    if pressure_kpa is None:  # Try redis key
        pressure_kpa = get_var(redis, 'pressure_kpa')
    if points is None:  # Try redis key
        points = get_var(redis, 'last_points', default={})
    if arrows is None:  # Try redis key
        arrows = get_var(redis, 'arrows')
    if interior_zones is None:  # Try redis key
        interior_zones = get_var(redis, 'interior_zones')

    p_label = ''
    if pressure_kpa is not None:
        chart_style['limits']['pressure_kpa'] = pressure_kpa
        p_label = 'P={:.1f} mb '.format(pressure_kpa * 10)
        chart_style['limits'].pop('altitude_m', None)
        logging.debug(f"using pressure: {pressure_kpa}")
    elif altitude is not None:
        chart_style['limits']['altitude_m'] = altitude
        p_label = 'H={:.0f} m '.format(altitude)

    # Make chart
    # chart = PsychroChart(chart_style, zones, logger=app.logger)
    chart = PsychroChart(chart_style, zones)

    # Append lines
    t_min, t_opt, t_max = 16, 23, 30
    chart.plot_vertical_dry_bulb_temp_line(t_min, {
        "color": [0.0, 0.125, 0.376],
        "lw": 2,
        "ls": ':'
    },
                                           ' TOO COLD, {:g}°C'.format(t_min),
                                           ha='left',
                                           loc=0.,
                                           fontsize=14)
    chart.plot_vertical_dry_bulb_temp_line(t_opt, {
        "color": [0.475, 0.612, 0.075],
        "lw": 2,
        "ls": ':'
    })
    chart.plot_vertical_dry_bulb_temp_line(t_max, {
        "color": [1.0, 0.0, 0.247],
        "lw": 2,
        "ls": ':'
    },
                                           'TOO HOT, {:g}°C '.format(t_max),
                                           ha='right',
                                           loc=1,
                                           reverse=True,
                                           fontsize=14)

    # Append pressure / altitude label
    if p_label:
        chart.axes.annotate(p_label, (1, 0),
                            xycoords='axes fraction',
                            ha='right',
                            va='bottom',
                            fontsize=15,
                            color='darkviolet')

    if arrows:
        chart.plot_arrows_dbt_rh(arrows)
        # Append history label
        points_dq = get_var(redis,
                            'deque_points',
                            default=[],
                            unpickle_object=True)
        if len(points_dq) > 2:
            start = list(points_dq[0].values())[0]
            end = list(points_dq[-1].values())[0]
            delta = (dt.datetime.fromtimestamp(end['ts']) -
                     dt.datetime.fromtimestamp(start['ts'])).total_seconds()
            # delta = history_config['delta_arrows']
            chart.axes.annotate('∆T:{:.1f}h'.format(delta / 3600.), (0, 0),
                                xycoords='axes fraction',
                                ha='left',
                                va='bottom',
                                fontsize=10,
                                color='darkgrey')

    if points:
        chart.plot_points_dbt_rh(points,
                                 connectors,
                                 convex_groups=interior_zones)

    chart.plot_legend(frameon=False,
                      fontsize=15,
                      labelspacing=.8,
                      markerscale=.8)

    bytes_svg = BytesIO()
    chart.save(bytes_svg, format='svg')
    bytes_svg.seek(0)
    set_var(redis, 'svg_chart', bytes_svg.read())
    set_var(redis, 'chart_axes', chart.axes, pickle_object=True)

    chart.remove_annotations()
    set_var(redis, 'chart', chart, pickle_object=True)

    return True
Exemplo n.º 11
0
def _load_homeassistant_config():
    yaml_config = get_var(redis, 'ha_yaml_config')
    if yaml_config is None:
        yaml_config = load_homeassistant_config()
    parse_config_ha(redis, yaml_config)
Exemplo n.º 12
0
def make_points_from_states(redis, states):
    # Make points
    sensors = get_var(redis, 'ha_sensors')
    points = get_var(redis, 'last_points', default={})
    points_unknown = get_var(redis, 'points_unknown', default=[])

    for sensor_group in sensors.values():
        if isinstance(sensor_group, str):
            try:
                set_var(redis, 'pressure_kpa',
                        _mb2kpa(float(states[sensor_group]['state'])))
            except ValueError:
                logging.error(f"Bad pressure read from {sensor_group}")
                # pass
            continue
        for key, p_config in sensor_group.items():
            try:
                points.update({
                    key: {
                        'xy': (float(states[p_config['temperature']]['state']),
                               float(states[p_config['humidity']]['state'])),
                        'style': {
                            'marker': 'o',
                            **p_config['style']
                        },
                        'ts': (states[p_config['humidity']]
                               ['last_updated'].timestamp()),
                        'label':
                        key
                    }
                })
                if key in points_unknown:
                    points_unknown.remove(key)
            except KeyError:
                logging.error(
                    f"KeyError with {key} [sensor_group: {sensor_group}]")
                points_unknown.append(key)
            except ValueError:
                logging.warning(
                    f"ERROR with {key} sensor [state: "
                    f"{states[p_config['temperature']]['state']}ºC, "
                    f"{states[p_config['humidity']]['state']}%]")
                points_unknown.append(key)
    set_var(redis, 'last_points', points)
    set_var(redis, 'points_unknown', points_unknown)

    # Make arrows
    history_config = get_var(redis, 'ha_history', default={})
    if 'delta_arrows' not in history_config or \
            not history_config['delta_arrows']:
        return

    delta_arrows = history_config['delta_arrows']
    scan_interval = history_config['scan_interval']
    len_deque = max(3, int(delta_arrows / scan_interval))
    points_dq = get_var(redis,
                        'deque_points',
                        default=deque([], maxlen=len_deque),
                        unpickle_object=True)
    points_dq.append(points)
    set_var(redis, 'deque_points', points_dq, pickle_object=True)

    num_points_dq = len(points_dq)
    if num_points_dq > 1:
        # arrows = {k: [p['xy'], points_dq[0][k]['xy']]
        #           for k, p in points.items() if k in points_dq[0]
        #           and p != points_dq[0][k]}
        arrows = {
            k: {
                'xy': [p['xy'], points_dq[0][k]['xy']],
                'style': _arrow_style(p['style'])
            }
            for k, p in points.items()
            if k in points_dq[0] and p != points_dq[0][k]
        }
        # logging.info('MAKE ARROWS: %s', arrows)
        set_var(redis, 'arrows', arrows)

    # Make evolution JSON endpoint with history
    if num_points_dq > 3:
        ev_data = {
            "num_points": num_points_dq,
            "pressure_kPa": get_var(redis, 'pressure_kpa')
        }

        start_p = points_dq[0]
        mid_p = points_dq[num_points_dq // 2 - 1]
        end_p = points_dq[-1]
        ev_data.update({
            key: _make_ev_data(start_p.get(key), mid_p.get(key), point)
            for key, point in end_p.items()
        })
        logging.debug(f"EVOLUTION_DATA: {ev_data}")
        set_var(redis, 'ha_evolution', ev_data)