예제 #1
0
def process_data_payload(data: dict) -> dict:
    """Process incoming data from an Ecowitt device."""
    for ignore_key in ("dateutc", "freq", "model", "stationtype"):
        data.pop(ignore_key, None)

    humidity = int(data[DATA_POINT_HUMIDITY])
    temperature = meteocalc.Temp(data[DATA_POINT_TEMPF], "f")
    wind_speed = float(data[DATA_POINT_WINDSPEEDMPH])

    dew_point = meteocalc.dew_point(temperature, humidity)
    data[DATA_POINT_DEWPOINT] = dew_point.f

    heat_index = meteocalc.heat_index(temperature, humidity)
    data[DATA_POINT_HEATINDEX] = heat_index.f

    try:
        wind_chill = meteocalc.wind_chill(temperature, wind_speed)
    except ValueError as err:
        LOGGER.debug(
            "%s (temperature: %s, wind speed: %s)",
            err,
            temperature.f,
            wind_speed,
        )
    else:
        data[DATA_POINT_WINDCHILL] = wind_chill.f

    feels_like = meteocalc.feels_like(temperature, humidity, wind_speed)
    data[DATA_POINT_FEELSLIKEF] = feels_like.f

    return data
예제 #2
0
파일: hass.py 프로젝트: bachya/ecowitt2mqtt
def get_entity_description(key: str, value: Union[float, int,
                                                  str]) -> EntityDescription:
    """Get an entity description for a data key.

    1. Return a specific data point if it exists.
    2. Return a globbed data point if it exists.
    3. Return defaults if no specific or globbed data points exist.
    """
    if DATA_POINT_GLOB_BATT in key and isinstance(value, str):
        # Because Ecowitt doesn't give us a clear way to know what sort of battery
        # we're looking at (a binary on/off battery or one that reports voltage), we
        # check its value: strings are binary, floats are voltage:
        return ENTITY_DESCRIPTIONS[DATA_POINT_GLOB_BATT_BINARY]

    if key in ENTITY_DESCRIPTIONS:
        return ENTITY_DESCRIPTIONS[key]

    globbed_descriptions = [
        v for k, v in ENTITY_DESCRIPTIONS.items() if k in key
    ]
    if globbed_descriptions:
        return globbed_descriptions[0]

    LOGGER.info("No entity description found for key: %s", key)
    return EntityDescription(platform=PLATFORM_SENSOR)
예제 #3
0
def main():
    """Run."""
    args = get_arguments()
    logging.basicConfig(level=getattr(logging, args.log_level))

    LOGGER.debug("Using arguments: %s", args)

    app = web.Application()
    app.add_routes([web.post(args.endpoint, async_respond_to_ecowitt_data)])

    app["args"] = args
    app["hass_discovery_managers"] = {}
    mqtt = app["mqtt"] = MQTT(
        args.mqtt_broker,
        port=args.mqtt_port,
        username=args.mqtt_username,
        password=args.mqtt_password,
    )

    async def connect_mqtt(_) -> None:
        """Connect the MQTT broker."""
        await mqtt.async_connect()

    async def disconnect_mqtt(_) -> None:
        """Disconnect the MQTT broker."""
        await mqtt.async_disconnect()

    app.on_startup.append(connect_mqtt)
    app.on_cleanup.append(disconnect_mqtt)

    web.run_app(app, port=args.port)
예제 #4
0
    async def async_disconnect(self) -> None:
        """Disconnect from the MQTT broker."""
        try:
            await self._client.disconnect()
        except MqttError as err:
            LOGGER.error("Error while disconnecting from MQTT broker: %s", err)

        LOGGER.debug("Disconnected from MQTT broker")
예제 #5
0
    async def async_connect(self) -> None:
        """Connect to the MQTT broker."""
        try:
            await self._client.connect()
        except MqttError as err:
            LOGGER.error("Error while connecting to MQTT broker: %s", err)

        LOGGER.debug("Connected to MQTT broker")
예제 #6
0
def main():
    """Run."""
    args = get_arguments()
    logging.basicConfig(level=getattr(logging, args.log_level))

    LOGGER.debug("Using arguments: %s", args)

    app = web.Application()
    app.add_routes([web.post(args.endpoint, async_publish_payload)])

    app["args"] = args
    app["hass_discovery_managers"] = {}

    web.run_app(app, port=args.port)
예제 #7
0
    async def async_publish(self, topic: str, data: Union[dict, float,
                                                          str]) -> None:
        """Publish data to an MQTT topic."""
        if isinstance(data, dict):
            payload = json.dumps(data).encode("utf-8")
        elif isinstance(data, str):
            payload = data.encode("utf-8")
        else:
            payload = str(data).encode("utf-8")

        try:
            await self._client.publish(topic, payload)
            LOGGER.info("Data published to topic %s: %s", topic, data)
        except MqttError as err:
            LOGGER.error("Error while publishing data to MQTT: %s", err)
예제 #8
0
async def async_publish_payload(request: web.Request):
    """Define the endpoint for the Ecowitt device to post data to."""
    args = request.app["args"]

    payload = dict(await request.post())
    LOGGER.debug("Received data from Ecowitt device: %s", payload)

    data_processor = DataProcessor(payload, args.unit_system)
    data = data_processor.generate_data()

    client = Client(
        args.mqtt_broker,
        port=args.mqtt_port,
        username=args.mqtt_username,
        password=args.mqtt_password,
        logger=LOGGER,
    )

    if args.hass_discovery:
        discovery_managers = request.app["hass_discovery_managers"]

        try:
            discovery_manager = discovery_managers[data_processor.unique_id]
        except KeyError:
            if args.hass_discovery_prefix:
                discovery_manager = discovery_managers[
                    data_processor.unique_id] = HassDiscovery(
                        data_processor.unique_id,
                        args.unit_system,
                        discovery_prefix=args.hass_discovery_prefix,
                    )
            else:
                discovery_manager = discovery_managers[
                    data_processor.unique_id] = HassDiscovery(
                        data_processor.unique_id, args.unit_system)

        await _async_publish_to_hass_discovery(client, data, discovery_manager)
    else:
        if args.mqtt_topic:
            topic = args.mqtt_topic
        else:
            topic = f"ecowitt2mqtt/{data_processor.unique_id}"

        await _async_publish_to_topic(client, data, topic=topic)
예제 #9
0
def calculate_wind_chill(
        temperature: float,
        wind_speed: float,
        *,
        input_unit_system: str = UNIT_SYSTEM_IMPERIAL,
        output_unit_system: str = UNIT_SYSTEM_IMPERIAL) -> Optional[float]:
    """Calculate wind chill in the appropriate unit system."""
    temp_obj = _get_temperature_object(temperature, input_unit_system)

    try:
        wind_chill_obj = meteocalc.wind_chill(temp_obj, wind_speed)
    except ValueError as err:
        LOGGER.debug("%s (temperature: %s, wind speed: %s)", err, temp_obj,
                     wind_speed)
        return None

    if output_unit_system == UNIT_SYSTEM_IMPERIAL:
        value = round(wind_chill_obj.f, 1)
    else:
        value = round(wind_chill_obj.c, 1)
    return cast(float, value)
예제 #10
0
async def _async_publish_to_hass_discovery(
        client: Client, data: dict, discovery_manager: HassDiscovery) -> None:
    """Publish data to appropriate topics for Home Assistant Discovery."""
    LOGGER.debug(
        "Publishing according to Home Assistant MQTT Discovery standard")

    try:
        async with client:
            tasks = []
            for key, value in data.items():
                config_payload = discovery_manager.get_config_payload(key)
                config_topic = discovery_manager.get_config_topic(key)

                tasks.append(
                    client.publish(config_topic,
                                   _generate_payload(config_payload)))
                tasks.append(
                    client.publish(
                        config_payload["availability_topic"],
                        _generate_payload("online"),
                    ))
                tasks.append(
                    client.publish(config_payload["state_topic"],
                                   _generate_payload(value)))

            await asyncio.gather(*tasks)
    except MqttError as err:
        LOGGER.error("Error while publishing to HASS Discovery: %s", err)
        return

    LOGGER.info("Published to HASS discovery: %s", data)
예제 #11
0
def get_device_from_raw_payload(payload: Dict[str, Any]) -> Device:
    """Return a device based upon a model string."""
    model = payload["model"]
    station_type = payload.get("stationtype", DEFAULT_STATION_TYPE)
    unique_id = payload.get("PASSKEY", DEFAULT_UNIQUE_ID)

    if model in DEVICE_DATA:
        manufacturer, name = DEVICE_DATA[model]
    else:
        matches = [v for k, v in DEVICE_DATA.items() if k in model]
        if matches:
            manufacturer, name = matches[0]
        else:
            LOGGER.info(
                ("Unknown device; please report it at "
                 "https://github.com/bachya/ecowitt2mqtt (payload: %s)"),
                payload,
            )
            manufacturer = DEFAULT_MANUFACTURER
            name = DEFAULT_NAME

    return Device(unique_id, manufacturer, name, station_type)
예제 #12
0
async def async_publish_payload(request: web.Request) -> None:
    """Define the endpoint for the Ecowitt device to post data to."""
    args = request.app["args"]

    payload = dict(await request.post())
    LOGGER.debug("Received data from Ecowitt device: %s", payload)

    data_processor = DataProcessor(payload, args)
    data = data_processor.generate_data()

    client = Client(
        args.mqtt_broker,
        port=args.mqtt_port,
        username=args.mqtt_username,
        password=args.mqtt_password,
        logger=LOGGER,
        max_concurrent_outgoing_calls=DEFAULT_MAX_MQTT_CALLS,
    )

    if args.hass_discovery:
        discovery_managers = request.app["hass_discovery_managers"]

        if data_processor.device.unique_id in discovery_managers:
            discovery_manager = discovery_managers[data_processor.device.unique_id]
        else:
            discovery_manager = HassDiscovery(data_processor.device, args)
            discovery_managers[data_processor.device.unique_id] = discovery_manager

        await _async_publish_to_hass_discovery(client, data, discovery_manager)
    else:
        if args.mqtt_topic:
            topic = args.mqtt_topic
        else:
            topic = f"ecowitt2mqtt/{data_processor.device.unique_id}"

        await _async_publish_to_topic(client, data, topic)
예제 #13
0
async def _async_publish_to_topic(
    client: Client, data: Dict[str, Any], topic: str
) -> None:
    """Publish data to a single MQTT topic."""
    LOGGER.debug("Publishing entire device payload to single topic: %s", topic)

    try:
        async with client:
            await client.publish(topic, _generate_payload(data))
    except MqttError as err:
        LOGGER.error("Error while publishing to %s: %s", topic, err)
        return

    LOGGER.info("Published to %s: %s", topic, data)
예제 #14
0
async def async_respond_to_ecowitt_data(request: web.Request):
    """Define the endpoint for the Ecowitt device to post data to."""
    args = request.app["args"]
    hass_discovery_managers = request.app["hass_discovery_managers"]
    mqtt = request.app["mqtt"]

    data = dict(await request.post())

    LOGGER.debug("Received data from Ecowitt device: %s", data)

    data = process_data_payload(data)

    unique_id = data.pop("PASSKEY")

    if not request.app["args"].hass_discovery:
        LOGGER.debug("Publishing entire device payload to single topic")

        if request.app["args"].mqtt_topic:
            topic = request.app["args"].mqtt_topic
        else:
            topic = f"ecowitt2mqtt/{unique_id}"

        return await mqtt.async_publish(topic, data)

    LOGGER.debug(
        "Publishing according to Home Assistant MQTT Discovery standard")

    try:
        discovery = hass_discovery_managers[unique_id]
    except KeyError:
        if args.hass_discovery_prefix:
            discovery = hass_discovery_managers[unique_id] = HassDiscovery(
                unique_id, discovery_prefix=args.hass_discovery_prefix)
        else:
            discovery = hass_discovery_managers[unique_id] = HassDiscovery(
                unique_id)

    tasks = []
    for key, value in data.items():
        config_payload = discovery.get_config_payload(key)
        config_topic = discovery.get_config_topic(key)
        tasks.append(mqtt.async_publish(config_topic, config_payload))
        tasks.append(
            mqtt.async_publish(config_payload["availability_topic"], "online"))
        tasks.append(mqtt.async_publish(config_payload["state_topic"], value))

    await asyncio.gather(*tasks)
예제 #15
0
    def __post_init__(self):
        """Set up some additional attributes from passed-in data."""
        object.__setattr__(self, "unique_id",
                           self._input.pop("PASSKEY", DEFAULT_UNIQUE_ID))

        # Only store data keys that we explicitly care about:
        object.__setattr__(
            self,
            "_data",
            {k: v
             for k, v in self._input.items() if k not in KEYS_TO_IGNORE},
        )

        # Determine properties necessary for the calculated properties:
        if DATA_POINT_TEMPF in self._data:
            object.__setattr__(
                self,
                "_temperature_obj",
                meteocalc.Temp(self._data[DATA_POINT_TEMPF], "f"),
            )
        if DATA_POINT_HUMIDITY in self._data:
            object.__setattr__(
                self, "_humidity",
                round(float(self._data[DATA_POINT_HUMIDITY]), 1))
        if DATA_POINT_WINDSPEEDMPH in self._data:
            object.__setattr__(
                self, "_wind_speed",
                round(float(self._data[DATA_POINT_WINDSPEEDMPH])))

        # Determine calculated properties:
        if self._temperature_obj and self._humidity:
            object.__setattr__(
                self,
                "_dew_point_obj",
                meteocalc.dew_point(self._temperature_obj, self._humidity),
            )
            object.__setattr__(
                self,
                "_heat_index_obj",
                meteocalc.heat_index(self._temperature_obj, self._humidity),
            )
        if self._temperature_obj and self._wind_speed:
            if self._humidity:
                object.__setattr__(
                    self,
                    "_feels_like_obj",
                    meteocalc.feels_like(self._temperature_obj, self._humidity,
                                         self._wind_speed),
                )

            try:
                object.__setattr__(
                    self,
                    "_wind_chill_obj",
                    meteocalc.wind_chill(self._temperature_obj,
                                         self._wind_speed),
                )
            except ValueError as err:
                LOGGER.debug(
                    "%s (temperature: %s, wind speed: %s)",
                    err,
                    self._temperature_obj,
                    self._wind_speed,
                )