diff --git a/sbapp/ui/objectdetails.py b/sbapp/ui/objectdetails.py index 9424195..2ac773d 100644 --- a/sbapp/ui/objectdetails.py +++ b/sbapp/ui/objectdetails.py @@ -117,84 +117,90 @@ class ObjectDetails(): toast("Reloaded telemetry for object") def set_source(self, source_dest, from_conv=False, from_telemetry=False, prefetched=None): - self.object_hash = source_dest - own_address = self.app.sideband.lxmf_destination.hash - telemetry_allowed = self.app.sideband.should_send_telemetry(source_dest) - if source_dest == own_address: - self.viewing_self = True - else: - self.viewing_self = False - - - if from_telemetry: - self.from_telemetry = True - else: - self.from_telemetry = False - if from_conv: - self.from_conv = True + try: + self.object_hash = source_dest + own_address = self.app.sideband.lxmf_destination.hash + telemetry_allowed = self.app.sideband.should_send_telemetry(source_dest) + if source_dest == own_address: + self.viewing_self = True else: - self.from_conv = False + self.viewing_self = 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 = pds - if source_dest == own_address: - self.screen.ids.name_label.text = pds+" (this device)" - elif source_dest == self.app.sideband.config["telemetry_collector"]: - self.screen.ids.name_label.text = pds+" (collector)" - - 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] - def djob(dt): - if self.viewing_self: - self.screen.ids.request_button.disabled = True - self.screen.ids.send_button.disabled = True + if from_telemetry: + self.from_telemetry = True else: - self.screen.ids.request_button.disabled = False - if telemetry_allowed: - self.screen.ids.send_button.disabled = 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 = pds + + if source_dest == own_address: + self.screen.ids.name_label.text = pds+" (this device)" + elif source_dest == self.app.sideband.config["telemetry_collector"]: + self.screen.ids.name_label.text = pds+" (collector)" + + 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] + def djob(dt): + if self.viewing_self: + self.screen.ids.request_button.disabled = True self.screen.ids.send_button.disabled = True + else: + self.screen.ids.request_button.disabled = False + if telemetry_allowed: + self.screen.ids.send_button.disabled = False + else: + self.screen.ids.send_button.disabled = True - if prefetched != None: - latest_telemetry = prefetched - else: - latest_telemetry = self.app.sideband.peer_telemetry(source_dest, limit=1) + if prefetched != None: + latest_telemetry = prefetched + else: + latest_telemetry = self.app.sideband.peer_telemetry(source_dest, limit=1) - if latest_telemetry != None and len(latest_telemetry) > 0: - telemetry_timestamp = latest_telemetry[0][0] - self.lastest_timestamp = telemetry_timestamp + if latest_telemetry != None and len(latest_telemetry) > 0: + telemetry_timestamp = latest_telemetry[0][0] + self.lastest_timestamp = telemetry_timestamp - telemeter = Telemeter.from_packed(latest_telemetry[0][1]) - self.raw_telemetry = telemeter.read_all() + telemeter = Telemeter.from_packed(latest_telemetry[0][1]) + self.raw_telemetry = telemeter.read_all() - relative_to = None - if source_dest != own_address: - relative_to = self.app.sideband.telemeter + relative_to = None + if source_dest != own_address: + relative_to = self.app.sideband.telemeter - rendered_telemetry = telemeter.render(relative_to=relative_to) - if "location" in telemeter.sensors: + rendered_telemetry = telemeter.render(relative_to=relative_to) + if "location" in telemeter.sensors: + def job(dt): + self.screen.ids.coordinates_button.disabled = False + Clock.schedule_once(job, 0.01) + + self.telemetry_list.update_source(rendered_telemetry) def job(dt): - self.screen.ids.coordinates_button.disabled = False + self.screen.ids.telemetry_button.disabled = False Clock.schedule_once(job, 0.01) - - self.telemetry_list.update_source(rendered_telemetry) - def job(dt): - self.screen.ids.telemetry_button.disabled = False - Clock.schedule_once(job, 0.01) - else: - def job(dt): - self.screen.ids.telemetry_button.disabled = True - Clock.schedule_once(job, 0.01) - self.telemetry_list.update_source(None) + else: + def job(dt): + self.screen.ids.telemetry_button.disabled = True + Clock.schedule_once(job, 0.01) + self.telemetry_list.update_source(None) - self.telemetry_list.effect_cls = ScrollEffect - Clock.schedule_once(djob, 0.1) + self.telemetry_list.effect_cls = ScrollEffect + Clock.schedule_once(djob, 0.1) + except Exception as e: + import traceback + exception_info = "".join(traceback.TracebackException.from_exception(e).format()) + RNS.log(f"An {str(type(e))} occurred while updating service telemetry: {str(e)}", RNS.LOG_ERROR) + RNS.log(exception_info, RNS.LOG_ERROR) def reload(self): self.clear_widget() @@ -282,280 +288,286 @@ class RVDetails(MDRecycleView): 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, - "Received": 90, - "Information": 5, - } - 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 = [] - def pass_job(sender=None): - pass - release_function = pass_job - formatted_values = None - name = s["name"] - if name == "Timestamp": - ts = s["values"]["UTC"] - 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})" - def copy_info(e=None): - Clipboard.copy(ts_str) - toast("Copied to clipboard") - release_function = copy_info - elif name == "Information": - info = s["values"]["contents"] - if info != None: - istr = str(info) - def copy_info(e=None): - Clipboard.copy(istr) - toast("Copied to clipboard") - release_function = copy_info - external_text = escape_markup(istr) - formatted_values = f"[b]Information[/b]: {external_text}" - elif name == "Received": - formatted_values = "" - by = s["values"]["by"]; - via = s["values"]["via"]; + try: + 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, + "Received": 90, + "Information": 5, + } + 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 = [] + def pass_job(sender=None): + pass + release_function = pass_job + formatted_values = None + name = s["name"] + if name == "Timestamp": + ts = s["values"]["UTC"] + 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})" + def copy_info(e=None): + Clipboard.copy(ts_str) + toast("Copied to clipboard") + release_function = copy_info + elif name == "Information": + info = s["values"]["contents"] + if info != None: + istr = str(info) + def copy_info(e=None): + Clipboard.copy(istr) + toast("Copied to clipboard") + release_function = copy_info + external_text = escape_markup(istr) + formatted_values = f"[b]Information[/b]: {external_text}" + elif name == "Received": + formatted_values = "" + by = s["values"]["by"]; + via = s["values"]["via"]; - if by == self.app.sideband.lxmf_destination.hash: - if via == self.delegate.object_hash: - formatted_values = "Collected directly by [b]this device[/b], directly [b]from emitter[/b]" + if by == self.app.sideband.lxmf_destination.hash: + if via == self.delegate.object_hash: + formatted_values = "Collected directly by [b]this device[/b], directly [b]from emitter[/b]" + else: + via_str = self.app.sideband.peer_display_name(via) + if via_str == None: + via_str = "an [b]unknown peer[/b]" + formatted_values = f"Collected directly by [b]this device[/b], via {via_str}" else: - via_str = self.app.sideband.peer_display_name(via) - if via_str == None: - via_str = "an [b]unknown peer[/b]" - formatted_values = f"Collected directly by [b]this device[/b], via {via_str}" - else: - if via != None and via == by: - vstr = self.app.sideband.peer_display_name(via) - formatted_values = f"Received from, and collected by [b]{vstr}[/b]" - - else: - if via != None: + if via != None and via == by: vstr = self.app.sideband.peer_display_name(via) - via_str = f"Received from [b]{vstr}[/b]" - else: - via_str = "Received from an [b]unknown peer[/b]" + formatted_values = f"Received from, and collected by [b]{vstr}[/b]" - if by != None: - dstr = self.app.sideband.peer_display_name(by) - by_str = f", collected by [b]{dstr}[/b]" else: - by_str = f", collected by an [b]unknown peer[/b]" - - formatted_values = f"{via_str}{by_str}" - - if formatted_values == "": - formatted_values = None - - if not by == self.app.sideband.lxmf_destination.hash and not self.app.sideband.is_trusted(by): - extra_entries.append({"icon": "alert", "text": "Collected by a [b]non-trusted[/b] peer"}) - - elif name == "Battery": - p = s["values"]["percent"] - cs = s["values"]["_meta"] - if cs != None: cs_str = f" ({cs})" - if p != None: formatted_values = f"{name} [b]{p}%[/b]"+cs_str - elif name == "Ambient Pressure": - p = s["values"]["mbar"] - if p != None: formatted_values = f"{name} [b]{p} mbar[/b]" - dt = "mbar" - if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: - d = s["deltas"][dt] - formatted_values += f" (Δ = {d} mbar)" - elif name == "Ambient Temperature": - c = s["values"]["c"] - if c != None: formatted_values = f"{name} [b]{c}° C[/b]" - dt = "c" - if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: - d = s["deltas"][dt] - formatted_values += f" (Δ = {d}° C)" - elif name == "Relative Humidity": - r = s["values"]["percent"] - if r != None: formatted_values = f"{name} [b]{r}%[/b]" - dt = "percent" - if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: - d = s["deltas"][dt] - formatted_values += f" (Δ = {d}%)" - elif name == "Physical Link": - rssi = s["values"]["rssi"]; rssi_str = None - snr = s["values"]["snr"]; snr_str = None - q = s["values"]["q"]; q_str = None - if q != None: q_str = f"Link Quality [b]{q}%[/b]" - if rssi != None: - rssi_str = f"RSSI [b]{rssi} dBm[/b]" - if q != None: rssi_str = ", "+rssi_str - if snr != None: - snr_str = f"SNR [b]{snr} dB[/b]" - if q != None or rssi != None: snr_str = ", "+snr_str - if q_str or rssi_str or snr_str: - formatted_values = q_str+rssi_str+snr_str - elif name == "Location": - lat = s["values"]["latitude"] - lon = s["values"]["longitude"] - alt = s["values"]["altitude"] - speed = s["values"]["speed"] - heading = s["values"]["heading"] - accuracy = s["values"]["accuracy"] - updated = s["values"]["updated"] - updated_str = f", logged [b]{RNS.prettytime(time.time()-updated, compact=True)} ago[/b]" - - coords = f"{lat}, {lon}" - fcoords = f"{round(lat,4)}, {round(lon,4)}" - self.delegate.coords = coords - if alt == 0: - alt_str = "0" - else: - alt_str = RNS.prettydistance(alt) - formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]" - if speed != None: - if speed > 0.000001: - speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]" - else: - speed_formatted_values = f"Speed [b]0 Km/h[/b]" - else: - speed_formatted_values = None - 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}) - if speed_formatted_values != None: - extra_entries.append({"icon": "speedometer", "text": speed_formatted_values}) - - if "distance" in s: - if "orthodromic" in s["distance"]: - od = s["distance"]["orthodromic"] - if od != None: - od_text = f"Geodesic distance [b]{RNS.prettydistance(od)}[/b]" - extra_entries.append({"icon": "earth", "text": od_text}) - - if "euclidian" in s["distance"]: - ed = s["distance"]["euclidian"] - if ed != None: - ed_text = f"Euclidian distance [b]{RNS.prettydistance(ed)}[/b]" - extra_entries.append({"icon": "axis-arrow", "text": ed_text}) - - if "vertical" in s["distance"]: - vd = s["distance"]["vertical"] - if vd != None: - if vd < 0: - relstr = "lower" - vd = abs(vd) + if via != None: + vstr = self.app.sideband.peer_display_name(via) + via_str = f"Received from [b]{vstr}[/b]" else: - relstr = "greater" - vd_text = f"Altitude is [b]{RNS.prettydistance(vd)}[/b] {relstr} than this device" - extra_entries.append({"icon": "altimeter", "text": vd_text}) + via_str = "Received from an [b]unknown peer[/b]" + + if by != None: + dstr = self.app.sideband.peer_display_name(by) + by_str = f", collected by [b]{dstr}[/b]" + else: + by_str = f", collected by an [b]unknown peer[/b]" - if "angle_to_horizon" in s["values"]: - oath = s["values"]["angle_to_horizon"] - if oath != None: - if self.delegate.viewing_self: - oath_text = f"Local horizon is at [b]{round(oath,3)}°[/b]" - else: - oath_text = f"Object's horizon is at [b]{round(oath,3)}°[/b]" - extra_entries.append({"icon": "arrow-split-horizontal", "text": oath_text}) + formatted_values = f"{via_str}{by_str}" - if 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"Radio horizon of [b]{range_text}[/b]" - extra_entries.append({"icon": "radio-tower", "text": rh_formatted_text}) + if formatted_values == "": + formatted_values = None - if "azalt" in s and "local_angle_to_horizon" in s["azalt"]: - lath = s["azalt"]["local_angle_to_horizon"] - if lath != None: - lath_text = f"Local horizon is at [b]{round(lath,3)}°[/b]" - extra_entries.append({"icon": "align-vertical-distribute", "text": lath_text}) - - if "azalt" in s: - azalt_formatted_text = "" - if "azimuth" in s["azalt"]: - az = s["azalt"]["azimuth"] - az_text = f"Azimuth [b]{round(az,3)}°[/b]" - azalt_formatted_text += az_text + if not by == self.app.sideband.lxmf_destination.hash and not self.app.sideband.is_trusted(by): + extra_entries.append({"icon": "alert", "text": "Collected by a [b]non-trusted[/b] peer"}) - if "altitude" in s["azalt"]: - al = s["azalt"]["altitude"] - al_text = f"altitude [b]{round(al,3)}°[/b]" - if len(azalt_formatted_text) != 0: azalt_formatted_text += ", " - azalt_formatted_text += al_text - - extra_entries.append({"icon": "compass-rose", "text": azalt_formatted_text}) - - if "above_horizon" in s["azalt"]: - astr = "above" if s["azalt"]["above_horizon"] == True else "below" - dstr = str(round(s["azalt"]["altitude_delta"], 3)) - 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": rh_icon, "text": rh_formatted_text}) - - 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]" - - dt = vn + elif name == "Battery": + p = s["values"]["percent"] + cs = s["values"]["_meta"] + if cs != None: cs_str = f" ({cs})" + if p != None: formatted_values = f"{name} [b]{p}%[/b]"+cs_str + elif name == "Ambient Pressure": + p = s["values"]["mbar"] + if p != None: formatted_values = f"{name} [b]{p} mbar[/b]" + dt = "mbar" if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: d = s["deltas"][dt] - formatted_values += f" (Δ = {d} {vn})" - - data = None - if formatted_values != None: - if release_function: - data = {"icon": s["icon"], "text": f"{formatted_values}", "on_release": release_function} - else: + formatted_values += f" (Δ = {d} mbar)" + elif name == "Ambient Temperature": + c = s["values"]["c"] + if c != None: formatted_values = f"{name} [b]{c}° C[/b]" + dt = "c" + if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: + d = s["deltas"][dt] + formatted_values += f" (Δ = {d}° C)" + elif name == "Relative Humidity": + r = s["values"]["percent"] + if r != None: formatted_values = f"{name} [b]{r}%[/b]" + dt = "percent" + if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: + d = s["deltas"][dt] + formatted_values += f" (Δ = {d}%)" + elif name == "Physical Link": + rssi = s["values"]["rssi"]; rssi_str = None + snr = s["values"]["snr"]; snr_str = None + q = s["values"]["q"]; q_str = None + if q != None: q_str = f"Link Quality [b]{q}%[/b]" + if rssi != None: + rssi_str = f"RSSI [b]{rssi} dBm[/b]" + if q != None: rssi_str = ", "+rssi_str + if snr != None: + snr_str = f"SNR [b]{snr} dB[/b]" + if q != None or rssi != None: snr_str = ", "+snr_str + if q_str or rssi_str or snr_str: + formatted_values = q_str+rssi_str+snr_str + elif name == "Location": + lat = s["values"]["latitude"] + lon = s["values"]["longitude"] + alt = s["values"]["altitude"] + speed = s["values"]["speed"] + heading = s["values"]["heading"] + accuracy = s["values"]["accuracy"] + updated = s["values"]["updated"] + updated_str = f", logged [b]{RNS.prettytime(time.time()-updated, compact=True)} ago[/b]" + + coords = f"{lat}, {lon}" + fcoords = f"{round(lat,4)}, {round(lon,4)}" + self.delegate.coords = coords + if alt == 0: + alt_str = "0" + else: + alt_str = RNS.prettydistance(alt) + formatted_values = f"Coordinates [b]{fcoords}[/b], altitude [b]{alt_str}[/b]" + if speed != None: + if speed > 0.000001: + speed_formatted_values = f"Speed [b]{speed} Km/h[/b], heading [b]{heading}°[/b]" + else: + speed_formatted_values = f"Speed [b]0 Km/h[/b]" + else: + speed_formatted_values = None + extra_formatted_values = f"Uncertainty [b]{accuracy} meters[/b]"+updated_str + data = {"icon": s["icon"], "text": f"{formatted_values}"} - if data != None: - self.entries.append(data) - for extra in extra_entries: - self.entries.append(extra) + extra_entries.append({"icon": "map-marker-question", "text": extra_formatted_values}) + if speed_formatted_values != None: + extra_entries.append({"icon": "speedometer", "text": speed_formatted_values}) - if len(self.entries) == 0: - self.entries.append({"icon": "timeline-question-outline", "text": f"No telemetry available for this device"}) + if "distance" in s: + if "orthodromic" in s["distance"]: + od = s["distance"]["orthodromic"] + if od != None: + od_text = f"Geodesic distance [b]{RNS.prettydistance(od)}[/b]" + extra_entries.append({"icon": "earth", "text": od_text}) + + if "euclidian" in s["distance"]: + ed = s["distance"]["euclidian"] + if ed != None: + ed_text = f"Euclidian distance [b]{RNS.prettydistance(ed)}[/b]" + extra_entries.append({"icon": "axis-arrow", "text": ed_text}) + + if "vertical" in s["distance"]: + vd = s["distance"]["vertical"] + if vd != None: + if vd < 0: + relstr = "lower" + vd = abs(vd) + else: + relstr = "greater" + vd_text = f"Altitude is [b]{RNS.prettydistance(vd)}[/b] {relstr} than this device" + extra_entries.append({"icon": "altimeter", "text": vd_text}) - self.data = self.entries + if "angle_to_horizon" in s["values"]: + oath = s["values"]["angle_to_horizon"] + if oath != None: + if self.delegate.viewing_self: + oath_text = f"Local horizon is at [b]{round(oath,3)}°[/b]" + else: + oath_text = f"Object's horizon is at [b]{round(oath,3)}°[/b]" + extra_entries.append({"icon": "arrow-split-horizontal", "text": oath_text}) + if 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"Radio horizon of [b]{range_text}[/b]" + extra_entries.append({"icon": "radio-tower", "text": rh_formatted_text}) + + if "azalt" in s and "local_angle_to_horizon" in s["azalt"]: + lath = s["azalt"]["local_angle_to_horizon"] + if lath != None: + lath_text = f"Local horizon is at [b]{round(lath,3)}°[/b]" + extra_entries.append({"icon": "align-vertical-distribute", "text": lath_text}) + + if "azalt" in s: + azalt_formatted_text = "" + if "azimuth" in s["azalt"]: + az = s["azalt"]["azimuth"] + az_text = f"Azimuth [b]{round(az,3)}°[/b]" + azalt_formatted_text += az_text + + if "altitude" in s["azalt"]: + al = s["azalt"]["altitude"] + al_text = f"altitude [b]{round(al,3)}°[/b]" + if len(azalt_formatted_text) != 0: azalt_formatted_text += ", " + azalt_formatted_text += al_text + + extra_entries.append({"icon": "compass-rose", "text": azalt_formatted_text}) + + if "above_horizon" in s["azalt"]: + astr = "above" if s["azalt"]["above_horizon"] == True else "below" + dstr = str(round(s["azalt"]["altitude_delta"], 3)) + 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": rh_icon, "text": rh_formatted_text}) + + 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]" + + dt = vn + if "deltas" in s and dt in s["deltas"] and s["deltas"][dt] != None: + d = s["deltas"][dt] + formatted_values += f" (Δ = {d} {vn})" + + data = None + if formatted_values != None: + if release_function: + data = {"icon": s["icon"], "text": f"{formatted_values}", "on_release": release_function} + else: + data = {"icon": s["icon"], "text": f"{formatted_values}"} + + if data != None: + self.entries.append(data) + for extra in extra_entries: + self.entries.append(extra) + + if len(self.entries) == 0: + self.entries.append({"icon": "timeline-question-outline", "text": f"No telemetry available for this device"}) + + self.data = self.entries + + except Exception as e: + import traceback + exception_info = "".join(traceback.TracebackException.from_exception(e).format()) + RNS.log(f"An {str(type(e))} occurred while updating service telemetry: {str(e)}", RNS.LOG_ERROR) + RNS.log(exception_info, RNS.LOG_ERROR) layout_object_details = """