Added received distance to message display, added custom info and receiver metrics to object display

This commit is contained in:
Mark Qvist 2023-10-25 23:21:06 +02:00
parent 6921a12f59
commit e3b857256f
5 changed files with 224 additions and 42 deletions

View File

@ -2981,6 +2981,12 @@ class SidebandApp(MDApp):
self.telemetry_screen.ids.telemetry_s_proximity.active = self.sideband.config["telemetry_s_proximity"]
self.telemetry_screen.ids.telemetry_s_proximity.bind(active=self.telemetry_save)
self.telemetry_screen.ids.telemetry_s_information.active = self.sideband.config["telemetry_s_information"]
self.telemetry_screen.ids.telemetry_s_information.bind(active=self.telemetry_save)
self.telemetry_screen.ids.telemetry_s_information_text.text = str(self.sideband.config["telemetry_s_information_text"])
self.telemetry_screen.ids.telemetry_s_information_text.bind(focus=self.telemetry_save)
self.telemetry_ready = True
@ -3048,6 +3054,8 @@ class SidebandApp(MDApp):
self.sideband.config["telemetry_s_angular_velocity"] = self.telemetry_screen.ids.telemetry_s_gyroscope.active
self.sideband.config["telemetry_s_acceleration"] = self.telemetry_screen.ids.telemetry_s_accelerometer.active
self.sideband.config["telemetry_s_proximity"] = self.telemetry_screen.ids.telemetry_s_proximity.active
self.sideband.config["telemetry_s_information"] = self.telemetry_screen.ids.telemetry_s_information.active
self.sideband.config["telemetry_s_information_text"] = self.telemetry_screen.ids.telemetry_s_information_text.text
run_telemetry_update = False
try:
@ -3264,18 +3272,27 @@ class SidebandApp(MDApp):
def map_display_telemetry(self, sender=None):
self.object_details_action(sender)
def map_display_own_telemetry(self, sender=None):
self.object_details_action(source_dest=self.sideband.lxmf_destination.hash,from_telemetry=True)
def close_sub_map_action(self, sender=None):
self.map_action(direction="right")
def object_details_action(self, sender=None, from_conv=False):
def object_details_action(self, sender=None, from_conv=False, from_telemetry=False, source_dest=None):
self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.nav_drawer.set_state("closed")
if sender != None and hasattr(sender, "source_dest") and sender.source_dest != None:
if source_dest != None:
telemetry_source = source_dest
else:
if sender != None and hasattr(sender, "source_dest") and sender.source_dest != None:
telemetry_source = sender.source_dest
if telemetry_source != None:
if self.object_details_screen == None:
self.object_details_screen = ObjectDetails(self)
Clock.schedule_once(lambda dt: self.object_details_screen.set_source(sender.source_dest, from_conv=from_conv), 0.0)
Clock.schedule_once(lambda dt: self.object_details_screen.set_source(telemetry_source, from_conv=from_conv, from_telemetry=from_telemetry), 0.0)
def vj(dt):
self.root.ids.screen_manager.current = "object_details_screen"

View File

@ -503,6 +503,10 @@ class SidebandCore():
self.config["telemetry_s_fixed_latlon"] = [0.0, 0.0]
if not "telemetry_s_fixed_altitude" in self.config:
self.config["telemetry_s_fixed_altitude"] = 0.0
if not "telemetry_s_information" in self.config:
self.config["telemetry_s_information"] = False
if not "telemetry_s_information_text" in self.config:
self.config["telemetry_s_information_text"] = ""
if not "map_history_limit" in self.config:
self.config["map_history_limit"] = 7*24*60*60
@ -1216,7 +1220,7 @@ class SidebandCore():
return results
def _db_save_telemetry(self, context_dest, telemetry, physical_link = None):
def _db_save_telemetry(self, context_dest, telemetry, physical_link = None, source_dest = None):
try:
remote_telemeter = Telemeter.from_packed(telemetry)
telemetry_timestamp = remote_telemeter.read_all()["time"]["utc"]
@ -1238,13 +1242,36 @@ class SidebandCore():
if "q" in physical_link: remote_telemeter.sensors["physical_link"].q = physical_link["q"]
remote_telemeter.sensors["physical_link"].update_data()
telemetry = remote_telemeter.packed()
if source_dest != None:
remote_telemeter.synthesize("received")
remote_telemeter.sensors["received"].by = self.lxmf_destination.hash
remote_telemeter.sensors["received"].via = source_dest
rl = remote_telemeter.read("location")
if rl and "latitude" in rl and "longtitude" in rl and "altitude" in rl:
if self.latest_telemetry != None and "location" in self.latest_telemetry:
ol = self.latest_telemetry["location"]
if "latitude" in ol and "longtitude" in ol and "altitude" in ol:
olat = ol["latitude"]; olon = ol["longtitude"]; oalt = ol["altitude"]
rlat = rl["latitude"]; rlon = rl["longtitude"]; ralt = rl["altitude"]
if olat != None and olon != None and oalt != None:
if rlat != None and rlon != None and ralt != None:
remote_telemeter.sensors["received"].set_distance(
(olat, olon, oalt), (rlat, rlon, ralt)
)
remote_telemeter.sensors["received"].update_data()
telemetry = remote_telemeter.packed()
query = "INSERT INTO telemetry (dest_context, ts, data) values (?, ?, ?)"
data = (context_dest, telemetry_timestamp, telemetry)
dbc.execute(query, data)
db.commit()
self.setstate("app.flags.last_telemetry", time.time())
return telemetry
except Exception as e:
RNS.log("An error occurred while saving telemetry to database: "+str(e), RNS.LOG_ERROR)
self.db = None
@ -1632,6 +1659,19 @@ class SidebandCore():
def _db_save_lxm(self, lxm, context_dest, originator = False):
state = lxm.state
packed_telemetry = None
if not originator and lxm.fields != None:
if LXMF.FIELD_ICON_APPEARANCE in lxm.fields:
self._db_update_appearance(context_dest, lxm.timestamp, lxm.fields[LXMF.FIELD_ICON_APPEARANCE])
if LXMF.FIELD_TELEMETRY in lxm.fields:
physical_link = {}
if lxm.rssi or lxm.snr or lxm.q:
physical_link["rssi"] = lxm.rssi
physical_link["snr"] = lxm.snr
physical_link["q"] = lxm.q
packed_telemetry = self._db_save_telemetry(context_dest, lxm.fields[LXMF.FIELD_TELEMETRY], physical_link=physical_link, source_dest=context_dest)
db = self.__db_connect()
dbc = db.cursor()
@ -1648,6 +1688,10 @@ class SidebandCore():
extras["rssi"] = lxm.rssi
extras["snr"] = lxm.snr
extras["q"] = lxm.q
if packed_telemetry != None:
extras["packed_telemetry"] = packed_telemetry
extras = msgpack.packb(extras)
query = "INSERT INTO lxm (lxm_hash, dest, source, title, tx_ts, rx_ts, state, method, t_encrypted, t_encryption, data, extra) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
@ -1669,18 +1713,6 @@ class SidebandCore():
dbc.execute(query, data)
db.commit()
if not originator and lxm.fields != None:
if LXMF.FIELD_ICON_APPEARANCE in lxm.fields:
self._db_update_appearance(context_dest, lxm.timestamp, lxm.fields[LXMF.FIELD_ICON_APPEARANCE])
if LXMF.FIELD_TELEMETRY in lxm.fields:
physical_link = {}
if lxm.rssi or lxm.snr or lxm.q:
physical_link["rssi"] = lxm.rssi
physical_link["snr"] = lxm.snr
physical_link["q"] = lxm.q
self._db_save_telemetry(context_dest, lxm.fields[LXMF.FIELD_TELEMETRY], physical_link=physical_link)
self.__event_conversation_changed(context_dest)
def _db_save_announce(self, destination_hash, app_data, dest_type="lxmf.delivery"):
@ -1782,6 +1814,9 @@ class SidebandCore():
self.telemeter.sensors["location"].longtitude = self.config["telemetry_s_fixed_latlon"][1]
self.telemeter.sensors["location"].altitude = self.config["telemetry_s_fixed_altitude"]
if self.config["telemetry_s_information"]:
self.telemeter.synthesize("information")
self.telemeter.sensors["information"].contents = self.config["telemetry_s_information_text"]
def get_telemetry(self):
if self.config["telemetry_enabled"] == True:

View File

@ -927,10 +927,10 @@ MDScreen:
spacing: dp(24)
size_hint_y: None
padding: [dp(0),dp(24),dp(0),dp(0)]
height: dp(160)
height: dp(232)
MDRectangleFlatIconButton:
id: telemetry_icons_button
id: telemetry_send_update_button
icon: "upload-lock"
text: "Send Telemetry Update Now"
padding: [dp(0), dp(14), dp(0), dp(14)]
@ -941,7 +941,7 @@ MDScreen:
disabled: False
MDRectangleFlatIconButton:
id: telemetry_icons_button
id: telemetry_request_button
icon: "arrow-down-bold-hexagon-outline"
text: "Request Telemetry From Collector"
padding: [dp(0), dp(14), dp(0), dp(14)]
@ -952,7 +952,7 @@ MDScreen:
disabled: False
MDRectangleFlatIconButton:
id: telemetry_icons_button
id: telemetry_copy_button
icon: "content-copy"
text: "Copy Telemetry Data To Clipboard"
padding: [dp(0), dp(14), dp(0), dp(14)]
@ -962,6 +962,17 @@ MDScreen:
on_release: root.app.telemetry_copy(self)
disabled: False
MDRectangleFlatIconButton:
id: telemetry_own_button
icon: "database-eye-outline"
text: "Display Own Telemetry"
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.app.map_display_own_telemetry(self)
disabled: False
MDBoxLayout:
id: telemetry_enabled_fields
orientation: "vertical"
@ -1324,6 +1335,37 @@ MDScreen:
pos_hint: {"center_y": 0.3}
active: False
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(24),dp(0)]
height: dp(48)
MDLabel:
text: "Information"
font_style: "H6"
MDSwitch:
id: telemetry_s_information
pos_hint: {"center_y": 0.3}
active: False
MDBoxLayout:
id: telemetry_information_fields
orientation: "horizontal"
size_hint_y: None
spacing: dp(16)
height: dp(64)
padding: [0, dp(0), 0, dp(0)]
MDTextField:
id: telemetry_s_information_text
size_hint: [1.0, None]
hint_text: "Custom information text"
max_text_length: 256
text: ""
font_size: dp(24)
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
@ -1660,7 +1702,7 @@ MDScreen:
height: dp(48)
MDLabel:
text: "Advanced Statistics"
text: "Advanced Metrics"
font_style: "H6"
MDSwitch:

View File

@ -42,6 +42,7 @@ class Messages():
self.app = app
self.context_dest = context_dest
self.source_dest = context_dest
self.is_trusted = self.app.sideband.is_trusted(self.context_dest)
self.screen = self.app.root.ids.screen_manager.get_screen("messages_screen")
self.ids = self.screen.ids
@ -178,6 +179,33 @@ class Messages():
rxstr = time.strftime(ts_format, time.localtime(m["received"]))
titlestr = ""
extra_telemetry = {}
telemeter = None
if "extras" in m and m["extras"] != None and "packed_telemetry" in m["extras"]:
try:
telemeter = Telemeter.from_packed(m["extras"]["packed_telemetry"])
except Exception as e:
pass
if telemeter == None and "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields:
try:
packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY]
telemeter = Telemeter.from_packed(packed_telemetry)
except Exception as e:
pass
rcvd_d_str = ""
trcvd = telemeter.read("received") if telemeter else None
if trcvd and "distance" in trcvd:
d = trcvd["distance"]
if "euclidian" in d:
edst = d["euclidian"]
if edst != None:
rcvd_d_str = " [b]Distance[/b] "+RNS.prettydistance(edst)
elif "geodesic" in d:
gdst = d["geodesic"]
if gdst != None:
rcvd_d_str = " [b]Distance[/b] "+RNS.prettydistance(gdst)
phy_stats_str = ""
if "extras" in m and m["extras"] != None:
@ -238,7 +266,12 @@ class Messages():
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
heading_str += phy_stats_str+"\n"
heading_str += "[b]Received[/b] "+rxstr+"\n[b]Sent[/b] "+txstr
heading_str += "[b]Received[/b] "+rxstr
if rcvd_d_str != "" and self.app.sideband.config["advanced_stats"]:
heading_str += rcvd_d_str
heading_str += "\n[b]Sent[/b] "+txstr
item = ListLXMessageCard(
text=m["content"].decode("utf-8"),
@ -253,6 +286,7 @@ class Messages():
item.ids.content_text.text_color = mt_color
item.ids.msg_submenu.theme_text_color = "Custom"
item.ids.msg_submenu.text_color = mt_color
item.ids.content_text.markup = self.is_trusted
def gen_del(mhash, item):
def x():
@ -299,10 +333,10 @@ class Messages():
return x
def gen_copy_telemetry(packed_telemetry, extra_telemetry, item):
def gen_copy_telemetry(telemeter, extra_telemetry, item):
def x():
try:
telemeter = Telemeter.from_packed(packed_telemetry)
telemeter
if extra_telemetry and len(extra_telemetry) != 0:
physical_link = extra_telemetry
telemeter.synthesize("physical_link")
@ -507,8 +541,7 @@ class Messages():
}
]
else:
if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields:
packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY]
if telemeter != None:
dm_items = [
{
"viewclass": "OneLineListItem",
@ -520,7 +553,7 @@ class Messages():
"viewclass": "OneLineListItem",
"text": "Copy telemetry",
"height": dp(40),
"on_release": gen_copy_telemetry(packed_telemetry, extra_telemetry, item)
"on_release": gen_copy_telemetry(telemeter, extra_telemetry, item)
},
{
"text": "Delete",
@ -694,11 +727,9 @@ Builder.load_string("""
MDLabel:
id: content_text
text: root.text
# adaptive_size: True
markup: False
size_hint_y: None
text_size: self.width, None
# theme_text_color: 'Custom'
# text_color: rgba(255,255,255,216)
height: self.texture_size[1]
<CustomOneLineIconListItem>

View File

@ -4,6 +4,7 @@ import RNS
from kivy.metrics import dp,sp
from kivy.lang.builder import Builder
from kivy.core.clipboard import Clipboard
from kivy.utils import escape_markup
from kivymd.uix.recycleview import MDRecycleView
from kivymd.uix.list import OneLineIconListItem
from kivy.properties import StringProperty, BooleanProperty
@ -28,6 +29,7 @@ class ObjectDetails():
self.object_hash = object_hash
self.coords = None
self.raw_telemetry = None
self.from_telemetry = False
self.from_conv = False
self.viewing_self = False
@ -45,23 +47,31 @@ class ObjectDetails():
self.screen.ids.object_details_container.add_widget(self.telemetry_list)
def close_action(self, sender=None):
if self.from_conv:
self.app.open_conversation(self.object_hash, direction="right")
if self.from_telemetry:
self.app.telemetry_action(direction="right")
else:
self.app.close_sub_map_action()
if self.from_conv:
self.app.open_conversation(self.object_hash, direction="right")
else:
self.app.close_sub_map_action()
def set_source(self, source_dest, from_conv=False):
def set_source(self, source_dest, from_conv=False, from_telemetry=False):
self.object_hash = source_dest
if from_conv:
self.from_conv = True
if from_telemetry:
self.from_telemetry = True
else:
self.from_conv = False
self.from_telemetry = False
if from_conv:
self.from_conv = True
else:
self.from_conv = False
self.coords = None
self.telemetry_list.data = []
pds = self.app.sideband.peer_display_name(source_dest)
appearance = self.app.sideband.peer_appearance(source_dest)
self.screen.ids.name_label.text = self.app.sideband.peer_display_name(source_dest)
self.screen.ids.name_label.text = pds
self.screen.ids.coordinates_button.disabled = True
self.screen.ids.object_appearance.icon = appearance[0]
self.screen.ids.object_appearance.icon_color = appearance[1]
@ -79,6 +89,7 @@ class ObjectDetails():
self.viewing_self = False
else:
self.viewing_self = True
self.screen.ids.name_label.text = pds+" (this device)"
rendered_telemetry = telemeter.render(relative_to=relative_to)
if "location" in telemeter.sensors:
@ -145,7 +156,9 @@ class RVDetails(MDRecycleView):
"Relative Humidity": 50,
"Ambient Pressure": 60,
"Battery": 70,
"Timestamp": 80,
"Timestamp": 80,
"Received": 90,
"Information": 100,
}
self.entries = []
rendered_telemetry.sort(key=lambda s: sort[s["name"]] if s["name"] in sort else 1000)
@ -161,6 +174,37 @@ class RVDetails(MDRecycleView):
if ts != None:
ts_str = datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
formatted_values = f"Recorded [b]{RNS.prettytime(time.time()-ts, compact=True)} ago[/b] ({ts_str})"
elif name == "Information":
info = s["values"]["contents"]
if info != None:
istr = str(info)
external_text = escape_markup(istr)
formatted_values = f"[b]Information[/b]: {external_text}"
elif name == "Received":
formatted_values = ""
by = s["values"]["by"]; by_str = ""
if by != None:
if by == self.app.sideband.lxmf_destination.hash:
by_str = "Directly by [b]this device[/b]"
else:
dstr = self.app.sideband.peer_display_name(by)
by_str = f"By [b]{dstr}[/b]"
formatted_values+=by_str
via = s["values"]["via"]; via_str = ""
if via != None:
if via == self.delegate.object_hash:
via_str = "directly [b]from emitter[/b]"
else:
dstr = self.app.sideband.peer_display_name(by)
via_str = f"via [b]{dstr}[/b]"
if len(formatted_values) != 0: formatted_values += ", "
formatted_values += via_str
if formatted_values != "":
formatted_values = f"Collected {formatted_values}"
else:
formatted_values = None
elif name == "Battery":
p = s["values"]["percent"]
cs = s["values"]["_meta"]
@ -213,7 +257,11 @@ class RVDetails(MDRecycleView):
coords = f"{lat}, {lon}"
fcoords = f"{round(lat,4)}, {round(lon,4)}"
self.delegate.coords = coords
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt} meters[/b]"
if alt == 0:
alt_str = "0"
else:
alt_str = RNS.prettydistance(alt)
formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]"
speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]"
extra_formatted_values = f"Uncertainty [b]{accuracy} meters[/b]"+updated_str
@ -277,14 +325,23 @@ class RVDetails(MDRecycleView):
ah_text = f"Object is [b]{astr}[/b] the horizon (Δ = {dstr}°)"
extra_entries.append({"icon": "angle-acute", "text": ah_text})
if not self.delegate.viewing_self and "radio_horizon" in s["values"]:
orh = s["values"]["radio_horizon"]
if orh != None:
range_text = RNS.prettydistance(orh)
rh_formatted_text = f"Object's radio horizon is [b]{range_text}[/b]"
extra_entries.append({"icon": "radio-tower", "text": rh_formatted_text})
if "radio_horizon" in s:
rh_icon = "circle-outline"
crange_text = RNS.prettydistance(s["radio_horizon"]["combined_range"])
if s["radio_horizon"]["within_range"]:
rh_formatted_text = f"[b]Within[/b] shared radio horizon of [b]{crange_text}[/b]"
rh_icon = "set-none"
else:
rh_formatted_text = f"[b]Outside[/b] shared radio horizon of [b]{crange_text}[/b]"
extra_entries.append({"icon": "radio-tower", "text": rh_formatted_text})
extra_entries.append({"icon": rh_icon, "text": rh_formatted_text})
extra_entries.append({"icon": "speedometer", "text": speed_formatted_values})