2023-10-22 14:01:11 +02:00
|
|
|
import time
|
|
|
|
import RNS
|
|
|
|
|
|
|
|
from kivy.metrics import dp,sp
|
|
|
|
from kivy.lang.builder import Builder
|
2023-10-24 01:14:59 +02:00
|
|
|
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
|
2023-10-24 01:28:49 +02:00
|
|
|
from kivy.clock import Clock
|
2023-10-24 01:14:59 +02:00
|
|
|
from sideband.sense import Telemeter
|
|
|
|
import threading
|
|
|
|
import webbrowser
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
2023-10-22 14:01:11 +02:00
|
|
|
|
|
|
|
if RNS.vendor.platformutils.get_platform() == "android":
|
|
|
|
from ui.helpers import ts_format
|
|
|
|
else:
|
|
|
|
from .helpers import ts_format
|
|
|
|
|
|
|
|
class ObjectDetails():
|
|
|
|
def __init__(self, app, object_hash = None):
|
|
|
|
self.app = app
|
|
|
|
self.widget = None
|
|
|
|
self.object_hash = object_hash
|
2023-10-24 01:14:59 +02:00
|
|
|
self.coords = None
|
|
|
|
self.raw_telemetry = None
|
|
|
|
self.from_conv = False
|
2023-10-22 14:01:11 +02:00
|
|
|
|
2023-10-23 01:26:44 +02:00
|
|
|
if not self.app.root.ids.screen_manager.has_screen("object_details_screen"):
|
|
|
|
self.screen = Builder.load_string(layou_object_details)
|
|
|
|
self.screen.app = self.app
|
2023-10-24 01:14:59 +02:00
|
|
|
self.screen.delegate = self
|
2023-10-23 01:26:44 +02:00
|
|
|
self.ids = self.screen.ids
|
|
|
|
self.app.root.ids.screen_manager.add_widget(self.screen)
|
|
|
|
|
2023-10-24 01:14:59 +02:00
|
|
|
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:
|
2023-10-24 01:28:49 +02:00
|
|
|
def job(dt):
|
|
|
|
self.screen.ids.coordinates_button.disabled = False
|
|
|
|
Clock.schedule_once(job, 0.01)
|
|
|
|
|
2023-10-24 01:14:59 +02:00
|
|
|
self.telemetry_list.update_source(rendered_telemetry)
|
2023-10-24 01:28:49 +02:00
|
|
|
def job(dt):
|
|
|
|
self.screen.ids.telemetry_button.disabled = False
|
|
|
|
Clock.schedule_once(job, 0.01)
|
2023-10-24 01:14:59 +02:00
|
|
|
else:
|
2023-10-24 01:28:49 +02:00
|
|
|
def job(dt):
|
|
|
|
self.screen.ids.telemetry_button.disabled = True
|
|
|
|
Clock.schedule_once(job, 0.01)
|
2023-10-24 01:14:59 +02:00
|
|
|
self.telemetry_list.update_source(None)
|
|
|
|
|
2023-10-22 14:01:11 +02:00
|
|
|
def reload(self):
|
|
|
|
self.clear_widget()
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def clear_widget(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
us = time.time()
|
|
|
|
self.update_widget()
|
|
|
|
RNS.log("Updated object details in "+RNS.prettytime(time.time()-us), RNS.LOG_DEBUG)
|
|
|
|
|
|
|
|
def update_widget(self):
|
|
|
|
if self.widget == None:
|
|
|
|
self.widget = MDLabel(text=RNS.prettyhexrep(self.object_hash))
|
|
|
|
|
|
|
|
def get_widget(self):
|
|
|
|
return self.widget
|
|
|
|
|
2023-10-24 01:14:59 +02:00
|
|
|
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 = []
|
2023-10-24 01:34:49 +02:00
|
|
|
def pass_job(sender=None):
|
|
|
|
pass
|
|
|
|
release_function = pass_job
|
2023-10-24 01:14:59 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-10-23 01:26:44 +02:00
|
|
|
layou_object_details = """
|
2023-10-24 01:14:59 +02:00
|
|
|
#: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"
|
|
|
|
|
2023-10-23 01:26:44 +02:00
|
|
|
MDScreen:
|
|
|
|
name: "object_details_screen"
|
|
|
|
|
|
|
|
BoxLayout:
|
|
|
|
orientation: "vertical"
|
|
|
|
|
|
|
|
MDTopAppBar:
|
2023-10-24 01:14:59 +02:00
|
|
|
id: details_bar
|
2023-10-23 01:26:44 +02:00
|
|
|
title: "Details"
|
|
|
|
anchor_title: "left"
|
|
|
|
elevation: 0
|
|
|
|
left_action_items:
|
|
|
|
[['menu', lambda x: root.app.nav_drawer.set_state("open")]]
|
|
|
|
right_action_items:
|
|
|
|
[
|
2023-10-24 01:14:59 +02:00
|
|
|
['close', lambda x: root.delegate.close_action()],
|
2023-10-23 01:26:44 +02:00
|
|
|
]
|
|
|
|
|
2023-10-24 01:14:59 +02:00
|
|
|
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
|
|
|
|
|
2023-10-23 01:26:44 +02:00
|
|
|
ScrollView:
|
|
|
|
id: object_details_scrollview
|
|
|
|
|
|
|
|
"""
|