Implemented object details view

This commit is contained in:
Mark Qvist 2023-10-24 01:14:59 +02:00
parent 3b0f75e9bc
commit 8fee0bae58
5 changed files with 406 additions and 60 deletions

View File

@ -723,6 +723,8 @@ class SidebandApp(MDApp):
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "w"): if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "w"):
if self.root.ids.screen_manager.current == "conversations_screen": if self.root.ids.screen_manager.current == "conversations_screen":
self.quit_action(self) self.quit_action(self)
elif self.root.ids.screen_manager.current == "object_details_screen":
self.object_details_screen.close_action()
else: else:
self.open_conversations(direction="right") self.open_conversations(direction="right")
if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "s" or text == "d"): if len(modifiers) > 0 and modifiers[0] == 'ctrl' and (text == "s" or text == "d"):
@ -759,6 +761,8 @@ class SidebandApp(MDApp):
self.close_sub_hardware_action() self.close_sub_hardware_action()
elif self.root.ids.screen_manager.current == "hardware_serial_screen": elif self.root.ids.screen_manager.current == "hardware_serial_screen":
self.close_sub_hardware_action() self.close_sub_hardware_action()
if self.root.ids.screen_manager.current == "object_details_screen":
self.object_details_screen.close_action()
else: else:
self.open_conversations(direction="right") self.open_conversations(direction="right")
@ -847,14 +851,14 @@ class SidebandApp(MDApp):
Clock.schedule_once(cb, 0.15) Clock.schedule_once(cb, 0.15)
Clock.schedule_once(cbu, 0.15+0.25) Clock.schedule_once(cbu, 0.15+0.25)
def open_conversation(self, context_dest): def open_conversation(self, context_dest, direction="left"):
self.outbound_mode_paper = False self.outbound_mode_paper = 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:
self.outbound_mode_propagation = False self.outbound_mode_propagation = False
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = direction
self.messages_view = Messages(self, context_dest) self.messages_view = Messages(self, context_dest)
self.messages_view.ids.messages_scrollview.effect_cls = ScrollEffect self.messages_view.ids.messages_scrollview.effect_cls = ScrollEffect
@ -952,6 +956,10 @@ class SidebandApp(MDApp):
context_dest = self.messages_view.ids.messages_scrollview.active_conversation context_dest = self.messages_view.ids.messages_scrollview.active_conversation
self.map_show_peer_location(context_dest) self.map_show_peer_location(context_dest)
def peer_show_telemetry_action(self, sender):
if self.root.ids.screen_manager.current == "messages_screen":
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_paper: if self.outbound_mode_paper:
self.outbound_mode_paper = False self.outbound_mode_paper = False
@ -1248,10 +1256,7 @@ class SidebandApp(MDApp):
def lj(): def lj():
import webbrowser import webbrowser
webbrowser.open("https://unsigned.io/donate") webbrowser.open("https://unsigned.io/donate")
if not RNS.vendor.platformutils.is_android(): threading.Thread(target=lj, daemon=True).start()
threading.Thread(target=lj, daemon=True).start()
else:
lj()
self.information_screen.ids.information_scrollview.effect_cls = ScrollEffect self.information_screen.ids.information_scrollview.effect_cls = ScrollEffect
self.information_screen.ids.information_logo.icon = self.sideband.asset_dir+"/rns_256.png" self.information_screen.ids.information_logo.icon = self.sideband.asset_dir+"/rns_256.png"
@ -2935,6 +2940,17 @@ class SidebandApp(MDApp):
self.telemetry_screen.ids.telemetry_s_location.active = self.sideband.config["telemetry_s_location"] self.telemetry_screen.ids.telemetry_s_location.active = self.sideband.config["telemetry_s_location"]
self.telemetry_screen.ids.telemetry_s_location.bind(active=self.telemetry_location_toggle) self.telemetry_screen.ids.telemetry_s_location.bind(active=self.telemetry_location_toggle)
self.telemetry_screen.ids.telemetry_s_fixed_location.active = self.sideband.config["telemetry_s_fixed_location"]
self.telemetry_screen.ids.telemetry_s_fixed_location.bind(active=self.telemetry_location_toggle)
self.telemetry_screen.ids.telemetry_s_fixed_latlon.bind(focus=self.telemetry_save)
self.telemetry_screen.ids.telemetry_s_fixed_altitude.bind(focus=self.telemetry_save)
self.telemetry_screen.ids.telemetry_s_fixed_altitude.text = str(self.sideband.config["telemetry_s_fixed_altitude"])
try:
lat = self.sideband.config["telemetry_s_fixed_latlon"][0]; lon = self.sideband.config["telemetry_s_fixed_latlon"][1]
except:
lat = 0.0; lon = 0.0
self.telemetry_screen.ids.telemetry_s_fixed_latlon.text = f"{lat}, {lon}"
self.telemetry_screen.ids.telemetry_s_battery.active = self.sideband.config["telemetry_s_battery"] self.telemetry_screen.ids.telemetry_s_battery.active = self.sideband.config["telemetry_s_battery"]
self.telemetry_screen.ids.telemetry_s_battery.bind(active=self.telemetry_save) self.telemetry_screen.ids.telemetry_s_battery.bind(active=self.telemetry_save)
@ -2986,6 +3002,14 @@ class SidebandApp(MDApp):
self.sideband.stop_telemetry() self.sideband.stop_telemetry()
def telemetry_location_toggle(self, sender=None, event=None): def telemetry_location_toggle(self, sender=None, event=None):
if sender == self.telemetry_screen.ids.telemetry_s_location:
if self.telemetry_screen.ids.telemetry_s_location.active:
self.telemetry_screen.ids.telemetry_s_fixed_location.active = False
if sender == self.telemetry_screen.ids.telemetry_s_fixed_location:
if self.telemetry_screen.ids.telemetry_s_fixed_location.active:
self.telemetry_screen.ids.telemetry_s_location.active = False
if self.telemetry_screen.ids.telemetry_s_location.active: if self.telemetry_screen.ids.telemetry_s_location.active:
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
if not check_permission("android.permission.ACCESS_COARSE_LOCATION") or not check_permission("android.permission.ACCESS_FINE_LOCATION"): if not check_permission("android.permission.ACCESS_COARSE_LOCATION") or not check_permission("android.permission.ACCESS_FINE_LOCATION"):
@ -3012,6 +3036,7 @@ class SidebandApp(MDApp):
self.sideband.config["telemetry_send_appearance"] = self.telemetry_screen.ids.telemetry_send_appearance.active self.sideband.config["telemetry_send_appearance"] = self.telemetry_screen.ids.telemetry_send_appearance.active
self.sideband.config["telemetry_s_location"] = self.telemetry_screen.ids.telemetry_s_location.active self.sideband.config["telemetry_s_location"] = self.telemetry_screen.ids.telemetry_s_location.active
self.sideband.config["telemetry_s_fixed_location"] = self.telemetry_screen.ids.telemetry_s_fixed_location.active
self.sideband.config["telemetry_s_battery"] = self.telemetry_screen.ids.telemetry_s_battery.active self.sideband.config["telemetry_s_battery"] = self.telemetry_screen.ids.telemetry_s_battery.active
self.sideband.config["telemetry_s_pressure"] = self.telemetry_screen.ids.telemetry_s_barometer.active self.sideband.config["telemetry_s_pressure"] = self.telemetry_screen.ids.telemetry_s_barometer.active
self.sideband.config["telemetry_s_temperature"] = self.telemetry_screen.ids.telemetry_s_temperature.active self.sideband.config["telemetry_s_temperature"] = self.telemetry_screen.ids.telemetry_s_temperature.active
@ -3022,6 +3047,28 @@ class SidebandApp(MDApp):
self.sideband.config["telemetry_s_angular_velocity"] = self.telemetry_screen.ids.telemetry_s_gyroscope.active 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_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_proximity"] = self.telemetry_screen.ids.telemetry_s_proximity.active
try:
alt = float(self.telemetry_screen.ids.telemetry_s_fixed_altitude.text.strip().replace(" ", ""))
self.telemetry_screen.ids.telemetry_s_fixed_altitude.text = str(alt)
self.sideband.config["telemetry_s_fixed_altitude"] = alt
except:
self.telemetry_screen.ids.telemetry_s_fixed_altitude.text = str(self.sideband.config["telemetry_s_fixed_altitude"])
try:
s = self.telemetry_screen.ids.telemetry_s_fixed_latlon.text
l = s.strip().replace(" ","").split(",")
lat = float(l[0]); lon = float(l[1])
self.sideband.config["telemetry_s_fixed_latlon"] = [lat, lon]
self.telemetry_screen.ids.telemetry_s_fixed_latlon.text = f"{lat}, {lon}"
except:
try:
lat = self.sideband.config["telemetry_s_fixed_latlon"][0]
lon = self.sideband.config["telemetry_s_fixed_latlon"][1]
self.telemetry_screen.ids.telemetry_s_fixed_latlon.text = f"{lat}, {lon}"
except:
self.sideband.config["telemetry_s_fixed_latlon"] = [0.0, 0.0]
self.telemetry_screen.ids.telemetry_s_fixed_latlon.text = "0.0, 0.0"
self.sideband.save_configuration() self.sideband.save_configuration()
self.sideband.setstate("app.flags.last_telemetry", time.time()) self.sideband.setstate("app.flags.last_telemetry", time.time())
@ -3205,22 +3252,24 @@ class SidebandApp(MDApp):
self.map_action() self.map_action()
self.map_show(location) self.map_show(location)
def map_display_telemetry(self, sender): def map_display_telemetry(self, sender=None):
RNS.log("Display telemetry from "+str(sender), RNS.LOG_WARNING) self.object_details_action(sender)
self.object_details_action()
def close_sub_map_action(self, sender=None): def close_sub_map_action(self, sender=None):
self.map_action(direction="right") self.map_action(direction="right")
def object_details_action(self, sender=None): def object_details_action(self, sender=None, from_conv=False):
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
if self.object_details_screen == None:
self.object_details_screen = ObjectDetails(self)
self.root.ids.screen_manager.current = "object_details_screen" if sender != None and hasattr(sender, "source_dest") and sender.source_dest != None:
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current) if self.object_details_screen == None:
self.object_details_screen = ObjectDetails(self)
self.object_details_screen.set_source(sender.source_dest, from_conv=from_conv)
self.root.ids.screen_manager.current = "object_details_screen"
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def map_create_marker(self, source, telemetry, appearance): def map_create_marker(self, source, telemetry, appearance):
try: try:
@ -3228,6 +3277,7 @@ class SidebandApp(MDApp):
a_icon = appearance[0] a_icon = appearance[0]
a_fg = appearance[1]; a_bg = appearance[2] a_fg = appearance[1]; a_bg = appearance[2]
marker = CustomMapMarker(lat=l["latitude"], lon=l["longtitude"], icon_bg=a_bg) marker = CustomMapMarker(lat=l["latitude"], lon=l["longtitude"], icon_bg=a_bg)
marker.app = self
marker.source_dest = source marker.source_dest = source
marker.location_time = l["last_update"] marker.location_time = l["last_update"]
marker.icon = MDMapIconButton( marker.icon = MDMapIconButton(
@ -3237,6 +3287,7 @@ class SidebandApp(MDApp):
on_release=self.map_display_telemetry, on_release=self.map_display_telemetry,
) )
marker.icon._default_icon_pad = dp(16) marker.icon._default_icon_pad = dp(16)
marker.icon.source_dest = marker.source_dest
marker.add_widget(marker.icon) marker.add_widget(marker.icon)
######## ########

View File

@ -472,6 +472,8 @@ class SidebandCore():
self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2] self.config["telemetry_bg"] = SidebandCore.DEFAULT_APPEARANCE[2]
if not "telemetry_send_appearance" in self.config: if not "telemetry_send_appearance" in self.config:
self.config["telemetry_send_appearance"] = False self.config["telemetry_send_appearance"] = False
if not "telemetry_display_trusted_only" in self.config:
self.config["telemetry_display_trusted_only"] = False
if not "telemetry_s_location" in self.config: if not "telemetry_s_location" in self.config:
self.config["telemetry_s_location"] = False self.config["telemetry_s_location"] = False
@ -495,8 +497,12 @@ class SidebandCore():
self.config["telemetry_s_acceleration"] = False self.config["telemetry_s_acceleration"] = False
if not "telemetry_s_proximity" in self.config: if not "telemetry_s_proximity" in self.config:
self.config["telemetry_s_proximity"] = False self.config["telemetry_s_proximity"] = False
if not "telemetry_display_trusted_only" in self.config: if not "telemetry_s_fixed_location" in self.config:
self.config["telemetry_display_trusted_only"] = False self.config["telemetry_s_fixed_location"] = False
if not "telemetry_s_fixed_latlon" in self.config:
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 "map_history_limit" in self.config: if not "map_history_limit" in self.config:
self.config["map_history_limit"] = 7*24*60*60 self.config["map_history_limit"] = 7*24*60*60
@ -671,6 +677,8 @@ class SidebandCore():
return self._db_get_appearance(context_dest) or SidebandCore.DEFAULT_APPEARANCE return self._db_get_appearance(context_dest) or SidebandCore.DEFAULT_APPEARANCE
def peer_display_name(self, context_dest): def peer_display_name(self, context_dest):
if context_dest == self.lxmf_destination.hash:
return self.config["display_name"]
try: try:
existing_conv = self._db_conversation(context_dest) existing_conv = self._db_conversation(context_dest)
if existing_conv != None: if existing_conv != None:
@ -753,10 +761,20 @@ class SidebandCore():
return self._db_telemetry(context_dest = context_dest, after = after, before = before, limit = limit) or [] return self._db_telemetry(context_dest = context_dest, after = after, before = before, limit = limit) or []
def peer_telemetry(self, context_dest, after = None, before = None, limit = None): def peer_telemetry(self, context_dest, after = None, before = None, limit = None):
pts = self._db_telemetry(context_dest, after = after, before = before, limit = limit) if context_dest == self.lxmf_destination.hash and limit == 1:
if pts != None: try:
if context_dest in pts: return [[self.latest_telemetry["time"]["utc"], self.latest_packed_telemetry]]
return pts[context_dest] except:
RNS.log("An error occurred while retrieving telemetry from the database: "+str(e), RNS.LOG_ERROR)
return []
try:
pts = self._db_telemetry(context_dest, after = after, before = before, limit = limit)
if pts != None:
if context_dest in pts:
return pts[context_dest]
except Exception as e:
RNS.log("An error occurred while retrieving telemetry from the database: "+str(e), RNS.LOG_ERROR)
return [] return []
@ -1142,8 +1160,10 @@ class SidebandCore():
dbc = db.cursor() dbc = db.cursor()
# TODO: Implement limit # TODO: Implement limit
limit_part = ""
order_part = " order by ts DESC" if limit:
limit_part = " LIMIT "+str(int(limit))
order_part = " order by ts DESC"+limit_part
if context_dest == None: if context_dest == None:
if after != None and before == None: if after != None and before == None:
query = "select * from telemetry where ts>:after_ts"+order_part query = "select * from telemetry where ts>:after_ts"+order_part
@ -1211,6 +1231,7 @@ class SidebandCore():
if "snr" in physical_link: remote_telemeter.sensors["physical_link"].snr = physical_link["snr"] if "snr" in physical_link: remote_telemeter.sensors["physical_link"].snr = physical_link["snr"]
if "q" in physical_link: remote_telemeter.sensors["physical_link"].q = physical_link["q"] if "q" in physical_link: remote_telemeter.sensors["physical_link"].q = physical_link["q"]
remote_telemeter.sensors["physical_link"].update_data() remote_telemeter.sensors["physical_link"].update_data()
telemetry = remote_telemeter.packed()
query = "INSERT INTO telemetry (dest_context, ts, data) values (?, ?, ?)" query = "INSERT INTO telemetry (dest_context, ts, data) values (?, ?, ?)"
data = (context_dest, telemetry_timestamp, telemetry) data = (context_dest, telemetry_timestamp, telemetry)
@ -1245,22 +1266,26 @@ class SidebandCore():
db.commit() db.commit()
def _db_get_appearance(self, context_dest): def _db_get_appearance(self, context_dest):
conv = self._db_conversation(context_dest) if context_dest == self.lxmf_destination.hash:
data_dict = conv["data"] return [self.config["telemetry_icon"], self.config["telemetry_fg"], self.config["telemetry_bg"]]
try: else:
if data_dict != None and "appearance" in data_dict: conv = self._db_conversation(context_dest)
def htf(cbytes): if conv != None and "data" in conv:
d = 1.0/255.0 data_dict = conv["data"]
r = round(struct.unpack("!B", bytes([cbytes[0]]))[0]*d, 4) try:
g = round(struct.unpack("!B", bytes([cbytes[1]]))[0]*d, 4) if data_dict != None and "appearance" in data_dict:
b = round(struct.unpack("!B", bytes([cbytes[2]]))[0]*d, 4) def htf(cbytes):
return [r,g,b] d = 1.0/255.0
r = round(struct.unpack("!B", bytes([cbytes[0]]))[0]*d, 4)
g = round(struct.unpack("!B", bytes([cbytes[1]]))[0]*d, 4)
b = round(struct.unpack("!B", bytes([cbytes[2]]))[0]*d, 4)
return [r,g,b]
appearance = [data_dict["appearance"][0], htf(data_dict["appearance"][1]), htf(data_dict["appearance"][2])] appearance = [data_dict["appearance"][0], htf(data_dict["appearance"][1]), htf(data_dict["appearance"][2])]
return appearance return appearance
except Exception as e: except Exception as e:
RNS.log("Could not retrieve appearance for "+RNS.prettyhexrep(context_dest)+": "+str(e), RNS.LOG_ERROR) RNS.log("Could not retrieve appearance for "+RNS.prettyhexrep(context_dest)+": "+str(e), RNS.LOG_ERROR)
return None return None
@ -1744,6 +1769,13 @@ class SidebandCore():
self.telemeter.enable(sensor) self.telemeter.enable(sensor)
else: else:
self.telemeter.disable(sensor) self.telemeter.disable(sensor)
if self.config["telemetry_s_fixed_location"]:
self.telemeter.synthesize("location")
self.telemeter.sensors["location"].latitude = self.config["telemetry_s_fixed_latlon"][0]
self.telemeter.sensors["location"].longtitude = self.config["telemetry_s_fixed_latlon"][1]
self.telemeter.sensors["location"].altitude = self.config["telemetry_s_fixed_altitude"]
def get_telemetry(self): def get_telemetry(self):
if self.config["telemetry_enabled"] == True: if self.config["telemetry_enabled"] == True:

View File

@ -868,6 +868,7 @@ MDScreen:
[ [
['arrow-down-bold-hexagon-outline', lambda x: root.app.telemetry_request_action(self)], ['arrow-down-bold-hexagon-outline', lambda x: root.app.telemetry_request_action(self)],
['upload-lock', lambda x: root.app.telemetry_send_update(self)], ['upload-lock', lambda x: root.app.telemetry_send_update(self)],
['wrench-cog', lambda x: root.app.close_any_action(self)],
['close', lambda x: root.app.close_any_action(self)], ['close', lambda x: root.app.close_any_action(self)],
] ]
@ -1323,6 +1324,44 @@ MDScreen:
pos_hint: {"center_y": 0.3} pos_hint: {"center_y": 0.3}
active: False active: False
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(24),dp(0)]
height: dp(48)
MDLabel:
text: "Fixed Location"
font_style: "H6"
MDSwitch:
id: telemetry_s_fixed_location
pos_hint: {"center_y": 0.3}
active: False
MDBoxLayout:
id: telemetry_fixed_location_fields
orientation: "horizontal"
size_hint_y: None
spacing: dp(16)
height: dp(64)
padding: [0, dp(0), 0, dp(0)]
# md_bg_color: [1,0,0,1]
MDTextField:
id: telemetry_s_fixed_latlon
size_hint: [0.618, None]
hint_text: "Latitude, longtitude"
text: ""
font_size: dp(24)
MDTextField:
id: telemetry_s_fixed_altitude
size_hint: [0.382, None]
hint_text: "Altitude"
text: ""
font_size: dp(24)
MDLabel: MDLabel:
markup: True markup: True
text: "\\n" text: "\\n"

View File

@ -41,6 +41,7 @@ class Messages():
def __init__(self, app, context_dest): def __init__(self, app, context_dest):
self.app = app self.app = app
self.context_dest = context_dest self.context_dest = context_dest
self.source_dest = context_dest
self.screen = self.app.root.ids.screen_manager.get_screen("messages_screen") self.screen = self.app.root.ids.screen_manager.get_screen("messages_screen")
self.ids = self.screen.ids self.ids = self.screen.ids
@ -302,9 +303,15 @@ class Messages():
def x(): def x():
try: try:
telemeter = Telemeter.from_packed(packed_telemetry) telemeter = Telemeter.from_packed(packed_telemetry)
tlm = telemeter.read_all()
if extra_telemetry and len(extra_telemetry) != 0: if extra_telemetry and len(extra_telemetry) != 0:
tlm["physical_link"] = extra_telemetry physical_link = extra_telemetry
telemeter.synthesize("physical_link")
if "rssi" in physical_link: telemeter.sensors["physical_link"].rssi = physical_link["rssi"]
if "snr" in physical_link: telemeter.sensors["physical_link"].snr = physical_link["snr"]
if "quality" in physical_link: telemeter.sensors["physical_link"].q = physical_link["quality"]
telemeter.sensors["physical_link"].update_data()
tlm = telemeter.read_all()
Clipboard.copy(str(tlm)) Clipboard.copy(str(tlm))
item.dmenu.dismiss() item.dmenu.dismiss()
except Exception as e: except Exception as e:
@ -598,6 +605,7 @@ MDScreen:
[['menu', lambda x: root.app.nav_drawer.set_state("open")],] [['menu', lambda x: root.app.nav_drawer.set_state("open")],]
right_action_items: right_action_items:
[ [
['map-marker-path', lambda x: root.app.peer_show_telemetry_action(self)],
['map-search', lambda x: root.app.peer_show_location_action(self)], ['map-search', lambda x: root.app.peer_show_location_action(self)],
['lan-connect', lambda x: root.app.message_propagation_action(self)], ['lan-connect', lambda x: root.app.message_propagation_action(self)],
['close', lambda x: root.app.close_settings_action(self)], ['close', lambda x: root.app.close_settings_action(self)],

View File

@ -3,6 +3,17 @@ import RNS
from kivy.metrics import dp,sp from kivy.metrics import dp,sp
from kivy.lang.builder import Builder from kivy.lang.builder import Builder
from kivy.core.clipboard import Clipboard
from kivymd.uix.recycleview import MDRecycleView
from kivymd.uix.list import OneLineIconListItem
from kivy.properties import StringProperty, BooleanProperty
from kivy.effects.scroll import ScrollEffect
from sideband.sense import Telemeter
import threading
import webbrowser
from datetime import datetime
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
from ui.helpers import ts_format from ui.helpers import ts_format
@ -14,13 +25,60 @@ class ObjectDetails():
self.app = app self.app = app
self.widget = None self.widget = None
self.object_hash = object_hash self.object_hash = object_hash
self.coords = None
self.raw_telemetry = None
self.from_conv = False
if not self.app.root.ids.screen_manager.has_screen("object_details_screen"): if not self.app.root.ids.screen_manager.has_screen("object_details_screen"):
self.screen = Builder.load_string(layou_object_details) self.screen = Builder.load_string(layou_object_details)
self.screen.app = self.app self.screen.app = self.app
self.screen.delegate = self
self.ids = self.screen.ids self.ids = self.screen.ids
self.app.root.ids.screen_manager.add_widget(self.screen) self.app.root.ids.screen_manager.add_widget(self.screen)
self.screen.ids.object_details_scrollview.effect_cls = ScrollEffect
self.telemetry_list = RVDetails()
self.telemetry_list.delegate = self
self.telemetry_list.app = self.app
self.screen.ids.object_details_scrollview.add_widget(self.telemetry_list)
def close_action(self, sender=None):
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):
self.object_hash = source_dest
if from_conv:
self.from_conv = True
else:
self.from_conv = False
self.coords = None
self.telemetry_list.data = []
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.coordinates_button.disabled = True
self.screen.ids.object_appearance.icon = appearance[0]
self.screen.ids.object_appearance.icon_color = appearance[1]
self.screen.ids.object_appearance.md_bg_color = appearance[2]
latest_telemetry = self.app.sideband.peer_telemetry(source_dest, limit=1)
if latest_telemetry != None and len(latest_telemetry) > 0:
telemeter = Telemeter.from_packed(latest_telemetry[0][1])
self.raw_telemetry = telemeter.read_all()
rendered_telemetry = telemeter.render()
if "location" in telemeter.sensors:
self.screen.ids.coordinates_button.disabled = False
self.telemetry_list.update_source(rendered_telemetry)
self.screen.ids.telemetry_button.disabled = False
else:
self.screen.ids.telemetry_button.disabled = True
self.telemetry_list.update_source(None)
def reload(self): def reload(self):
self.clear_widget() self.clear_widget()
self.update() self.update()
@ -40,7 +98,131 @@ class ObjectDetails():
def get_widget(self): def get_widget(self):
return self.widget return self.widget
def copy_coordinates(self, sender=None):
Clipboard.copy(str(self.coords or "No data"))
def copy_telemetry(self, sender=None):
Clipboard.copy(str(self.raw_telemetry or "No data"))
class ODView(OneLineIconListItem):
icon = StringProperty()
def __init__(self):
super().__init__()
class RVDetails(MDRecycleView):
def __init__(self):
super().__init__()
self.data = []
def update_source(self, rendered_telemetry=None):
if not rendered_telemetry:
rendered_telemetry = []
sort = {
"Physical Link": 10,
"Location": 20,
"Ambient Light": 30,
"Ambient Temperature": 40,
"Relative Humidity": 50,
"Ambient Pressure": 60,
"Battery": 70,
"Timestamp": 80,
}
self.entries = []
rendered_telemetry.sort(key=lambda s: sort[s["name"]] if s["name"] in sort else 1000)
for s in rendered_telemetry:
extra_entries = []
release_function = None
name = s["name"]
if name == "Timestamp":
ts = s["values"]["UTC"]
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 == "Battery":
p = s["values"]["percent"]
cs = s["values"]["_meta"]
formatted_values = f"{name}: [b]{p}%[/b] ({cs})"
elif name == "Ambient Pressure":
p = s["values"]["mbar"]
formatted_values = f"{name}: [b]{p} mbar[/b]"
elif name == "Ambient Temperature":
c = s["values"]["c"]
formatted_values = f"{name}: [b]{c}° C[/b]"
elif name == "Relative Humidity":
r = s["values"]["percent"]
formatted_values = f"{name}: [b]{r}%[/b]"
elif name == "Physical Link":
rssi = s["values"]["rssi"]
snr = s["values"]["snr"]
q = s["values"]["q"]
formatted_values = f"Link Quality: [b]{q}%[/b], RSSI: [b]{rssi} dBm[/b], SNR: [b]{snr} dB[/b]"
elif name == "Location":
lat = s["values"]["latitude"]
lon = s["values"]["longtitude"]
alt = s["values"]["altitude"]
speed = s["values"]["speed"]
bearing = s["values"]["bearing"]
accuracy = s["values"]["accuracy"]
updated = s["values"]["updated"]
updated_str = f", Logged: [b]{RNS.prettytime(time.time()-updated, compact=True)} ago[/b]"
if speed > 0.01:
speed_str = ", Speed: [b]{speed} Km/h[/b]"
else:
speed_str = ""
coords = f"{lat}, {lon}"
self.delegate.coords = coords
formatted_values = f"Coordinates: [b]{coords}[/b], Altitude: [b]{alt} meters[/b]"+speed_str+f", Bearing: [b]{bearing}°[/b]"
extra_formatted_values = f"Uncertainty: [b]{accuracy} meters[/b]"+updated_str
data = {"icon": s["icon"], "text": f"{formatted_values}"}
extra_entries.append({"icon": "map-marker-question", "text": extra_formatted_values})
def select(e=None):
geo_uri = f"geo:{lat},{lon}"
def lj():
webbrowser.open(geo_uri)
threading.Thread(target=lj, daemon=True).start()
release_function = select
else:
formatted_values = f"{name}:"
for vn in s["values"]:
v = s["values"][vn]
formatted_values += f" [b]{v} {vn}[/b]"
if release_function:
data = {"icon": s["icon"], "text": f"{formatted_values}", "on_release": release_function}
else:
data = {"icon": s["icon"], "text": f"{formatted_values}"}
self.entries.append(data)
for extra in extra_entries:
self.entries.append(extra)
if len(self.entries) == 0:
self.entries.append({"icon": "account-question-outline", "text": f"No information known about this peer"})
self.data = self.entries
layou_object_details = """ layou_object_details = """
#:import MDLabel kivymd.uix.label.MDLabel
#:import OneLineIconListItem kivymd.uix.list.OneLineIconListItem
#:import Button kivy.uix.button.Button
<ODView>
IconLeftWidget:
icon: root.icon
<RVDetails>:
viewclass: "ODView"
RecycleBoxLayout:
default_size: None, dp(50)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
MDScreen: MDScreen:
name: "object_details_screen" name: "object_details_screen"
@ -48,6 +230,7 @@ MDScreen:
orientation: "vertical" orientation: "vertical"
MDTopAppBar: MDTopAppBar:
id: details_bar
title: "Details" title: "Details"
anchor_title: "left" anchor_title: "left"
elevation: 0 elevation: 0
@ -55,29 +238,62 @@ MDScreen:
[['menu', lambda x: root.app.nav_drawer.set_state("open")]] [['menu', lambda x: root.app.nav_drawer.set_state("open")]]
right_action_items: right_action_items:
[ [
['close', lambda x: root.app.close_sub_map_action(self)], ['close', lambda x: root.delegate.close_action()],
] ]
MDBoxLayout:
id: object_header
orientation: "horizontal"
spacing: dp(24)
size_hint_y: None
height: self.minimum_height
padding: dp(24)
MDIconButton:
id: object_appearance
icon: "map-marker-star-outline"
icon_color: [0,0,0,1]
md_bg_color: [1,1,1,1]
theme_icon_color: "Custom"
icon_size: dp(32)
MDLabel:
id: name_label
markup: True
text: "Object Name"
font_style: "H6"
MDBoxLayout:
id: object_header
orientation: "horizontal"
spacing: dp(24)
size_hint_y: None
height: self.minimum_height
padding: [dp(24), dp(0), dp(24), dp(12)]
MDRectangleFlatIconButton:
id: telemetry_button
icon: "content-copy"
text: "Copy 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.delegate.copy_telemetry(self)
disabled: False
MDRectangleFlatIconButton:
id: coordinates_button
icon: "map-marker-outline"
text: "Copy Coordinates"
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.copy_coordinates(self)
disabled: False
ScrollView: ScrollView:
id: object_details_scrollview id: object_details_scrollview
MDBoxLayout:
orientation: "vertical"
spacing: dp(48)
size_hint_y: None
height: self.minimum_height
padding: [dp(28), dp(48), dp(28), dp(16)]
MDLabel:
id: name_label
markup: True
text: "Object Name"
font_style: "H6"
MDLabel:
id: test_label
markup: True
text: "Test"
font_style: "H6"
""" """