diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38372f7..a895acc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,67 +1,31 @@ -# Contributing to Reticulum +# Contribution Guidelines -Welcome, and thank you for your interest in contributing to Reticulum! +Welcome, and thank you for your interest in contributing to this project! -Before interacting with this community, read these short and simple guidelines. - -If you didn't shit your pants after reading this document, you may well find that we are all really quite cuddly and nice to be around. Mostly. +Apart from writing code, there are many ways in which you can contribute. Before interacting with this community, read these short and simple guidelines. ## Expected Conduct -First and foremost, there is one simple requirement for taking part in the development of this technology: While we primarily interact virtually, your actions matter and have real consequences. Therefore: **Act like a responsible, civilized person** - especially in the face of disputes and heated disagreements. Speak your mind here; discussions are welcome. Just do so in the spirit of being face-to-face with everyone else. Thank you. +First and foremost, there is one simple requirement for taking part in this community: While we primarily interact virtually, your actions matter and have real consequences. Therefore: **Act like a responsible, civilized person** - also in the face of disputes and heated disagreements. Speak your mind here, discussions are welcome. Just do so in the spirit of being face-to-face with everyone else. Thank you. -In order to keep the discourse navigable and useful, the following types of posts and inquiries will be deleted and ignored: +## Asking Questions -- Spam. -- Questions that have already been adequately answered elsewhere. Utilize search functions. -- Low-effort posts or comments that contain no actual information or useful substance. [This is not a tea-house](./This%20Is%20Not%20a%20Teahouse.md). -- Post or comments solely containing personal opinions or beliefs without adding anything to the discussion. Facebook and X exists. -- Content that simply waste the developer's / maintainer's time with completely obvious "ideas", "insights" or "recommendations". Yes, we have *at least* 8 neurons ourselves. -- Posts that fail to understand that developing a highly complex software project with a very small amount of resources and people takes time. Imagining perfection on our behalf is useless. +If you want to ask a question, **do not open an issue**. The issue tracker is used by people *working on Sideband* to track bugs, issues and improvements. -If you're new to the community and start out your engagement with any of the above transgressions, you will simply be banned permanently without notice or explanation, and any further communication from you will be ignored. +Instead, ask away on the [discussions](https://github.com/markqvist/Reticulum/discussions) or on the [Reticulum Matrix channel](https://matrix.to/#/#reticulum:matrix.org) at `#reticulum:matrix.org` -Except for the case of real, well-grounded, clearly documented, human-written security advisories, technical investigations or bug reports, if your inquiry or communication is not attached to a Reticulum Identity, expect it to be ignored. +## Providing Feedback & Ideas -If you find this "harsh", "unfair" or "unwelcoming", go somewhere else. This is not social club, but a work environment for the people contributing to the project. +Likewise, feedback, ideas and feature requests are a very welcome way to contribute, and should also be posted on the [discussions](https://github.com/markqvist/Reticulum/discussions), or on the [Reticulum Matrix channel](https://matrix.to/#/#reticulum:matrix.org) at `#reticulum:matrix.org`. -## Reporting Bugs +Please do not post feature requests or general ideas on the issue tracker, or in direct messages to the primary developers. You are much more likely to get a response and start a constructive discussion by posting your ideas in the public channels created for these purposes. -If you have found a bug or issue in this project, please report it to the maintainer over LXMF, or if strictly not possible, over email. Detailed and serious bug reports can also be posted in the various on-network forums and RRC channels that the developers frequent. Due to the realities described in [This Is Not a Teahouse](./This%20Is%20Not%20a%20Teahouse.md), there is no way to report issues or request features over the internet. +## Reporting Issues + +If you have found a bug or issue in this project, please report it using the issue tracker. If at all possible, be sure to include details on how to reproduce the bug. + +Anything submitted to the issue tracker that does not follow these guidelines will be closed and removed without comments or explanation. ## Writing Code -Patches and code contributions are *generally* only welcomed from developers already known to the community, with a proven track-record of competence, skill, careful and considerate design and responsible conduct. If we do not know you yet, but you have created something that you believe lives up to such standards, we are eager to hear from you. - -Current development priorities and work documents are not publicly available on the internet, but shared and coordinated directly between the developers over Reticulum itself, following the principles outlined in [Distributed Development](https://reticulum.network/manual/distributed.html). - -## Commit Signing - -All commits in Reticulum and related projects **must** be signed with a Reticulum identity. If you want your author credentials to be included directly in the commit log, ensure that you have [configured commit signing](https://reticulum.network/manual/git.html#commit-signing). Bare patches submitted manually or fetched remote commits without signatures are still very welcome, but we will review, squash merge and sign with the key of the maintainer that approved the changes. Attribution will be provided in the commit message instead (unless you wish to remain anonymous). - -## Machine Assistance & LLMs - -For a highly complex, critical piece of cryptographically based networking infrastructure, we should not need to state the following, but alas, experience has proven otherwise. Therefore: - -**Including material generated using LLMs or other "AI" programs in Reticulum is, almost without exception, prohibited.** - -Before even *thinking* about using a generative software system to "create" something for Reticulum, read [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html), in full. If you do not have the capacity for that, you have failed the first requirement for contributing: Basic literacy. - -*If* you are not already a known contributor with a proven record of being able to responsibly handle these tools, a **full** audit trail of all ML-utilizing workflows, including prompts, LLM reasoning traces, tool calls and outputs (that is, the *entire*, unaltered session log) will need to be provided with any proposed contribution. Without that, we will not even *consider* looking at it. - -This applies to all Reticulum-related projects and documentation, as well as all submitted issues, reports and discussion, **except** of course in cases where language translation, speech recogntion, or other assistive technologies are required. - -In **all** circumstances, clear disclosure is **mandatory**. Any piece of material, file, code snippet, method, function or similar that was generated with machine assistance must be prepended with a disclosure header or comment that states the framework, exact model used and reviewer, such as: - -```python -# Generated with lc/deepseek_v4_flash_q8_k_xl -# Reviewed and corrected by Mark Qvist -``` - -Additionally, any explanatory comments or doc-strings generated by software must be manually re-written in clear, concise language by the reviewer stated in the disclosure header. - -## Contributor License Agreement - -By contributing code to this project, you agree that copyright for the code is transferred to the Reticulum maintainers and that the code is irrevocably placed under the [Reticulum License](./LICENSE). - -For a more in-depth elaboration on the current CLA position, see [this post](https://rns.recipes/forum/general/brandolinis-reference-or-alternatively-agentsmd?page=1#post-3002). +If you are interested in contributing code, fixing open issues or adding features, please coordinate the effort with the maintainer or one of the main developers first, to ensure your efforts are in alignment with the [Roadmap](./Roadmap.md) and current development focus. diff --git a/README.md b/README.md index d9ff57c..4e7648b 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,10 @@ Sideband ========= -> [!WARNING] -> Several hastily launched, incorrect, and in most cases entirely LLM-generated fakes of Sideband and associated tools are currently being circulated and marketed. Most of these "projects" violate the license that Sideband was published under, but claim independent ownership and license grants. -> -> Such claims or grants are **not legally valid**, and **not** recognized by the Sideband author and copyright holder. -> -> Any claimed assertion of copyright or grant of license that such projects are making are [void grants](https://reticulum.network/manual/brandolinis.html#void-grants-legal-foundations-of-machine-generated-code). -> -> Both the violators themselves, and **all** downstream projects using the infringing derivatives, are **directly** and **personally** liable for the legal consequences. -> -> For more detailed information, see the relevant sections of the [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html) chapter of the Reticulum Manual. For software and projects recognized by the Reticulum community, see the [Programs Using Reticulum](https://reticulum.network/manual/software.html) chapter. -> -> Do **not** use these "projects", they are a hazard to the entire ecosystem we have been carefully building over the last ten years. - -*Due to the blatant copyright infringement, LLM-laundering and disrespect for open-source developers, the source code in this repository is no longer updated. All development is happening over Reticulum, where the latest source is always available.* - -*Packages and updates continue to ship on PyPI and over `rngit`. Compiled APKs, AppImages, wheels and portable exe bundles will continue to be published here as well.* - ---- +*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere.* To understand the foundational philosophy and goals of this system, read the [Zen of Reticulum](Zen%20of%20Reticulum.md). -For an Android-specific quickstart guide for new users, see the community-provided [Sideband Quick-start by RNS Moscow](https://docs.rns.moscow/sideband-quickstart/). - Sideband is an extensible LXMF messaging and LXST telephony client, situational awareness tracker and remote control and monitoring system for Android, Linux, macOS and Windows. It allows you to communicate with other people or LXMF-compatible systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR Paper Messages, or anything else Reticulum supports. ![Screenshot](https://github.com/markqvist/Sideband/raw/main/docs/screenshots/devices_small.webp) @@ -83,28 +64,7 @@ This also means that Sideband is designed to be fully compatible with custom (an ## On Linux -On most Linux-based operating systems, you can simply download the pre-compiled AppImage from the [latest release page](https://github.com/markqvist/Sideband/releases/latest) and run it. - -Depending on your Linux distribution, you *may* need to install a few other packages for full voice call, audio messaging and clipboard functionality. If any of those features do not work, make sure you have `opusfile`, and a clipboard manager like `xclip` or `wl-clipboard` installed: - -```bash -# For Debian 12+, Ubuntu 22.04+ and derivatives -sudo apt install libopusfile0 xclip xsel - -# For Manjaro and derivatives -pamac install opusfile xclip xsel - -# For Arch and derivatives -sudo pacman -Sy opusfile xclip xsel - -# Clipboard support on Wayland: If the clipboard is not -# working correctly on Wayland with xclip and xsel, you -# can install wl-clipboard and wl-clipboard-x11 instead. -pamac remove xclip xsel -pamac install wl-clipboard wl-clipboard-x11 -``` - -You can also install Sideband as a `pip`/`pipx` package. You can either pull and install Sideband directly from the `pip` repository, or download and install locally using `whl` package from the [latest release page](https://github.com/markqvist/Sideband/releases/latest). +On all Linux-based operating systems, Sideband is available as a `pip`/`pipx` package. You can either pull and install Sideband directly from the `pip` repository, or download and install locally using `whl` package from the [latest release page](https://github.com/markqvist/Sideband/releases/latest). This installation method **includes desktop integration**, so that Sideband will show up in your applications menu and launchers. Below are install steps for the most common recent Linux distros. For Debian 11, see the end of this section. @@ -200,9 +160,7 @@ python3 -m sbapp.main ## On Raspberry Pi -The easiest way to install Sideband on Raspberry Pi is to simply download and run the `aarch64` AppImage. On some versions of Raspberry Pi OS, you *may* also need to install additional libraries for full voice and clipboard functionality with `sudo apt install libopusfile0 xclip xsel`. - -You can also install Sideband using `pip` on all Raspberry Pi models that support 64-bit operating systems, and can run at least Python version 3.11. +You can install Sideband on all Raspberry Pi models that support 64-bit operating systems, and can run at least Python version 3.11. The install instructions below assume that you are installing Sideband on 64-bit Raspberry Pi OS (based on Debian 13 "Trixie" or later). If you're running something else on your Pi, you might need to modify some commands slightly. diff --git a/sbapp/main.py b/sbapp/main.py index f7e9032..baab1a9 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1,6 +1,6 @@ __debug_build__ = False __disable_shaders__ = False -__version__ = "1.9.2" +__version__ = "1.9.4" __variant__ = "" import sys diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index baef83b..6be50b2 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -20,7 +20,7 @@ from copy import deepcopy from threading import Lock from collections import deque from .res import sideband_fb_data -from .sense import Telemeter, Commands +from .sense import Telemeter, Commands, valid_telemetry_timebase, TB_DIFF_GRACE from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin from .mqtt import MQTT @@ -114,6 +114,7 @@ class SidebandCore(): PERIODIC_JOBS_INTERVAL = 60 PERIODIC_SYNC_RETRY = 360 TELEMETRY_KEEP = 60*60*24*7 + TELEMETRY_FUTURE_GRACE = TB_DIFF_GRACE TELEMETRY_INTERVAL = 60 SERVICE_TELEMETRY_INTERVAL = 300 TELEMETRY_CLEAN_INTERVAL = 3600 @@ -1352,7 +1353,7 @@ class SidebandCore(): else: desired_method = LXMF.LXMessage.DIRECT - request_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.timebase") or now - self.telemetry_request_max_history + request_timebase = self._telemetry_timebase(from_addr) or now - self.telemetry_request_max_history lxm_fields = { LXMF.FIELD_COMMANDS: [ {Commands.TELEMETRY_REQUEST: [request_timebase, is_collector_request]}, ]} @@ -1431,6 +1432,27 @@ class SidebandCore(): RNS.log(ed, RNS.LOG_DEBUG) return ed + def _last_telemetry_send_success_timebase(self, addr): + tb = self.getpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.last_send_success_timebase") + if tb == None: return None + if not valid_telemetry_timebase(tb): + RNS.log(f"Invalid telemetry send success timebase found for {RNS.prettyhexrep(addr)}, clearing value", RNS.LOG_WARNING) + self.setpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.last_send_success_timebase", None) + return None + + else: return tb + + + def _telemetry_timebase(self, addr): + tb = self.getpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.timebase") + if tb == None: return None + if not valid_telemetry_timebase(tb): + RNS.log(f"Invalid telemetry timebase found for {RNS.prettyhexrep(addr)}, clearing value", RNS.LOG_WARNING) + self.setpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.timebase", None) + return None + + else: return tb + def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False): if not RNS.vendor.platformutils.is_android(): return False @@ -1452,17 +1474,15 @@ class SidebandCore(): def send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False, is_collector_response=False): if self.allow_service_dispatch and self.is_client: - try: - return self._service_send_latest_telemetry(to_addr, stream, is_authorized_telemetry_request) + try: return self._service_send_latest_telemetry(to_addr, stream, is_authorized_telemetry_request) except Exception as e: - RNS.log("Error requesting latest telemetry: "+str(e), RNS.LOG_ERROR) + RNS.log("Error sending latest telemetry: "+str(e), RNS.LOG_ERROR) RNS.trace_exception(e) return "not_sent" else: - if to_addr == None or to_addr == self.lxmf_destination.hash: - return "no_address" + if to_addr == None or to_addr == self.lxmf_destination.hash: return "no_address" else: if to_addr == self.config["telemetry_collector"]: is_authorized_telemetry_request = True @@ -1483,22 +1503,19 @@ class SidebandCore(): dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") source = self.lxmf_destination - if self.config["telemetry_use_propagation_only"] == True: - desired_method = LXMF.LXMessage.PROPAGATED + if self.config["telemetry_use_propagation_only"] == True: 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: - desired_method = LXMF.LXMessage.DIRECT + + else: desired_method = LXMF.LXMessage.DIRECT lxm_fields = self.get_message_fields(to_addr, is_authorized_telemetry_request=is_authorized_telemetry_request, signal_already_sent=True, is_collector_response=is_collector_response) - if lxm_fields == False and stream == None: - return "already_sent" + if lxm_fields == False and stream == None: return "already_sent" if stream != None and len(stream) > 0: - if lxm_fields == False: - lxm_fields = {} + if lxm_fields == False: lxm_fields = {} lxm_fields[LXMF.FIELD_TELEMETRY_STREAM] = stream if lxm_fields != None and lxm_fields != False and (LXMF.FIELD_TELEMETRY in lxm_fields or LXMF.FIELD_TELEMETRY_STREAM in lxm_fields): @@ -1509,9 +1526,14 @@ class SidebandCore(): telemetry_timebase = 0 for te in lxm_fields[LXMF.FIELD_TELEMETRY_STREAM]: ts = te[1] - telemetry_timebase = max(telemetry_timebase, ts) + t_src = RNS.prettyhexrep(te[0]) if te[0] else "unknown" + if not valid_telemetry_timebase(ts): + RNS.log(f"Skipping invalid timebase {ts} from {t_src} in send timebase accounting for {RNS.prettyhexrep(to_addr)}", RNS.LOG_WARNING) - if telemetry_timebase > (self.getpersistent(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.last_send_success_timebase") or 0): + else: telemetry_timebase = max(telemetry_timebase, ts) + + last_sent_timebase = (self._last_telemetry_send_success_timebase(to_addr) or 0) + if telemetry_timebase > last_sent_timebase: lxm = LXMF.LXMessage(dest, source, "", desired_method=desired_method, fields = lxm_fields, include_ticket=self.is_trusted(to_addr)) lxm.telemetry_timebase = telemetry_timebase lxm.register_delivery_callback(self.outbound_telemetry_finished) @@ -2314,6 +2336,12 @@ class SidebandCore(): read_telemetry = remote_telemeter.read_all() telemetry_timestamp = read_telemetry["time"]["utc"] + if not valid_telemetry_timebase(telemetry_timestamp): + now = time.time() + RNS.log(f"Invalid timestamp in telemetry from source {RNS.prettyhexrep(context_dest)}, not saving", RNS.LOG_WARNING) + if telemetry_timestamp > now: RNS.log(f"The timestamp was {RNS.prettytime(telemetry_timestamp - now)} in the future", RNS.LOG_WARNING) + return None + db = self.__db_connect() dbc = db.cursor() @@ -2361,6 +2389,7 @@ class SidebandCore(): if "by" in remote_telemeter.sensors["received"].data: remote_telemeter.sensors["received"].by = remote_telemeter.sensors["received"].data["by"] + if "distance" in remote_telemeter.sensors["received"].data: remote_telemeter.sensors["received"].geodesic_distance = remote_telemeter.sensors["received"].data["distance"]["geodesic"] remote_telemeter.sensors["received"].euclidian_distance = remote_telemeter.sensors["received"].data["distance"]["euclidian"] @@ -2373,21 +2402,16 @@ class SidebandCore(): data = (context_dest, telemetry_timestamp, telemetry) dbc.execute(query, data) - try: - db.commit() + try: db.commit() except Exception as e: RNS.log("An error occurred while commiting telemetry to database: "+str(e), RNS.LOG_ERROR) self.__db_reconnect() - # if not is_retry: - # RNS.log("Retrying operation...", RNS.LOG_ERROR) - # self._db_save_telemetry(context_dest, telemetry, physical_link, source_dest, via, is_retry = True) return self.setstate("app.flags.last_telemetry", time.time()) if self.config["telemetry_to_mqtt"] == True: - def mqtt_job(): - self.mqtt_handle_telemetry(context_dest, telemetry) + def mqtt_job(): self.mqtt_handle_telemetry(context_dest, telemetry) threading.Thread(target=mqtt_job, daemon=True).start() return telemetry @@ -2850,18 +2874,27 @@ class SidebandCore(): db.commit() def _db_clean_telemetry(self): - RNS.log("Cleaning telemetry... ", RNS.LOG_DEBUG) + RNS.log("Cleaning telemetry...", RNS.LOG_NOTICE) clean_time = time.time()-self.TELEMETRY_KEEP + future_time = time.time()-self.TELEMETRY_FUTURE_GRACE with self.db_lock: db = self.__db_connect() dbc = db.cursor() query = f"delete from telemetry where (ts < {clean_time});" - dbc.execute(query, {"outbound_state": LXMF.LXMessage.OUTBOUND, "sending_state": LXMF.LXMessage.SENDING}) + dbc.execute(query) + cleaned_old = dbc.rowcount + + query = f"delete from telemetry where (ts > {future_time});" + dbc.execute(query) + cleaned_future = dbc.rowcount + db.commit() self.last_telemetry_clean = time.time() + if cleaned_old: RNS.log(f"Cleaned {cleaned_old} expired telemetry entries", RNS.LOG_NOTICE) + if cleaned_future: RNS.log(f"Cleaned {cleaned_future} telemetry entries with timestamps too far in the future", RNS.LOG_NOTICE) def _db_message_set_state(self, lxm_hash, state, is_retry=False, ratchet_id=None, originator_stamp=None): msg_extras = None @@ -3088,25 +3121,25 @@ class SidebandCore(): packed_telemetry = self._db_save_telemetry(context_dest, lxm.fields[LXMF.FIELD_TELEMETRY], physical_link=physical_link, source_dest=context_dest) if LXMF.FIELD_TELEMETRY_STREAM in lxm.fields: - max_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(context_dest, delimit=False)}.timebase") or 0 + max_timebase = self._telemetry_timebase(context_dest) or 0 if lxm.fields[LXMF.FIELD_TELEMETRY_STREAM] != None and len(lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]) > 0: for telemetry_entry in lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]: tsource = telemetry_entry[0] ttstamp = telemetry_entry[1] tpacked = telemetry_entry[2] appearance = telemetry_entry[3] - max_timebase = max(max_timebase, ttstamp) if self._db_save_telemetry(tsource, tpacked, via = context_dest): RNS.log("Saved telemetry stream entry from "+RNS.prettyhexrep(tsource), RNS.LOG_DEBUG) + max_timebase = max(max_timebase, ttstamp) if appearance != None: self._db_update_appearance(tsource, ttstamp, appearance, from_bulk_telemetry=True) RNS.log("Updated appearance entry from "+RNS.prettyhexrep(tsource), RNS.LOG_DEBUG) self.setpersistent(f"telemetry.{RNS.hexrep(context_dest, delimit=False)}.timebase", max_timebase) + RNS.log(f"Telemetry timebase for {RNS.hexrep(context_dest, delimit=False)} is now {max_timebase}", RNS.LOG_DEBUG) - else: - RNS.log("Received telemetry stream field with no data: "+str(lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]), RNS.LOG_DEBUG) + else: RNS.log("Received telemetry stream field with no data: "+str(lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]), RNS.LOG_DEBUG) attach_msg = False if lxm.fields != None and len(lxm.fields) > 0: @@ -3683,7 +3716,7 @@ class SidebandCore(): try: now = time.time() collector_address = self.config["telemetry_collector"] - last_send_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(collector_address, delimit=False)}.last_send_success_timebase") or 0 + last_send_timebase = self._last_telemetry_send_success_timebase(collector_address) or 0 send_interval = self.config["telemetry_send_interval"] next_send = last_send_timebase+send_interval @@ -3698,7 +3731,7 @@ class SidebandCore(): self.pending_telemetry_send = True self.pending_telemetry_send_try += 1 if self.config["telemetry_send_all_to_collector"]: - last_timebase = (self.getpersistent(f"telemetry.{RNS.hexrep(collector_address, delimit=False)}.last_send_success_timebase") or 0) + last_timebase = self._last_telemetry_send_success_timebase(collector_address) or 0 self.create_telemetry_collector_response(to_addr=collector_address, timebase=last_timebase, is_authorized_telemetry_request=True) else: self.send_latest_telemetry(to_addr=collector_address) @@ -4446,8 +4479,7 @@ class SidebandCore(): self._db_message_set_state(message.hash, LXMF.LXMessage.OUTBOUND) self.message_router.handle_outbound(message) else: - if not no_display: - self.lxm_ingest(message, originator=True) + if not no_display: self.lxm_ingest(message, originator=True) if message.fields != None and LXMF.FIELD_TELEMETRY in message.fields: if len(message.fields[LXMF.FIELD_TELEMETRY]) > 0: @@ -4467,9 +4499,9 @@ class SidebandCore(): if send_telemetry and self.latest_packed_telemetry != None: telemeter = Telemeter.from_packed(self.latest_packed_telemetry) telemetry_timebase = telemeter.read_all()["time"]["utc"] - last_success_tb = (self.getpersistent(f"telemetry.{RNS.hexrep(context_dest, delimit=False)}.last_send_success_timebase") or 0) + last_success_tb = self._last_telemetry_send_success_timebase(context_dest) or 0 if is_collector_response and self.lxmf_destination.hash in self.telemetry_response_excluded: - RNS.log("Not embedding own telemetry collector response since own destination hash is excluded", RNS.LOG_DEBUG) + RNS.log("Not embedding own telemetry in collector response since own destination hash is excluded", RNS.LOG_DEBUG) send_telemetry = False elif telemetry_timebase > last_success_tb: RNS.log("Embedding own telemetry in message since current telemetry is newer than latest successful timebase", RNS.LOG_DEBUG) @@ -5212,10 +5244,8 @@ class SidebandCore(): if self.allow_request_from(context_dest): commands = message.fields[LXMF.FIELD_COMMANDS] self.handle_commands(commands, message) - else: - # TODO: Add these event to built-in log/event viewer - # when it is implemented. - RNS.log("Unauthorized command received from "+RNS.prettyhexrep(context_dest), RNS.LOG_WARNING) + + else: RNS.log("Unauthorized command received from "+RNS.prettyhexrep(context_dest), RNS.LOG_WARNING) else: self.lxm_ingest(message) @@ -5250,8 +5280,9 @@ class SidebandCore(): command_timebase = command[Commands.TELEMETRY_REQUEST] enable_collector_request = True - timebase = int(command_timebase) - RNS.log("Handling telemetry request with timebase "+str(timebase), RNS.LOG_DEBUG) + timebase = int(command_timebase); tbdiff = time.time()-timebase + if tbdiff < 0: RNS.log(f"Request timebase is in {RNS.prettytime(abs(tbdiff))} the future", RNS.LOG_WARNING) + RNS.log(f"Handling telemetry request with timebase "+str(timebase), RNS.LOG_DEBUG) if self.config["telemetry_collector_enabled"] and enable_collector_request: RNS.log(f"Collector requests enabled, returning complete telemetry response for all known objects since {timebase}", RNS.LOG_DEBUG) self.create_telemetry_collector_response(to_addr=context_dest, timebase=timebase, is_authorized_telemetry_request=True, is_collector_response=True) diff --git a/sbapp/sideband/sense.py b/sbapp/sideband/sense.py index 0a22767..4d49d90 100644 --- a/sbapp/sideband/sense.py +++ b/sbapp/sideband/sense.py @@ -329,29 +329,23 @@ class Time(Sensor): def pack(self): d = self.data - if d == None: - return None - else: - return d["utc"] + if d == None: return None + else: return d["utc"] def unpack(self, packed): try: - if packed == None: - return None - else: - return {"utc": packed} - except: - return None + if packed == None: return None + else: return {"utc": packed} + + except: return None def render(self, relative_to=None): if self.data != None: - rendered = { - "icon": "clock-time-ten-outline", - "name": "Timestamp", - "values": { "UTC": self.data["utc"] }, - } - else: - rendered = None + rendered = { "icon": "clock-time-ten-outline", + "name": "Timestamp", + "values": { "UTC": self.data["utc"] } } + + else: rendered = None return rendered @@ -3148,4 +3142,10 @@ def mqtt_hash(ihash): if type(ihash) == bytes: return RNS.hexrep(ihash, delimit=False) else: - return None \ No newline at end of file + return None + +TB_DIFF_GRACE = 4*60 +def valid_telemetry_timebase(ts): + if type(ts) not in [int, float]: return False + if ts > time.time()+TB_DIFF_GRACE: return False + else: return True \ No newline at end of file diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index 5e2026b..7c3e18a 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -363,7 +363,7 @@ class Messages(): sphrase = f"Generating PN stamp with cost {prop_cost}" prgstr = "" else: - sphrase = "Waiting for path" + sphrase = "Preparing" elif prg <= 0.01: sphrase = "Waiting for path" elif prg <= 0.03: diff --git a/sbapp/ui/objectdetails.py b/sbapp/ui/objectdetails.py index e61b36e..5615ba9 100644 --- a/sbapp/ui/objectdetails.py +++ b/sbapp/ui/objectdetails.py @@ -364,7 +364,9 @@ class RVDetails(MDRecycleView): 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})" + tsdiff = time.time()-ts + tspec = "ago" if tsdiff >= 0 else "in the future" + formatted_values = f"Recorded [b]{RNS.prettytime(abs(tsdiff), compact=True)} {tspec}[/b] ({ts_str})" def copy_info(e=None): Clipboard.copy(ts_str) toast("Copied to clipboard") diff --git a/setup.py b/setup.py index cd8e2e8..0aa1b84 100644 --- a/setup.py +++ b/setup.py @@ -114,8 +114,8 @@ setuptools.setup( ] }, install_requires=[ - "rns>=1.2.0", - "lxmf>=0.9.6", + "rns>=1.2.5", + "lxmf>=0.9.8", "lxst>=0.4.6", "kivy>=2.3.0", "numpy>=2.0.0", diff --git a/sideband_1.9.5.rsm b/sideband_1.9.5.rsm deleted file mode 100644 index 373843f..0000000 Binary files a/sideband_1.9.5.rsm and /dev/null differ diff --git a/sideband_1.9.6.rsm b/sideband_1.9.6.rsm deleted file mode 100644 index 440a724..0000000 Binary files a/sideband_1.9.6.rsm and /dev/null differ