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
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)
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)
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")
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")
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)
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)
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)
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)
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)
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)
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, )