From cb936697403fc09182d345e7ac0efa09e11cc580 Mon Sep 17 00:00:00 2001 From: Pavel Boldin Date: Sat, 8 Oct 2022 16:52:27 +0300 Subject: [PATCH 1/7] python: add QueueStatus support This makes Python API parse QueueStatus packages returned by the firmware allowing for TX Queue status control from the phone or PC. This makes it easier to not overwhelm device with packages that are going to be ignored anyways. Signed-off-by: Pavel Boldin --- meshtastic/mesh_interface.py | 71 +++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 10633bd..3301c62 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -4,6 +4,7 @@ import sys import random import time import logging +import collections from typing import AnyStr import threading from datetime import datetime @@ -55,6 +56,8 @@ class MeshInterface: self.configId = None self.gotResponse = False # used in gpio read self.mask = None # used in gpio read and gpio watch + self.queueStatus = None + self.queue = collections.OrderedDict() def close(self): """Shutdown this interface""" @@ -505,13 +508,61 @@ class MeshInterface: m.disconnect = True self._sendToRadio(m) + def _queueHasFreeSpace(self): + # We never got queueStatus, maybe the firmware is old + if self.queueStatus is None: + return True + return self.queueStatus.free > 0 + + def _queueClaim(self): + if self.queueStatus is None: + return + self.queueStatus.free -= 1 + def _sendToRadio(self, toRadio): """Send a ToRadio protobuf to the device""" if self.noProto: logging.warning(f"Not sending packet because protocol use is disabled by noProto") else: #logging.debug(f"Sending toRadio: {stripnl(toRadio)}") - self._sendToRadioImpl(toRadio) + + if not toRadio.HasField('packet'): + # not a meshpacket -- send immediately, give queue a chance, + # this makes heartbeat trigger queue + self._sendToRadioImpl(toRadio) + else: + # meshpacket -- queue + self.queue[toRadio.packet.id] = toRadio + + resentQueue = collections.OrderedDict() + + while self.queue: + #logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue)) + while not self._queueHasFreeSpace(): + logging.debug("Waiting for free space in TX Queue") + time.sleep(0.5) + try: + toResend = self.queue.popitem(last=False) + except KeyError: + break + packetId, packet = toResend + #logging.warn(f"packet: {packetId:08x} {packet}") + resentQueue[packetId] = packet + if packet is False: + continue + self._queueClaim() + if packet != toRadio: + logging.debug(f"Resending packet ID {packetId:08x} {packet}") + self._sendToRadioImpl(packet) + + #logging.warn("resentQueue: " + " ".join(f'{k:08x}' for k in resentQueue)) + for packetId, packet in resentQueue.items(): + if self.queue.pop(packetId, False) is False: # Packet got acked under us + logging.debug(f"packet {packetId:08x} got acked under us") + continue + if packet: + self.queue[packetId] = packet + #logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue)) def _sendToRadioImpl(self, toRadio): """Send a ToRadio protobuf to the device""" @@ -524,6 +575,21 @@ class MeshInterface: """ self.localNode.requestChannels() + def _handleQueueStatusFromRadio(self, queueStatus): + self.queueStatus = queueStatus + logging.debug(f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} ") + + if queueStatus.res: + return + + #logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue)) + justQueued = self.queue.pop(queueStatus.mesh_packet_id, None) + + if justQueued is None and queueStatus.mesh_packet_id != 0: + self.queue[queueStatus.mesh_packet_id] = False + logging.debug(f"Reply for unexpected packet ID {queueStatus.mesh_packet_id:08x}") + #logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue)) + def _handleFromRadio(self, fromRadioBytes): """ Handle a packet that arrived from the radio(update model and publish events) @@ -580,6 +646,9 @@ class MeshInterface: elif fromRadio.HasField("packet"): self._handlePacketFromRadio(fromRadio.packet) + elif fromRadio.HasField('queueStatus'): + self._handleQueueStatusFromRadio(fromRadio.queueStatus) + elif fromRadio.rebooted: # Tell clients the device went away. Careful not to call the overridden # subclass version that closes the serial port From 62843ea39cce510e1cfbe673939ad7006ffd873e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 31 Mar 2023 10:04:42 +0200 Subject: [PATCH 2/7] Ignore the generated files --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ec9bbc..abde86e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: which meshtastic meshtastic --version - name: Run pylint - run: pylint meshtastic examples/ + run: pylint meshtastic examples/ --ignore-patterns ".*_pb2.py$" - name: Run tests with pytest run: pytest --cov=meshtastic - name: Generate coverage report From c28b61fbb134bf1a6354fe410fb6bbd36b0edc85 Mon Sep 17 00:00:00 2001 From: Manuel Verch Date: Thu, 30 Mar 2023 14:37:01 +0200 Subject: [PATCH 3/7] Output of --info "Nodes in mesh" to JSON Format --- meshtastic/mesh_interface.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 3301c62..06036cc 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -3,6 +3,7 @@ import sys import random import time +import json import logging import collections from typing import AnyStr @@ -82,8 +83,8 @@ class MeshInterface: myinfo = '' if self.myInfo: myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}" - mesh = "\nNodes in mesh:" - nodes = "" + mesh = "\n\nNodes in mesh: " + nodes = {} if self.nodes: for n in self.nodes.values(): # when the TBeam is first booted, it sometimes shows the raw data @@ -98,8 +99,11 @@ class MeshInterface: addr = convert_mac_addr(val) n2['user']['macaddr'] = addr - nodes = nodes + f" {stripnl(n2)}" - infos = owner + myinfo + mesh + nodes + # use id as dictionary key for correct json format in list of nodes + nodeid = n2['user']['id'] + n2['user'].pop('id') + nodes[nodeid] = n2 + infos = owner + myinfo + mesh + json.dumps(nodes) print(infos) return infos From b8b268feac120f7bb6c219f5935073039d20b3dc Mon Sep 17 00:00:00 2001 From: Manuel Verch Date: Thu, 30 Mar 2023 20:14:59 +0200 Subject: [PATCH 4/7] Check most current meshtastic version --- meshtastic/__main__.py | 4 ++++ meshtastic/util.py | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 48fe55f..e0bf0f5 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -633,6 +633,10 @@ def onConnected(interface): interface.getNode(args.dest).showInfo() closeNow = True print("") + pypi_version = meshtastic.util.check_if_newer_version() + if pypi_version: + print(f'*** A newer version v{pypi_version} is available!' + ' Consider running "pip install --upgrade meshtastic" ***\n') else: print("Showing info of remote node is not supported.") print("Use the '--get' command for a specific configuration (e.g. 'lora') instead.") diff --git a/meshtastic/util.py b/meshtastic/util.py index 257d4d7..9d4bcc5 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -14,6 +14,8 @@ import subprocess import serial import serial.tools.list_ports import pkg_resources +import requests + from meshtastic.supported_device import supported_devices @@ -241,7 +243,11 @@ def support_info(): print(f' Encoding (stdin): {sys.stdin.encoding}') print(f' Encoding (stdout): {sys.stdout.encoding}') the_version = pkg_resources.get_distribution("meshtastic").version - print(f' meshtastic: v{the_version}') + pypi_version = check_if_newer_version() + if pypi_version: + print(f' meshtastic: v{the_version} (*** newer version v{pypi_version} available ***)') + else: + print(f' meshtastic: v{the_version}') print(f' Executable: {sys.argv[0]}') print(f' Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}') print('') @@ -545,3 +551,19 @@ def detect_windows_port(sd): #print(f'x:{x}') ports.add(f'COM{x}') return ports + + +def check_if_newer_version(): + """Check pip to see if we are running the latest version.""" + pypi_version = None + try: + url = "https://pypi.org/pypi/meshtastic/json" + data = requests.get(url).json() + pypi_version = data["info"]["version"] + except Exception as e: + #print(f"could not get version from pypi e:{e}") + pass + act_version = pkg_resources.get_distribution("meshtastic").version + if pypi_version and pkg_resources.parse_version(pypi_version) <= pkg_resources.parse_version(act_version): + return None + return pypi_version From edb537947adaf17646eb64d0c8493c44ded4ce44 Mon Sep 17 00:00:00 2001 From: Manuel Verch Date: Fri, 31 Mar 2023 09:02:42 +0200 Subject: [PATCH 5/7] Add module "requests" as requirement --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 8c351a1..a52ecd9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ pyqrcode tabulate timeago webencodings +requests pyparsing twine autopep8 From 72510088c98bc85201672ecacb1fa408d09c3b64 Mon Sep 17 00:00:00 2001 From: Manuel Verch Date: Fri, 31 Mar 2023 09:46:27 +0200 Subject: [PATCH 6/7] reverted file belonging to another PR --- meshtastic/mesh_interface.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 06036cc..3301c62 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -3,7 +3,6 @@ import sys import random import time -import json import logging import collections from typing import AnyStr @@ -83,8 +82,8 @@ class MeshInterface: myinfo = '' if self.myInfo: myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}" - mesh = "\n\nNodes in mesh: " - nodes = {} + mesh = "\nNodes in mesh:" + nodes = "" if self.nodes: for n in self.nodes.values(): # when the TBeam is first booted, it sometimes shows the raw data @@ -99,11 +98,8 @@ class MeshInterface: addr = convert_mac_addr(val) n2['user']['macaddr'] = addr - # use id as dictionary key for correct json format in list of nodes - nodeid = n2['user']['id'] - n2['user'].pop('id') - nodes[nodeid] = n2 - infos = owner + myinfo + mesh + json.dumps(nodes) + nodes = nodes + f" {stripnl(n2)}" + infos = owner + myinfo + mesh + nodes print(infos) return infos From 78f85efe56cf3afba8504d713de160fde7154632 Mon Sep 17 00:00:00 2001 From: Manuel Verch Date: Fri, 31 Mar 2023 10:05:19 +0200 Subject: [PATCH 7/7] Added install_requires requests --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eaa448a..c24aa8a 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup( ], packages=["meshtastic"], include_package_data=True, - install_requires=["pyserial>=3.4", "protobuf>=3.13.0", + install_requires=["pyserial>=3.4", "protobuf>=3.13.0", "requests>=2.25.0", "pypubsub>=4.0.3", "dotmap>=1.3.14", "pexpect>=4.6.0", "pyqrcode>=1.2.1", "tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml", "pygatt>=4.0.5 ; platform_system=='Linux'"],