Added per-object livetracking functionality

This commit is contained in:
Mark Qvist 2024-12-08 21:17:41 +01:00
parent d2efd8e91a
commit a2575559cb
3 changed files with 154 additions and 31 deletions

View File

@ -160,6 +160,7 @@ class SidebandCore():
self.telemetry_send_blocked_until = 0 self.telemetry_send_blocked_until = 0
self.pending_telemetry_request = False self.pending_telemetry_request = False
self.telemetry_request_max_history = 7*24*60*60 self.telemetry_request_max_history = 7*24*60*60
self.live_tracked_objects = {}
self.default_lxm_limit = 128*1000 self.default_lxm_limit = 128*1000
self.state_db = {} self.state_db = {}
self.state_lock = Lock() self.state_lock = Lock()
@ -1253,7 +1254,7 @@ class SidebandCore():
else: else:
return False return False
def request_latest_telemetry(self, from_addr=None): def request_latest_telemetry(self, from_addr=None, is_livetrack=False):
if self.allow_service_dispatch and self.is_client: if self.allow_service_dispatch and self.is_client:
try: try:
return self._service_request_latest_telemetry(from_addr) return self._service_request_latest_telemetry(from_addr)
@ -1286,6 +1287,10 @@ class SidebandCore():
if self.config["telemetry_use_propagation_only"] == True: if self.config["telemetry_use_propagation_only"] == True:
desired_method = LXMF.LXMessage.PROPAGATED desired_method = LXMF.LXMessage.PROPAGATED
else:
if not self.message_router.delivery_link_available(from_addr) and RNS.Identity.current_ratchet_id(from_addr) != None:
RNS.log(f"Have ratchet for {RNS.prettyhexrep(from_addr)}, requesting opportunistic delivery of telemetry request", RNS.LOG_DEBUG)
desired_method = LXMF.LXMessage.OPPORTUNISTIC
else: else:
desired_method = LXMF.LXMessage.DIRECT desired_method = LXMF.LXMessage.DIRECT
@ -1300,7 +1305,7 @@ class SidebandCore():
lxm.register_failed_callback(self.telemetry_request_finished) lxm.register_failed_callback(self.telemetry_request_finished)
if self.message_router.get_outbound_propagation_node() != None: if self.message_router.get_outbound_propagation_node() != None:
if self.config["telemetry_try_propagation_on_fail"]: if self.config["telemetry_try_propagation_on_fail"] and not is_livetrack:
lxm.try_propagation_on_fail = True lxm.try_propagation_on_fail = True
RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG) RNS.log(f"Sending telemetry request with timebase {request_timebase}", RNS.LOG_DEBUG)
@ -1312,6 +1317,62 @@ class SidebandCore():
else: else:
return "not_sent" return "not_sent"
def _is_tracking(self, object_addr):
return object_addr in self.live_tracked_objects
def is_tracking(self, object_addr, allow_cache=False):
if not RNS.vendor.platformutils.is_android():
return self._is_tracking(object_addr)
else:
if self.is_service:
return self._is_tracking(object_addr)
else:
try:
return self.service_rpc_request({"is_tracking": object_addr})
except Exception as e:
ed = "Error while getting tracking state over RPC: "+str(e)
RNS.log(ed, RNS.LOG_DEBUG)
return ed
def _start_tracking(self, object_addr, interval, duration):
RNS.log("Starting tracking of "+RNS.prettyhexrep(object_addr), RNS.LOG_DEBUG)
self.live_tracked_objects[object_addr] = [interval, 0, time.time()+duration]
def start_tracking(self, object_addr, interval, duration, allow_cache=False):
if not RNS.vendor.platformutils.is_android():
return self._start_tracking(object_addr, interval, duration)
else:
if self.is_service:
return self._start_tracking(object_addr, interval, duration)
else:
try:
args = {"object_addr": object_addr, "interval": interval, "duration": duration}
return self.service_rpc_request({"start_tracking": args})
except Exception as e:
ed = "Error while starting tracking over RPC: "+str(e)
RNS.log(ed, RNS.LOG_DEBUG)
return ed
def _stop_tracking(self, object_addr):
RNS.log("Stopping tracking of "+RNS.prettyhexrep(object_addr), RNS.LOG_DEBUG)
if object_addr in self.live_tracked_objects:
self.live_tracked_objects.pop(object_addr)
def stop_tracking(self, object_addr, allow_cache=False):
if not RNS.vendor.platformutils.is_android():
return self._stop_tracking(object_addr)
else:
if self.is_service:
return self._stop_tracking(object_addr)
else:
try:
args = {"object_addr": object_addr}
return self.service_rpc_request({"stop_tracking": args})
except Exception as e:
ed = "Error while stopping tracking over RPC: "+str(e)
RNS.log(ed, RNS.LOG_DEBUG)
return ed
def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False): def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False):
if not RNS.vendor.platformutils.is_android(): if not RNS.vendor.platformutils.is_android():
return False return False
@ -1366,6 +1427,10 @@ class SidebandCore():
if self.config["telemetry_use_propagation_only"] == True: if self.config["telemetry_use_propagation_only"] == True:
desired_method = LXMF.LXMessage.PROPAGATED desired_method = LXMF.LXMessage.PROPAGATED
else:
if not self.message_router.delivery_link_available(to_addr) and RNS.Identity.current_ratchet_id(to_addr) != None:
RNS.log(f"Have ratchet for {RNS.prettyhexrep(to_addr)}, requesting opportunistic delivery of telemetry", RNS.LOG_DEBUG)
desired_method = LXMF.LXMessage.OPPORTUNISTIC
else: else:
desired_method = LXMF.LXMessage.DIRECT desired_method = LXMF.LXMessage.DIRECT
@ -1778,6 +1843,14 @@ class SidebandCore():
elif "get_lxm_stamp_cost" in call: elif "get_lxm_stamp_cost" in call:
args = call["get_lxm_stamp_cost"] args = call["get_lxm_stamp_cost"]
connection.send(self.get_lxm_stamp_cost(args["lxm_hash"])) connection.send(self.get_lxm_stamp_cost(args["lxm_hash"]))
elif "is_tracking" in call:
connection.send(self.is_tracking(call["is_tracking"]))
elif "start_tracking" in call:
args = call["start_tracking"]
connection.send(self.start_tracking(object_addr=args["object_addr"], interval=args["interval"], duration=args["duration"]))
elif "stop_tracking" in call:
args = call["stop_tracking"]
connection.send(self.stop_tracking(object_addr=args["object_addr"]))
else: else:
connection.send(None) connection.send(None)
@ -3403,6 +3476,28 @@ class SidebandCore():
except Exception as e: except Exception as e:
RNS.log("An error occurred while requesting scheduled telemetry from collector: "+str(e), RNS.LOG_ERROR) RNS.log("An error occurred while requesting scheduled telemetry from collector: "+str(e), RNS.LOG_ERROR)
stale_entries = []
if len(self.live_tracked_objects) > 0:
now = time.time()
for object_hash in self.live_tracked_objects:
tracking_entry = self.live_tracked_objects[object_hash]
tracking_int = tracking_entry[0]
tracking_last = tracking_entry[1]
tracking_end = tracking_entry[2]
if now < tracking_end:
if now > tracking_last+tracking_int:
RNS.log("Next live tracking request time reached for "+str(RNS.prettyhexrep(object_hash)))
self.request_latest_telemetry(from_addr=object_hash, is_livetrack=True)
tracking_entry[1] = time.time()
else:
stale_entries.append(object_hash)
for object_hash in stale_entries:
RNS.log("Terminating live tracking for "+RNS.prettyhexrep(object_hash)+", tracking duration reached", RNS.LOG_DEBUG)
self.live_tracked_objects.pop(object_hash)
def __start_jobs_deferred(self): def __start_jobs_deferred(self):
if self.is_service: if self.is_service:
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True) self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)

View File

@ -148,6 +148,15 @@ class ObjectDetails():
else: else:
self.from_objects = False self.from_objects = False
if self.viewing_self:
self.screen.ids.track_button.disabled = True
else:
self.screen.ids.track_button.disabled = False
if self.app.sideband.is_tracking(source_dest):
self.screen.ids.track_button.text = "Stop Live Tracking"
else:
self.screen.ids.track_button.text = "Start Live Tracking"
self.coords = None self.coords = None
self.telemetry_list.data = [] self.telemetry_list.data = []
pds = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(source_dest))).encode("utf-8")).decode("utf-8") pds = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(source_dest))).encode("utf-8")).decode("utf-8")
@ -218,6 +227,15 @@ class ObjectDetails():
self.clear_widget() self.clear_widget()
self.update() self.update()
def live_tracking(self, sender):
if not self.viewing_self:
if not self.app.sideband.is_tracking(self.object_hash):
self.app.sideband.start_tracking(self.object_hash, interval=59, duration=7*24*60*60)
self.screen.ids.track_button.text = "Stop Live Tracking"
else:
self.app.sideband.stop_tracking(self.object_hash)
self.screen.ids.track_button.text = "Start Live Tracking"
def send_update(self): def send_update(self):
if not self.viewing_self: if not self.viewing_self:
result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash) result = self.app.sideband.send_latest_telemetry(to_addr=self.object_hash)
@ -643,10 +661,9 @@ class RVDetails(MDRecycleView):
alt_str = RNS.prettydistance(alt) alt_str = RNS.prettydistance(alt)
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]" formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
if speed != None: if speed != None:
if speed > 0.02: if speed > 0.1:
speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]" speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]"
else: else:
# speed_formatted_values = f"Speed [b]0 Km/h[/b]"
speed_formatted_values = f"Object is [b]stationary[/b]" speed_formatted_values = f"Object is [b]stationary[/b]"
else: else:
speed_formatted_values = None speed_formatted_values = None
@ -972,22 +989,22 @@ MDScreen:
on_release: root.delegate.request_update() on_release: root.delegate.request_update()
disabled: False disabled: False
# MDBoxLayout: MDBoxLayout:
# orientation: "horizontal" orientation: "horizontal"
# spacing: dp(16) spacing: dp(16)
# size_hint_y: None size_hint_y: None
# height: self.minimum_height height: self.minimum_height
# padding: [dp(24), dp(16), dp(24), dp(24)] padding: [dp(24), dp(0), dp(24), dp(24)]
# MDRectangleFlatIconButton: MDRectangleFlatIconButton:
# id: delete_button id: track_button
# icon: "trash-can-outline" icon: "crosshairs-gps"
# text: "Delete All Telemetry" text: "Start Live Tracking"
# padding: [dp(0), dp(14), dp(0), dp(14)] padding: [dp(0), dp(14), dp(0), dp(14)]
# icon_size: dp(24) icon_size: dp(24)
# font_size: dp(16) font_size: dp(16)
# size_hint: [1.0, None] size_hint: [1.0, None]
# on_release: root.delegate.copy_telemetry(self) on_release: root.delegate.live_tracking(self)
# disabled: False disabled: False
""" """

View File

@ -39,13 +39,13 @@ class Utilities():
self.app.root.ids.screen_manager.add_widget(self.screen) self.app.root.ids.screen_manager.add_widget(self.screen)
self.screen.ids.telemetry_scrollview.effect_cls = ScrollEffect self.screen.ids.telemetry_scrollview.effect_cls = ScrollEffect
info = "\nYou can use various RNS utilities from Sideband. " info = "This section contains various utilities and diagnostics tools, "
info += "" info += "that can be helpful while using Sideband and Reticulum."
if self.app.theme_cls.theme_style == "Dark": if self.app.theme_cls.theme_style == "Dark":
info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]" info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]"
self.screen.ids.telemetry_info.text = info self.screen.ids.utilities_info.text = info
### rnstatus screen ### rnstatus screen
@ -151,14 +151,14 @@ MDScreen:
orientation: "vertical" orientation: "vertical"
size_hint_y: None size_hint_y: None
height: self.minimum_height height: self.minimum_height
padding: [dp(28), dp(48), dp(28), dp(16)] padding: [dp(28), dp(32), dp(28), dp(16)]
# MDLabel:
# text: "Utilities & Tools"
# font_style: "H6"
MDLabel: MDLabel:
text: "Utilities & Tools" id: utilities_info
font_style: "H6"
MDLabel:
id: telemetry_info
markup: True markup: True
text: "" text: ""
size_hint_y: None size_hint_y: None
@ -194,6 +194,17 @@ MDScreen:
on_release: root.delegate.logviewer_action(self) on_release: root.delegate.logviewer_action(self)
disabled: False disabled: False
MDRectangleFlatIconButton:
id: advanced_button
icon: "network-pos"
text: "Advanced RNS Configuration"
padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24)
font_size: dp(16)
size_hint: [1.0, None]
on_release: root.delegate.advanced_action(self)
disabled: False
""" """
layout_rnstatus_screen = """ layout_rnstatus_screen = """