def _decrypt_payload_helper( key: str | None, ciphertext: str, get_key_bytes: Callable[[str, int], str | bytes], key_encoder, ) -> dict[str, str] | None: """Decrypt encrypted payload.""" try: keylen, decrypt = setup_decrypt(key_encoder) except OSError: _LOGGER.warning( "Ignoring encrypted payload because libsodium not installed") return None if key is None: _LOGGER.warning( "Ignoring encrypted payload because no decryption key known") return None key_bytes = get_key_bytes(key, keylen) msg_bytes = decrypt(ciphertext, key_bytes) message = json_loads(msg_bytes) _LOGGER.debug("Successfully decrypted mobile_app payload") return message
def to_native(self, validate_entity_id: bool = True) -> State | None: """Convert to an HA state object.""" context = Context( id=self.context_id, user_id=self.context_user_id, parent_id=self.context_parent_id, ) try: attrs = json_loads(self.attributes) if self.attributes else {} except JSON_DECODE_EXCEPTIONS: # When json_loads fails _LOGGER.exception("Error converting row to state: %s", self) return None if self.last_changed is None or self.last_changed == self.last_updated: last_changed = last_updated = process_timestamp(self.last_updated) else: last_updated = process_timestamp(self.last_updated) last_changed = process_timestamp(self.last_changed) return State( self.entity_id, self.state, # Join the state_attributes table on attributes_id to get the attributes # for newer states attrs, last_changed, last_updated, context=context, validate_entity_id=validate_entity_id, )
def state_received(msg): """Handle new MQTT messages.""" values = json_loads(msg.payload) if values["state"] == "ON": self._state = True elif values["state"] == "OFF": self._state = False elif values["state"] is None: self._state = None if self._supported_features and SUPPORT_COLOR and "color" in values: if values["color"] is None: self._hs = None else: self._update_color(values) if self._config[CONF_COLOR_MODE] and "color_mode" in values: self._update_color(values) if self._supported_features and SUPPORT_BRIGHTNESS: try: self._brightness = int( values["brightness"] / float(self._config[CONF_BRIGHTNESS_SCALE]) * 255 ) except KeyError: pass except (TypeError, ValueError): _LOGGER.warning("Invalid brightness value received") if ( self._supported_features and SUPPORT_COLOR_TEMP and not self._config[CONF_COLOR_MODE] ): try: if values["color_temp"] is None: self._color_temp = None else: self._color_temp = int(values["color_temp"]) except KeyError: pass except ValueError: _LOGGER.warning("Invalid color temp value received") if self._supported_features and LightEntityFeature.EFFECT: with suppress(KeyError): self._effect = values["effect"] if self._supported_features and SUPPORT_WHITE_VALUE: try: self._white_value = int(values["white_value"]) except KeyError: pass except ValueError: _LOGGER.warning("Invalid white value received") self.async_write_ha_state()
def to_native(self) -> dict[str, Any]: """Convert to an HA state object.""" try: return cast(dict[str, Any], json_loads(self.shared_data)) except JSON_DECODE_EXCEPTIONS: _LOGGER.exception("Error converting row to event data: %s", self) return {}
async def post(self, request, domain, service): """Call a service. Returns a list of changed states. """ hass: ha.HomeAssistant = request.app["hass"] body = await request.text() try: data = json_loads(body) if body else None except ValueError: return self.json_message("Data should be valid JSON.", HTTPStatus.BAD_REQUEST) context = self.context(request) try: await hass.services.async_call(domain, service, data, blocking=True, context=context) except (vol.Invalid, ServiceNotFound) as ex: raise HTTPBadRequest() from ex changed_states = [] for state in hass.states.async_all(): if state.context is context: changed_states.append(state) return self.json(changed_states)
async def post(self, request, event_type): """Fire events.""" if not request["hass_user"].is_admin: raise Unauthorized() body = await request.text() try: event_data = json_loads(body) if body else None except ValueError: return self.json_message("Event data should be valid JSON.", HTTPStatus.BAD_REQUEST) if event_data is not None and not isinstance(event_data, dict): return self.json_message("Event data should be a JSON object", HTTPStatus.BAD_REQUEST) # Special case handling for event STATE_CHANGED # We will try to convert state dicts back to State objects if event_type == ha.EVENT_STATE_CHANGED and event_data: for key in ("old_state", "new_state"): state = ha.State.from_dict(event_data.get(key)) if state: event_data[key] = state request.app["hass"].bus.async_fire(event_type, event_data, ha.EventOrigin.remote, self.context(request)) return self.json_message(f"Event {event_type} fired.")
def to_native(self) -> dict[str, Any]: """Convert to an HA state object.""" try: return cast(dict[str, Any], json_loads(self.shared_attrs)) except JSON_DECODE_EXCEPTIONS: # When json_loads fails _LOGGER.exception("Error converting row to state attributes: %s", self) return {}
def state_message_received(msg): """Handle state MQTT message.""" payload = json_loads(msg.payload) if STATE in payload and (payload[STATE] in POSSIBLE_STATES or payload[STATE] is None): self._state = (POSSIBLE_STATES[payload[STATE]] if payload[STATE] else None) del payload[STATE] self._state_attrs.update(payload) self.async_write_ha_state()
def state_message_received(msg): """Handle new MQTT state messages.""" payload = self._value_template(msg.payload) if not payload or payload == PAYLOAD_EMPTY_JSON: _LOGGER.debug( "Ignoring empty payload '%s' after rendering for topic %s", payload, msg.topic, ) return json_payload = {} if payload in [self._state_on, self._state_off, PAYLOAD_NONE]: json_payload = {STATE: payload} else: try: json_payload = json_loads(payload) _LOGGER.debug( "JSON payload detected after processing payload '%s' on topic %s", json_payload, msg.topic, ) except JSON_DECODE_EXCEPTIONS: _LOGGER.warning( "No valid (JSON) payload detected after processing payload '%s' on topic %s", json_payload, msg.topic, ) return if STATE in json_payload: if json_payload[STATE] == self._state_on: self._attr_is_on = True if json_payload[STATE] == self._state_off: self._attr_is_on = False if json_payload[STATE] == PAYLOAD_NONE: self._attr_is_on = None del json_payload[STATE] if json_payload: # process attributes try: vol.All(TURN_ON_SCHEMA)(json_payload) except vol.MultipleInvalid as invalid_siren_parameters: _LOGGER.warning( "Unable to update siren state attributes from payload '%s': %s", json_payload, invalid_siren_parameters, ) return self._update(process_turn_on_params(self, json_payload)) self.async_write_ha_state()
def position_message_received(msg): """Handle new MQTT position messages.""" payload = self._get_position_template(msg.payload) if not payload: _LOGGER.debug("Ignoring empty position message from '%s'", msg.topic) return try: payload = json_loads(payload) except JSON_DECODE_EXCEPTIONS: pass if isinstance(payload, dict): if "position" not in payload: _LOGGER.warning( "Template (position_template) returned JSON without position attribute" ) return if "tilt_position" in payload: if not self._config.get(CONF_TILT_STATE_OPTIMISTIC): # reset forced set tilt optimistic self._tilt_optimistic = False self.tilt_payload_received(payload["tilt_position"]) payload = payload["position"] try: percentage_payload = self.find_percentage_in_range( float(payload), COVER_PAYLOAD ) except ValueError: _LOGGER.warning("Payload '%s' is not numeric", payload) return self._position = percentage_payload if self._config.get(CONF_STATE_TOPIC) is None: self._state = ( STATE_CLOSED if percentage_payload == DEFAULT_POSITION_CLOSED else STATE_OPEN ) self.async_write_ha_state()
def to_native(self, validate_entity_id: bool = True) -> Event | None: """Convert to a native HA Event.""" context = Context( id=self.context_id, user_id=self.context_user_id, parent_id=self.context_parent_id, ) try: return Event( self.event_type, json_loads(self.event_data) if self.event_data else {}, EventOrigin(self.origin) if self.origin else EVENT_ORIGIN_ORDER[self.origin_idx], process_timestamp(self.time_fired), context=context, ) except JSON_DECODE_EXCEPTIONS: # When json_loads fails _LOGGER.exception("Error converting to event: %s", self) return None
def attributes_message_received(msg: ReceiveMessage) -> None: try: payload = attr_tpl(msg.payload) json_dict = json_loads(payload) if isinstance(payload, str) else None if isinstance(json_dict, dict): filtered_dict = { k: v for k, v in json_dict.items() if k not in MQTT_ATTRIBUTES_BLOCKED and k not in self._attributes_extra_blocked } self._attributes = filtered_dict self.async_write_ha_state() else: _LOGGER.warning("JSON result was not a dictionary") self._attributes = None except ValueError: _LOGGER.warning("Erroneous JSON: %s", payload) self._attributes = None
def mqtt_automation_listener(mqttmsg): """Listen for MQTT messages.""" payload = mqttmsg.payload if value_template is not None: payload = value_template.async_render_with_possible_json_value( payload, error_value=None, ) if wanted_payload is None or wanted_payload == payload: data = { **trigger_data, "platform": "mqtt", "topic": mqttmsg.topic, "payload": mqttmsg.payload, "qos": mqttmsg.qos, "description": f"mqtt topic {mqttmsg.topic}", } with suppress(ValueError): data["payload_json"] = json_loads(mqttmsg.payload) hass.async_run_hass_job(job, {"trigger": data})
async def async_start( # noqa: C901 hass: HomeAssistant, discovery_topic, config_entry=None) -> None: """Start MQTT Discovery.""" mqtt_integrations = {} async def async_discovery_message_received(msg): """Process the received message.""" hass.data[LAST_DISCOVERY] = time.time() payload = msg.payload topic = msg.topic topic_trimmed = topic.replace(f"{discovery_topic}/", "", 1) if not (match := TOPIC_MATCHER.match(topic_trimmed)): if topic_trimmed.endswith("config"): _LOGGER.warning( "Received message on illegal discovery topic '%s'. The topic contains " "not allowed characters. For more information see " "https://www.home-assistant.io/docs/mqtt/discovery/#discovery-topic", topic, ) return component, node_id, object_id = match.groups() if component not in SUPPORTED_COMPONENTS: _LOGGER.warning("Integration %s is not supported", component) return if payload: try: payload = json_loads(payload) except ValueError: _LOGGER.warning("Unable to parse JSON %s: '%s'", object_id, payload) return payload = MQTTConfig(payload) for key in list(payload): abbreviated_key = key key = ABBREVIATIONS.get(key, key) payload[key] = payload.pop(abbreviated_key) if CONF_DEVICE in payload: device = payload[CONF_DEVICE] for key in list(device): abbreviated_key = key key = DEVICE_ABBREVIATIONS.get(key, key) device[key] = device.pop(abbreviated_key) if TOPIC_BASE in payload: base = payload.pop(TOPIC_BASE) for key, value in payload.items(): if isinstance(value, str) and value: if value[0] == TOPIC_BASE and key.endswith("topic"): payload[key] = f"{base}{value[1:]}" if value[-1] == TOPIC_BASE and key.endswith("topic"): payload[key] = f"{value[:-1]}{base}" if payload.get(CONF_AVAILABILITY): for availability_conf in cv.ensure_list( payload[CONF_AVAILABILITY]): if not isinstance(availability_conf, dict): continue if topic := availability_conf.get(CONF_TOPIC): if topic[0] == TOPIC_BASE: availability_conf[ CONF_TOPIC] = f"{base}{topic[1:]}" if topic[-1] == TOPIC_BASE: availability_conf[ CONF_TOPIC] = f"{topic[:-1]}{base}"
def _update_from_rest_data(self): """Update state from the rest data.""" value = self.rest.data _LOGGER.debug("Data fetched from resource: %s", value) if self.rest.headers is not None: # If the http request failed, headers will be None content_type = self.rest.headers.get("content-type") if content_type and ( content_type.startswith("text/xml") or content_type.startswith("application/xml") or content_type.startswith("application/xhtml+xml") or content_type.startswith("application/rss+xml")): try: value = json_dumps(xmltodict.parse(value)) _LOGGER.debug("JSON converted from XML: %s", value) except ExpatError: _LOGGER.warning( "REST xml result could not be parsed and converted to JSON" ) _LOGGER.debug("Erroneous XML: %s", value) if self._json_attrs: self._attributes = {} if value: try: json_dict = json_loads(value) if self._json_attrs_path is not None: json_dict = jsonpath(json_dict, self._json_attrs_path) # jsonpath will always store the result in json_dict[0] # so the next line happens to work exactly as needed to # find the result if isinstance(json_dict, list): json_dict = json_dict[0] if isinstance(json_dict, dict): attrs = { k: json_dict[k] for k in self._json_attrs if k in json_dict } self._attributes = attrs else: _LOGGER.warning( "JSON result was not a dictionary" " or list with 0th element a dictionary") except ValueError: _LOGGER.warning("REST result could not be parsed as JSON") _LOGGER.debug("Erroneous JSON: %s", value) else: _LOGGER.warning("Empty reply found when expecting JSON data") if value is not None and self._value_template is not None: value = self._value_template.async_render_with_possible_json_value( value, None) if value is None or self.device_class not in ( SensorDeviceClass.DATE, SensorDeviceClass.TIMESTAMP, ): self._state = value return self._state = async_parse_date_datetime(value, self.entity_id, self.device_class)
return (other.__class__ in [self.__class__, State] and self.entity_id == other.entity_id and self.state == other.state and self.attributes == other.attributes) def decode_attributes_from_row( row: Row, attr_cache: dict[str, dict[str, Any]]) -> dict[str, Any]: """Decode attributes from a database row.""" source: str = row.shared_attrs or row.attributes if (attributes := attr_cache.get(source)) is not None: return attributes if not source or source == EMPTY_JSON_OBJECT: return {} try: attr_cache[source] = attributes = json_loads(source) except ValueError: _LOGGER.exception("Error converting row to state attributes: %s", source) attr_cache[source] = attributes = {} return attributes def row_to_compressed_state( row: Row, attr_cache: dict[str, dict[str, Any]], start_time: datetime | None = None, ) -> dict[str, Any]: """Convert a database row to a compressed state.""" comp_state = { COMPRESSED_STATE_STATE: row.state,