Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions custom_components/meshtastic/aiomeshtastic/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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,
)

Expand Down
12 changes: 12 additions & 0 deletions custom_components/meshtastic/aiomeshtastic/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion custom_components/meshtastic/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,15 @@ 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,
*,
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
Expand All @@ -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,
Expand Down Expand Up @@ -317,6 +319,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,
},
)

Expand Down
1 change: 1 addition & 0 deletions custom_components/meshtastic/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 7 additions & 0 deletions custom_components/meshtastic/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
}
)

Expand All @@ -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,
}
)

Expand All @@ -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,
}
)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
15 changes: 15 additions & 0 deletions custom_components/meshtastic/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ send_text:
selector:
text:
multiline: false
is_reaction:
required: false
default: false
selector:
boolean: {}

send_direct_message:
fields:
Expand All @@ -62,6 +67,11 @@ send_direct_message:
selector:
text:
multiline: false
is_reaction:
required: false
default: false
selector:
boolean: {}

broadcast_channel_message:
fields:
Expand All @@ -87,6 +97,11 @@ broadcast_channel_message:
selector:
text:
multiline: false
is_reaction:
required: false
default: false
selector:
boolean: {}

request_telemetry:
fields:
Expand Down
12 changes: 12 additions & 0 deletions custom_components/meshtastic/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
},
Expand All @@ -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."
}
}
},
Expand All @@ -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."
}
}
},
Expand Down