Added command handling and sending

This commit is contained in:
Mark Qvist 2023-10-30 02:28:35 +01:00
parent c1b5b776d7
commit 7076fae5cc
4 changed files with 172 additions and 25 deletions

View File

@ -829,7 +829,9 @@ class SidebandApp(MDApp):
self.message_send_action() self.message_send_action()
if text == "l": if text == "l":
if self.root.ids.screen_manager.current == "map_screen": if self.root.ids.screen_manager.current == "messages_screen":
self.message_propagation_action(self)
elif self.root.ids.screen_manager.current == "map_screen":
self.map_layers_action() self.map_layers_action()
else: else:
self.announces_action(self) self.announces_action(self)
@ -1006,6 +1008,7 @@ class SidebandApp(MDApp):
def open_conversation(self, context_dest, direction="left"): def open_conversation(self, context_dest, direction="left"):
self.outbound_mode_paper = False self.outbound_mode_paper = False
self.outbound_mode_command = False
if self.sideband.config["propagation_by_default"]: if self.sideband.config["propagation_by_default"]:
self.outbound_mode_propagation = True self.outbound_mode_propagation = True
else: else:
@ -1075,7 +1078,26 @@ class SidebandApp(MDApp):
else: else:
msg_content = self.messages_view.ids.message_text.text msg_content = self.messages_view.ids.message_text.text
context_dest = self.messages_view.ids.messages_scrollview.active_conversation context_dest = self.messages_view.ids.messages_scrollview.active_conversation
if self.outbound_mode_paper: if self.outbound_mode_command:
if self.sideband.send_command(msg_content, context_dest, False):
self.messages_view.ids.message_text.text = ""
self.messages_view.ids.messages_scrollview.scroll_y = 0
self.jobs(0)
else:
self.messages_view.send_error_dialog = MDDialog(
title="Error",
text="Could not send the command. Check that the syntax is correct, and that the command is supported.",
buttons=[
MDRectangleFlatButton(
text="OK",
font_size=dp(18),
on_release=self.messages_view.close_send_error_dialog
)
],
)
self.messages_view.send_error_dialog.open()
elif self.outbound_mode_paper:
if self.sideband.paper_message(msg_content, context_dest): if self.sideband.paper_message(msg_content, context_dest):
self.messages_view.ids.message_text.text = "" self.messages_view.ids.message_text.text = ""
self.messages_view.ids.messages_scrollview.scroll_y = 0 self.messages_view.ids.messages_scrollview.scroll_y = 0
@ -1117,16 +1139,24 @@ class SidebandApp(MDApp):
self.object_details_action(self.messages_view, from_conv=True) self.object_details_action(self.messages_view, from_conv=True)
def message_propagation_action(self, sender): def message_propagation_action(self, sender):
if self.outbound_mode_command:
self.outbound_mode_paper = False
self.outbound_mode_propagation = False
self.outbound_mode_command = False
else:
if self.outbound_mode_paper: if self.outbound_mode_paper:
self.outbound_mode_paper = False self.outbound_mode_paper = False
self.outbound_mode_propagation = False self.outbound_mode_propagation = False
self.outbound_mode_command = True
else: else:
if self.outbound_mode_propagation: if self.outbound_mode_propagation:
self.outbound_mode_paper = True self.outbound_mode_paper = True
self.outbound_mode_propagation = False self.outbound_mode_propagation = False
self.outbound_mode_command = False
else: else:
self.outbound_mode_propagation = True self.outbound_mode_propagation = True
self.outbound_mode_paper = False self.outbound_mode_paper = False
self.outbound_mode_command = False
self.update_message_widgets() self.update_message_widgets()
@ -1137,6 +1167,10 @@ class SidebandApp(MDApp):
if self.outbound_mode_paper: if self.outbound_mode_paper:
mode_item.icon = "qrcode" mode_item.icon = "qrcode"
self.messages_view.ids.message_text.hint_text = "Paper message" self.messages_view.ids.message_text.hint_text = "Paper message"
else:
if self.outbound_mode_command:
mode_item.icon = "console"
self.messages_view.ids.message_text.hint_text = "Send command or request"
else: else:
if not self.outbound_mode_propagation: if not self.outbound_mode_propagation:
mode_item.icon = "lan-connect" mode_item.icon = "lan-connect"

View File

@ -1976,7 +1976,7 @@ class SidebandCore():
messages = messages[-limit:] messages = messages[-limit:]
return messages return messages
def _db_save_lxm(self, lxm, context_dest, originator = False): def _db_save_lxm(self, lxm, context_dest, originator = False, own_command = False):
state = lxm.state state = lxm.state
packed_telemetry = None packed_telemetry = None
@ -1998,7 +1998,7 @@ class SidebandCore():
# TODO: Implement # TODO: Implement
pass pass
if len(lxm.content) != 0 or len(lxm.title) != 0: if own_command or len(lxm.content) != 0 or len(lxm.title) != 0:
db = self.__db_connect() db = self.__db_connect()
dbc = db.cursor() dbc = db.cursor()
@ -2977,7 +2977,7 @@ class SidebandCore():
RNS.log("Error while creating paper message: "+str(e), RNS.LOG_ERROR) RNS.log("Error while creating paper message: "+str(e), RNS.LOG_ERROR)
return False return False
def send_message(self, content, destination_hash, propagation): def send_message(self, content, destination_hash, propagation, skip_fields=False):
try: try:
if content == "": if content == "":
raise ValueError("Message content cannot be empty") raise ValueError("Message content cannot be empty")
@ -2991,7 +2991,54 @@ class SidebandCore():
else: else:
desired_method = LXMF.LXMessage.DIRECT desired_method = LXMF.LXMessage.DIRECT
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = self.get_message_fields(destination_hash)) if skip_fields:
fields = {}
else:
fields = self.get_message_fields(destination_hash)
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields)
lxm.register_delivery_callback(self.message_notification)
lxm.register_failed_callback(self.message_notification)
if self.message_router.get_outbound_propagation_node() != None:
if self.config["lxmf_try_propagation_on_fail"]:
lxm.try_propagation_on_fail = True
self.message_router.handle_outbound(lxm)
self.lxm_ingest(lxm, originator=True)
return True
except Exception as e:
RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR)
return False
def send_command(self, content, destination_hash, propagation):
try:
if content == "":
return False
commands = []
if content.startswith("echo "):
echo_content = content.replace("echo ", "").encode("utf-8")
if len(echo_content) > 0:
commands.append({Commands.ECHO: echo_content})
elif content.startswith("sig"):
commands.append({Commands.SIGNAL_REPORT: True})
if len(commands) == 0:
return False
dest_identity = RNS.Identity.recall(destination_hash)
dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery")
source = self.lxmf_destination
if propagation:
desired_method = LXMF.LXMessage.PROPAGATED
else:
desired_method = LXMF.LXMessage.DIRECT
lxm = LXMF.LXMessage(dest, source, "", title="", desired_method=desired_method, fields = {LXMF.FIELD_COMMANDS: commands})
lxm.register_delivery_callback(self.message_notification) lxm.register_delivery_callback(self.message_notification)
lxm.register_failed_callback(self.message_notification) lxm.register_failed_callback(self.message_notification)
@ -3060,6 +3107,7 @@ class SidebandCore():
should_notify = False should_notify = False
is_trusted = False is_trusted = False
telemetry_only = False telemetry_only = False
own_command = False
unread_reason_tx = False unread_reason_tx = False
if originator: if originator:
@ -3069,18 +3117,21 @@ class SidebandCore():
context_dest = message.source_hash context_dest = message.source_hash
is_trusted = self.is_trusted(context_dest) is_trusted = self.is_trusted(context_dest)
if originator and LXMF.FIELD_COMMANDS in message.fields:
own_command = True
if self._db_message(message.hash): if self._db_message(message.hash):
RNS.log("Message exists, setting state to: "+str(message.state), RNS.LOG_DEBUG) RNS.log("Message exists, setting state to: "+str(message.state), RNS.LOG_DEBUG)
self._db_message_set_state(message.hash, message.state) self._db_message_set_state(message.hash, message.state)
else: else:
RNS.log("Message does not exist, saving", RNS.LOG_DEBUG) RNS.log("Message does not exist, saving", RNS.LOG_DEBUG)
self._db_save_lxm(message, context_dest, originator) self._db_save_lxm(message, context_dest, originator, own_command=own_command)
if is_trusted: if is_trusted:
should_notify = True should_notify = True
if len(message.content) == 0 and len(message.title) == 0: if len(message.content) == 0 and len(message.title) == 0:
if (LXMF.FIELD_TELEMETRY in message.fields or LXMF.FIELD_TELEMETRY_STREAM in message.fields): if (LXMF.FIELD_TELEMETRY in message.fields or LXMF.FIELD_TELEMETRY_STREAM in message.fields or LXMF.FIELD_COMMANDS in message.fields):
RNS.log("Squelching notification due to telemetry-only message", RNS.LOG_DEBUG) RNS.log("Squelching notification due to telemetry-only message", RNS.LOG_DEBUG)
telemetry_only = True telemetry_only = True
@ -3236,16 +3287,57 @@ class SidebandCore():
RNS.log("LXMF delivery "+str(time_string)+". "+str(signature_string)+".", RNS.LOG_DEBUG) RNS.log("LXMF delivery "+str(time_string)+". "+str(signature_string)+".", RNS.LOG_DEBUG)
try: try:
if self.config["lxmf_ignore_unknown"] == True:
context_dest = message.source_hash context_dest = message.source_hash
if self.config["lxmf_ignore_unknown"] == True:
if self._db_conversation(context_dest) == None: if self._db_conversation(context_dest) == None:
RNS.log("Dropping message from unknown sender "+RNS.prettyhexrep(context_dest), RNS.LOG_DEBUG) RNS.log("Dropping message from unknown sender "+RNS.prettyhexrep(context_dest), RNS.LOG_DEBUG)
return return
if message.signature_validated and LXMF.FIELD_COMMANDS in message.fields:
if self.requests_allowed_from(context_dest):
commands = message.fields[LXMF.FIELD_COMMANDS]
self.handle_commands(commands, message)
else:
self.lxm_ingest(message) self.lxm_ingest(message)
except Exception as e: except Exception as e:
RNS.log("Error while ingesting LXMF message "+RNS.prettyhexrep(message.hash)+" to database: "+str(e)) RNS.log("Error while ingesting LXMF message "+RNS.prettyhexrep(message.hash)+" to database: "+str(e))
def handle_commands(self, commands, message):
try:
context_dest = message.source_hash
RNS.log("Handling commands from "+str(context_dest), RNS.LOG_DEBUG)
for command in commands:
if Commands.TELEMETRY_REQUEST in command:
timebase = int(command[Commands.TELEMETRY_REQUEST])
RNS.log("Handling telemetry request with timebase "+str(timebase), RNS.LOG_DEBUG)
elif Commands.ECHO in command:
msg_content = "Echo reply: "+command[Commands.ECHO].decode("utf-8")
RNS.log("Handling echo request", RNS.LOG_DEBUG)
self.send_message(msg_content, context_dest, False, skip_fields=True)
elif Commands.SIGNAL_REPORT in command:
RNS.log("Handling signal report", RNS.LOG_DEBUG)
phy_str = ""
if message.q != None:
phy_str += f"Link Quality: {message.q}%\n"
if message.rssi != None:
phy_str += f"RSSI: {message.rssi} dBm\n"
if message.snr != None:
phy_str += f"SNR: {message.rssi} dB\n"
if len(phy_str) != 0:
phy_str = phy_str[:-1]
else:
phy_str = "No reception info available"
self.send_message(phy_str, context_dest, False, skip_fields=True)
except Exception as e:
RNS.log("Error while handling commands: "+str(e), RNS.LOG_ERROR)
def get_display_name_bytes(self): def get_display_name_bytes(self):
return self.config["display_name"].encode("utf-8") return self.config["display_name"].encode("utf-8")

View File

@ -10,6 +10,8 @@ from .geo import azalt, angle_to_horizon, radio_horizon, shared_radio_horizon
class Commands(): class Commands():
TELEMETRY_REQUEST = 0x01 TELEMETRY_REQUEST = 0x01
ECHO = 0x02
SIGNAL_REPORT = 0x03
class Telemeter(): class Telemeter():
@staticmethod @staticmethod

View File

@ -22,11 +22,11 @@ import subprocess
import shlex import shlex
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
from sideband.sense import Telemeter from sideband.sense import Telemeter, Commands
from ui.helpers import ts_format, file_ts_format, mdc from ui.helpers import ts_format, file_ts_format, mdc
from ui.helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light from ui.helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
else: else:
from sbapp.sideband.sense import Telemeter from sbapp.sideband.sense import Telemeter, Commands
from .helpers import ts_format, file_ts_format, mdc from .helpers import ts_format, file_ts_format, mdc
from .helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light from .helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
@ -178,14 +178,29 @@ class Messages():
txstr = time.strftime(ts_format, time.localtime(m["sent"])) txstr = time.strftime(ts_format, time.localtime(m["sent"]))
rxstr = time.strftime(ts_format, time.localtime(m["received"])) rxstr = time.strftime(ts_format, time.localtime(m["received"]))
titlestr = "" titlestr = ""
extra_content = ""
extra_telemetry = {} extra_telemetry = {}
telemeter = None telemeter = None
signature_valid = False
if "lxm" in m and m["lxm"] != None and m["lxm"].signature_validated:
signature_valid = True
if "extras" in m and m["extras"] != None and "packed_telemetry" in m["extras"]: if "extras" in m and m["extras"] != None and "packed_telemetry" in m["extras"]:
try: try:
telemeter = Telemeter.from_packed(m["extras"]["packed_telemetry"]) telemeter = Telemeter.from_packed(m["extras"]["packed_telemetry"])
except Exception as e: except Exception as e:
pass pass
if "lxm" in m and m["lxm"] != None and m["lxm"].fields != None and LXMF.FIELD_COMMANDS in m["lxm"].fields:
commands = m["lxm"].fields[LXMF.FIELD_COMMANDS]
for command in commands:
if Commands.ECHO in command:
extra_content = "[font=RobotoMono-Regular]> echo "+command[Commands.ECHO].decode("utf-8")+"[/font]\n"
if Commands.SIGNAL_REPORT in command:
extra_content = "[font=RobotoMono-Regular]> sig[/font]\n"
extra_content = extra_content[:-1]
if telemeter == None and "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields: if telemeter == None and "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields:
try: try:
packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY] packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY]
@ -272,8 +287,12 @@ class Messages():
if rcvd_d_str != "": if rcvd_d_str != "":
heading_str += rcvd_d_str heading_str += rcvd_d_str
pre_content = ""
if not signature_valid:
pre_content += "[b]Warning![/b] The signature for this message could not be validated. Check that you have received an announce from this sender. If you already have, or other messages from the sender do not display this warning, [b]this message is likely to be fake[/b].\n\n"
item = ListLXMessageCard( item = ListLXMessageCard(
text=m["content"].decode("utf-8"), text=pre_content+m["content"].decode("utf-8")+extra_content,
heading=heading_str, heading=heading_str,
md_bg_color=msg_color, md_bg_color=msg_color,
) )