From c5b663f7bec0e483d28bff89c3c272bb5e9f0da9 Mon Sep 17 00:00:00 2001 From: Sergio Conde Date: Mon, 23 Feb 2026 16:00:10 +0100 Subject: [PATCH 1/3] feat: add extra information to message event --- custom_components/meshtastic/aiomeshtastic/packet.py | 12 ++++++++++++ custom_components/meshtastic/api.py | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/custom_components/meshtastic/aiomeshtastic/packet.py b/custom_components/meshtastic/aiomeshtastic/packet.py index be37311..50c14b1 100644 --- a/custom_components/meshtastic/aiomeshtastic/packet.py +++ b/custom_components/meshtastic/aiomeshtastic/packet.py @@ -47,6 +47,18 @@ def data(self) -> mesh_pb2.Data | None: def port_num(self) -> portnums_pb2.PortNum | None: return self.data.portnum if self.data is not None else None + @property + def via_mqtt(self) -> bool: + return self.mesh_packet.via_mqtt if self.mesh_packet is not None else False + + @property + def hop_start(self) -> int | None: + return self.mesh_packet.hop_start if self.mesh_packet is not None else None + + @property + def hop_limit(self) -> int | None: + return self.mesh_packet.hop_limit if self.mesh_packet is not None else None + @cached_property def app_payload(self) -> T: # noqa: PLR0911 data = self.data diff --git a/custom_components/meshtastic/api.py b/custom_components/meshtastic/api.py index 29458a9..9b2175c 100644 --- a/custom_components/meshtastic/api.py +++ b/custom_components/meshtastic/api.py @@ -317,6 +317,11 @@ async def _on_text_message(self, node: MeshNode, packet: Packet) -> None: "to": {"node": to_node, "channel": to_channel}, "gateway": self.get_own_node()["num"], "message": packet.app_payload, + "hop_start": packet.mesh_packet.hop_start, + "hop_limit": packet.mesh_packet.hop_limit, + "rx_snr": packet.mesh_packet.rx_snr, + "rx_rssi": packet.mesh_packet.rx_rssi, + "via_mqtt": packet.mesh_packet.via_mqtt, }, ) From 575fc94bd7b640be3adb42c971f239a42a8a655d Mon Sep 17 00:00:00 2001 From: Sergio Conde Date: Mon, 23 Feb 2026 22:08:52 +0100 Subject: [PATCH 2/3] feat: add the option to react with emojis --- .../aiomeshtastic/connection/__init__.py | 3 +++ .../meshtastic/aiomeshtastic/interface.py | 6 ++++++ custom_components/meshtastic/api.py | 2 ++ custom_components/meshtastic/const.py | 1 + custom_components/meshtastic/services.py | 7 +++++++ custom_components/meshtastic/services.yaml | 15 +++++++++++++++ custom_components/meshtastic/translations/en.json | 12 ++++++++++++ 7 files changed, 46 insertions(+) diff --git a/custom_components/meshtastic/aiomeshtastic/connection/__init__.py b/custom_components/meshtastic/aiomeshtastic/connection/__init__.py index d0a5640..87f816d 100644 --- a/custom_components/meshtastic/aiomeshtastic/connection/__init__.py +++ b/custom_components/meshtastic/aiomeshtastic/connection/__init__.py @@ -252,6 +252,7 @@ async def send_mesh_packet( # noqa: PLR0913 from_node: int | None = None, ack: bool = False, reply_id: int | None = None, + emoji: bool = False, want_response: bool = False, out_callback: Callable[[Packet], Awaitable[None]] | None = None, ack_callback: Callable[[Packet[mesh_pb2.Routing]], Awaitable[None]] | None = None, @@ -267,6 +268,8 @@ async def send_mesh_packet( # noqa: PLR0913 mesh_packet.decoded.want_response = want_response if reply_id is not None: mesh_packet.decoded.reply_id = reply_id + if emoji: + mesh_packet.decoded.emoji = 1 mesh_packet.id = self._generate_packet_id() if from_node is not None: mesh_packet.__setattr__("from", from_node) diff --git a/custom_components/meshtastic/aiomeshtastic/interface.py b/custom_components/meshtastic/aiomeshtastic/interface.py index c6fc13a..71e1a24 100644 --- a/custom_components/meshtastic/aiomeshtastic/interface.py +++ b/custom_components/meshtastic/aiomeshtastic/interface.py @@ -1061,6 +1061,7 @@ async def send_text_message( # noqa: PLR0912, PLR0913 channel_index: int | None = None, priority: MeshPacket.Priority | None = None, reply_id: int | None = None, + is_reaction: bool = False, on_message_sent: Callable[[Packet], Awaitable[None]] | None = None, ) -> None: if isinstance(destination, MeshNode): @@ -1092,6 +1093,10 @@ async def send_text_message( # noqa: PLR0912, PLR0913 msg = f"Channel #{channel_index} is disabled" raise ValueError(msg) + if is_reaction and reply_id is None: + msg = "reply_id is required when sending a reaction" + raise ValueError(msg) + if on_message_sent is not None: async def out_callback(packet: Packet) -> None: @@ -1109,6 +1114,7 @@ async def out_callback(packet: Packet) -> None: want_response=False, ack=want_ack, reply_id=reply_id, + emoji=is_reaction, out_callback=out_callback, ) diff --git a/custom_components/meshtastic/api.py b/custom_components/meshtastic/api.py index 9b2175c..922271e 100644 --- a/custom_components/meshtastic/api.py +++ b/custom_components/meshtastic/api.py @@ -247,6 +247,7 @@ async def send_text( want_ack: bool = False, channel_index: int | None = None, reply_id: int | None = None, + is_reaction: bool = False, ) -> bool: async def _on_message_sent(packet: Packet) -> None: # publish event so that outgoing messages are recorded to logbook @@ -262,6 +263,7 @@ async def _on_message_sent(packet: Packet) -> None: want_ack=want_ack, channel_index=channel_index, reply_id=reply_id, + is_reaction=is_reaction, on_message_sent=_on_message_sent, ), timeout=30, diff --git a/custom_components/meshtastic/const.py b/custom_components/meshtastic/const.py index 017e6d1..b59c479 100644 --- a/custom_components/meshtastic/const.py +++ b/custom_components/meshtastic/const.py @@ -67,6 +67,7 @@ class ConfigOptionNotifyPlatformNodes(enum.StrEnum): ATTR_SERVICE_DATA_FROM = "from" ATTR_SERVICE_DATA_ACK = "ack" ATTR_SERVICE_DATA_REPLY_ID = "reply_id" +ATTR_SERVICE_DATA_IS_REACTION = "is_reaction" ATTR_SERVICE_SEND_TEXT_DATA_TEXT = "text" diff --git a/custom_components/meshtastic/services.py b/custom_components/meshtastic/services.py index 7777ff3..adc2661 100644 --- a/custom_components/meshtastic/services.py +++ b/custom_components/meshtastic/services.py @@ -37,6 +37,7 @@ ATTR_SERVICE_DATA_ACK, ATTR_SERVICE_DATA_CHANNEL, ATTR_SERVICE_DATA_FROM, + ATTR_SERVICE_DATA_IS_REACTION, ATTR_SERVICE_DATA_REPLY_ID, ATTR_SERVICE_DATA_TO, ATTR_SERVICE_REQUEST_TELEMETRY_DATA_TYPE, @@ -63,6 +64,7 @@ vol.Optional(ATTR_SERVICE_DATA_CHANNEL): cv.string, vol.Required(ATTR_SERVICE_DATA_ACK, default=False): cv.boolean, vol.Optional(ATTR_SERVICE_DATA_REPLY_ID): cv.positive_int, + vol.Optional(ATTR_SERVICE_DATA_IS_REACTION, default=False): cv.boolean, } ) @@ -72,6 +74,7 @@ vol.Required(ATTR_SERVICE_SEND_DIRECT_MESSAGE_DATA_MESSAGE): cv.string, vol.Required(ATTR_SERVICE_DATA_ACK, default=True): cv.boolean, vol.Optional(ATTR_SERVICE_DATA_REPLY_ID): cv.positive_int, + vol.Optional(ATTR_SERVICE_DATA_IS_REACTION, default=False): cv.boolean, } ) @@ -81,6 +84,7 @@ vol.Required(ATTR_SERVICE_BROADCAST_CHANNEL_MESSAGE_DATA_MESSAGE): cv.string, vol.Required(ATTR_SERVICE_DATA_ACK, default=True): cv.boolean, vol.Optional(ATTR_SERVICE_DATA_REPLY_ID): cv.positive_int, + vol.Optional(ATTR_SERVICE_DATA_IS_REACTION, default=False): cv.boolean, } ) @@ -293,6 +297,7 @@ async def handle_service_call(call: ServiceCall) -> ServiceResponse | object: destination_id=to_node_id, want_ack=call.data[ATTR_SERVICE_DATA_ACK], reply_id=call.data.get(ATTR_SERVICE_DATA_REPLY_ID, None), + is_reaction=call.data.get(ATTR_SERVICE_DATA_IS_REACTION, False), ) return None @@ -329,6 +334,7 @@ async def handle_service_call(call: ServiceCall) -> ServiceResponse | object: channel_index=channel_index, want_ack=call.data[ATTR_SERVICE_DATA_ACK], reply_id=call.data.get(ATTR_SERVICE_DATA_REPLY_ID, None), + is_reaction=call.data.get(ATTR_SERVICE_DATA_IS_REACTION, False), ) return None @@ -373,6 +379,7 @@ async def handler(call: ServiceCall, to: int, channel_index: int | None) -> None channel_index=channel_index, want_ack=call.data[ATTR_SERVICE_DATA_ACK], reply_id=call.data.get(ATTR_SERVICE_DATA_REPLY_ID, None), + is_reaction=call.data.get(ATTR_SERVICE_DATA_IS_REACTION, False), ) _service_handlers[entry.entry_id][SERVICE_SEND_TEXT] = await _build_default_handler(hass, client, handler) diff --git a/custom_components/meshtastic/services.yaml b/custom_components/meshtastic/services.yaml index 7820875..3c6e73b 100644 --- a/custom_components/meshtastic/services.yaml +++ b/custom_components/meshtastic/services.yaml @@ -39,6 +39,11 @@ send_text: selector: text: multiline: false + is_reaction: + required: false + default: false + selector: + boolean: {} send_direct_message: fields: @@ -62,6 +67,11 @@ send_direct_message: selector: text: multiline: false + is_reaction: + required: false + default: false + selector: + boolean: {} broadcast_channel_message: fields: @@ -87,6 +97,11 @@ broadcast_channel_message: selector: text: multiline: false + is_reaction: + required: false + default: false + selector: + boolean: {} request_telemetry: fields: diff --git a/custom_components/meshtastic/translations/en.json b/custom_components/meshtastic/translations/en.json index 8713c8c..28ae3c3 100644 --- a/custom_components/meshtastic/translations/en.json +++ b/custom_components/meshtastic/translations/en.json @@ -180,6 +180,10 @@ "reply_id": { "name": "Reply ID", "description": "A message ID to reply to." + }, + "is_reaction": { + "name": "Send as Reaction", + "description": "Send the message as a reaction (emoji). Requires Reply ID to be set and the message must be a single emoji." } } }, @@ -202,6 +206,10 @@ "reply_id": { "name": "Reply ID", "description": "A message ID to reply to." + }, + "is_reaction": { + "name": "Send as Reaction", + "description": "Send the message as a reaction (emoji). Requires Reply ID to be set and the message must be a single emoji." } } }, @@ -224,6 +232,10 @@ "reply_id": { "name": "Reply ID", "description": "A message ID to reply to." + }, + "is_reaction": { + "name": "Send as Reaction", + "description": "Send the message as a reaction (emoji). Requires Reply ID to be set and the message must be a single emoji." } } }, From 1f41d19586ad615e42b5dc57be1aa0bfc37d9704 Mon Sep 17 00:00:00 2001 From: Sergio Conde Date: Tue, 24 Feb 2026 09:22:30 +0100 Subject: [PATCH 3/3] fix: ignore linter error too many arguments in function --- custom_components/meshtastic/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/meshtastic/api.py b/custom_components/meshtastic/api.py index 922271e..60edbf7 100644 --- a/custom_components/meshtastic/api.py +++ b/custom_components/meshtastic/api.py @@ -239,7 +239,7 @@ async def _publish_event_text_message_out( event_data["message_id"] = message_id self._hass.bus.async_fire(EVENT_MESHTASTIC_API_TEXT_MESSAGE_OUT, event_data) - async def send_text( + async def send_text( # noqa: PLR0913 self, text: str, destination_id: int | str = MeshInterface.BROADCAST_ADDR,