diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5840970 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf +*.{sh,[sS][hH]} text eol=lf diff --git a/.github/workflows/cleanup_artifacts.yml b/.github/workflows/cleanup_artifacts.yml index d95304c..bea6d78 100644 --- a/.github/workflows/cleanup_artifacts.yml +++ b/.github/workflows/cleanup_artifacts.yml @@ -3,8 +3,8 @@ name: Remove old artifacts on: schedule: # Every day at 1am - - cron: '0 1 * * *' - + - cron: "0 1 * * *" + workflow_dispatch: jobs: @@ -13,8 +13,8 @@ jobs: timeout-minutes: 10 steps: - - name: Remove old artifacts - uses: c-hive/gha-remove-artifacts@v1 - with: - age: '1 month' - skip-tags: true + - name: Remove old artifacts + uses: c-hive/gha-remove-artifacts@v1 + with: + age: "1 month" + skip-tags: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d6d96e..22b0915 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,76 +11,73 @@ jobs: new_sha: ${{ steps.commit_updated.outputs.sha }} steps: + - name: Checkout + uses: actions/checkout@v2 - - name: Checkout - uses: actions/checkout@v2 + - name: Bump version + run: >- + bin/bump_version.py - - name: Bump version - run: >- - bin/bump_version.py + - name: Commit updated version.py + id: commit_updated + run: | + git config --global user.name 'github-actions' + git config --global user.email 'bot@noreply.github.com' + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + git add setup.py + git commit -m "bump version" && git push || echo "No changes to commit" + git log -n 1 --pretty=format:"%H" | tail -n 1 | awk '{print "::set-output name=sha::"$0}' - - name: Commit updated version.py - id: commit_updated - run: | - git config --global user.name 'github-actions' - git config --global user.email 'bot@noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} - git add setup.py - git commit -m "bump version" && git push || echo "No changes to commit" - git log -n 1 --pretty=format:"%H" | tail -n 1 | awk '{print "::set-output name=sha::"$0}' + - name: Get version + id: get_version + run: >- + bin/show_version.py - - name: Get version - id: get_version - run: >- - bin/show_version.py + - name: Create GitHub release + uses: actions/create-release@v1 + id: create_release - - name: Create GitHub release - uses: actions/create-release@v1 - id: create_release + with: + draft: true + prerelease: true + release_name: Meshtastic Python ${{ steps.get_version.outputs.version }} + tag_name: ${{ steps.get_version.outputs.version }} + body: | + Autogenerated by github action, developer should edit as required before publishing... + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - draft: true - prerelease: true - release_name: Meshtastic Python ${{ steps.get_version.outputs.version }} - tag_name: ${{ steps.get_version.outputs.version }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - . - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} build-and-publish-mac: runs-on: macos-latest needs: release_create steps: - - name: Checkout uses: actions/checkout@v3 with: @@ -127,7 +124,6 @@ jobs: runs-on: ubuntu-latest needs: release_create steps: - - name: Checkout uses: actions/checkout@v3 with: @@ -169,7 +165,6 @@ jobs: runs-on: windows-latest needs: release_create steps: - - name: Checkout uses: actions/checkout@v3 with: diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 0000000..cf2f254 --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1,7 @@ +*out +*logs +*actions +*notifications +plugins +user_trunk.yaml +user.yaml diff --git a/.trunk/configs/.isort.cfg b/.trunk/configs/.isort.cfg new file mode 100644 index 0000000..b9fb3f3 --- /dev/null +++ b/.trunk/configs/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile=black diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml new file mode 100644 index 0000000..fb94039 --- /dev/null +++ b/.trunk/configs/.markdownlint.yaml @@ -0,0 +1,10 @@ +# Autoformatter friendly markdownlint config (all formatting rules disabled) +default: true +blank_lines: false +bullet: false +html: false +indentation: false +line_length: false +spaces: false +url: false +whitespace: false diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc new file mode 100644 index 0000000..8c7b1ad --- /dev/null +++ b/.trunk/configs/.shellcheckrc @@ -0,0 +1,7 @@ +enable=all +source-path=SCRIPTDIR +disable=SC2154 + +# If you're having issues with shellcheck following source, disable the errors via: +# disable=SC1090 +# disable=SC1091 diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml new file mode 100644 index 0000000..4d44466 --- /dev/null +++ b/.trunk/configs/.yamllint.yaml @@ -0,0 +1,10 @@ +rules: + quoted-strings: + required: only-when-needed + extra-allowed: ["{|}"] + empty-values: + forbid-in-block-mappings: true + forbid-in-flow-mappings: true + key-duplicates: {} + octal-values: + forbid-implicit-octal: true diff --git a/.trunk/configs/ruff.toml b/.trunk/configs/ruff.toml new file mode 100644 index 0000000..346b1d9 --- /dev/null +++ b/.trunk/configs/ruff.toml @@ -0,0 +1,5 @@ +# Generic, formatter-friendly config. +select = ["B", "D3", "D4", "E", "F"] + +# Never enforce `E501` (line length violations). This should be handled by formatters. +ignore = ["E501"] diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 0000000..8316534 --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,39 @@ +version: 0.1 +cli: + version: 1.7.0 +plugins: + sources: + - id: trunk + ref: v0.0.14 + uri: https://github.com/trunk-io/plugins +lint: + ignore: + - linters: [ALL] + paths: + # Ignore generated files + - meshtastic/*_pb2.py + enabled: + - actionlint@1.6.23 + - black@23.3.0 + - git-diff-check + - gitleaks@8.16.2 + - isort@5.12.0 + - markdownlint@0.33.0 + - prettier@2.8.7 + - pylint@2.17.1 + - ruff@0.0.260 + - shellcheck@0.9.0 + - shfmt@3.5.0 + - yamllint@1.30.0 +runtimes: + enabled: + - go@1.19.5 + - node@18.12.1 + - python@3.10.8 +actions: + disabled: + - trunk-announce + - trunk-check-pre-push + - trunk-fmt-pre-commit + enabled: + - trunk-upgrade-available diff --git a/bin/bump_version.py b/bin/bump_version.py index 0b64ba4..7279cdf 100755 --- a/bin/bump_version.py +++ b/bin/bump_version.py @@ -6,14 +6,14 @@ version_filename = "setup.py" lines = None -with open(version_filename, 'r', encoding='utf-8') as f: +with open(version_filename, "r", encoding="utf-8") as f: lines = f.readlines() -with open(version_filename, 'w', encoding='utf-8') as f: +with open(version_filename, "w", encoding="utf-8") as f: for line in lines: if line.lstrip().startswith("version="): # get rid of quotes around the version - line = line.replace('"', '') + line = line.replace('"', "") # get rid of trailing comma line = line.replace(",", "") # split on '=' @@ -21,8 +21,10 @@ with open(version_filename, 'w', encoding='utf-8') as f: # split the version into parts (by period) v = words[1].split(".") build_num = re.findall(r"\d+", v[2])[0] - new_build_num = str(int(build_num)+1) - ver = f'{v[0]}.{v[1]}.{v[2].replace(build_num, new_build_num)}'.replace('\n', '') + new_build_num = str(int(build_num) + 1) + ver = f"{v[0]}.{v[1]}.{v[2].replace(build_num, new_build_num)}".replace( + "\n", "" + ) f.write(f' version="{ver}",\n') else: f.write(line) diff --git a/bin/regen-protobufs.sh b/bin/regen-protobufs.sh index b8432b9..d0c95f7 100755 --- a/bin/regen-protobufs.sh +++ b/bin/regen-protobufs.sh @@ -9,11 +9,11 @@ # workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628 if [[ $OSTYPE == 'darwin'* ]]; then - sed -i '' -E 's/^(import.*_pb2)/from . \1/' meshtastic/*.py - # automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27) - sed -i '' -E "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py + sed -i '' -E 's/^(import.*_pb2)/from . \1/' meshtastic/*.py + # automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27) + sed -i '' -E "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py else - sed -i -e 's/^import.*_pb2/from . \0/' meshtastic/*.py - # automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27) - sed -i -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py + sed -i -e 's/^import.*_pb2/from . \0/' meshtastic/*.py + # automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27) + sed -i -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py fi diff --git a/bin/show_version.py b/bin/show_version.py index c01ebc0..8cfb092 100755 --- a/bin/show_version.py +++ b/bin/show_version.py @@ -5,16 +5,16 @@ version_filename = "setup.py" lines = None -with open(version_filename, 'r', encoding='utf-8') as f: +with open(version_filename, "r", encoding="utf-8") as f: lines = f.readlines() for line in lines: if line.lstrip().startswith("version="): # get rid of quotes around the version - line2 = line.replace('"', '') + line2 = line.replace('"', "") # get rid of the trailing comma - line2 = line2.replace(',', '') + line2 = line2.replace(",", "") # split on = words = line2.split("=") # Note: This format is for github actions - print(f'::set-output name=version::{words[1].strip()}') + print(f"::set-output name=version::{words[1].strip()}") diff --git a/bin/test-release.sh b/bin/test-release.sh index e8c6bff..333d1dc 100755 --- a/bin/test-release.sh +++ b/bin/test-release.sh @@ -7,4 +7,4 @@ python3 setup.py sdist bdist_wheel python3 -m twine check dist/* # test the upload python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* -echo "view the upload at https://test.pypi.org/ it it looks good upload for real" \ No newline at end of file +echo "view the upload at https://test.pypi.org/ it it looks good upload for real" diff --git a/exampleConfig.yaml b/exampleConfig.yaml index 39117dd..a546e5a 100644 --- a/exampleConfig.yaml +++ b/exampleConfig.yaml @@ -11,6 +11,6 @@ location: userPrefs: region: 1 - isAlwaysPowered: 'true' + isAlwaysPowered: "true" screenOnSecs: 31536000 waitBluetoothSecs: 31536000 diff --git a/example_config.yaml b/example_config.yaml index c4542b0..4020525 100644 --- a/example_config.yaml +++ b/example_config.yaml @@ -9,7 +9,7 @@ location: lon: -93.88888 alt: 304 -config: +config: bluetooth: enabled: true fixedPin: 123456 @@ -36,7 +36,7 @@ config: meshSdsTimeoutSecs: 7200 minWakeSecs: 10 sdsSecs: 4294967295 - + module_config: telemetry: deviceUpdateInterval: 900 diff --git a/examples/get_hw.py b/examples/get_hw.py index 751e2e2..cfa92bd 100644 --- a/examples/get_hw.py +++ b/examples/get_hw.py @@ -3,6 +3,7 @@ """ import sys + import meshtastic import meshtastic.serial_interface @@ -15,6 +16,6 @@ if len(sys.argv) != 1: iface = meshtastic.serial_interface.SerialInterface() if iface.nodes: for n in iface.nodes.values(): - if n['num'] == iface.myInfo.my_node_num: - print(n['user']['hwModel']) + if n["num"] == iface.myInfo.my_node_num: + print(n["user"]["hwModel"]) iface.close() diff --git a/examples/hello_world_serial.py b/examples/hello_world_serial.py index 5c0b0b6..bed81db 100644 --- a/examples/hello_world_serial.py +++ b/examples/hello_world_serial.py @@ -3,6 +3,7 @@ """ import sys + import meshtastic import meshtastic.serial_interface diff --git a/examples/info_example.py b/examples/info_example.py index 4b0a2af..a4059f4 100644 --- a/examples/info_example.py +++ b/examples/info_example.py @@ -8,16 +8,16 @@ import meshtastic.serial_interface iface = meshtastic.serial_interface.SerialInterface() # call showInfo() just to ensure values are populated -#info = iface.showInfo() +# info = iface.showInfo() if iface.myInfo: - #print(f'myInfo:{iface.myInfo}') - print(f'firmware_version:{iface.myInfo.firmware_version}') + # print(f'myInfo:{iface.myInfo}') + print(f"firmware_version:{iface.myInfo.firmware_version}") if iface.nodes: for n in iface.nodes.values(): - if n['num'] == iface.myInfo.my_node_num: - print(n['user']['hwModel']) + if n["num"] == iface.myInfo.my_node_num: + print(n["user"]["hwModel"]) break iface.close() diff --git a/examples/pub_sub_example.py b/examples/pub_sub_example.py index 79d49cf..fa3cb41 100644 --- a/examples/pub_sub_example.py +++ b/examples/pub_sub_example.py @@ -3,6 +3,7 @@ """ import sys + from pubsub import pub import meshtastic @@ -13,10 +14,13 @@ if len(sys.argv) < 2: print(f"usage: {sys.argv[0]} host") sys.exit(1) -def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument + +def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument """This is called when we (re)connect to the radio.""" print(interface.myInfo) interface.close() + + pub.subscribe(onConnection, "meshtastic.connection.established") try: diff --git a/examples/pub_sub_example2.py b/examples/pub_sub_example2.py index 03edacd..c5d3791 100644 --- a/examples/pub_sub_example2.py +++ b/examples/pub_sub_example2.py @@ -4,6 +4,7 @@ import sys import time + from pubsub import pub import meshtastic @@ -14,15 +15,18 @@ if len(sys.argv) < 2: print(f"usage: {sys.argv[0]} host") sys.exit(1) -def onReceive(packet, interface): # pylint: disable=unused-argument + +def onReceive(packet, interface): # pylint: disable=unused-argument """called when a packet arrives""" print(f"Received: {packet}") -def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument + +def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument """called when we (re)connect to the radio""" # defaults to broadcast, specify a destination ID if you wish interface.sendText("hello mesh") + pub.subscribe(onReceive, "meshtastic.receive") pub.subscribe(onConnection, "meshtastic.connection.established") try: @@ -30,6 +34,6 @@ try: while True: time.sleep(1000) iface.close() -except(Exception) as ex: +except Exception as ex: print(f"Error: Could not connect to {sys.argv[1]} {ex}") sys.exit(1) diff --git a/examples/scan_for_devices.py b/examples/scan_for_devices.py index 8c4f021..8c6707a 100644 --- a/examples/scan_for_devices.py +++ b/examples/scan_for_devices.py @@ -3,7 +3,12 @@ """ import sys -from meshtastic.util import detect_supported_devices, get_unique_vendor_ids, active_ports_on_supported_devices + +from meshtastic.util import ( + active_ports_on_supported_devices, + detect_supported_devices, + get_unique_vendor_ids, +) # simple arg check if len(sys.argv) != 1: @@ -12,13 +17,13 @@ if len(sys.argv) != 1: sys.exit(3) vids = get_unique_vendor_ids() -print(f'Searching for all devices with these vendor ids {vids}') +print(f"Searching for all devices with these vendor ids {vids}") sds = detect_supported_devices() if len(sds) > 0: - print('Detected possible devices:') + print("Detected possible devices:") for d in sds: - print(f' name:{d.name}{d.version} firmware:{d.for_firmware}') + print(f" name:{d.name}{d.version} firmware:{d.for_firmware}") ports = active_ports_on_supported_devices(sds) -print(f'ports:{ports}') +print(f"ports:{ports}") diff --git a/examples/set_owner.py b/examples/set_owner.py index 67edb34..bc5fff4 100644 --- a/examples/set_owner.py +++ b/examples/set_owner.py @@ -3,6 +3,7 @@ """ import sys + import meshtastic import meshtastic.serial_interface diff --git a/examples/tcp_gps_example.py b/examples/tcp_gps_example.py index 6bb064e..399fb05 100644 --- a/examples/tcp_gps_example.py +++ b/examples/tcp_gps_example.py @@ -9,6 +9,6 @@ radio_hostname = "meshtastic.local" # Can also be an IP iface = meshtastic.tcp_interface.TCPInterface(radio_hostname) my_node_num = iface.myInfo.my_node_num pos = iface.nodesByNum[my_node_num]["position"] -print (pos) +print(pos) iface.close() diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index e5ea3d1..815bfb7 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -62,33 +62,42 @@ import os import platform import random import socket -import sys import stat +import sys import threading -import traceback import time +import traceback from datetime import datetime from typing import * + +import google.protobuf.json_format import serial import timeago -import google.protobuf.json_format -from pubsub import pub from dotmap import DotMap -from tabulate import tabulate from google.protobuf.json_format import MessageToJson -from meshtastic.util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout -from meshtastic.node import Node -from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, - telemetry_pb2, remote_hardware_pb2, - channel_pb2, config_pb2, util) +from pubsub import pub +from tabulate import tabulate +from meshtastic import ( + admin_pb2, + apponly_pb2, + channel_pb2, + config_pb2, + mesh_pb2, + portnums_pb2, + remote_hardware_pb2, + telemetry_pb2, + util, +) +from meshtastic.node import Node +from meshtastic.util import DeferredExecution, Timeout, catchAndIgnore, fixme, stripnl # Note: To follow PEP224, comments should be after the module variable. LOCAL_ADDR = "^local" """A special ID that means the local node""" -BROADCAST_NUM = 0xffffffff +BROADCAST_NUM = 0xFFFFFFFF """if using 8 bit nodenums this will be shortend on the target""" BROADCAST_ADDR = "^all" @@ -106,6 +115,7 @@ publishingThread = DeferredExecution("publishing") class ResponseHandler(NamedTuple): """A pending response callback, waiting for a response to one of our messages""" + # requestId: int - used only as a key callback: Callable # FIXME, add timestamp and age out old requests @@ -113,6 +123,7 @@ class ResponseHandler(NamedTuple): class KnownProtocol(NamedTuple): """Used to automatically decode known protocol payloads""" + name: str # portnum: int, now a key # If set, will be called to prase as a protocol buffer @@ -129,7 +140,7 @@ def _onTextReceive(iface, asDict): # # Usually btw this problem is caused by apps sending binary data but setting the payload type to # text. - logging.debug(f'in _onTextReceive() asDict:{asDict}') + logging.debug(f"in _onTextReceive() asDict:{asDict}") try: asBytes = asDict["decoded"]["payload"] asDict["decoded"]["text"] = asBytes.decode("utf-8") @@ -140,22 +151,22 @@ def _onTextReceive(iface, asDict): def _onPositionReceive(iface, asDict): """Special auto parsing for received messages""" - logging.debug(f'in _onPositionReceive() asDict:{asDict}') - if 'decoded' in asDict: - if 'position' in asDict['decoded'] and 'from' in asDict: + logging.debug(f"in _onPositionReceive() asDict:{asDict}") + if "decoded" in asDict: + if "position" in asDict["decoded"] and "from" in asDict: p = asDict["decoded"]["position"] - logging.debug(f'p:{p}') + logging.debug(f"p:{p}") p = iface._fixupPosition(p) - logging.debug(f'after fixup p:{p}') + logging.debug(f"after fixup p:{p}") # update node DB as needed iface._getOrCreateByNum(asDict["from"])["position"] = p def _onNodeInfoReceive(iface, asDict): """Special auto parsing for received messages""" - logging.debug(f'in _onNodeInfoReceive() asDict:{asDict}') - if 'decoded' in asDict: - if 'user' in asDict['decoded'] and 'from' in asDict: + logging.debug(f"in _onNodeInfoReceive() asDict:{asDict}") + if "decoded" in asDict: + if "user" in asDict["decoded"] and "from" in asDict: p = asDict["decoded"]["user"] # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg # update node DB as needed @@ -176,13 +187,25 @@ def _receiveInfoUpdate(iface, asDict): """Well known message payloads can register decoders for automatic protobuf parsing""" protocols = { - portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol("text", onReceive=_onTextReceive), - portnums_pb2.PortNum.POSITION_APP: KnownProtocol("position", mesh_pb2.Position, _onPositionReceive), - portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol("user", mesh_pb2.User, _onNodeInfoReceive), + portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol( + "text", onReceive=_onTextReceive + ), + portnums_pb2.PortNum.POSITION_APP: KnownProtocol( + "position", mesh_pb2.Position, _onPositionReceive + ), + portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol( + "user", mesh_pb2.User, _onNodeInfoReceive + ), portnums_pb2.PortNum.ADMIN_APP: KnownProtocol("admin", admin_pb2.AdminMessage), portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing), - portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol("telemetry", telemetry_pb2.Telemetry), - portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol("remotehw", remote_hardware_pb2.HardwareMessage), + portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol( + "telemetry", telemetry_pb2.Telemetry + ), + portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol( + "remotehw", remote_hardware_pb2.HardwareMessage + ), portnums_pb2.PortNum.SIMULATOR_APP: KnownProtocol("simulator", mesh_pb2.Compressed), - portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol("traceroute", mesh_pb2.RouteDiscovery) + portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol( + "traceroute", mesh_pb2.RouteDiscovery + ), } diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index e0bf0f5..35d5d3a 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -3,52 +3,59 @@ """ import argparse -import platform import logging import os +import platform import sys import time -import yaml -from pubsub import pub -import pyqrcode + import pkg_resources +import pyqrcode +import yaml from google.protobuf.json_format import MessageToDict -import meshtastic.util +from pubsub import pub + import meshtastic.test -from meshtastic import remote_hardware -from meshtastic.ble_interface import BLEInterface -from meshtastic import portnums_pb2, channel_pb2, config_pb2 -from meshtastic.globals import Globals +import meshtastic.util +from meshtastic import channel_pb2, config_pb2, portnums_pb2, remote_hardware from meshtastic.__init__ import BROADCAST_ADDR +from meshtastic.ble_interface import BLEInterface +from meshtastic.globals import Globals + def onReceive(packet, interface): """Callback invoked when a packet arrives""" our_globals = Globals.getInstance() args = our_globals.get_args() try: - d = packet.get('decoded') - logging.debug(f'in onReceive() d:{d}') + d = packet.get("decoded") + logging.debug(f"in onReceive() d:{d}") # Exit once we receive a reply - if args and args.sendtext and packet["to"] == interface.myInfo.my_node_num and d["portnum"] == portnums_pb2.PortNum.TEXT_MESSAGE_APP: + if ( + args + and args.sendtext + and packet["to"] == interface.myInfo.my_node_num + and d["portnum"] == portnums_pb2.PortNum.TEXT_MESSAGE_APP + ): interface.close() # after running command then exit # Reply to every received message with some stats if args and args.reply: - msg = d.get('text') + msg = d.get("text") if msg: - rxSnr = packet['rxSnr'] - hopLimit = packet['hopLimit'] + rxSnr = packet["rxSnr"] + hopLimit = packet["hopLimit"] print(f"message: {msg}") - reply = f"got msg \'{msg}\' with rxSnr: {rxSnr} and hopLimit: {hopLimit}" + reply = f"got msg '{msg}' with rxSnr: {rxSnr} and hopLimit: {hopLimit}" print("Sending reply: ", reply) interface.sendText(reply) except Exception as ex: - print(f'Warning: There is no field {ex} in the packet.') + print(f"Warning: There is no field {ex} in the packet.") -def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613 +def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613 """Callback invoked when we connect/disconnect from a radio""" print(f"Connection changed: {topic.getName()}") @@ -57,13 +64,13 @@ def getPref(node, comp_name): """Get a channel or preferences value""" name = splitCompoundName(comp_name) - wholeField = name[0] == name[1] # We want the whole field + wholeField = name[0] == name[1] # We want the whole field camel_name = meshtastic.util.snake_to_camel(name[1]) # Note: protobufs has the keys in snake_case, so snake internally snake_name = meshtastic.util.camel_to_snake(name[1]) - logging.debug(f'snake_name:{snake_name} camel_name:{camel_name}') - logging.debug(f'use camel:{Globals.getInstance().get_camel_case()}') + logging.debug(f"snake_name:{snake_name} camel_name:{camel_name}") + logging.debug(f"use camel:{Globals.getInstance().get_camel_case()}") # First validate the input localConfig = node.localConfig @@ -79,17 +86,21 @@ def getPref(node, comp_name): found = True break - if not found: + if not found: if Globals.getInstance().get_camel_case(): - print(f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have an attribute {snake_name}.") + print( + f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have an attribute {snake_name}." + ) else: - print(f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have attribute {snake_name}.") + print( + f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have attribute {snake_name}." + ) print("Choices are...") printConfig(localConfig) printConfig(moduleConfig) return False - # Check if we need to request the config + # Check if we need to request the config if len(config.ListFields()) != 0: # read the value config_values = getattr(config, config_type.name) @@ -97,27 +108,33 @@ def getPref(node, comp_name): pref_value = getattr(config_values, pref.name) if Globals.getInstance().get_camel_case(): print(f"{str(config_type.name)}.{camel_name}: {str(pref_value)}") - logging.debug(f"{str(config_type.name)}.{camel_name}: {str(pref_value)}") + logging.debug( + f"{str(config_type.name)}.{camel_name}: {str(pref_value)}" + ) else: print(f"{str(config_type.name)}.{snake_name}: {str(pref_value)}") - logging.debug(f"{str(config_type.name)}.{snake_name}: {str(pref_value)}") + logging.debug( + f"{str(config_type.name)}.{snake_name}: {str(pref_value)}" + ) else: print(f"{str(config_type.name)}:\n{str(config_values)}") logging.debug(f"{str(config_type.name)}: {str(config_values)}") - else: + else: # Always show whole field for remote node node.requestConfig(config_type) - + return True + def splitCompoundName(comp_name): """Split compound (dot separated) preference name into parts""" - name = comp_name.split(".",1) + name = comp_name.split(".", 1) if len(name) != 2: - name[0]=comp_name + name[0] = comp_name name.append(comp_name) return name + def setPref(config, comp_name, valStr): """Set a channel or preferences value""" @@ -125,8 +142,8 @@ def setPref(config, comp_name, valStr): snake_name = meshtastic.util.camel_to_snake(name[1]) camel_name = meshtastic.util.snake_to_camel(name[1]) - logging.debug(f'snake_name:{snake_name}') - logging.debug(f'camel_name:{camel_name}') + logging.debug(f"snake_name:{snake_name}") + logging.debug(f"camel_name:{camel_name}") objDesc = config.DESCRIPTOR config_type = objDesc.fields_by_name.get(name[0]) @@ -141,9 +158,9 @@ def setPref(config, comp_name, valStr): return False val = meshtastic.util.fromStr(valStr) - logging.debug(f'valStr:{valStr} val:{val}') + logging.debug(f"valStr:{valStr} val:{val}") - if snake_name == 'psk' and len(valStr) < 8: + if snake_name == "psk" and len(valStr) < 8: print(f"Warning: wifi.psk must be 8 or more characters.") return False @@ -156,20 +173,24 @@ def setPref(config, comp_name, valStr): val = e.number else: if Globals.getInstance().get_camel_case(): - print(f"{name[0]}.{camel_name} does not have an enum called {val}, so you can not set it.") + print( + f"{name[0]}.{camel_name} does not have an enum called {val}, so you can not set it." + ) else: - print(f"{name[0]}.{snake_name} does not have an enum called {val}, so you can not set it.") + print( + f"{name[0]}.{snake_name} does not have an enum called {val}, so you can not set it." + ) print(f"Choices in sorted order are:") names = [] for f in enumType.values: # Note: We must use the value of the enum (regardless if camel or snake case) - names.append(f'{f.name}') + names.append(f"{f.name}") for temp_name in sorted(names): print(f" {temp_name}") return False # note: 'ignore_incoming' is a repeating field - if snake_name != 'ignore_incoming': + if snake_name != "ignore_incoming": try: if config_type.message_type is not None: config_values = getattr(config, config_type.name) @@ -201,7 +222,9 @@ def setPref(config, comp_name, valStr): def onConnected(interface): """Callback invoked when we connect to a radio""" closeNow = False # Should we drop the connection after we finish? - waitForAckNak = False # Should we wait for an acknowledgment if we send to a remote node? + waitForAckNak = ( + False # Should we wait for an acknowledgment if we send to a remote node? + ) try: our_globals = Globals.getInstance() args = our_globals.get_args() @@ -234,7 +257,7 @@ def onConnected(interface): print("Setting device position") # can include lat/long/alt etc: latitude = 37.5, longitude = -122.1 interface.sendPosition(lat, lon, alt) - interface.localNode.writeConfig('position') + interface.localNode.writeConfig("position") elif not args.no_time: # We normally provide a current time to the mesh when we connect interface.sendPosition() @@ -249,14 +272,18 @@ def onConnected(interface): closeNow = True waitForAckNak = True print(f"Setting device owner short to {args.set_owner_short}") - interface.getNode(args.dest, False).setOwner(long_name=None, short_name=args.set_owner_short) + interface.getNode(args.dest, False).setOwner( + long_name=None, short_name=args.set_owner_short + ) # TODO: add to export-config and configure if args.set_canned_message: closeNow = True waitForAckNak = True print(f"Setting canned plugin message to {args.set_canned_message}") - interface.getNode(args.dest, False).set_canned_message(args.set_canned_message) + interface.getNode(args.dest, False).set_canned_message( + args.set_canned_message + ) # TODO: add to export-config and configure if args.set_ringtone: @@ -279,13 +306,15 @@ def onConnected(interface): except ValueError: print("ERROR: supported position fields are:") print(positionConfig.PositionFlags.keys()) - print("If no fields are specified, will read and display current value.") + print( + "If no fields are specified, will read and display current value." + ) else: print(f"Setting position fields to {allFields}") - setPref(positionConfig, 'position_flags', f'{allFields:d}') + setPref(positionConfig, "position_flags", f"{allFields:d}") print("Writing modified preferences to device") - interface.getNode(args.dest).writeConfig('position') + interface.getNode(args.dest).writeConfig("position") elif args.pos_fields is not None: # If --pos-fields invoked without args, read and display current value @@ -296,7 +325,7 @@ def onConnected(interface): for bit in positionConfig.PositionFlags.values(): if positionConfig.position_flags & bit: fieldNames.append(positionConfig.PositionFlags.Name(bit)) - print(' '.join(fieldNames)) + print(" ".join(fieldNames)) if args.set_ham: closeNow = True @@ -348,22 +377,33 @@ def onConnected(interface): if args.ch_index is not None: channelIndex = int(args.ch_index) ch = interface.localNode.getChannelByChannelIndex(channelIndex) - logging.debug(f'ch:{ch}') + logging.debug(f"ch:{ch}") if ch and ch.role != channel_pb2.Channel.Role.DISABLED: - print(f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}") - interface.sendText(args.sendtext, args.dest, wantAck=True, channelIndex=channelIndex) + print( + f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}" + ) + interface.sendText( + args.sendtext, args.dest, wantAck=True, channelIndex=channelIndex + ) else: - meshtastic.util.our_exit(f"Warning: {channelIndex} is not a valid channel. Channel must not be DISABLED.") + meshtastic.util.our_exit( + f"Warning: {channelIndex} is not a valid channel. Channel must not be DISABLED." + ) if args.sendping: payload = str.encode("test string") print(f"Sending ping message to {args.dest}") - interface.sendData(payload, args.dest, portNum=portnums_pb2.PortNum.REPLY_APP, - wantAck=True, wantResponse=True) + interface.sendData( + payload, + args.dest, + portNum=portnums_pb2.PortNum.REPLY_APP, + wantAck=True, + wantResponse=True, + ) if args.traceroute: - loraConfig = getattr(interface.localNode.localConfig, 'lora') - hopLimit = getattr(loraConfig, 'hop_limit') + loraConfig = getattr(interface.localNode.localConfig, "lora") + hopLimit = getattr(loraConfig, "hop_limit") dest = str(args.traceroute) print(f"Sending traceroute request to {dest} (this could take a while)") interface.sendTraceRoute(dest, hopLimit) @@ -377,10 +417,12 @@ def onConnected(interface): if args.gpio_wrb: bitmask = 0 bitval = 0 - for wrpair in (args.gpio_wrb or []): + for wrpair in args.gpio_wrb or []: bitmask |= 1 << int(wrpair[0]) bitval |= int(wrpair[1]) << int(wrpair[0]) - print(f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}") + print( + f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}" + ) rhc.writeGPIOs(args.dest, bitmask, bitval) closeNow = True @@ -394,11 +436,13 @@ def onConnected(interface): time.sleep(1) if interface.gotResponse: break - logging.debug(f'end of gpio_rd') + logging.debug(f"end of gpio_rd") if args.gpio_watch: bitmask = int(args.gpio_watch, 16) - print(f"Watching GPIO mask 0x{bitmask:x} from {args.dest}. Press ctrl-c to exit") + print( + f"Watching GPIO mask 0x{bitmask:x} from {args.dest}. Press ctrl-c to exit" + ) while True: rhc.watchGPIOs(args.dest, bitmask) time.sleep(1) @@ -418,7 +462,9 @@ def onConnected(interface): config_type = config.DESCRIPTOR.fields_by_name.get(field) if config_type: if len(config.ListFields()) == 0: - node.requestConfig(config.DESCRIPTOR.fields_by_name.get(field)) + node.requestConfig( + config.DESCRIPTOR.fields_by_name.get(field) + ) found = setPref(config, pref[0], pref[1]) if found: break @@ -428,78 +474,102 @@ def onConnected(interface): node.writeConfig(field) else: if Globals.getInstance().get_camel_case(): - print(f"{node.localConfig.__class__.__name__} and {node.moduleConfig.__class__.__name__} do not have an attribute {pref[0]}.") + print( + f"{node.localConfig.__class__.__name__} and {node.moduleConfig.__class__.__name__} do not have an attribute {pref[0]}." + ) else: - print(f"{node.localConfig.__class__.__name__} and {node.moduleConfig.__class__.__name__} do not have attribute {pref[0]}.") + print( + f"{node.localConfig.__class__.__name__} and {node.moduleConfig.__class__.__name__} do not have attribute {pref[0]}." + ) print("Choices are...") printConfig(node.localConfig) printConfig(node.moduleConfig) if args.configure: - with open(args.configure[0], encoding='utf8') as file: + with open(args.configure[0], encoding="utf8") as file: configuration = yaml.safe_load(file) closeNow = True interface.getNode(args.dest, False).beginSettingsTransaction() - if 'owner' in configuration: + if "owner" in configuration: print(f"Setting device owner to {configuration['owner']}") waitForAckNak = True - interface.getNode(args.dest, False).setOwner(configuration['owner']) + interface.getNode(args.dest, False).setOwner(configuration["owner"]) - if 'owner_short' in configuration: - print(f"Setting device owner short to {configuration['owner_short']}") + if "owner_short" in configuration: + print( + f"Setting device owner short to {configuration['owner_short']}" + ) waitForAckNak = True - interface.getNode(args.dest, False).setOwner(long_name=None, short_name=configuration['owner_short']) + interface.getNode(args.dest, False).setOwner( + long_name=None, short_name=configuration["owner_short"] + ) - if 'ownerShort' in configuration: - print(f"Setting device owner short to {configuration['ownerShort']}") + if "ownerShort" in configuration: + print( + f"Setting device owner short to {configuration['ownerShort']}" + ) waitForAckNak = True - interface.getNode(args.dest, False).setOwner(long_name=None, short_name=configuration['ownerShort']) + interface.getNode(args.dest, False).setOwner( + long_name=None, short_name=configuration["ownerShort"] + ) - if 'channel_url' in configuration: - print("Setting channel url to", configuration['channel_url']) - interface.getNode(args.dest).setURL(configuration['channel_url']) + if "channel_url" in configuration: + print("Setting channel url to", configuration["channel_url"]) + interface.getNode(args.dest).setURL(configuration["channel_url"]) - if 'channelUrl' in configuration: - print("Setting channel url to", configuration['channelUrl']) - interface.getNode(args.dest).setURL(configuration['channelUrl']) + if "channelUrl" in configuration: + print("Setting channel url to", configuration["channelUrl"]) + interface.getNode(args.dest).setURL(configuration["channelUrl"]) - if 'location' in configuration: + if "location" in configuration: alt = 0 lat = 0.0 lon = 0.0 localConfig = interface.localNode.localConfig - if 'alt' in configuration['location']: - alt = int(configuration['location']['alt']) + if "alt" in configuration["location"]: + alt = int(configuration["location"]["alt"]) localConfig.position.fixed_position = True print(f"Fixing altitude at {alt} meters") - if 'lat' in configuration['location']: - lat = float(configuration['location']['lat']) + if "lat" in configuration["location"]: + lat = float(configuration["location"]["lat"]) localConfig.position.fixed_position = True print(f"Fixing latitude at {lat} degrees") - if 'lon' in configuration['location']: - lon = float(configuration['location']['lon']) + if "lon" in configuration["location"]: + lon = float(configuration["location"]["lon"]) localConfig.position.fixed_position = True print(f"Fixing longitude at {lon} degrees") print("Setting device position") interface.sendPosition(lat, lon, alt) - interface.localNode.writeConfig('position') + interface.localNode.writeConfig("position") - if 'config' in configuration: + if "config" in configuration: localConfig = interface.getNode(args.dest).localConfig - for section in configuration['config']: - for pref in configuration['config'][section]: - setPref(localConfig, f"{meshtastic.util.camel_to_snake(section)}.{pref}", str(configuration['config'][section][pref])) - interface.getNode(args.dest).writeConfig(meshtastic.util.camel_to_snake(section)) + for section in configuration["config"]: + for pref in configuration["config"][section]: + setPref( + localConfig, + f"{meshtastic.util.camel_to_snake(section)}.{pref}", + str(configuration["config"][section][pref]), + ) + interface.getNode(args.dest).writeConfig( + meshtastic.util.camel_to_snake(section) + ) - if 'module_config' in configuration: + if "module_config" in configuration: moduleConfig = interface.getNode(args.dest).moduleConfig - for section in configuration['module_config']: - for pref in configuration['module_config'][section]: - setPref(moduleConfig, f"{meshtastic.util.camel_to_snake(section)}.{pref}", str(configuration['module_config'][section][pref])) - interface.getNode(args.dest).writeConfig(meshtastic.util.camel_to_snake(section)) + for section in configuration["module_config"]: + for pref in configuration["module_config"][section]: + setPref( + moduleConfig, + f"{meshtastic.util.camel_to_snake(section)}.{pref}", + str(configuration["module_config"][section][pref]), + ) + interface.getNode(args.dest).writeConfig( + meshtastic.util.camel_to_snake(section) + ) interface.getNode(args.dest, False).commitSettingsTransaction() print("Writing modified configuration to device") @@ -518,11 +588,15 @@ def onConnected(interface): if args.ch_add: closeNow = True if len(args.ch_add) > 10: - meshtastic.util.our_exit("Warning: Channel name must be shorter. Channel not added.") + meshtastic.util.our_exit( + "Warning: Channel name must be shorter. Channel not added." + ) n = interface.getNode(args.dest) ch = n.getChannelByName(args.ch_add) if ch: - meshtastic.util.our_exit(f"Warning: This node already has a '{args.ch_add}' channel. No changes were made.") + meshtastic.util.our_exit( + f"Warning: This node already has a '{args.ch_add}' channel. No changes were made." + ) else: # get the first channel that is disabled (i.e., available) ch = n.getDisabledChannel() @@ -541,10 +615,14 @@ def onConnected(interface): channelIndex = our_globals.get_channel_index() if channelIndex is None: - meshtastic.util.our_exit("Warning: Need to specify '--ch-index' for '--ch-del'.", 1) + meshtastic.util.our_exit( + "Warning: Need to specify '--ch-index' for '--ch-del'.", 1 + ) else: if channelIndex == 0: - meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1) + meshtastic.util.our_exit( + "Warning: Cannot delete primary channel.", 1 + ) else: print(f"Deleting channel {channelIndex}") ch = interface.getNode(args.dest).deleteChannel(channelIndex) @@ -554,7 +632,7 @@ def onConnected(interface): # Overwrite modem_preset prefs = interface.getNode(args.dest).localConfig prefs.lora.modem_preset = modem_preset - interface.getNode(args.dest).writeConfig('lora') + interface.getNode(args.dest).writeConfig("lora") # handle the simple radio set commands if args.ch_vlongslow: @@ -587,9 +665,10 @@ def onConnected(interface): ch = interface.getNode(args.dest).channels[channelIndex] if args.ch_enable or args.ch_disable: - if channelIndex == 0: - meshtastic.util.our_exit("Warning: Cannot enable/disable PRIMARY channel.") + meshtastic.util.our_exit( + "Warning: Cannot enable/disable PRIMARY channel." + ) enable = True # default to enable if args.ch_enable: @@ -598,7 +677,7 @@ def onConnected(interface): enable = False # Handle the channel settings - for pref in (args.ch_set or []): + for pref in args.ch_set or []: if pref[0] == "psk": ch.settings.psk = meshtastic.util.fromPSK(pref[1]) else: @@ -606,8 +685,11 @@ def onConnected(interface): enable = True # If we set any pref, assume the user wants to enable the channel if enable: - ch.role = channel_pb2.Channel.Role.PRIMARY if ( - channelIndex == 0) else channel_pb2.Channel.Role.SECONDARY + ch.role = ( + channel_pb2.Channel.Role.PRIMARY + if (channelIndex == 0) + else channel_pb2.Channel.Role.SECONDARY + ) else: ch.role = channel_pb2.Channel.Role.DISABLED @@ -635,11 +717,15 @@ def onConnected(interface): 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') + 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.") + print( + "Use the '--get' command for a specific configuration (e.g. 'lora') instead." + ) if args.get: closeNow = True @@ -664,10 +750,11 @@ def onConnected(interface): qr = pyqrcode.create(url) print(qr.terminal()) - have_tunnel = platform.system() == 'Linux' + have_tunnel = platform.system() == "Linux" if have_tunnel and args.tunnel: # pylint: disable=C0415 from . import tunnel + # Even if others said we could close, stay open if the user asked for a tunnel closeNow = False if interface.noProto: @@ -676,7 +763,9 @@ def onConnected(interface): tunnel.Tunnel(interface, subnet=args.tunnel_net) if args.dest != BROADCAST_ADDR and waitForAckNak: - print(f"Waiting for an acknowledgment from remote node (this could take a while)") + print( + f"Waiting for an acknowledgment from remote node (this could take a while)" + ) interface.getNode(args.dest, False).iface.waitForAckNak() # if the user didn't ask for serial debugging output, we might want to exit after we've done our operation @@ -687,6 +776,7 @@ def onConnected(interface): print(f"Aborting due to: {ex}") interface.close() # close the connection now, so that our app exits + def printConfig(config): """print configuration""" objDesc = config.DESCRIPTOR @@ -696,13 +786,14 @@ def printConfig(config): print(f"{config_section.name}:") names = [] for field in config.message_type.fields: - tmp_name = f'{config_section.name}.{field.name}' + tmp_name = f"{config_section.name}.{field.name}" if Globals.getInstance().get_camel_case(): tmp_name = meshtastic.util.snake_to_camel(tmp_name) names.append(tmp_name) for temp_name in sorted(names): print(f" {temp_name}") + def onNode(node): """Callback invoked when the node DB changes""" print(f"Node changed: {node}") @@ -727,14 +818,14 @@ def export_config(interface): owner_short = interface.getShortName() channel_url = interface.localNode.getURL() myinfo = interface.getMyNodeInfo() - pos = myinfo.get('position') + pos = myinfo.get("position") lat = None lon = None alt = None if pos: - lat = pos.get('latitude') - lon = pos.get('longitude') - alt = pos.get('altitude') + lat = pos.get("latitude") + lon = pos.get("longitude") + alt = pos.get("altitude") if owner: configObj["owner"] = owner @@ -746,7 +837,7 @@ def export_config(interface): else: configObj["channel_url"] = channel_url if lat or lon or alt: - configObj["location"] = { "lat": lat, "lon": lon, "alt": alt } + configObj["location"] = {"lat": lat, "lon": lon, "alt": alt} config = MessageToDict(interface.localNode.localConfig) if config: @@ -786,8 +877,10 @@ def common(): our_globals = Globals.getInstance() args = our_globals.get_args() parser = our_globals.get_parser() - logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, - format='%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s') + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.INFO, + format="%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s", + ) if len(sys.argv) == 1: parser.print_help(sys.stderr) @@ -812,9 +905,10 @@ def common(): if args.deprecated is not None: logging.error( - 'This option has been deprecated, see help below for the correct replacement...') + "This option has been deprecated, see help below for the correct replacement..." + ) parser.print_help(sys.stderr) - meshtastic.util.our_exit('', 1) + meshtastic.util.our_exit("", 1) elif args.test: result = meshtastic.test.testAll() if not result: @@ -832,21 +926,27 @@ def common(): logging.info(f"Logging serial output to {args.seriallog}") # Note: using "line buffering" # pylint: disable=R1732 - logfile = open(args.seriallog, 'w+', buffering=1, encoding='utf8') + logfile = open(args.seriallog, "w+", buffering=1, encoding="utf8") our_globals.set_logfile(logfile) subscribe() if args.ble: client = BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto) elif args.host: - client = meshtastic.tcp_interface.TCPInterface(args.host, debugOut=logfile, noProto=args.noproto) + client = meshtastic.tcp_interface.TCPInterface( + args.host, debugOut=logfile, noProto=args.noproto + ) else: try: - client = meshtastic.serial_interface.SerialInterface(args.port, debugOut=logfile, noProto=args.noproto) + client = meshtastic.serial_interface.SerialInterface( + args.port, debugOut=logfile, noProto=args.noproto + ) except PermissionError as ex: username = os.getlogin() message = "Permission Error:\n" - message += " Need to add yourself to the 'dialout' group by running:\n" + message += ( + " Need to add yourself to the 'dialout' group by running:\n" + ) message += f" sudo usermod -a -G dialout {username}\n" message += " After running that command, log out and re-login for it to take effect.\n" message += f"Error was:{ex}" @@ -855,8 +955,10 @@ def common(): # We assume client is fully connected now onConnected(client) - have_tunnel = platform.system() == 'Linux' - if args.noproto or args.reply or (have_tunnel and args.tunnel): # loop until someone presses ctrlc + have_tunnel = platform.system() == "Linux" + if ( + args.noproto or args.reply or (have_tunnel and args.tunnel) + ): # loop until someone presses ctrlc while True: time.sleep(1000) @@ -873,210 +975,341 @@ def initParser(): parser.add_argument( "--configure", help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.", - action='append') + action="append", + ) parser.add_argument( "--export-config", help="Export the configuration in yaml(.yml) format.", - action='store_true') + action="store_true", + ) parser.add_argument( "--port", help="The port the Meshtastic device is connected to, i.e. /dev/ttyUSB0. If unspecified, we'll try to find it.", - default=None) + default=None, + ) parser.add_argument( "--host", help="The hostname/ipaddr of the device to connect to (over TCP)", - default=None) + default=None, + ) parser.add_argument( "--seriallog", - help="Log device serial output to either 'stdout', 'none' or a filename to append to.") - - parser.add_argument("--info", help="Read and display the radio config information", - action="store_true") - - parser.add_argument("--get-canned-message", help="Show the canned message plugin message", - action="store_true") - - parser.add_argument("--get-ringtone", help="Show the stored ringtone", - action="store_true") - - parser.add_argument("--nodes", help="Print Node List in a pretty formatted table", - action="store_true") - - parser.add_argument("--qr", help="Display the QR code that corresponds to the current channel", - action="store_true") + help="Log device serial output to either 'stdout', 'none' or a filename to append to.", + ) parser.add_argument( - "--get", help=("Get a preferences field. Use an invalid field such as '0' to get a list of all fields." - " Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')"), - nargs=1, action='append') + "--info", + help="Read and display the radio config information", + action="store_true", + ) parser.add_argument( - "--set", help="Set a preferences field. Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')", nargs=2, action='append') + "--get-canned-message", + help="Show the canned message plugin message", + action="store_true", + ) parser.add_argument( - "--seturl", help="Set a channel URL", action="store") + "--get-ringtone", help="Show the stored ringtone", action="store_true" + ) parser.add_argument( - "--ch-index", help="Set the specified channel index. Channels start at 0 (0 is the PRIMARY channel).", action="store") + "--nodes", + help="Print Node List in a pretty formatted table", + action="store_true", + ) parser.add_argument( - "--ch-add", help="Add a secondary channel, you must specify a channel name", default=None) + "--qr", + help="Display the QR code that corresponds to the current channel", + action="store_true", + ) parser.add_argument( - "--ch-del", help="Delete the ch-index channel", action='store_true') + "--get", + help=( + "Get a preferences field. Use an invalid field such as '0' to get a list of all fields." + " Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')" + ), + nargs=1, + action="append", + ) parser.add_argument( - "--ch-enable", help="Enable the specified channel", action="store_true", dest="ch_enable", default=False) + "--set", + help="Set a preferences field. Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')", + nargs=2, + action="append", + ) + + parser.add_argument("--seturl", help="Set a channel URL", action="store") + + parser.add_argument( + "--ch-index", + help="Set the specified channel index. Channels start at 0 (0 is the PRIMARY channel).", + action="store", + ) + + parser.add_argument( + "--ch-add", + help="Add a secondary channel, you must specify a channel name", + default=None, + ) + + parser.add_argument( + "--ch-del", help="Delete the ch-index channel", action="store_true" + ) + + parser.add_argument( + "--ch-enable", + help="Enable the specified channel", + action="store_true", + dest="ch_enable", + default=False, + ) # Note: We are doing a double negative here (Do we want to disable? If ch_disable==True, then disable.) parser.add_argument( - "--ch-disable", help="Disable the specified channel", action="store_true", dest="ch_disable", default=False) + "--ch-disable", + help="Disable the specified channel", + action="store_true", + dest="ch_disable", + default=False, + ) parser.add_argument( - "--ch-set", help=("Set a channel parameter. To see channel settings available:'--ch-set all all --ch-index 0'. " - "Can set the 'psk' using this command. To disable encryption on primary channel:'--ch-set psk none --ch-index 0'. " - "To set encryption with a new random key on second channel:'--ch-set psk random --ch-index 1'. " - "To set encryption back to the default:'--ch-set psk default --ch-index 0'. To set encryption with your " - "own key: '--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --ch-index 0'."), - nargs=2, action='append') + "--ch-set", + help=( + "Set a channel parameter. To see channel settings available:'--ch-set all all --ch-index 0'. " + "Can set the 'psk' using this command. To disable encryption on primary channel:'--ch-set psk none --ch-index 0'. " + "To set encryption with a new random key on second channel:'--ch-set psk random --ch-index 1'. " + "To set encryption back to the default:'--ch-set psk default --ch-index 0'. To set encryption with your " + "own key: '--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --ch-index 0'." + ), + nargs=2, + action="append", + ) parser.add_argument( - "--ch-vlongslow", help="Change to the very long-range and slow channel", action='store_true') + "--ch-vlongslow", + help="Change to the very long-range and slow channel", + action="store_true", + ) parser.add_argument( - "--ch-longslow", help="Change to the long-range and slow channel", action='store_true') + "--ch-longslow", + help="Change to the long-range and slow channel", + action="store_true", + ) parser.add_argument( - "--ch-longfast", help="Change to the long-range and fast channel", action='store_true') + "--ch-longfast", + help="Change to the long-range and fast channel", + action="store_true", + ) parser.add_argument( - "--ch-medslow", help="Change to the med-range and slow channel", action='store_true') + "--ch-medslow", + help="Change to the med-range and slow channel", + action="store_true", + ) parser.add_argument( - "--ch-medfast", help="Change to the med-range and fast channel", action='store_true') + "--ch-medfast", + help="Change to the med-range and fast channel", + action="store_true", + ) parser.add_argument( - "--ch-shortslow", help="Change to the short-range and slow channel", action='store_true') + "--ch-shortslow", + help="Change to the short-range and slow channel", + action="store_true", + ) parser.add_argument( - "--ch-shortfast", help="Change to the short-range and fast channel", action='store_true') + "--ch-shortfast", + help="Change to the short-range and fast channel", + action="store_true", + ) + + parser.add_argument("--set-owner", help="Set device owner name", action="store") parser.add_argument( - "--set-owner", help="Set device owner name", action="store") + "--set-canned-message", + help="Set the canned messages plugin message (up to 200 characters).", + action="store", + ) parser.add_argument( - "--set-canned-message", help="Set the canned messages plugin message (up to 200 characters).", action="store") + "--set-ringtone", + help="Set the Notification Ringtone (up to 230 characters).", + action="store", + ) parser.add_argument( - "--set-ringtone", help="Set the Notification Ringtone (up to 230 characters).", action="store") + "--set-owner-short", help="Set device owner short name", action="store" + ) parser.add_argument( - "--set-owner-short", help="Set device owner short name", action="store") + "--set-ham", help="Set licensed Ham ID and turn off encryption", action="store" + ) parser.add_argument( - "--set-ham", help="Set licensed Ham ID and turn off encryption", action="store") + "--dest", + help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate", + default=None, + ) parser.add_argument( - "--dest", help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate", default=None) + "--sendtext", + help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.", + ) parser.add_argument( - "--sendtext", help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.") + "--sendping", + help="Send a ping message (which requests a reply)", + action="store_true", + ) parser.add_argument( - "--sendping", help="Send a ping message (which requests a reply)", action="store_true") + "--traceroute", + help="Traceroute from connected node to a destination. " + "You need pass the destination ID as argument, like " + "this: '--traceroute !ba4bf9d0' " + "Only nodes that have the encryption key can be traced.", + ) parser.add_argument( - "--traceroute", help="Traceroute from connected node to a destination. " \ - "You need pass the destination ID as argument, like " \ - "this: '--traceroute !ba4bf9d0' " \ - "Only nodes that have the encryption key can be traced.") + "--reboot", help="Tell the destination node to reboot", action="store_true" + ) parser.add_argument( - "--reboot", help="Tell the destination node to reboot", action="store_true") + "--reboot-ota", + help="Tell the destination node to reboot into factory firmware", + action="store_true", + ) parser.add_argument( - "--reboot-ota", help="Tell the destination node to reboot into factory firmware", action="store_true") + "--shutdown", help="Tell the destination node to shutdown", action="store_true" + ) parser.add_argument( - "--shutdown", help="Tell the destination node to shutdown", action="store_true") + "--device-metadata", + help="Get the device metadata from the node", + action="store_true", + ) parser.add_argument( - "--device-metadata", help="Get the device metadata from the node", action="store_true") + "--begin-edit", + help="Tell the node to open a transaction to edit settings", + action="store_true", + ) parser.add_argument( - "--begin-edit", help="Tell the node to open a transaction to edit settings", action="store_true") + "--commit-edit", + help="Tell the node to commit open settings transaction", + action="store_true", + ) parser.add_argument( - "--commit-edit", help="Tell the node to commit open settings transaction", action="store_true") + "--factory-reset", + help="Tell the destination node to install the default config", + action="store_true", + ) parser.add_argument( - "--factory-reset", help="Tell the destination node to install the default config", action="store_true") + "--reset-nodedb", + help="Tell the destination node clear its list of nodes", + action="store_true", + ) parser.add_argument( - "--reset-nodedb", help="Tell the destination node clear its list of nodes", action="store_true") + "--reply", help="Reply to received messages", action="store_true" + ) parser.add_argument( - "--reply", help="Reply to received messages", - action="store_true") + "--gpio-wrb", nargs=2, help="Set a particular GPIO # to 1 or 0", action="append" + ) + + parser.add_argument("--gpio-rd", help="Read from a GPIO mask (ex: '0x10')") parser.add_argument( - "--gpio-wrb", nargs=2, help="Set a particular GPIO # to 1 or 0", action='append') + "--gpio-watch", help="Start watching a GPIO mask for changes (ex: '0x10')" + ) parser.add_argument( - "--gpio-rd", help="Read from a GPIO mask (ex: '0x10')") + "--no-time", + help="Suppress sending the current time to the mesh", + action="store_true", + ) + + parser.add_argument("--setalt", help="Set device altitude (allows use without GPS)") + + parser.add_argument("--setlat", help="Set device latitude (allows use without GPS)") parser.add_argument( - "--gpio-watch", help="Start watching a GPIO mask for changes (ex: '0x10')") + "--setlon", help="Set device longitude (allows use without GPS)" + ) parser.add_argument( - "--no-time", help="Suppress sending the current time to the mesh", action="store_true") + "--pos-fields", + help="Specify fields to send when sending a position. Use no argument for a list of valid values. " + "Can pass multiple values as a space separated list like " + "this: '--pos-fields POS_ALTITUDE POS_ALT_MSL'", + nargs="*", + action="store", + ) parser.add_argument( - "--setalt", help="Set device altitude (allows use without GPS)") + "--debug", help="Show API library debug log messages", action="store_true" + ) parser.add_argument( - "--setlat", help="Set device latitude (allows use without GPS)") + "--test", + help="Run stress test against all connected Meshtastic devices", + action="store_true", + ) parser.add_argument( - "--setlon", help="Set device longitude (allows use without GPS)") + "--ble", + help="BLE mac address to connect to (BLE is not yet supported for this tool)", + default=None, + ) parser.add_argument( - "--pos-fields", help="Specify fields to send when sending a position. Use no argument for a list of valid values. "\ - "Can pass multiple values as a space separated list like "\ - "this: '--pos-fields POS_ALTITUDE POS_ALT_MSL'", - nargs="*", action="store") + "--noproto", + help="Don't start the API, just function as a dumb serial terminal.", + action="store_true", + ) - parser.add_argument("--debug", help="Show API library debug log messages", - action="store_true") - - parser.add_argument("--test", help="Run stress test against all connected Meshtastic devices", - action="store_true") - - parser.add_argument("--ble", help="BLE mac address to connect to (BLE is not yet supported for this tool)", - default=None) - - parser.add_argument("--noproto", help="Don't start the API, just function as a dumb serial terminal.", - action="store_true") - - have_tunnel = platform.system() == 'Linux' + have_tunnel = platform.system() == "Linux" if have_tunnel: - parser.add_argument('--tunnel', action='store_true', - help="Create a TUN tunnel device for forwarding IP packets over the mesh") - parser.add_argument("--subnet", dest='tunnel_net', - help="Sets the local-end subnet address for the TUN IP bridge. (ex: 10.115' which is the default)", - default=None) + parser.add_argument( + "--tunnel", + action="store_true", + help="Create a TUN tunnel device for forwarding IP packets over the mesh", + ) + parser.add_argument( + "--subnet", + dest="tunnel_net", + help="Sets the local-end subnet address for the TUN IP bridge. (ex: 10.115' which is the default)", + default=None, + ) parser.set_defaults(deprecated=None) the_version = pkg_resources.get_distribution("meshtastic").version - parser.add_argument('--version', action='version', version=f"{the_version}") + parser.add_argument("--version", action="version", version=f"{the_version}") parser.add_argument( - "--support", action='store_true', help="Show support info (useful when troubleshooting an issue)") + "--support", + action="store_true", + help="Show support info (useful when troubleshooting an issue)", + ) args = parser.parse_args() our_globals.set_args(args) @@ -1095,7 +1328,6 @@ def main(): logfile.close() - def tunnelMain(): """Run a meshtastic IP tunnel""" our_globals = Globals.getInstance() diff --git a/meshtastic/ble_interface.py b/meshtastic/ble_interface.py index 4f476a5..171bde0 100644 --- a/meshtastic/ble_interface.py +++ b/meshtastic/ble_interface.py @@ -6,12 +6,11 @@ import platform from meshtastic.mesh_interface import MeshInterface from meshtastic.util import our_exit -if platform.system() == 'Linux': +if platform.system() == "Linux": # pylint: disable=E0401 import pygatt - # Our standard BLE characteristics TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7" FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5" @@ -22,7 +21,7 @@ class BLEInterface(MeshInterface): """A not quite ready - FIXME - BLE interface to devices""" def __init__(self, address, noProto=False, debugOut=None): - if platform.system() != 'Linux': + if platform.system() != "Linux": our_exit("Linux is the only platform with experimental BLE support.", 1) self.address = address if not noProto: @@ -39,7 +38,7 @@ class BLEInterface(MeshInterface): self._readFromRadio() # read the initial responses - def handle_data(handle, data): # pylint: disable=W0613 + def handle_data(handle, data): # pylint: disable=W0613 self._handleFromRadio(data) if self.device: @@ -47,7 +46,7 @@ class BLEInterface(MeshInterface): def _sendToRadioImpl(self, toRadio): """Send a ToRadio protobuf to the device""" - #logging.debug(f"Sending: {stripnl(toRadio)}") + # logging.debug(f"Sending: {stripnl(toRadio)}") b = toRadio.SerializeToString() self.device.char_write(TORADIO_UUID, b) diff --git a/meshtastic/globals.py b/meshtastic/globals.py index a8a2bc7..07ae2e3 100644 --- a/meshtastic/globals.py +++ b/meshtastic/globals.py @@ -8,8 +8,10 @@ """ + class Globals: """Globals class is a Singleton.""" + __instance = None @staticmethod diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 06036cc..869309b 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -1,27 +1,42 @@ """Mesh Interface class """ -import sys + +import collections +import logging import random +import sys +import threading import time import json -import logging -import collections -from typing import AnyStr -import threading from datetime import datetime -import timeago -from tabulate import tabulate +from typing import AnyStr import google.protobuf.json_format - -from pubsub import pub +import timeago from google.protobuf.json_format import MessageToJson - +from pubsub import pub +from tabulate import tabulate import meshtastic.node -from meshtastic import portnums_pb2, mesh_pb2 -from meshtastic.util import stripnl, Timeout, Acknowledgment, our_exit, remove_keys_from_dict, convert_mac_addr -from meshtastic.__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols +from meshtastic import mesh_pb2, portnums_pb2 +from meshtastic.__init__ import ( + BROADCAST_ADDR, + BROADCAST_NUM, + LOCAL_ADDR, + OUR_APP_VERSION, + ResponseHandler, + protocols, + publishingThread, +) +from meshtastic.util import ( + Acknowledgment, + Timeout, + convert_mac_addr, + our_exit, + remove_keys_from_dict, + stripnl, +) + class MeshInterface: """Interface class for meshtastic devices @@ -47,16 +62,18 @@ class MeshInterface: self.localNode = meshtastic.node.Node(self, -1) # We fixup nodenum later self.myInfo = None # We don't have device info yet self.responseHandlers = {} # A map from request ID to the handler - self.failure = None # If we've encountered a fatal exception it will be kept here + self.failure = ( + None # If we've encountered a fatal exception it will be kept here + ) self._timeout = Timeout() self._acknowledgment = Acknowledgment() self.heartbeatTimer = None random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it - self.currentPacketId = random.randint(0, 0xffffffff) + self.currentPacketId = random.randint(0, 0xFFFFFFFF) self.nodesByNum = None self.configId = None - self.gotResponse = False # used in gpio read - self.mask = None # used in gpio read and gpio watch + self.gotResponse = False # used in gpio read + self.mask = None # used in gpio read and gpio watch self.queueStatus = None self.queue = collections.OrderedDict() @@ -72,15 +89,17 @@ class MeshInterface: def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None and exc_value is not None: - logging.error(f'An exception of type {exc_type} with value {exc_value} has occurred') + logging.error( + f"An exception of type {exc_type} with value {exc_value} has occurred" + ) if traceback is not None: - logging.error(f'Traceback: {traceback}') + logging.error(f"Traceback: {traceback}") self.close() - def showInfo(self, file=sys.stdout): # pylint: disable=W0613 + def showInfo(self, file=sys.stdout): # pylint: disable=W0613 """Show human readable summary about this object""" owner = f"Owner: {self.getLongName()} ({self.getShortName()})" - myinfo = '' + myinfo = "" if self.myInfo: myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}" mesh = "\n\nNodes in mesh: " @@ -89,15 +108,15 @@ class MeshInterface: for n in self.nodes.values(): # when the TBeam is first booted, it sometimes shows the raw data # so, we will just remove any raw keys - keys_to_remove = ('raw', 'decoded', 'payload') + keys_to_remove = ("raw", "decoded", "payload") n2 = remove_keys_from_dict(keys_to_remove, n) # if we have 'macaddr', re-format it - if 'macaddr' in n2['user']: - val = n2['user']['macaddr'] + if "macaddr" in n2["user"]: + val = n2["user"]["macaddr"] # decode the base64 value addr = convert_mac_addr(val) - n2['user']['macaddr'] = addr + n2["user"]["macaddr"] = addr # use id as dictionary key for correct json format in list of nodes nodeid = n2['user']['id'] @@ -107,76 +126,94 @@ class MeshInterface: print(infos) return infos - def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613 + def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613 """Show table summary of nodes in mesh""" - def formatFloat(value, precision=2, unit=''): + + def formatFloat(value, precision=2, unit=""): """Format a float value with precsion.""" - return f'{value:.{precision}f}{unit}' if value else None + return f"{value:.{precision}f}{unit}" if value else None def getLH(ts): """Format last heard""" - return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None + return ( + datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None + ) def getTimeAgo(ts): """Format how long ago have we heard from this node (aka timeago).""" - return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None + return ( + timeago.format(datetime.fromtimestamp(ts), datetime.now()) + if ts + else None + ) rows = [] if self.nodes: - logging.debug(f'self.nodes:{self.nodes}') + logging.debug(f"self.nodes:{self.nodes}") for node in self.nodes.values(): - if not includeSelf and node['num'] == self.localNode.nodeNum: + if not includeSelf and node["num"] == self.localNode.nodeNum: continue row = {"N": 0} - user = node.get('user') + user = node.get("user") if user: - row.update({ - "User": user['longName'], - "AKA": user['shortName'], - "ID": user['id'], - }) + row.update( + { + "User": user["longName"], + "AKA": user["shortName"], + "ID": user["id"], + } + ) - pos = node.get('position') + pos = node.get("position") if pos: - row.update({ - "Latitude": formatFloat(pos.get("latitude"), 4, "°"), - "Longitude": formatFloat(pos.get("longitude"), 4, "°"), - "Altitude": formatFloat(pos.get("altitude"), 0, " m"), - }) - - metrics = node.get('deviceMetrics') - if metrics: - batteryLevel = metrics.get('batteryLevel') + row.update( + { + "Latitude": formatFloat(pos.get("latitude"), 4, "°"), + "Longitude": formatFloat(pos.get("longitude"), 4, "°"), + "Altitude": formatFloat(pos.get("altitude"), 0, " m"), + } + ) + + metrics = node.get("deviceMetrics") + if metrics: + batteryLevel = metrics.get("batteryLevel") if batteryLevel is not None: if batteryLevel == 0: batteryString = "Powered" else: - batteryString = str(batteryLevel)+"%" - row.update({"Battery": batteryString}) - row.update({ - "Channel util.": formatFloat(metrics.get('channelUtilization'), 2, "%"), - "Tx air util.": formatFloat(metrics.get('airUtilTx'), 2, "%"), - }) + batteryString = str(batteryLevel) + "%" + row.update({"Battery": batteryString}) + row.update( + { + "Channel util.": formatFloat( + metrics.get("channelUtilization"), 2, "%" + ), + "Tx air util.": formatFloat( + metrics.get("airUtilTx"), 2, "%" + ), + } + ) - row.update({ - "SNR": formatFloat(node.get("snr"), 2, " dB"), - "LastHeard": getLH(node.get("lastHeard")), - "Since": getTimeAgo(node.get("lastHeard")), - }) + row.update( + { + "SNR": formatFloat(node.get("snr"), 2, " dB"), + "LastHeard": getLH(node.get("lastHeard")), + "Since": getTimeAgo(node.get("lastHeard")), + } + ) rows.append(row) - rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True) + rows.sort(key=lambda r: r.get("LastHeard") or "0000", reverse=True) for i, row in enumerate(rows): - row['N'] = i+1 + row["N"] = i + 1 - table = tabulate(rows, headers='keys', missingval='N/A', tablefmt='fancy_grid') + table = tabulate(rows, headers="keys", missingval="N/A", tablefmt="fancy_grid") print(table) return table - def getNode(self, nodeId, requestChannels=True): """Return a node object which contains device settings and channel info""" if nodeId in (LOCAL_ADDR, BROADCAST_ADDR): @@ -191,12 +228,15 @@ class MeshInterface: our_exit("Error: Timed out waiting for channels") return n - def sendText(self, text: AnyStr, - destinationId=BROADCAST_ADDR, - wantAck=False, - wantResponse=False, - onResponse=None, - channelIndex=0): + def sendText( + self, + text: AnyStr, + destinationId=BROADCAST_ADDR, + wantAck=False, + wantResponse=False, + onResponse=None, + channelIndex=0, + ): """Send a utf8 string to some other node, if the node has a display it will also be shown on the device. @@ -217,18 +257,26 @@ class MeshInterface: and can be used to track future message acks/naks. """ - return self.sendData(text.encode("utf-8"), destinationId, - portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP, - wantAck=wantAck, - wantResponse=wantResponse, - onResponse=onResponse, - channelIndex=channelIndex) + return self.sendData( + text.encode("utf-8"), + destinationId, + portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP, + wantAck=wantAck, + wantResponse=wantResponse, + onResponse=onResponse, + channelIndex=channelIndex, + ) - def sendData(self, data, destinationId=BROADCAST_ADDR, - portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False, - wantResponse=False, - onResponse=None, - channelIndex=0): + def sendData( + self, + data, + destinationId=BROADCAST_ADDR, + portNum=portnums_pb2.PortNum.PRIVATE_APP, + wantAck=False, + wantResponse=False, + onResponse=None, + channelIndex=0, + ): """Send a data packet to some other node Keyword Arguments: @@ -257,11 +305,15 @@ class MeshInterface: data = data.SerializeToString() logging.debug(f"len(data): {len(data)}") - logging.debug(f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}") + logging.debug( + f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}" + ) if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN: raise Exception("Data payload too big") - if portNum == portnums_pb2.PortNum.UNKNOWN_APP: # we are now more strict wrt port numbers + if ( + portNum == portnums_pb2.PortNum.UNKNOWN_APP + ): # we are now more strict wrt port numbers our_exit("Warning: A non-zero port number must be specified") meshPacket = mesh_pb2.MeshPacket() @@ -276,8 +328,16 @@ class MeshInterface: p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck) return p - def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0, - destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False): + def sendPosition( + self, + latitude=0.0, + longitude=0.0, + altitude=0, + timeSec=0, + destinationId=BROADCAST_ADDR, + wantAck=False, + wantResponse=False, + ): """ Send a position packet to some other node (normally a broadcast) @@ -292,33 +352,41 @@ class MeshInterface: p = mesh_pb2.Position() if latitude != 0.0: p.latitude_i = int(latitude / 1e-7) - logging.debug(f'p.latitude_i:{p.latitude_i}') + logging.debug(f"p.latitude_i:{p.latitude_i}") if longitude != 0.0: p.longitude_i = int(longitude / 1e-7) - logging.debug(f'p.longitude_i:{p.longitude_i}') + logging.debug(f"p.longitude_i:{p.longitude_i}") if altitude != 0: p.altitude = int(altitude) - logging.debug(f'p.altitude:{p.altitude}') + logging.debug(f"p.altitude:{p.altitude}") if timeSec == 0: timeSec = time.time() # returns unix timestamp in seconds p.time = int(timeSec) - logging.debug(f'p.time:{p.time}') + logging.debug(f"p.time:{p.time}") - return self.sendData(p, destinationId, - portNum=portnums_pb2.PortNum.POSITION_APP, - wantAck=wantAck, - wantResponse=wantResponse) + return self.sendData( + p, + destinationId, + portNum=portnums_pb2.PortNum.POSITION_APP, + wantAck=wantAck, + wantResponse=wantResponse, + ) def sendTraceRoute(self, dest, hopLimit): """Send the trace route""" r = mesh_pb2.RouteDiscovery() - self.sendData(r, destinationId=dest, portNum=portnums_pb2.PortNum.TRACEROUTE_APP, - wantResponse=True, onResponse=self.onResponseTraceRoute) + self.sendData( + r, + destinationId=dest, + portNum=portnums_pb2.PortNum.TRACEROUTE_APP, + wantResponse=True, + onResponse=self.onResponseTraceRoute, + ) # extend timeout based on number of nodes, limit by configured hopLimit - waitFactor = min(len(self.nodes)-1, hopLimit) + waitFactor = min(len(self.nodes) - 1, hopLimit) self.waitForTraceRoute(waitFactor) def onResponseTraceRoute(self, p): @@ -340,9 +408,7 @@ class MeshInterface: def _addResponseHandler(self, requestId, callback): self.responseHandlers[requestId] = ResponseHandler(callback) - def _sendPacket(self, meshPacket, - destinationId=BROADCAST_ADDR, - wantAck=False): + def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False): """Send a MeshPacket to the specified node (or if unspecified, broadcast). You probably don't want this - use sendData instead. @@ -351,7 +417,7 @@ class MeshInterface: """ # We allow users to talk to the local node before we've completed the full connection flow... - if(self.myInfo is not None and destinationId != self.myInfo.my_node_num): + if self.myInfo is not None and destinationId != self.myInfo.my_node_num: self._waitConnected() toRadio = mesh_pb2.ToRadio() @@ -376,14 +442,14 @@ class MeshInterface: node = self.nodes.get(destinationId) if not node: our_exit(f"Warning: NodeId {destinationId} not found in DB") - nodeNum = node['num'] + nodeNum = node["num"] else: logging.warning("Warning: There were no self.nodes.") meshPacket.to = nodeNum meshPacket.want_ack = wantAck - loraConfig = getattr(self.localNode.localConfig, 'lora') - hopLimit = getattr(loraConfig, 'hop_limit') + loraConfig = getattr(self.localNode.localConfig, "lora") + hopLimit = getattr(loraConfig, "hop_limit") meshPacket.hop_limit = hopLimit # if the user hasn't set an ID for this packet (likely and recommended), @@ -393,7 +459,9 @@ class MeshInterface: toRadio.packet.CopyFrom(meshPacket) if self.noProto: - logging.warning(f"Not sending packet because protocol use is disabled by noProto") + logging.warning( + f"Not sending packet because protocol use is disabled by noProto" + ) else: logging.debug(f"Sending packet: {stripnl(meshPacket)}") self._sendToRadio(toRadio) @@ -401,7 +469,10 @@ class MeshInterface: def waitForConfig(self): """Block until radio config is received. Returns True if config has been received.""" - success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig() + success = ( + self._timeout.waitForSet(self, attrs=("myInfo", "nodes")) + and self.localNode.waitForConfig() + ) if not success: raise Exception("Timed out waiting for interface config") @@ -421,28 +492,28 @@ class MeshInterface: """Get info about my node.""" if self.myInfo is None: return None - logging.debug(f'self.nodesByNum:{self.nodesByNum}') + logging.debug(f"self.nodesByNum:{self.nodesByNum}") return self.nodesByNum.get(self.myInfo.my_node_num) def getMyUser(self): """Get user""" nodeInfo = self.getMyNodeInfo() if nodeInfo is not None: - return nodeInfo.get('user') + return nodeInfo.get("user") return None def getLongName(self): """Get long name""" user = self.getMyUser() if user is not None: - return user.get('longName', None) + return user.get("longName", None) return None def getShortName(self): """Get short name""" user = self.getMyUser() if user is not None: - return user.get('shortName', None) + return user.get("shortName", None) return None def _waitConnected(self, timeout=15.0): @@ -461,16 +532,19 @@ class MeshInterface: if self.currentPacketId is None: raise Exception("Not connected yet, can not generate packet") else: - self.currentPacketId = (self.currentPacketId + 1) & 0xffffffff + self.currentPacketId = (self.currentPacketId + 1) & 0xFFFFFFFF return self.currentPacketId def _disconnected(self): """Called by subclasses to tell clients this interface has disconnected""" self.isConnected.clear() - publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.lost", interface=self)) + publishingThread.queueWork( + lambda: pub.sendMessage("meshtastic.connection.lost", interface=self) + ) def _startHeartbeat(self): """We need to send a heartbeat message to the device every X seconds""" + def callback(): self.heartbeatTimer = None prefs = self.localNode.localConfig @@ -485,15 +559,18 @@ class MeshInterface: callback() # run our periodic callback now, it will make another timer if necessary def _connected(self): - """Called by this class to tell clients we are now fully connected to a node - """ + """Called by this class to tell clients we are now fully connected to a node""" # (because I'm lazy) _connected might be called when remote Node # objects complete their config reads, don't generate redundant isConnected # for the local interface if not self.isConnected.is_set(): self.isConnected.set() self._startHeartbeat() - publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.established", interface=self)) + publishingThread.queueWork( + lambda: pub.sendMessage( + "meshtastic.connection.established", interface=self + ) + ) def _startConfig(self): """Start device packets flowing""" @@ -502,7 +579,7 @@ class MeshInterface: self.nodesByNum = {} # nodes keyed by nodenum startConfig = mesh_pb2.ToRadio() - self.configId = random.randint(0, 0xffffffff) + self.configId = random.randint(0, 0xFFFFFFFF) startConfig.want_config_id = self.configId self._sendToRadio(startConfig) @@ -526,11 +603,13 @@ class MeshInterface: 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") + logging.warning( + f"Not sending packet because protocol use is disabled by noProto" + ) else: - #logging.debug(f"Sending toRadio: {stripnl(toRadio)}") + # logging.debug(f"Sending toRadio: {stripnl(toRadio)}") - if not toRadio.HasField('packet'): + if not toRadio.HasField("packet"): # not a meshpacket -- send immediately, give queue a chance, # this makes heartbeat trigger queue self._sendToRadioImpl(toRadio) @@ -541,7 +620,7 @@ class MeshInterface: resentQueue = collections.OrderedDict() while self.queue: - #logging.warn("queue: " + " ".join(f'{k:08x}' for k in 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) @@ -550,7 +629,7 @@ class MeshInterface: except KeyError: break packetId, packet = toResend - #logging.warn(f"packet: {packetId:08x} {packet}") + # logging.warn(f"packet: {packetId:08x} {packet}") resentQueue[packetId] = packet if packet is False: continue @@ -559,14 +638,16 @@ class MeshInterface: logging.debug(f"Resending packet ID {packetId:08x} {packet}") self._sendToRadioImpl(packet) - #logging.warn("resentQueue: " + " ".join(f'{k:08x}' for k in resentQueue)) + # 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 + 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)) + # logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue)) def _sendToRadioImpl(self, toRadio): """Send a ToRadio protobuf to the device""" @@ -581,18 +662,22 @@ class MeshInterface: 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} ") + 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)) + # 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)) + 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): """ @@ -601,7 +686,9 @@ class MeshInterface: Called by subclasses.""" fromRadio = mesh_pb2.FromRadio() fromRadio.ParseFromString(fromRadioBytes) - logging.debug(f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}") + logging.debug( + f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}" + ) asDict = google.protobuf.json_format.MessageToDict(fromRadio) logging.debug(f"Received from radio: {fromRadio}") if fromRadio.HasField("my_info"): @@ -612,13 +699,17 @@ class MeshInterface: failmsg = None # Check for app too old if self.myInfo.min_app_version > OUR_APP_VERSION: - failmsg = "This device needs a newer python client, run 'pip install --upgrade meshtastic'."\ - "For more information see https://tinyurl.com/5bjsxu32" + failmsg = ( + "This device needs a newer python client, run 'pip install --upgrade meshtastic'." + "For more information see https://tinyurl.com/5bjsxu32" + ) # check for firmware too old if self.myInfo.max_channels == 0: - failmsg = "This version of meshtastic-python requires device firmware version 1.2 or later. "\ - "For more information see https://tinyurl.com/5bjsxu32" + failmsg = ( + "This version of meshtastic-python requires device firmware version 1.2 or later. " + "For more information see https://tinyurl.com/5bjsxu32" + ) if failmsg: self.failure = Exception(failmsg) @@ -639,8 +730,11 @@ class MeshInterface: if "user" in node: # Some nodes might not have user/ids assigned yet if "id" in node["user"]: self.nodes[node["user"]["id"]] = node - publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated", - node=node, interface=self)) + publishingThread.queueWork( + lambda: pub.sendMessage( + "meshtastic.node.updated", node=node, interface=self + ) + ) elif fromRadio.config_complete_id == self.configId: # we ignore the config_complete_id, it is unneeded for our # stream API fromRadio.config_complete_id @@ -650,7 +744,7 @@ class MeshInterface: elif fromRadio.HasField("packet"): self._handlePacketFromRadio(fromRadio.packet) - elif fromRadio.HasField('queueStatus'): + elif fromRadio.HasField("queueStatus"): self._handleQueueStatusFromRadio(fromRadio.queueStatus) elif fromRadio.rebooted: @@ -674,24 +768,38 @@ class MeshInterface: elif fromRadio.config.HasField("lora"): self.localNode.localConfig.lora.CopyFrom(fromRadio.config.lora) elif fromRadio.config.HasField("bluetooth"): - self.localNode.localConfig.bluetooth.CopyFrom(fromRadio.config.bluetooth) + self.localNode.localConfig.bluetooth.CopyFrom( + fromRadio.config.bluetooth + ) elif fromRadio.moduleConfig.HasField("mqtt"): self.localNode.moduleConfig.mqtt.CopyFrom(fromRadio.moduleConfig.mqtt) elif fromRadio.moduleConfig.HasField("serial"): - self.localNode.moduleConfig.serial.CopyFrom(fromRadio.moduleConfig.serial) + self.localNode.moduleConfig.serial.CopyFrom( + fromRadio.moduleConfig.serial + ) elif fromRadio.moduleConfig.HasField("external_notification"): - self.localNode.moduleConfig.external_notification.CopyFrom(fromRadio.moduleConfig.external_notification) + self.localNode.moduleConfig.external_notification.CopyFrom( + fromRadio.moduleConfig.external_notification + ) elif fromRadio.moduleConfig.HasField("range_test"): - self.localNode.moduleConfig.range_test.CopyFrom(fromRadio.moduleConfig.range_test) + self.localNode.moduleConfig.range_test.CopyFrom( + fromRadio.moduleConfig.range_test + ) elif fromRadio.moduleConfig.HasField("telemetry"): - self.localNode.moduleConfig.telemetry.CopyFrom(fromRadio.moduleConfig.telemetry) + self.localNode.moduleConfig.telemetry.CopyFrom( + fromRadio.moduleConfig.telemetry + ) elif fromRadio.moduleConfig.HasField("canned_message"): - self.localNode.moduleConfig.canned_message.CopyFrom(fromRadio.moduleConfig.canned_message) + self.localNode.moduleConfig.canned_message.CopyFrom( + fromRadio.moduleConfig.canned_message + ) elif fromRadio.moduleConfig.HasField("audio"): self.localNode.moduleConfig.audio.CopyFrom(fromRadio.moduleConfig.audio) elif fromRadio.moduleConfig.HasField("remote_hardware"): - self.localNode.moduleConfig.remote_hardware.CopyFrom(fromRadio.moduleConfig.remote_hardware) + self.localNode.moduleConfig.remote_hardware.CopyFrom( + fromRadio.moduleConfig.remote_hardware + ) else: logging.debug("Unexpected FromRadio payload") @@ -765,8 +873,12 @@ class MeshInterface: # from might be missing if the nodenum was zero. if not hack and "from" not in asDict: asDict["from"] = 0 - logging.error(f"Device returned a packet we sent, ignoring: {stripnl(asDict)}") - print(f"Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}") + logging.error( + f"Device returned a packet we sent, ignoring: {stripnl(asDict)}" + ) + print( + f"Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}" + ) return if "to" not in asDict: asDict["to"] = 0 @@ -786,7 +898,7 @@ class MeshInterface: topic = "meshtastic.receive" # Generic unknown packet type decoded = None - if 'decoded' in asDict: + if "decoded" in asDict: decoded = asDict["decoded"] # The default MessageToDict converts byte arrays into base64 strings. # We don't want that - it messes up data payload. So slam in the correct @@ -842,5 +954,6 @@ class MeshInterface: handler.callback(asDict) logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ") - publishingThread.queueWork(lambda: pub.sendMessage( - topic, packet=asDict, interface=self)) + publishingThread.queueWork( + lambda: pub.sendMessage(topic, packet=asDict, interface=self) + ) diff --git a/meshtastic/node.py b/meshtastic/node.py index 7ede26b..ad1c0a6 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -1,12 +1,21 @@ """Node class """ -import logging import base64 +import logging import time + from google.protobuf.json_format import MessageToJson -from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2, localonly_pb2 -from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK, camel_to_snake + +from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2 +from meshtastic.util import ( + Timeout, + camel_to_snake, + fromPSK, + our_exit, + pskToString, + stripnl, +) class Node: @@ -36,13 +45,15 @@ class Node: """Show human readable description of our channels.""" print("Channels:") if self.channels: - logging.debug(f'self.channels:{self.channels}') + logging.debug(f"self.channels:{self.channels}") for c in self.channels: - #print('c.settings.psk:', c.settings.psk) + # print('c.settings.psk:', c.settings.psk) cStr = stripnl(MessageToJson(c.settings)) # only show if there is no psk (meaning disabled channel) if c.settings.psk: - print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}") + print( + f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}" + ) publicURL = self.getURL(includeAll=False) adminURL = self.getURL(includeAll=True) print(f"\nPrimary channel URL: {publicURL}") @@ -68,10 +79,10 @@ class Node: self.partialChannels = [] # We keep our channels in a temp array until finished self._requestChannel(0) - + def onResponseRequestSettings(self, p): """Handle the response packets for requesting settings _requestSettings()""" - logging.debug(f'onResponseRequestSetting() p:{p}') + logging.debug(f"onResponseRequestSetting() p:{p}") if "routing" in p["decoded"]: if p["decoded"]["routing"]["errorReason"] != "NONE": print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}') @@ -83,15 +94,21 @@ class Node: if "getConfigResponse" in adminMessage: resp = adminMessage["getConfigResponse"] field = list(resp.keys())[0] - config_type = self.localConfig.DESCRIPTOR.fields_by_name.get(camel_to_snake(field)) + config_type = self.localConfig.DESCRIPTOR.fields_by_name.get( + camel_to_snake(field) + ) config_values = getattr(self.localConfig, config_type.name) elif "getModuleConfigResponse" in adminMessage: resp = adminMessage["getModuleConfigResponse"] field = list(resp.keys())[0] - config_type = self.moduleConfig.DESCRIPTOR.fields_by_name.get(camel_to_snake(field)) + config_type = self.moduleConfig.DESCRIPTOR.fields_by_name.get( + camel_to_snake(field) + ) config_values = getattr(self.moduleConfig, config_type.name) - else: - print("Did not receive a valid response. Make sure to have a shared channel named 'admin'.") + else: + print( + "Did not receive a valid response. Make sure to have a shared channel named 'admin'." + ) return for key, value in resp[field].items(): setattr(config_values, camel_to_snake(key), value) @@ -100,7 +117,7 @@ class Node: def requestConfig(self, configType): if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onResponseRequestSettings print("Requesting current config from remote node (this can take a while).") @@ -109,7 +126,7 @@ class Node: p = admin_pb2.AdminMessage() p.get_config_request = msgIndex self._sendAdmin(p, wantResponse=True, onResponse=onResponse) - else: + else: p = admin_pb2.AdminMessage() p.get_module_config_request = msgIndex self._sendAdmin(p, wantResponse=True, onResponse=onResponse) @@ -122,126 +139,9 @@ class Node: print("Writing modified channels to device") self.writeChannel(0) - def waitForConfig(self, attribute='channels'): + def waitForConfig(self, attribute="channels"): """Block until radio config is received. Returns True if config has been received.""" - return self._timeout.waitForSet(self, attrs=('localConfig', attribute)) - - def writeConfig(self): - """Write the current (edited) localConfig to the device""" - if self.localConfig is None: - our_exit("Error: No localConfig has been read") - - if self.localConfig.device: - p = admin_pb2.AdminMessage() - p.set_config.device.CopyFrom(self.localConfig.device) - self._sendAdmin(p) - logging.debug("Wrote device") - time.sleep(0.3) - - if self.localConfig.position: - p = admin_pb2.AdminMessage() - p.set_config.position.CopyFrom(self.localConfig.position) - self._sendAdmin(p) - logging.debug("Wrote position") - time.sleep(0.3) - - if self.localConfig.power: - p = admin_pb2.AdminMessage() - p.set_config.power.CopyFrom(self.localConfig.power) - self._sendAdmin(p) - logging.debug("Wrote power") - time.sleep(0.3) - - if self.localConfig.network: - p = admin_pb2.AdminMessage() - p.set_config.network.CopyFrom(self.localConfig.network) - self._sendAdmin(p) - logging.debug("Wrote network") - time.sleep(0.3) - - if self.localConfig.display: - p = admin_pb2.AdminMessage() - p.set_config.display.CopyFrom(self.localConfig.display) - self._sendAdmin(p) - logging.debug("Wrote display") - time.sleep(0.3) - - if self.localConfig.lora: - p = admin_pb2.AdminMessage() - p.set_config.lora.CopyFrom(self.localConfig.lora) - self._sendAdmin(p) - logging.debug("Wrote lora") - time.sleep(0.3) - - if self.localConfig.bluetooth: - p = admin_pb2.AdminMessage() - p.set_config.bluetooth.CopyFrom(self.localConfig.bluetooth) - self._sendAdmin(p) - logging.debug("Wrote bluetooth") - time.sleep(0.3) - - if self.moduleConfig.mqtt: - p = admin_pb2.AdminMessage() - p.set_module_config.mqtt.CopyFrom(self.moduleConfig.mqtt) - self._sendAdmin(p) - logging.debug("Wrote module: mqtt") - time.sleep(0.3) - - if self.moduleConfig.serial: - p = admin_pb2.AdminMessage() - p.set_module_config.serial.CopyFrom(self.moduleConfig.serial) - self._sendAdmin(p) - logging.debug("Wrote module: serial") - time.sleep(0.3) - - if self.moduleConfig.external_notification: - p = admin_pb2.AdminMessage() - p.set_module_config.external_notification.CopyFrom(self.moduleConfig.external_notification) - self._sendAdmin(p) - logging.debug("Wrote module: external_notification") - time.sleep(0.3) - - if self.moduleConfig.store_forward: - p = admin_pb2.AdminMessage() - p.set_module_config.store_forward.CopyFrom(self.moduleConfig.store_forward) - self._sendAdmin(p) - logging.debug("Wrote module: store_forward") - time.sleep(0.3) - - if self.moduleConfig.range_test: - p = admin_pb2.AdminMessage() - p.set_module_config.range_test.CopyFrom(self.moduleConfig.range_test) - self._sendAdmin(p) - logging.debug("Wrote module: range_test") - time.sleep(0.3) - - if self.moduleConfig.telemetry: - p = admin_pb2.AdminMessage() - p.set_module_config.telemetry.CopyFrom(self.moduleConfig.telemetry) - self._sendAdmin(p) - logging.debug("Wrote module: telemetry") - time.sleep(0.3) - - if self.moduleConfig.canned_message: - p = admin_pb2.AdminMessage() - p.set_module_config.canned_message.CopyFrom(self.moduleConfig.canned_message) - self._sendAdmin(p) - logging.debug("Wrote module: canned_message") - time.sleep(0.3) - - if self.moduleConfig.audio: - p = admin_pb2.AdminMessage() - p.set_module_config.audio.CopyFrom(self.moduleConfig.audio) - self._sendAdmin(p) - logging.debug("Wrote module: audio") - time.sleep(0.3) - - if self.moduleConfig.remote_hardware: - p = admin_pb2.AdminMessage() - p.set_module_config.remote_hardware.CopyFrom(self.moduleConfig.remote_hardware) - self._sendAdmin(p) - logging.debug("Wrote module: remote_hardware") - time.sleep(0.3) + return self._timeout.waitForSet(self, attrs=("localConfig", attribute)) def writeConfig(self, config_name): """Write the current (edited) localConfig to the device""" @@ -250,45 +150,51 @@ class Node: p = admin_pb2.AdminMessage() - if config_name == 'device': + if config_name == "device": p.set_config.device.CopyFrom(self.localConfig.device) - elif config_name == 'position': + elif config_name == "position": p.set_config.position.CopyFrom(self.localConfig.position) - elif config_name == 'power': + elif config_name == "power": p.set_config.power.CopyFrom(self.localConfig.power) - elif config_name == 'network': + elif config_name == "network": p.set_config.network.CopyFrom(self.localConfig.network) - elif config_name == 'display': + elif config_name == "display": p.set_config.display.CopyFrom(self.localConfig.display) - elif config_name == 'lora': + elif config_name == "lora": p.set_config.lora.CopyFrom(self.localConfig.lora) - elif config_name == 'bluetooth': + elif config_name == "bluetooth": p.set_config.bluetooth.CopyFrom(self.localConfig.bluetooth) - elif config_name == 'mqtt': + elif config_name == "mqtt": p.set_module_config.mqtt.CopyFrom(self.moduleConfig.mqtt) - elif config_name == 'serial': + elif config_name == "serial": p.set_module_config.serial.CopyFrom(self.moduleConfig.serial) - elif config_name == 'external_notification': - p.set_module_config.external_notification.CopyFrom(self.moduleConfig.external_notification) - elif config_name == 'store_forward': + elif config_name == "external_notification": + p.set_module_config.external_notification.CopyFrom( + self.moduleConfig.external_notification + ) + elif config_name == "store_forward": p.set_module_config.store_forward.CopyFrom(self.moduleConfig.store_forward) - elif config_name == 'range_test': + elif config_name == "range_test": p.set_module_config.range_test.CopyFrom(self.moduleConfig.range_test) - elif config_name == 'telemetry': + elif config_name == "telemetry": p.set_module_config.telemetry.CopyFrom(self.moduleConfig.telemetry) - elif config_name == 'canned_message': - p.set_module_config.canned_message.CopyFrom(self.moduleConfig.canned_message) - elif config_name == 'audio': + elif config_name == "canned_message": + p.set_module_config.canned_message.CopyFrom( + self.moduleConfig.canned_message + ) + elif config_name == "audio": p.set_module_config.audio.CopyFrom(self.moduleConfig.audio) - elif config_name == 'remote_hardware': - p.set_module_config.remote_hardware.CopyFrom(self.moduleConfig.remote_hardware) + elif config_name == "remote_hardware": + p.set_module_config.remote_hardware.CopyFrom( + self.moduleConfig.remote_hardware + ) else: our_exit(f"Error: No valid config with name {config_name}") - + logging.debug(f"Wrote: {config_name}") if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak self._sendAdmin(p, onResponse=onResponse) @@ -302,8 +208,8 @@ class Node: def getChannelByChannelIndex(self, channelIndex): """Get channel by channelIndex - channelIndex: number, typically 0-7; based on max number channels - returns: None if there is no channel found + channelIndex: number, typically 0-7; based on max number channels + returns: None if there is no channel found """ ch = None if self.channels and 0 <= channelIndex < len(self.channels): @@ -313,7 +219,10 @@ class Node: def deleteChannel(self, channelIndex): """Delete the specifed channelIndex and shift other channels up""" ch = self.channels[channelIndex] - if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED): + if ch.role not in ( + channel_pb2.Channel.Role.SECONDARY, + channel_pb2.Channel.Role.DISABLED, + ): our_exit("Warning: Only SECONDARY channels can be deleted") # we are careful here because if we move the "admin" channel the channelIndex we need to use @@ -337,7 +246,7 @@ class Node: def getChannelByName(self, name): """Try to find the named channel or return None""" - for c in (self.channels or []): + for c in self.channels or []: if c.settings and c.settings.name == name: return c return None @@ -375,13 +284,13 @@ class Node: p.set_owner.short_name = short_name # Note: These debug lines are used in unit tests - logging.debug(f'p.set_owner.long_name:{p.set_owner.long_name}:') - logging.debug(f'p.set_owner.short_name:{p.set_owner.short_name}:') - logging.debug(f'p.set_owner.is_licensed:{p.set_owner.is_licensed}') + logging.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:") + logging.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:") + logging.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}") # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) @@ -391,12 +300,14 @@ class Node: channelSet = apponly_pb2.ChannelSet() if self.channels: for c in self.channels: - if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY): + if c.role == channel_pb2.Channel.Role.PRIMARY or ( + includeAll and c.role == channel_pb2.Channel.Role.SECONDARY + ): channelSet.settings.append(c.settings) channelSet.lora_config.CopyFrom(self.localConfig.lora) some_bytes = channelSet.SerializeToString() - s = base64.urlsafe_b64encode(some_bytes).decode('ascii') + s = base64.urlsafe_b64encode(some_bytes).decode("ascii") s = s.replace("=", "").replace("+", "-").replace("/", "_") return f"https://meshtastic.org/e/#{s}" @@ -415,24 +326,27 @@ class Node: # per https://stackoverflow.com/a/9807138 missing_padding = len(b64) % 4 if missing_padding: - b64 += '=' * (4 - missing_padding) + b64 += "=" * (4 - missing_padding) decodedURL = base64.urlsafe_b64decode(b64) channelSet = apponly_pb2.ChannelSet() channelSet.ParseFromString(decodedURL) - if len(channelSet.settings) == 0: our_exit("Warning: There were no settings.") i = 0 for chs in channelSet.settings: ch = channel_pb2.Channel() - ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY + ch.role = ( + channel_pb2.Channel.Role.PRIMARY + if i == 0 + else channel_pb2.Channel.Role.SECONDARY + ) ch.index = i ch.settings.CopyFrom(chs) self.channels[ch.index] = ch - logging.debug(f'Channel i:{i} ch:{ch}') + logging.debug(f"Channel i:{i} ch:{ch}") self.writeChannel(ch.index) i = i + 1 @@ -442,7 +356,7 @@ class Node: def onResponseRequestRingtone(self, p): """Handle the response packet for requesting ringtone part 1""" - logging.debug(f'onResponseRequestRingtone() p:{p}') + logging.debug(f"onResponseRequestRingtone() p:{p}") errorFound = False if "routing" in p["decoded"]: if p["decoded"]["routing"]["errorReason"] != "NONE": @@ -452,30 +366,33 @@ class Node: if "decoded" in p: if "admin" in p["decoded"]: if "raw" in p["decoded"]["admin"]: - self.ringtonePart = p["decoded"]["admin"]["raw"].get_ringtone_response - logging.debug(f'self.ringtonePart:{self.ringtonePart}') + self.ringtonePart = p["decoded"]["admin"][ + "raw" + ].get_ringtone_response + logging.debug(f"self.ringtonePart:{self.ringtonePart}") self.gotResponse = True def get_ringtone(self): """Get the ringtone. Concatenate all pieces together and return a single string.""" - logging.debug(f'in get_ringtone()') + logging.debug(f"in get_ringtone()") if not self.ringtone: - p1 = admin_pb2.AdminMessage() p1.get_ringtone_request = True self.gotResponse = False - self._sendAdmin(p1, wantResponse=True, onResponse=self.onResponseRequestRingtone) + self._sendAdmin( + p1, wantResponse=True, onResponse=self.onResponseRequestRingtone + ) while self.gotResponse is False: time.sleep(0.1) - logging.debug(f'self.ringtone:{self.ringtone}') + logging.debug(f"self.ringtone:{self.ringtone}") self.ringtone = "" if self.ringtonePart: self.ringtone += self.ringtonePart - - print(f'ringtone:{self.ringtone}') - logging.debug(f'ringtone:{self.ringtone}') + + print(f"ringtone:{self.ringtone}") + logging.debug(f"ringtone:{self.ringtone}") return self.ringtone def set_ringtone(self, ringtone): @@ -488,10 +405,10 @@ class Node: chunks = [] chunks_size = 230 for i in range(0, len(ringtone), chunks_size): - chunks.append(ringtone[i: i + chunks_size]) + chunks.append(ringtone[i : i + chunks_size]) # for each chunk, send a message to set the values - #for i in range(0, len(chunks)): + # for i in range(0, len(chunks)): for i, chunk in enumerate(chunks): p = admin_pb2.AdminMessage() @@ -503,13 +420,13 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - + def onResponseRequestCannedMessagePluginMessageMessages(self, p): """Handle the response packet for requesting canned message plugin message part 1""" - logging.debug(f'onResponseRequestCannedMessagePluginMessageMessages() p:{p}') + logging.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}") errorFound = False if "routing" in p["decoded"]: if p["decoded"]["routing"]["errorReason"] != "NONE": @@ -519,31 +436,39 @@ class Node: if "decoded" in p: if "admin" in p["decoded"]: if "raw" in p["decoded"]["admin"]: - self.cannedPluginMessageMessages = p["decoded"]["admin"]["raw"].get_canned_message_module_messages_response - logging.debug(f'self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}') + self.cannedPluginMessageMessages = p["decoded"]["admin"][ + "raw" + ].get_canned_message_module_messages_response + logging.debug( + f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}" + ) self.gotResponse = True - def get_canned_message(self): """Get the canned message string. Concatenate all pieces together and return a single string.""" - logging.debug(f'in get_canned_message()') + logging.debug(f"in get_canned_message()") if not self.cannedPluginMessage: - p1 = admin_pb2.AdminMessage() p1.get_canned_message_module_messages_request = True self.gotResponse = False - self._sendAdmin(p1, wantResponse=True, onResponse=self.onResponseRequestCannedMessagePluginMessageMessages) + self._sendAdmin( + p1, + wantResponse=True, + onResponse=self.onResponseRequestCannedMessagePluginMessageMessages, + ) while self.gotResponse is False: time.sleep(0.1) - logging.debug(f'self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}') + logging.debug( + f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}" + ) self.cannedPluginMessage = "" if self.cannedPluginMessageMessages: self.cannedPluginMessage += self.cannedPluginMessageMessages - print(f'canned_plugin_message:{self.cannedPluginMessage}') - logging.debug(f'canned_plugin_message:{self.cannedPluginMessage}') + print(f"canned_plugin_message:{self.cannedPluginMessage}") + logging.debug(f"canned_plugin_message:{self.cannedPluginMessage}") return self.cannedPluginMessage def set_canned_message(self, message): @@ -556,10 +481,10 @@ class Node: chunks = [] chunks_size = 200 for i in range(0, len(message), chunks_size): - chunks.append(message[i: i + chunks_size]) + chunks.append(message[i : i + chunks_size]) # for each chunk, send a message to set the values - #for i in range(0, len(chunks)): + # for i in range(0, len(chunks)): for i, chunk in enumerate(chunks): p = admin_pb2.AdminMessage() @@ -571,16 +496,16 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) def exitSimulator(self): """Tell a simulator node to exit (this message - is ignored for other nodes)""" + is ignored for other nodes)""" p = admin_pb2.AdminMessage() p.exit_simulator = True - logging.debug('in exitSimulator()') + logging.debug("in exitSimulator()") return self._sendAdmin(p) @@ -593,10 +518,10 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - + def beginSettingsTransaction(self): """Tell the node to open a transaction to edit settings.""" p = admin_pb2.AdminMessage() @@ -606,7 +531,7 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) @@ -619,7 +544,7 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) @@ -632,9 +557,9 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak - return self._sendAdmin(p, onResponse=onResponse) + return self._sendAdmin(p, onResponse=onResponse) def shutdown(self, secs: int = 10): """Tell the node to shutdown.""" @@ -645,7 +570,7 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) @@ -655,7 +580,9 @@ class Node: p.get_device_metadata_request = True logging.info(f"Requesting device metadata") - return self._sendAdmin(p, wantResponse=True, onResponse=self.onRequestGetMetadata) + return self._sendAdmin( + p, wantResponse=True, onResponse=self.onRequestGetMetadata + ) def factoryReset(self): """Tell the node to factory reset.""" @@ -666,9 +593,9 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak - return self._sendAdmin(p, onResponse=onResponse) + return self._sendAdmin(p, onResponse=onResponse) def resetNodeDb(self): """Tell the node to reset its list of nodes.""" @@ -679,7 +606,7 @@ class Node: # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None - else: + else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) @@ -705,20 +632,23 @@ class Node: self.channels.append(ch) index += 1 - def onRequestGetMetadata(self, p): """Handle the response packet for requesting device metadata getMetadata()""" - logging.debug(f'onRequestGetMetadata() p:{p}') + logging.debug(f"onRequestGetMetadata() p:{p}") - if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(portnums_pb2.PortNum.ROUTING_APP): + if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name( + portnums_pb2.PortNum.ROUTING_APP + ): if p["decoded"]["routing"]["errorReason"] != "NONE": - logging.warning(f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}') - self._timeout.expireTime = time.time() # Do not wait any longer - return # Don't try to parse this routing message + logging.warning( + f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}' + ) + self._timeout.expireTime = time.time() # Do not wait any longer + return # Don't try to parse this routing message logging.debug(f"Retrying metadata request.") self.getMetadata() - return - + return + c = p["decoded"]["admin"]["raw"].get_device_metadata_response self._timeout.reset() # We made foreward progress logging.debug(f"Received metadata {stripnl(c)}") @@ -727,13 +657,17 @@ class Node: def onResponseRequestChannel(self, p): """Handle the response packet for requesting a channel _requestChannel()""" - logging.debug(f'onResponseRequestChannel() p:{p}') + logging.debug(f"onResponseRequestChannel() p:{p}") - if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(portnums_pb2.PortNum.ROUTING_APP): + if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name( + portnums_pb2.PortNum.ROUTING_APP + ): if p["decoded"]["routing"]["errorReason"] != "NONE": - logging.warning(f'Channel request failed, error reason: {p["decoded"]["routing"]["errorReason"]}') - self._timeout.expireTime = time.time() # Do not wait any longer - return # Don't try to parse this routing message + logging.warning( + f'Channel request failed, error reason: {p["decoded"]["routing"]["errorReason"]}' + ) + self._timeout.expireTime = time.time() # Do not wait any longer + return # Don't try to parse this routing message lastTried = 0 if len(self.partialChannels) > 0: lastTried = self.partialChannels[-1].index @@ -752,7 +686,9 @@ class Node: # Once we see a response that has NO settings, assume # we are at the end of channels and stop fetching - quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload + quitEarly = ( + c.role == channel_pb2.Channel.Role.DISABLED + ) and fastChannelDownload if quitEarly or index >= self.iface.myInfo.max_channels - 1: logging.debug("Finished downloading channels") @@ -767,47 +703,68 @@ class Node: def onAckNak(self, p): if p["decoded"]["routing"]["errorReason"] != "NONE": - print(f'Received a NAK, error reason: {p["decoded"]["routing"]["errorReason"]}') + print( + f'Received a NAK, error reason: {p["decoded"]["routing"]["errorReason"]}' + ) self.iface._acknowledgment.receivedNak = True - else: + else: if int(p["from"]) == self.iface.localNode.nodeNum: - print(f'Received an implicit ACK. Packet will likely arrive, but cannot be guaranteed.') - self.iface._acknowledgment.receivedImplAck = True - else: - print(f'Received an ACK.') - self.iface._acknowledgment.receivedAck = True - + print( + f"Received an implicit ACK. Packet will likely arrive, but cannot be guaranteed." + ) + self.iface._acknowledgment.receivedImplAck = True + else: + print(f"Received an ACK.") + self.iface._acknowledgment.receivedAck = True + def _requestChannel(self, channelNum: int): """Done with initial config messages, now send regular - MeshPackets to ask for settings""" + MeshPackets to ask for settings""" p = admin_pb2.AdminMessage() p.get_channel_request = channelNum + 1 # Show progress message for super slow operations if self != self.iface.localNode: - print(f"Requesting channel {channelNum} info from remote node (this could take a while)") - logging.debug(f"Requesting channel {channelNum} info from remote node (this could take a while)") + print( + f"Requesting channel {channelNum} info from remote node (this could take a while)" + ) + logging.debug( + f"Requesting channel {channelNum} info from remote node (this could take a while)" + ) else: logging.debug(f"Requesting channel {channelNum}") - return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel) - + return self._sendAdmin( + p, wantResponse=True, onResponse=self.onResponseRequestChannel + ) # pylint: disable=R1710 - def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=True, - onResponse=None, adminIndex=0): + def _sendAdmin( + self, + p: admin_pb2.AdminMessage, + wantResponse=True, + onResponse=None, + adminIndex=0, + ): """Send an admin message to the specified node (or the local node if destNodeNum is zero)""" if self.noProto: - logging.warning(f"Not sending packet because protocol use is disabled by noProto") + logging.warning( + f"Not sending packet because protocol use is disabled by noProto" + ) else: - if adminIndex == 0: # unless a special channel index was used, we want to use the admin index + if ( + adminIndex == 0 + ): # unless a special channel index was used, we want to use the admin index adminIndex = self.iface.localNode._getAdminChannelIndex() - logging.debug(f'adminIndex:{adminIndex}') - - return self.iface.sendData(p, self.nodeNum, - portNum=portnums_pb2.PortNum.ADMIN_APP, - wantAck=False, - wantResponse=wantResponse, - onResponse=onResponse, - channelIndex=adminIndex) + logging.debug(f"adminIndex:{adminIndex}") + + return self.iface.sendData( + p, + self.nodeNum, + portNum=portnums_pb2.PortNum.ADMIN_APP, + wantAck=False, + wantResponse=wantResponse, + onResponse=onResponse, + channelIndex=adminIndex, + ) diff --git a/meshtastic/remote_hardware.py b/meshtastic/remote_hardware.py index 767a7c6..b8fbc94 100644 --- a/meshtastic/remote_hardware.py +++ b/meshtastic/remote_hardware.py @@ -1,14 +1,15 @@ """Remote hardware """ import logging + from pubsub import pub + from meshtastic import portnums_pb2, remote_hardware_pb2 from meshtastic.util import our_exit def onGPIOreceive(packet, interface): - """Callback for received GPIO responses - """ + """Callback for received GPIO responses""" logging.debug(f"packet:{packet} interface:{interface}") gpioValue = 0 hw = packet["decoded"]["remotehw"] @@ -21,9 +22,11 @@ def onGPIOreceive(packet, interface): # so, we set it here gpioValue = 0 - #print(f'mask:{interface.mask}') + # print(f'mask:{interface.mask}') value = int(gpioValue) & int(interface.mask) - print(f'Received RemoteHardware type={hw["type"]}, gpio_value={gpioValue} value={value}') + print( + f'Received RemoteHardware type={hw["type"]}, gpio_value={gpioValue} value={value}' + ) interface.gotResponse = True @@ -44,36 +47,45 @@ class RemoteHardwareClient: ch = iface.localNode.getChannelByName("gpio") if not ch: our_exit( - "Warning: No channel named 'gpio' was found.\n"\ - "On the sending and receive nodes create a channel named 'gpio'.\n"\ - "For example, run '--ch-add gpio' on one device, then '--seturl' on\n"\ - "the other devices using the url from the device where the channel was added.") + "Warning: No channel named 'gpio' was found.\n" + "On the sending and receive nodes create a channel named 'gpio'.\n" + "For example, run '--ch-add gpio' on one device, then '--seturl' on\n" + "the other devices using the url from the device where the channel was added." + ) self.channelIndex = ch.index pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw") def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None): if not nodeid: - our_exit(r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)") - return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP, - wantAck=True, channelIndex=self.channelIndex, - wantResponse=wantResponse, onResponse=onResponse) + our_exit( + r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)" + ) + return self.iface.sendData( + r, + nodeid, + portnums_pb2.REMOTE_HARDWARE_APP, + wantAck=True, + channelIndex=self.channelIndex, + wantResponse=wantResponse, + onResponse=onResponse, + ) def writeGPIOs(self, nodeid, mask, vals): """ Write the specified vals bits to the device GPIOs. Only bits in mask that are 1 will be changed """ - logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}') + logging.debug(f"writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}") r = remote_hardware_pb2.HardwareMessage() r.type = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS r.gpio_mask = mask r.gpio_value = vals return self._sendHardware(nodeid, r) - def readGPIOs(self, nodeid, mask, onResponse = None): + def readGPIOs(self, nodeid, mask, onResponse=None): """Read the specified bits from GPIO inputs on the device""" - logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}') + logging.debug(f"readGPIOs nodeid:{nodeid} mask:{mask}") r = remote_hardware_pb2.HardwareMessage() r.type = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS r.gpio_mask = mask @@ -81,7 +93,7 @@ class RemoteHardwareClient: def watchGPIOs(self, nodeid, mask): """Watch the specified bits from GPIO inputs on the device for changes""" - logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}') + logging.debug(f"watchGPIOs nodeid:{nodeid} mask:{mask}") r = remote_hardware_pb2.HardwareMessage() r.type = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS r.gpio_mask = mask diff --git a/meshtastic/serial_interface.py b/meshtastic/serial_interface.py index 282acfa..afa8dd1 100644 --- a/meshtastic/serial_interface.py +++ b/meshtastic/serial_interface.py @@ -1,16 +1,18 @@ """ Serial interface class """ import logging -import time import platform +import time + import serial import meshtastic.util from meshtastic.stream_interface import StreamInterface -if platform.system() != 'Windows': +if platform.system() != "Windows": import termios + class SerialInterface(StreamInterface): """Interface class for meshtastic devices over a serial link""" @@ -42,19 +44,23 @@ class SerialInterface(StreamInterface): # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR # see https://github.com/pyserial/pyserial/issues/124 - if platform.system() != 'Windows': - with open(self.devPath, encoding='utf8') as f: + if platform.system() != "Windows": + with open(self.devPath, encoding="utf8") as f: attrs = termios.tcgetattr(f) attrs[2] = attrs[2] & ~termios.HUPCL termios.tcsetattr(f, termios.TCSAFLUSH, attrs) f.close() time.sleep(0.1) - self.stream = serial.Serial(self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0) + self.stream = serial.Serial( + self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0 + ) self.stream.flush() time.sleep(0.1) - StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) + StreamInterface.__init__( + self, debugOut=debugOut, noProto=noProto, connectNow=connectNow + ) def close(self): """Close a connection to the device""" diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index f2c3a74..11982cc 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -4,15 +4,14 @@ import logging import threading import time import traceback + import serial - from meshtastic.mesh_interface import MeshInterface -from meshtastic.util import stripnl, is_windows11 - +from meshtastic.util import is_windows11, stripnl START1 = 0x94 -START2 = 0xc3 +START2 = 0xC3 HEADER_LEN = 4 MAX_TO_FROM_RADIO_SIZE = 512 @@ -32,9 +31,10 @@ class StreamInterface(MeshInterface): Exception: [description] """ - if not hasattr(self, 'stream') and not noProto: + if not hasattr(self, "stream") and not noProto: raise Exception( - "StreamInterface is now abstract (to update existing code create SerialInterface instead)") + "StreamInterface is now abstract (to update existing code create SerialInterface instead)" + ) self._rxBuf = bytes() # empty self._wantExit = False @@ -110,8 +110,8 @@ class StreamInterface(MeshInterface): b = toRadio.SerializeToString() bufLen = len(b) # We convert into a string, because the TCP code doesn't work with byte arrays - header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff]) - logging.debug(f'sending header:{header} b:{b}') + header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF]) + logging.debug(f"sending header:{header} b:{b}") self._writeBytes(header + b) def close(self): @@ -126,18 +126,18 @@ class StreamInterface(MeshInterface): def __reader(self): """The reader thread that reads bytes from our stream""" - logging.debug('in __reader()') + logging.debug("in __reader()") empty = bytes() try: while not self._wantExit: - #logging.debug("reading character") + # logging.debug("reading character") b = self._readBytes(1) - #logging.debug("In reader loop") - #logging.debug(f"read returned {b}") + # logging.debug("In reader loop") + # logging.debug(f"read returned {b}") if len(b) > 0: c = b[0] - #logging.debug(f'c:{c}') + # logging.debug(f'c:{c}') ptr = len(self._rxBuf) # Assume we want to append this byte, fixme use bytearray instead @@ -150,38 +150,54 @@ class StreamInterface(MeshInterface): try: self.debugOut.write(b.decode("utf-8")) except: - self.debugOut.write('?') + self.debugOut.write("?") elif ptr == 1: # looking for START2 if c != START2: self._rxBuf = empty # failed to find start2 elif ptr >= HEADER_LEN - 1: # we've at least got a header - #logging.debug('at least we received a header') + # logging.debug('at least we received a header') # big endian length follows header packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3] - if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length + if ( + ptr == HEADER_LEN - 1 + ): # we _just_ finished reading the header, validate length if packetlen > MAX_TO_FROM_RADIO_SIZE: - self._rxBuf = empty # length was out out bounds, restart + self._rxBuf = ( + empty # length was out out bounds, restart + ) if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN: try: self._handleFromRadio(self._rxBuf[HEADER_LEN:]) except Exception as ex: - logging.error(f"Error while handling message from radio {ex}") + logging.error( + f"Error while handling message from radio {ex}" + ) traceback.print_exc() self._rxBuf = empty else: # logging.debug(f"timeout") pass except serial.SerialException as ex: - if not self._wantExit: # We might intentionally get an exception during shutdown - logging.warning(f"Meshtastic serial port disconnected, disconnecting... {ex}") + if ( + not self._wantExit + ): # We might intentionally get an exception during shutdown + logging.warning( + f"Meshtastic serial port disconnected, disconnecting... {ex}" + ) except OSError as ex: - if not self._wantExit: # We might intentionally get an exception during shutdown - logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}") + if ( + not self._wantExit + ): # We might intentionally get an exception during shutdown + logging.error( + f"Unexpected OSError, terminating meshtastic reader... {ex}" + ) except Exception as ex: - logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}") + logging.error( + f"Unexpected exception, terminating meshtastic reader... {ex}" + ) finally: logging.debug("reader is exiting") self._disconnected() diff --git a/meshtastic/supported_device.py b/meshtastic/supported_device.py index 7ddb547..f4c2b35 100755 --- a/meshtastic/supported_device.py +++ b/meshtastic/supported_device.py @@ -5,89 +5,224 @@ # Goal is to detect which device and port to use from the supported devices # without installing any libraries that are not currently in the python meshtastic library -class SupportedDevice(): + +class SupportedDevice: """Devices supported on Meshtastic""" - def __init__(self, name, version=None, for_firmware=None, device_class="esp32", - baseport_on_linux=None, baseport_on_mac=None, baseport_on_windows="COM", - usb_vendor_id_in_hex=None, usb_product_id_in_hex=None): - """ constructor """ + def __init__( + self, + name, + version=None, + for_firmware=None, + device_class="esp32", + baseport_on_linux=None, + baseport_on_mac=None, + baseport_on_windows="COM", + usb_vendor_id_in_hex=None, + usb_product_id_in_hex=None, + ): + """constructor""" self.name = name self.version = version self.for_firmware = for_firmware - self.device_class = device_class # could be "nrf52" + self.device_class = device_class # could be "nrf52" # when you run "lsusb -d xxxx:" in linux - self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case - self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case + self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case + self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case - self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM + self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM self.baseport_on_mac = baseport_on_mac self.baseport_on_windows = baseport_on_windows -# supported devices -tbeam_v0_7 = SupportedDevice(name="T-Beam", version="0.7", for_firmware="tbeam0.7", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -tbeam_v1_1 = SupportedDevice(name="T-Beam", version="1.1", for_firmware="tbeam", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -tbeam_M8N = SupportedDevice(name="T-Beam", version="M8N", for_firmware="tbeam", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -tbeam_M8N_SX1262 = SupportedDevice(name="T-Beam", version="M8N_SX1262", for_firmware="tbeam", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -tlora_v1 = SupportedDevice(name="T-Lora", version="1", for_firmware="tlora-v1", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -tlora_v1_3 = SupportedDevice(name="T-Lora", version="1.3", for_firmware="tlora-v1-3", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial", - usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") -tlora_v2 = SupportedDevice(name="T-Lora", version="2", for_firmware="tlora-v2", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -tlora_v2_1_1_6 = SupportedDevice(name="T-Lora", version="2.1-1.6", for_firmware="tlora-v2-1-1.6", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -heltec_v1 = SupportedDevice(name="Heltec", version="1", for_firmware="heltec-v1", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", - usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") -heltec_v2_0 = SupportedDevice(name="Heltec", version="2.0", for_firmware="heltec-v2.0", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", - usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") -heltec_v2_1 = SupportedDevice(name="Heltec", version="2.1", for_firmware="heltec-v2.1", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", - usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") -rak11200 = SupportedDevice(name="RAK 11200", version="", for_firmware="rak11200", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="7523") -meshtastic_diy_v1 = SupportedDevice(name="Meshtastic DIY", version="1", for_firmware="meshtastic-diy-v1", - baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", - usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") -# Note: The T-Echo reports product id in boot mode -techo_1 = SupportedDevice(name="T-Echo", version="1", for_firmware="t-echo-1", device_class="nrf52", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029") -rak4631_5005 = SupportedDevice(name="RAK 4631 5005", version="", for_firmware="rak4631_5005", - device_class="nrf52", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029") -rak4631_5005_epaper = SupportedDevice(name="RAK 4631 5005 14000 epaper", version="", for_firmware="rak4631_5005_epaper", - device_class="nrf52", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029") -# Note: The 19003 reports same product id as 5005 in boot mode -rak4631_19003 = SupportedDevice(name="RAK 4631 19003", version="", for_firmware="rak4631_19003", - device_class="nrf52", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="239a", usb_product_id_in_hex="8029") -nano_g1 = SupportedDevice(name="Nano G1", version="", for_firmware="nano-g1", - baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", - usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") -supported_devices = [tbeam_v0_7, tbeam_v1_1, tbeam_M8N, tbeam_M8N_SX1262, - tlora_v1, tlora_v1_3, tlora_v2, tlora_v2_1_1_6, - heltec_v1, heltec_v2_0, heltec_v2_1, - meshtastic_diy_v1, techo_1, rak4631_5005, rak4631_5005_epaper, rak4631_19003, - rak11200, nano_g1] +# supported devices +tbeam_v0_7 = SupportedDevice( + name="T-Beam", + version="0.7", + for_firmware="tbeam0.7", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +tbeam_v1_1 = SupportedDevice( + name="T-Beam", + version="1.1", + for_firmware="tbeam", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +tbeam_M8N = SupportedDevice( + name="T-Beam", + version="M8N", + for_firmware="tbeam", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +tbeam_M8N_SX1262 = SupportedDevice( + name="T-Beam", + version="M8N_SX1262", + for_firmware="tbeam", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +tlora_v1 = SupportedDevice( + name="T-Lora", + version="1", + for_firmware="tlora-v1", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +tlora_v1_3 = SupportedDevice( + name="T-Lora", + version="1.3", + for_firmware="tlora-v1-3", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial", + usb_vendor_id_in_hex="10c4", + usb_product_id_in_hex="ea60", +) +tlora_v2 = SupportedDevice( + name="T-Lora", + version="2", + for_firmware="tlora-v2", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +tlora_v2_1_1_6 = SupportedDevice( + name="T-Lora", + version="2.1-1.6", + for_firmware="tlora-v2-1-1.6", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) +heltec_v1 = SupportedDevice( + name="Heltec", + version="1", + for_firmware="heltec-v1", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial-", + usb_vendor_id_in_hex="10c4", + usb_product_id_in_hex="ea60", +) +heltec_v2_0 = SupportedDevice( + name="Heltec", + version="2.0", + for_firmware="heltec-v2.0", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial-", + usb_vendor_id_in_hex="10c4", + usb_product_id_in_hex="ea60", +) +heltec_v2_1 = SupportedDevice( + name="Heltec", + version="2.1", + for_firmware="heltec-v2.1", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial-", + usb_vendor_id_in_hex="10c4", + usb_product_id_in_hex="ea60", +) +rak11200 = SupportedDevice( + name="RAK 11200", + version="", + for_firmware="rak11200", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial-", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="7523", +) +meshtastic_diy_v1 = SupportedDevice( + name="Meshtastic DIY", + version="1", + for_firmware="meshtastic-diy-v1", + baseport_on_linux="ttyUSB", + baseport_on_mac="cu.usbserial-", + usb_vendor_id_in_hex="10c4", + usb_product_id_in_hex="ea60", +) +# Note: The T-Echo reports product id in boot mode +techo_1 = SupportedDevice( + name="T-Echo", + version="1", + for_firmware="t-echo-1", + device_class="nrf52", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="239a", + usb_product_id_in_hex="0029", +) +rak4631_5005 = SupportedDevice( + name="RAK 4631 5005", + version="", + for_firmware="rak4631_5005", + device_class="nrf52", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="239a", + usb_product_id_in_hex="0029", +) +rak4631_5005_epaper = SupportedDevice( + name="RAK 4631 5005 14000 epaper", + version="", + for_firmware="rak4631_5005_epaper", + device_class="nrf52", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="239a", + usb_product_id_in_hex="0029", +) +# Note: The 19003 reports same product id as 5005 in boot mode +rak4631_19003 = SupportedDevice( + name="RAK 4631 19003", + version="", + for_firmware="rak4631_19003", + device_class="nrf52", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="239a", + usb_product_id_in_hex="8029", +) +nano_g1 = SupportedDevice( + name="Nano G1", + version="", + for_firmware="nano-g1", + baseport_on_linux="ttyACM", + baseport_on_mac="cu.usbmodem", + usb_vendor_id_in_hex="1a86", + usb_product_id_in_hex="55d4", +) + +supported_devices = [ + tbeam_v0_7, + tbeam_v1_1, + tbeam_M8N, + tbeam_M8N_SX1262, + tlora_v1, + tlora_v1_3, + tlora_v2, + tlora_v2_1_1_6, + heltec_v1, + heltec_v2_0, + heltec_v2_1, + meshtastic_diy_v1, + techo_1, + rak4631_5005, + rak4631_5005_epaper, + rak4631_19003, + rak11200, + nano_g1, +] diff --git a/meshtastic/tcp_interface.py b/meshtastic/tcp_interface.py index eb26287..4a5346d 100644 --- a/meshtastic/tcp_interface.py +++ b/meshtastic/tcp_interface.py @@ -6,11 +6,18 @@ from typing import AnyStr from meshtastic.stream_interface import StreamInterface + class TCPInterface(StreamInterface): """Interface class for meshtastic devices over a TCP link""" - def __init__(self, hostname: AnyStr, debugOut=None, noProto=False, - connectNow=True, portNumber=4403): + def __init__( + self, + hostname: AnyStr, + debugOut=None, + noProto=False, + connectNow=True, + portNumber=4403, + ): """Constructor, opens a connection to a specified IP address/hostname Keyword Arguments: @@ -30,12 +37,13 @@ class TCPInterface(StreamInterface): else: self.socket = None - StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, - connectNow=connectNow) + StreamInterface.__init__( + self, debugOut=debugOut, noProto=noProto, connectNow=connectNow + ) def _socket_shutdown(self): """Shutdown the socket. - Note: Broke out this line so the exception could be unit tested. + Note: Broke out this line so the exception could be unit tested. """ self.socket.shutdown(socket.SHUT_RDWR) diff --git a/meshtastic/test.py b/meshtastic/test.py index 295496f..85d881a 100644 --- a/meshtastic/test.py +++ b/meshtastic/test.py @@ -2,17 +2,18 @@ messages and report back if successful. """ import logging -import time import sys +import time import traceback + from dotmap import DotMap from pubsub import pub + import meshtastic.util from meshtastic.__init__ import BROADCAST_NUM from meshtastic.serial_interface import SerialInterface from meshtastic.tcp_interface import TCPInterface - """The interfaces we are using for our tests""" interfaces = None @@ -52,7 +53,9 @@ def subscribe(): pub.subscribe(onNode, "meshtastic.node") -def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False): +def testSend( + fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False +): """ Sends one test packet between two nodes and then returns success or failure @@ -73,19 +76,19 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want else: toNode = toInterface.myInfo.my_node_num - logging.debug( - f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}") + logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}") # pylint: disable=W0603 global sendingInterface sendingInterface = fromInterface if not asBinary: fromInterface.sendText(f"Test {testNumber}", toNode, wantAck=wantAck) else: - fromInterface.sendData((f"Binary {testNumber}").encode( - "utf-8"), toNode, wantAck=wantAck) + fromInterface.sendData( + (f"Binary {testNumber}").encode("utf-8"), toNode, wantAck=wantAck + ) for _ in range(60): # max of 60 secs before we timeout time.sleep(1) - if len(receivedPackets) >= 1: + if len(receivedPackets) >= 1: return True return False # Failed to send @@ -102,15 +105,18 @@ def runTests(numTests=50, wantAck=False, maxFailures=0): isBroadcast = True # asBinary=(i % 2 == 0) success = testSend( - interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck) + interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck + ) if not success: numFail = numFail + 1 logging.error( - f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)") + f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)" + ) else: numSuccess = numSuccess + 1 logging.info( - f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far") + f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far" + ) time.sleep(1) @@ -140,7 +146,7 @@ def openDebugLog(portName): """Open the debug log file""" debugname = "log" + portName.replace("/", "_") logging.info(f"Writing serial debugging to {debugname}") - return open(debugname, 'w+', buffering=1, encoding='utf8') + return open(debugname, "w+", buffering=1, encoding="utf8") def testAll(numTests=5): @@ -151,14 +157,22 @@ def testAll(numTests=5): """ ports = meshtastic.util.findPorts(True) if len(ports) < 2: - meshtastic.util.our_exit("Warning: Must have at least two devices connected to USB.") + meshtastic.util.our_exit( + "Warning: Must have at least two devices connected to USB." + ) pub.subscribe(onConnection, "meshtastic.connection") pub.subscribe(onReceive, "meshtastic.receive") # pylint: disable=W0603 global interfaces - interfaces = list(map(lambda port: SerialInterface( - port, debugOut=openDebugLog(port), connectNow=True), ports)) + interfaces = list( + map( + lambda port: SerialInterface( + port, debugOut=openDebugLog(port), connectNow=True + ), + ports, + ) + ) logging.info("Ports opened, starting test") result = testThread(numTests) diff --git a/meshtastic/tests/conftest.py b/meshtastic/tests/conftest.py index 56df27c..25e45c6 100644 --- a/meshtastic/tests/conftest.py +++ b/meshtastic/tests/conftest.py @@ -1,11 +1,12 @@ """Common pytest code (place for fixtures).""" import argparse - from unittest.mock import MagicMock + import pytest from meshtastic.__main__ import Globals + from ..mesh_interface import MeshInterface @@ -22,36 +23,34 @@ def reset_globals(): def iface_with_nodes(): """Fixture to setup some nodes.""" nodesById = { - '!9388f81c': { - 'num': 2475227164, - 'user': { - 'id': '!9388f81c', - 'longName': 'Unknown f81c', - 'shortName': '?1C', - 'macaddr': 'RBeTiPgc', - 'hwModel': 'TBEAM' - }, - 'position': {}, - 'lastHeard': 1640204888 - } - } + "!9388f81c": { + "num": 2475227164, + "user": { + "id": "!9388f81c", + "longName": "Unknown f81c", + "shortName": "?1C", + "macaddr": "RBeTiPgc", + "hwModel": "TBEAM", + }, + "position": {}, + "lastHeard": 1640204888, + } + } nodesByNum = { - 2475227164: { - 'num': 2475227164, - 'user': { - 'id': '!9388f81c', - 'longName': 'Unknown f81c', - 'shortName': '?1C', - 'macaddr': 'RBeTiPgc', - 'hwModel': 'TBEAM' - }, - 'position': { - 'time': 1640206266 - }, - 'lastHeard': 1640206266 - } - } + 2475227164: { + "num": 2475227164, + "user": { + "id": "!9388f81c", + "longName": "Unknown f81c", + "shortName": "?1C", + "macaddr": "RBeTiPgc", + "hwModel": "TBEAM", + }, + "position": {"time": 1640206266}, + "lastHeard": 1640206266, + } + } iface = MeshInterface(noProto=True) iface.nodes = nodesById iface.nodesByNum = nodesByNum diff --git a/meshtastic/tests/test_ble_interface.py b/meshtastic/tests/test_ble_interface.py index bb8d135..7ef49e4 100644 --- a/meshtastic/tests/test_ble_interface.py +++ b/meshtastic/tests/test_ble_interface.py @@ -2,14 +2,16 @@ from unittest.mock import patch + import pytest from ..ble_interface import BLEInterface + @pytest.mark.unit -@patch('platform.system', return_value='Linux') +@patch("platform.system", return_value="Linux") def test_BLEInterface(mock_platform): """Test that we can instantiate a BLEInterface""" - iface = BLEInterface('foo', debugOut=True, noProto=True) + iface = BLEInterface("foo", debugOut=True, noProto=True) iface.close() mock_platform.assert_called() diff --git a/meshtastic/tests/test_examples.py b/meshtastic/tests/test_examples.py index 9f162d4..b27a542 100644 --- a/meshtastic/tests/test_examples.py +++ b/meshtastic/tests/test_examples.py @@ -6,19 +6,24 @@ import subprocess import pytest + @pytest.mark.examples def test_examples_hello_world_serial_no_arg(): """Test hello_world_serial without any args""" - return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py') + return_value, _ = subprocess.getstatusoutput( + "source venv/bin/activate; python3 examples/hello_world_serial.py" + ) assert return_value == 3 @pytest.mark.examples def test_examples_hello_world_serial_with_arg(capsys): """Test hello_world_serial with arg""" - return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py hello') + return_value, _ = subprocess.getstatusoutput( + "source venv/bin/activate; python3 examples/hello_world_serial.py hello" + ) assert return_value == 1 _, err = capsys.readouterr() - assert err == '' + assert err == "" # TODO: Why does this not work? # assert out == 'Warning: No Meshtastic devices detected.' diff --git a/meshtastic/tests/test_init.py b/meshtastic/tests/test_init.py index 75c9609..a1d5440 100644 --- a/meshtastic/tests/test_init.py +++ b/meshtastic/tests/test_init.py @@ -1,14 +1,15 @@ """Meshtastic unit tests for __init__.py""" -import re import logging - +import re from unittest.mock import MagicMock + import pytest -from meshtastic.__init__ import _onTextReceive, _onPositionReceive, _onNodeInfoReceive -from ..serial_interface import SerialInterface +from meshtastic.__init__ import _onNodeInfoReceive, _onPositionReceive, _onTextReceive + from ..globals import Globals +from ..serial_interface import SerialInterface @pytest.mark.unit @@ -20,8 +21,8 @@ def test_init_onTextReceive_with_exception(caplog): packet = {} with caplog.at_level(logging.DEBUG): _onTextReceive(iface, packet) - assert re.search(r'in _onTextReceive', caplog.text, re.MULTILINE) - assert re.search(r'Malformatted', caplog.text, re.MULTILINE) + assert re.search(r"in _onTextReceive", caplog.text, re.MULTILINE) + assert re.search(r"Malformatted", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -30,15 +31,10 @@ def test_init_onPositionReceive(caplog): args = MagicMock() Globals.getInstance().set_args(args) iface = MagicMock(autospec=SerialInterface) - packet = { - 'from': 'foo', - 'decoded': { - 'position': {} - } - } + packet = {"from": "foo", "decoded": {"position": {}}} with caplog.at_level(logging.DEBUG): _onPositionReceive(iface, packet) - assert re.search(r'in _onPositionReceive', caplog.text, re.MULTILINE) + assert re.search(r"in _onPositionReceive", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -49,13 +45,13 @@ def test_init_onNodeInfoReceive(caplog, iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 packet = { - 'from': 'foo', - 'decoded': { - 'user': { - 'id': 'bar', - }, - } - } + "from": "foo", + "decoded": { + "user": { + "id": "bar", + }, + }, + } with caplog.at_level(logging.DEBUG): _onNodeInfoReceive(iface, packet) - assert re.search(r'in _onNodeInfoReceive', caplog.text, re.MULTILINE) + assert re.search(r"in _onNodeInfoReceive", caplog.text, re.MULTILINE) diff --git a/meshtastic/tests/test_int.py b/meshtastic/tests/test_int.py index a46e86e..987c6aa 100644 --- a/meshtastic/tests/test_int.py +++ b/meshtastic/tests/test_int.py @@ -8,39 +8,39 @@ import pytest @pytest.mark.int def test_int_meshtastic_no_args(): """Test meshtastic without any args""" - return_value, out = subprocess.getstatusoutput('meshtastic') - assert re.match(r'usage: meshtastic', out) + return_value, out = subprocess.getstatusoutput("meshtastic") + assert re.match(r"usage: meshtastic", out) assert return_value == 1 @pytest.mark.int def test_int_mesh_tunnel_no_args(): """Test mesh-tunnel without any args""" - return_value, out = subprocess.getstatusoutput('mesh-tunnel') - assert re.match(r'usage: mesh-tunnel', out) + return_value, out = subprocess.getstatusoutput("mesh-tunnel") + assert re.match(r"usage: mesh-tunnel", out) assert return_value == 1 @pytest.mark.int def test_int_version(): """Test '--version'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --version') - assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out) + return_value, out = subprocess.getstatusoutput("meshtastic --version") + assert re.match(r"[0-9]+\.[0-9]+\.[0-9]", out) assert return_value == 0 @pytest.mark.int def test_int_help(): """Test '--help'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --help') - assert re.match(r'usage: meshtastic ', out) + return_value, out = subprocess.getstatusoutput("meshtastic --help") + assert re.match(r"usage: meshtastic ", out) assert return_value == 0 @pytest.mark.int def test_int_support(): """Test '--support'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --support') - assert re.search(r'System', out) - assert re.search(r'Python', out) + return_value, out = subprocess.getstatusoutput("meshtastic --support") + assert re.search(r"System", out) + assert re.search(r"Python", out) assert return_value == 0 diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index 66df41e..371f15a 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -1,44 +1,59 @@ """Meshtastic unit tests for __main__.py""" # pylint: disable=C0302 -import sys -import os -import re import logging +import os import platform +import re +import sys +from unittest.mock import MagicMock, patch -from unittest.mock import patch, MagicMock import pytest -from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, export_config, getPref, setPref, onNode, tunnelMain -#from ..radioconfig_pb2 import UserPreferences -#import meshtastic.config_pb2 +from meshtastic.__main__ import ( + Globals, + export_config, + getPref, + initParser, + main, + onConnection, + onNode, + onReceive, + setPref, + tunnelMain, +) + +from ..channel_pb2 import Channel + +# from ..ble_interface import BLEInterface +from ..node import Node + +# from ..radioconfig_pb2 import UserPreferences +# import meshtastic.config_pb2 from ..serial_interface import SerialInterface from ..tcp_interface import TCPInterface -#from ..ble_interface import BLEInterface -from ..node import Node -from ..channel_pb2 import Channel -#from ..remote_hardware import onGPIOreceive -#from ..config_pb2 import Config + +# from ..remote_hardware import onGPIOreceive +# from ..config_pb2 import Config @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_init_parser_no_args(capsys): """Test no arguments""" - sys.argv = [''] + sys.argv = [""] Globals.getInstance().set_args(sys.argv) initParser() out, err = capsys.readouterr() - assert out == '' - assert err == '' + assert out == "" + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_init_parser_version(capsys): """Test --version""" - sys.argv = ['', '--version'] + sys.argv = ["", "--version"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -46,15 +61,15 @@ def test_main_init_parser_version(capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 out, err = capsys.readouterr() - assert re.match(r'[0-9]+\.[0-9]+[\.a][0-9]', out) - assert err == '' + assert re.match(r"[0-9]+\.[0-9]+[\.a][0-9]", out) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_main_version(capsys): """Test --version""" - sys.argv = ['', '--version'] + sys.argv = ["", "--version"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -62,15 +77,15 @@ def test_main_main_version(capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 out, err = capsys.readouterr() - assert re.match(r'[0-9]+\.[0-9]+[\.a][0-9]', out) - assert err == '' + assert re.match(r"[0-9]+\.[0-9]+[\.a][0-9]", out) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_main_no_args(capsys): """Test with no args""" - sys.argv = [''] + sys.argv = [""] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -78,14 +93,14 @@ def test_main_main_no_args(capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 _, err = capsys.readouterr() - assert re.search(r'usage:', err, re.MULTILINE) + assert re.search(r"usage:", err, re.MULTILINE) @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_support(capsys): """Test --support""" - sys.argv = ['', '--support'] + sys.argv = ["", "--support"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -93,19 +108,19 @@ def test_main_support(capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 out, err = capsys.readouterr() - assert re.search(r'System', out, re.MULTILINE) - assert re.search(r'Platform', out, re.MULTILINE) - assert re.search(r'Machine', out, re.MULTILINE) - assert re.search(r'Executable', out, re.MULTILINE) - assert err == '' + assert re.search(r"System", out, re.MULTILINE) + assert re.search(r"Platform", out, re.MULTILINE) + assert re.search(r"Machine", out, re.MULTILINE) + assert re.search(r"Executable", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.util.findPorts', return_value=[]) +@patch("meshtastic.util.findPorts", return_value=[]) def test_main_ch_index_no_devices(patched_find_ports, capsys): """Test --ch-index 1""" - sys.argv = ['', '--ch-index', '1'] + sys.argv = ["", "--ch-index", "1"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -114,17 +129,17 @@ def test_main_ch_index_no_devices(patched_find_ports, capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: No Meshtastic devices detected", out, re.MULTILINE) + assert err == "" patched_find_ports.assert_called() @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.util.findPorts', return_value=[]) +@patch("meshtastic.util.findPorts", return_value=[]) def test_main_test_no_ports(patched_find_ports, capsys): """Test --test with no hardware""" - sys.argv = ['', '--test'] + sys.argv = ["", "--test"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -133,16 +148,18 @@ def test_main_test_no_ports(patched_find_ports, capsys): assert pytest_wrapped_e.value.code == 1 patched_find_ports.assert_called() out, err = capsys.readouterr() - assert re.search(r'Warning: Must have at least two devices connected to USB', out, re.MULTILINE) - assert err == '' + assert re.search( + r"Warning: Must have at least two devices connected to USB", out, re.MULTILINE + ) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1']) +@patch("meshtastic.util.findPorts", return_value=["/dev/ttyFake1"]) def test_main_test_one_port(patched_find_ports, capsys): """Test --test with one fake port""" - sys.argv = ['', '--test'] + sys.argv = ["", "--test"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -151,16 +168,18 @@ def test_main_test_one_port(patched_find_ports, capsys): assert pytest_wrapped_e.value.code == 1 patched_find_ports.assert_called() out, err = capsys.readouterr() - assert re.search(r'Warning: Must have at least two devices connected to USB', out, re.MULTILINE) - assert err == '' + assert re.search( + r"Warning: Must have at least two devices connected to USB", out, re.MULTILINE + ) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.test.testAll', return_value=True) +@patch("meshtastic.test.testAll", return_value=True) def test_main_test_two_ports_success(patched_test_all, capsys): """Test --test two fake ports and testAll() is a simulated success""" - sys.argv = ['', '--test'] + sys.argv = ["", "--test"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -169,16 +188,16 @@ def test_main_test_two_ports_success(patched_test_all, capsys): assert pytest_wrapped_e.value.code == 0 patched_test_all.assert_called() out, err = capsys.readouterr() - assert re.search(r'Test was a success.', out, re.MULTILINE) - assert err == '' + assert re.search(r"Test was a success.", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.test.testAll', return_value=False) +@patch("meshtastic.test.testAll", return_value=False) def test_main_test_two_ports_fails(patched_test_all, capsys): """Test --test two fake ports and testAll() is a simulated failure""" - sys.argv = ['', '--test'] + sys.argv = ["", "--test"] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -187,78 +206,86 @@ def test_main_test_two_ports_fails(patched_test_all, capsys): assert pytest_wrapped_e.value.code == 1 patched_test_all.assert_called() out, err = capsys.readouterr() - assert re.search(r'Test was not successful.', out, re.MULTILINE) - assert err == '' + assert re.search(r"Test was not successful.", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_info(capsys, caplog): """Test --info""" - sys.argv = ['', '--info'] + sys.argv = ["", "--info"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_showInfo(): - print('inside mocked showInfo') + print("inside mocked showInfo") + iface.showInfo.side_effect = mock_showInfo with caplog.at_level(logging.DEBUG): - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch( + "meshtastic.serial_interface.SerialInterface", return_value=iface + ) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked showInfo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked showInfo", out, re.MULTILINE) + assert err == "" mo.assert_called() @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('os.getlogin') +@patch("os.getlogin") def test_main_info_with_permission_error(patched_getlogin, capsys, caplog): """Test --info""" - sys.argv = ['', '--info'] + sys.argv = ["", "--info"] Globals.getInstance().set_args(sys.argv) - patched_getlogin.return_value = 'me' + patched_getlogin.return_value = "me" iface = MagicMock(autospec=SerialInterface) with caplog.at_level(logging.DEBUG): with pytest.raises(SystemExit) as pytest_wrapped_e: - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: - mo.side_effect = PermissionError('bla bla') + with patch( + "meshtastic.serial_interface.SerialInterface", return_value=iface + ) as mo: + mo.side_effect = PermissionError("bla bla") main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() patched_getlogin.assert_called() - assert re.search(r'Need to add yourself', out, re.MULTILINE) - assert err == '' + assert re.search(r"Need to add yourself", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_info_with_tcp_interface(capsys): """Test --info""" - sys.argv = ['', '--info', '--host', 'meshtastic.local'] + sys.argv = ["", "--info", "--host", "meshtastic.local"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=TCPInterface) + def mock_showInfo(): - print('inside mocked showInfo') + print("inside mocked showInfo") + iface.showInfo.side_effect = mock_showInfo - with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo: + with patch("meshtastic.tcp_interface.TCPInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked showInfo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked showInfo", out, re.MULTILINE) + assert err == "" mo.assert_called() # TODO: comment out ble (for now) -#@pytest.mark.unit -#def test_main_info_with_ble_interface(capsys): +# @pytest.mark.unit +# def test_main_info_with_ble_interface(capsys): # """Test --info""" # sys.argv = ['', '--info', '--ble', 'foo'] # Globals.getInstance().set_args(sys.argv) @@ -280,48 +307,52 @@ def test_main_info_with_tcp_interface(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_no_proto(capsys): """Test --noproto (using --info for output)""" - sys.argv = ['', '--info', '--noproto'] + sys.argv = ["", "--info", "--noproto"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_showInfo(): - print('inside mocked showInfo') + print("inside mocked showInfo") + iface.showInfo.side_effect = mock_showInfo # Override the time.sleep so there is no loop def my_sleep(amount): - print(f'amount:{amount}') + print(f"amount:{amount}") sys.exit(0) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): - with patch('time.sleep', side_effect=my_sleep): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): + with patch("time.sleep", side_effect=my_sleep): with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked showInfo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked showInfo", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_info_with_seriallog_stdout(capsys): """Test --info""" - sys.argv = ['', '--info', '--seriallog', 'stdout'] + sys.argv = ["", "--info", "--seriallog", "stdout"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_showInfo(): - print('inside mocked showInfo') + print("inside mocked showInfo") + iface.showInfo.side_effect = mock_showInfo - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked showInfo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked showInfo", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -329,41 +360,43 @@ def test_main_info_with_seriallog_stdout(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_info_with_seriallog_output_txt(capsys): """Test --info""" - sys.argv = ['', '--info', '--seriallog', 'output.txt'] + sys.argv = ["", "--info", "--seriallog", "output.txt"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_showInfo(): - print('inside mocked showInfo') + print("inside mocked showInfo") + iface.showInfo.side_effect = mock_showInfo - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked showInfo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked showInfo", out, re.MULTILINE) + assert err == "" mo.assert_called() # do some cleanup - os.remove('output.txt') + os.remove("output.txt") @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_qr(capsys): """Test --qr""" - sys.argv = ['', '--qr'] + sys.argv = ["", "--qr"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) # TODO: could mock/check url - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Primary channel URL', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Primary channel URL", out, re.MULTILINE) # if a qr code is generated it will have lots of these - assert re.search(r'\[7m', out, re.MULTILINE) - assert err == '' + assert re.search(r"\[7m", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -371,20 +404,20 @@ def test_main_qr(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_onConnected_exception(capsys): """Test the exception in onConnected""" - sys.argv = ['', '--qr'] + sys.argv = ["", "--qr"] Globals.getInstance().set_args(sys.argv) def throw_an_exception(junk): raise Exception("Fake exception.") iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): - with patch('pyqrcode.create', side_effect=throw_an_exception): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): + with patch("pyqrcode.create", side_effect=throw_an_exception): with pytest.raises(Exception) as pytest_wrapped_e: main() out, err = capsys.readouterr() - assert re.search('Aborting due to: Fake exception', out, re.MULTILINE) - assert err == '' + assert re.search("Aborting due to: Fake exception", out, re.MULTILINE) + assert err == "" assert pytest_wrapped_e.type == Exception @@ -392,19 +425,21 @@ def test_main_onConnected_exception(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_nodes(capsys): """Test --nodes""" - sys.argv = ['', '--nodes'] + sys.argv = ["", "--nodes"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_showNodes(): - print('inside mocked showNodes') + print("inside mocked showNodes") + iface.showNodes.side_effect = mock_showNodes - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked showNodes', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked showNodes", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -412,16 +447,16 @@ def test_main_nodes(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_set_owner_to_bob(capsys): """Test --set-owner bob""" - sys.argv = ['', '--set-owner', 'bob'] + sys.argv = ["", "--set-owner", "bob"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Setting device owner to bob', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting device owner to bob", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -429,53 +464,55 @@ def test_main_set_owner_to_bob(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_set_owner_short_to_bob(capsys): """Test --set-owner-short bob""" - sys.argv = ['', '--set-owner-short', 'bob'] + sys.argv = ["", "--set-owner-short", "bob"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Setting device owner short to bob', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting device owner short to bob", out, re.MULTILINE) + assert err == "" mo.assert_called() @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_set_canned_messages(capsys): - """Test --set-canned-message """ - sys.argv = ['', '--set-canned-message', 'foo'] + """Test --set-canned-message""" + sys.argv = ["", "--set-canned-message", "foo"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Setting canned plugin message to foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting canned plugin message to foo", out, re.MULTILINE) + assert err == "" mo.assert_called() @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_get_canned_messages(capsys, caplog, iface_with_nodes): - """Test --get-canned-message """ - sys.argv = ['', '--get-canned-message'] + """Test --get-canned-message""" + sys.argv = ["", "--get-canned-message"] Globals.getInstance().set_args(sys.argv) iface = iface_with_nodes - iface.localNode.cannedPluginMessage = 'foo' + iface.localNode.cannedPluginMessage = "foo" with caplog.at_level(logging.DEBUG): - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch( + "meshtastic.serial_interface.SerialInterface", return_value=iface + ) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'canned_plugin_message:foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"canned_plugin_message:foo", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -483,28 +520,35 @@ def test_main_get_canned_messages(capsys, caplog, iface_with_nodes): @pytest.mark.usefixtures("reset_globals") def test_main_set_ham_to_KI123(capsys): """Test --set-ham KI123""" - sys.argv = ['', '--set-ham', 'KI123'] + sys.argv = ["", "--set-ham", "KI123"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) + def mock_turnOffEncryptionOnPrimaryChannel(): - print('inside mocked turnOffEncryptionOnPrimaryChannel') + print("inside mocked turnOffEncryptionOnPrimaryChannel") + def mock_setOwner(name, is_licensed): - print(f'inside mocked setOwner name:{name} is_licensed:{is_licensed}') - mocked_node.turnOffEncryptionOnPrimaryChannel.side_effect = mock_turnOffEncryptionOnPrimaryChannel + print(f"inside mocked setOwner name:{name} is_licensed:{is_licensed}") + + mocked_node.turnOffEncryptionOnPrimaryChannel.side_effect = ( + mock_turnOffEncryptionOnPrimaryChannel + ) mocked_node.setOwner.side_effect = mock_setOwner iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Setting Ham ID to KI123', out, re.MULTILINE) - assert re.search(r'inside mocked setOwner', out, re.MULTILINE) - assert re.search(r'inside mocked turnOffEncryptionOnPrimaryChannel', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting Ham ID to KI123", out, re.MULTILINE) + assert re.search(r"inside mocked setOwner", out, re.MULTILINE) + assert re.search( + r"inside mocked turnOffEncryptionOnPrimaryChannel", out, re.MULTILINE + ) + assert err == "" mo.assert_called() @@ -512,23 +556,25 @@ def test_main_set_ham_to_KI123(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_reboot(capsys): """Test --reboot""" - sys.argv = ['', '--reboot'] + sys.argv = ["", "--reboot"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) + def mock_reboot(): - print('inside mocked reboot') + print("inside mocked reboot") + mocked_node.reboot.side_effect = mock_reboot iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked reboot', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked reboot", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -536,23 +582,25 @@ def test_main_reboot(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_shutdown(capsys): """Test --shutdown""" - sys.argv = ['', '--shutdown'] + sys.argv = ["", "--shutdown"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) + def mock_shutdown(): - print('inside mocked shutdown') + print("inside mocked shutdown") + mocked_node.shutdown.side_effect = mock_shutdown iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'inside mocked shutdown', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"inside mocked shutdown", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -560,22 +608,24 @@ def test_main_shutdown(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_sendtext(capsys): """Test --sendtext""" - sys.argv = ['', '--sendtext', 'hello'] + sys.argv = ["", "--sendtext", "hello"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_sendText(text, dest, wantAck, channelIndex): - print('inside mocked sendText') - print(f'{text} {dest} {wantAck} {channelIndex}') + print("inside mocked sendText") + print(f"{text} {dest} {wantAck} {channelIndex}") + iface.sendText.side_effect = mock_sendText - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Sending text message', out, re.MULTILINE) - assert re.search(r'inside mocked sendText', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Sending text message", out, re.MULTILINE) + assert re.search(r"inside mocked sendText", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -583,23 +633,25 @@ def test_main_sendtext(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_sendtext_with_channel(capsys): """Test --sendtext""" - sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1'] + sys.argv = ["", "--sendtext", "hello", "--ch-index", "1"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_sendText(text, dest, wantAck, channelIndex): - print('inside mocked sendText') - print(f'{text} {dest} {wantAck} {channelIndex}') + print("inside mocked sendText") + print(f"{text} {dest} {wantAck} {channelIndex}") + iface.sendText.side_effect = mock_sendText - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Sending text message', out, re.MULTILINE) - assert re.search(r'on channelIndex:1', out, re.MULTILINE) - assert re.search(r'inside mocked sendText', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Sending text message", out, re.MULTILINE) + assert re.search(r"on channelIndex:1", out, re.MULTILINE) + assert re.search(r"inside mocked sendText", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -607,21 +659,23 @@ def test_main_sendtext_with_channel(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_sendtext_with_invalid_channel(caplog, capsys): """Test --sendtext""" - sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1'] + sys.argv = ["", "--sendtext", "hello", "--ch-index", "-1"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) iface.localNode.getChannelByChannelIndex.return_value = None with caplog.at_level(logging.DEBUG): - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch( + "meshtastic.serial_interface.SerialInterface", return_value=iface + ) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'is not a valid channel', out, re.MULTILINE) - assert err == '' + assert re.search(r"is not a valid channel", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -629,21 +683,23 @@ def test_main_sendtext_with_invalid_channel(caplog, capsys): @pytest.mark.usefixtures("reset_globals") def test_main_sendtext_with_invalid_channel_nine(caplog, capsys): """Test --sendtext""" - sys.argv = ['', '--sendtext', 'hello', '--ch-index', '9'] + sys.argv = ["", "--sendtext", "hello", "--ch-index", "9"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) iface.localNode.getChannelByChannelIndex.return_value = None with caplog.at_level(logging.DEBUG): - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch( + "meshtastic.serial_interface.SerialInterface", return_value=iface + ) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'is not a valid channel', out, re.MULTILINE) - assert err == '' + assert re.search(r"is not a valid channel", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -651,7 +707,7 @@ def test_main_sendtext_with_invalid_channel_nine(caplog, capsys): @pytest.mark.usefixtures("reset_globals") def test_main_sendtext_with_dest(capsys, caplog, iface_with_nodes): """Test --sendtext with --dest""" - sys.argv = ['', '--sendtext', 'hello', '--dest', 'foo'] + sys.argv = ["", "--sendtext", "hello", "--dest", "foo"] Globals.getInstance().set_args(sys.argv) iface = iface_with_nodes @@ -659,41 +715,46 @@ def test_main_sendtext_with_dest(capsys, caplog, iface_with_nodes): mocked_channel = MagicMock(autospec=Channel) iface.localNode.getChannelByChannelIndex = mocked_channel - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): with caplog.at_level(logging.DEBUG): - with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert not re.search(r"Warning: 0 is not a valid channel", out, re.MULTILINE) - assert not re.search(r"There is a SECONDARY channel named 'admin'", out, re.MULTILINE) - assert re.search(r'Warning: NodeId foo not found in DB', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert not re.search( + r"Warning: 0 is not a valid channel", out, re.MULTILINE + ) + assert not re.search( + r"There is a SECONDARY channel named 'admin'", out, re.MULTILINE + ) + assert re.search(r"Warning: NodeId foo not found in DB", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_sendping(capsys): """Test --sendping""" - sys.argv = ['', '--sendping'] + sys.argv = ["", "--sendping"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + def mock_sendData(payload, dest, portNum, wantAck, wantResponse): - print('inside mocked sendData') - print(f'{payload} {dest} {portNum} {wantAck} {wantResponse}') + print("inside mocked sendData") + print(f"{payload} {dest} {portNum} {wantAck} {wantResponse}") + iface.sendData.side_effect = mock_sendData - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Sending ping message', out, re.MULTILINE) - assert re.search(r'inside mocked sendData', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Sending ping message", out, re.MULTILINE) + assert re.search(r"inside mocked sendData", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -701,30 +762,34 @@ def test_main_sendping(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_setlat(capsys): """Test --sendlat""" - sys.argv = ['', '--setlat', '37.5'] + sys.argv = ["", "--setlat", "37.5"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) + def mock_writeConfig(): - print('inside mocked writeConfig') + print("inside mocked writeConfig") + mocked_node.writeConfig.side_effect = mock_writeConfig iface = MagicMock(autospec=SerialInterface) + def mock_sendPosition(lat, lon, alt): - print('inside mocked sendPosition') - print(f'{lat} {lon} {alt}') + print("inside mocked sendPosition") + print(f"{lat} {lon} {alt}") + iface.sendPosition.side_effect = mock_sendPosition iface.localNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Fixing latitude', out, re.MULTILINE) - assert re.search(r'Setting device position', out, re.MULTILINE) - assert re.search(r'inside mocked sendPosition', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Fixing latitude", out, re.MULTILINE) + assert re.search(r"Setting device position", out, re.MULTILINE) + assert re.search(r"inside mocked sendPosition", out, re.MULTILINE) # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE) - assert err == '' + assert err == "" mo.assert_called() @@ -732,30 +797,34 @@ def test_main_setlat(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_setlon(capsys): """Test --setlon""" - sys.argv = ['', '--setlon', '-122.1'] + sys.argv = ["", "--setlon", "-122.1"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) + def mock_writeConfig(): - print('inside mocked writeConfig') + print("inside mocked writeConfig") + mocked_node.writeConfig.side_effect = mock_writeConfig iface = MagicMock(autospec=SerialInterface) + def mock_sendPosition(lat, lon, alt): - print('inside mocked sendPosition') - print(f'{lat} {lon} {alt}') + print("inside mocked sendPosition") + print(f"{lat} {lon} {alt}") + iface.sendPosition.side_effect = mock_sendPosition iface.localNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Fixing longitude', out, re.MULTILINE) - assert re.search(r'Setting device position', out, re.MULTILINE) - assert re.search(r'inside mocked sendPosition', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Fixing longitude", out, re.MULTILINE) + assert re.search(r"Setting device position", out, re.MULTILINE) + assert re.search(r"inside mocked sendPosition", out, re.MULTILINE) # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE) - assert err == '' + assert err == "" mo.assert_called() @@ -763,45 +832,50 @@ def test_main_setlon(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_setalt(capsys): """Test --setalt""" - sys.argv = ['', '--setalt', '51'] + sys.argv = ["", "--setalt", "51"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) + def mock_writeConfig(): - print('inside mocked writeConfig') + print("inside mocked writeConfig") + mocked_node.writeConfig.side_effect = mock_writeConfig iface = MagicMock(autospec=SerialInterface) + def mock_sendPosition(lat, lon, alt): - print('inside mocked sendPosition') - print(f'{lat} {lon} {alt}') + print("inside mocked sendPosition") + print(f"{lat} {lon} {alt}") + iface.sendPosition.side_effect = mock_sendPosition iface.localNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Fixing altitude', out, re.MULTILINE) - assert re.search(r'Setting device position', out, re.MULTILINE) - assert re.search(r'inside mocked sendPosition', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Fixing altitude", out, re.MULTILINE) + assert re.search(r"Setting device position", out, re.MULTILINE) + assert re.search(r"inside mocked sendPosition", out, re.MULTILINE) # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE) - assert err == '' + assert err == "" mo.assert_called() + @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_seturl(capsys): """Test --seturl (url used below is what is generated after a factory_reset)""" - sys.argv = ['', '--seturl', 'https://www.meshtastic.org/d/#CgUYAyIBAQ'] + sys.argv = ["", "--seturl", "https://www.meshtastic.org/d/#CgUYAyIBAQ"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -809,7 +883,7 @@ def test_main_seturl(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_set_valid(capsys): """Test --set with valid field""" - sys.argv = ['', '--set', 'wifi_ssid', 'foo'] + sys.argv = ["", "--set", "wifi_ssid", "foo"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -817,12 +891,12 @@ def test_main_set_valid(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Set wifi_ssid to foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Set wifi_ssid to foo", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -830,7 +904,7 @@ def test_main_set_valid(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_set_valid_wifi_passwd(capsys): """Test --set with valid field""" - sys.argv = ['', '--set', 'wifi_password', '123456789'] + sys.argv = ["", "--set", "wifi_password", "123456789"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -838,12 +912,12 @@ def test_main_set_valid_wifi_passwd(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Set wifi_password to 123456789', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Set wifi_password to 123456789", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -851,7 +925,7 @@ def test_main_set_valid_wifi_passwd(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_set_invalid_wifi_passwd(capsys): """Test --set with an invalid value (password must be 8 or more characters)""" - sys.argv = ['', '--set', 'wifi_password', '1234567'] + sys.argv = ["", "--set", "wifi_password", "1234567"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -859,20 +933,23 @@ def test_main_set_invalid_wifi_passwd(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert not re.search(r'Set wifi_password to 1234567', out, re.MULTILINE) - assert re.search(r'Warning: wifi_password must be 8 or more characters.', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert not re.search(r"Set wifi_password to 1234567", out, re.MULTILINE) + assert re.search( + r"Warning: wifi_password must be 8 or more characters.", out, re.MULTILINE + ) + assert err == "" mo.assert_called() + @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_set_valid_camel_case(capsys): """Test --set with valid field""" - sys.argv = ['', '--set', 'wifi_ssid', 'foo'] + sys.argv = ["", "--set", "wifi_ssid", "foo"] Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_camel_case() @@ -881,12 +958,12 @@ def test_main_set_valid_camel_case(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Set wifiSsid to foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Set wifiSsid to foo", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -894,24 +971,24 @@ def test_main_set_valid_camel_case(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_set_with_invalid(capsys): """Test --set with invalid field""" - sys.argv = ['', '--set', 'foo', 'foo'] + sys.argv = ["", "--set", "foo", "foo"] Globals.getInstance().set_args(sys.argv) mocked_user_prefs = MagicMock() mocked_user_prefs.DESCRIPTOR.fields_by_name.get.return_value = None mocked_node = MagicMock(autospec=Node) - mocked_node.radioConfig.preferences = ( mocked_user_prefs ) + mocked_node.radioConfig.preferences = mocked_user_prefs iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'does not have an attribute called foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"does not have an attribute called foo", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -920,7 +997,7 @@ def test_main_set_with_invalid(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_configure_with_snake_case(capsys): """Test --configure with valid file""" - sys.argv = ['', '--configure', 'example_config.yaml'] + sys.argv = ["", "--configure", "example_config.yaml"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -928,19 +1005,19 @@ def test_main_configure_with_snake_case(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Setting device owner', out, re.MULTILINE) - assert re.search(r'Setting device owner short', out, re.MULTILINE) - assert re.search(r'Setting channel url', out, re.MULTILINE) - assert re.search(r'Fixing altitude', out, re.MULTILINE) - assert re.search(r'Fixing latitude', out, re.MULTILINE) - assert re.search(r'Fixing longitude', out, re.MULTILINE) - assert re.search(r'Set location_share to LocEnabled', out, re.MULTILINE) - assert re.search(r'Writing modified preferences', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting device owner", out, re.MULTILINE) + assert re.search(r"Setting device owner short", out, re.MULTILINE) + assert re.search(r"Setting channel url", out, re.MULTILINE) + assert re.search(r"Fixing altitude", out, re.MULTILINE) + assert re.search(r"Fixing latitude", out, re.MULTILINE) + assert re.search(r"Fixing longitude", out, re.MULTILINE) + assert re.search(r"Set location_share to LocEnabled", out, re.MULTILINE) + assert re.search(r"Writing modified preferences", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -948,7 +1025,7 @@ def test_main_configure_with_snake_case(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_configure_with_camel_case_keys(capsys): """Test --configure with valid file""" - sys.argv = ['', '--configure', 'exampleConfig.yaml'] + sys.argv = ["", "--configure", "exampleConfig.yaml"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -956,25 +1033,26 @@ def test_main_configure_with_camel_case_keys(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Setting device owner', out, re.MULTILINE) - assert re.search(r'Setting device owner short', out, re.MULTILINE) - assert re.search(r'Setting channel url', out, re.MULTILINE) - assert re.search(r'Fixing altitude', out, re.MULTILINE) - assert re.search(r'Fixing latitude', out, re.MULTILINE) - assert re.search(r'Fixing longitude', out, re.MULTILINE) - assert re.search(r'Writing modified preferences', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting device owner", out, re.MULTILINE) + assert re.search(r"Setting device owner short", out, re.MULTILINE) + assert re.search(r"Setting channel url", out, re.MULTILINE) + assert re.search(r"Fixing altitude", out, re.MULTILINE) + assert re.search(r"Fixing latitude", out, re.MULTILINE) + assert re.search(r"Fixing longitude", out, re.MULTILINE) + assert re.search(r"Writing modified preferences", out, re.MULTILINE) + assert err == "" mo.assert_called() + @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_ch_add_valid(capsys): """Test --ch-add with valid channel name, and that channel name does not already exist""" - sys.argv = ['', '--ch-add', 'testing'] + sys.argv = ["", "--ch-add", "testing"] Globals.getInstance().set_args(sys.argv) mocked_channel = MagicMock(autospec=Channel) @@ -989,12 +1067,12 @@ def test_main_ch_add_valid(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1002,7 +1080,7 @@ def test_main_ch_add_valid(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_add_invalid_name_too_long(capsys): """Test --ch-add with invalid channel name, name too long""" - sys.argv = ['', '--ch-add', 'testingtestingtesting'] + sys.argv = ["", "--ch-add", "testingtestingtesting"] Globals.getInstance().set_args(sys.argv) mocked_channel = MagicMock(autospec=Channel) @@ -1017,15 +1095,15 @@ def test_main_ch_add_invalid_name_too_long(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: Channel name must be shorter', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: Channel name must be shorter", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1033,7 +1111,7 @@ def test_main_ch_add_invalid_name_too_long(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_add_but_name_already_exists(capsys): """Test --ch-add with a channel name that already exists""" - sys.argv = ['', '--ch-add', 'testing'] + sys.argv = ["", "--ch-add", "testing"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1043,15 +1121,15 @@ def test_main_ch_add_but_name_already_exists(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: This node already has', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: This node already has", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1059,7 +1137,7 @@ def test_main_ch_add_but_name_already_exists(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_add_but_no_more_channels(capsys): """Test --ch-add with but there are no more channels""" - sys.argv = ['', '--ch-add', 'testing'] + sys.argv = ["", "--ch-add", "testing"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1071,15 +1149,15 @@ def test_main_ch_add_but_no_more_channels(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: No free channels were found', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: No free channels were found", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1087,7 +1165,7 @@ def test_main_ch_add_but_no_more_channels(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_del(capsys): """Test --ch-del with valid secondary channel to be deleted""" - sys.argv = ['', '--ch-del', '--ch-index', '1'] + sys.argv = ["", "--ch-del", "--ch-index", "1"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1095,12 +1173,12 @@ def test_main_ch_del(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Deleting channel', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Deleting channel", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1108,7 +1186,7 @@ def test_main_ch_del(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_del_no_ch_index_specified(capsys): """Test --ch-del without a valid ch-index""" - sys.argv = ['', '--ch-del'] + sys.argv = ["", "--ch-del"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1116,15 +1194,15 @@ def test_main_ch_del_no_ch_index_specified(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1132,7 +1210,7 @@ def test_main_ch_del_no_ch_index_specified(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_del_primary_channel(capsys): """Test --ch-del on ch-index=0""" - sys.argv = ['', '--ch-del', '--ch-index', '0'] + sys.argv = ["", "--ch-del", "--ch-index", "0"] Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_channel_index(1) @@ -1141,15 +1219,15 @@ def test_main_ch_del_primary_channel(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: Cannot delete primary channel", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1157,7 +1235,7 @@ def test_main_ch_del_primary_channel(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_enable_valid_secondary_channel(capsys): """Test --ch-enable with --ch-index""" - sys.argv = ['', '--ch-enable', '--ch-index', '1'] + sys.argv = ["", "--ch-enable", "--ch-index", "1"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1165,12 +1243,12 @@ def test_main_ch_enable_valid_secondary_channel(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Writing modified channels', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Writing modified channels", out, re.MULTILINE) + assert err == "" assert Globals.getInstance().get_channel_index() == 1 mo.assert_called() @@ -1179,7 +1257,7 @@ def test_main_ch_enable_valid_secondary_channel(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_disable_valid_secondary_channel(capsys): """Test --ch-disable with --ch-index""" - sys.argv = ['', '--ch-disable', '--ch-index', '1'] + sys.argv = ["", "--ch-disable", "--ch-index", "1"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1187,12 +1265,12 @@ def test_main_ch_disable_valid_secondary_channel(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Writing modified channels', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Writing modified channels", out, re.MULTILINE) + assert err == "" assert Globals.getInstance().get_channel_index() == 1 mo.assert_called() @@ -1201,7 +1279,7 @@ def test_main_ch_disable_valid_secondary_channel(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_enable_without_a_ch_index(capsys): """Test --ch-enable without --ch-index""" - sys.argv = ['', '--ch-enable'] + sys.argv = ["", "--ch-enable"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1209,15 +1287,15 @@ def test_main_ch_enable_without_a_ch_index(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) + assert err == "" assert Globals.getInstance().get_channel_index() is None mo.assert_called() @@ -1226,7 +1304,7 @@ def test_main_ch_enable_without_a_ch_index(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_enable_primary_channel(capsys): """Test --ch-enable with --ch-index = 0""" - sys.argv = ['', '--ch-enable', '--ch-index', '0'] + sys.argv = ["", "--ch-enable", "--ch-index", "0"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1234,23 +1312,23 @@ def test_main_ch_enable_primary_channel(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: Cannot enable/disable PRIMARY', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: Cannot enable/disable PRIMARY", out, re.MULTILINE) + assert err == "" assert Globals.getInstance().get_channel_index() == 0 mo.assert_called() # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_ch_range_options(capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_ch_range_options(capsys): # """Test changing the various range options.""" # range_options = ['--ch-vlongslow', '--ch-longslow', '--ch-longfast', '--ch-midslow', # '--ch-midfast', '--ch-shortslow', '--ch-shortfast'] @@ -1276,7 +1354,7 @@ def test_main_ch_enable_primary_channel(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_longsfast_on_non_primary_channel(capsys): """Test --ch-longfast --ch-index 1""" - sys.argv = ['', '--ch-longfast', '--ch-index', '1'] + sys.argv = ["", "--ch-longfast", "--ch-index", "1"] Globals.getInstance().set_args(sys.argv) mocked_node = MagicMock(autospec=Node) @@ -1284,15 +1362,15 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys): iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Warning: Standard channel settings', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Warning: Standard channel settings", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1311,9 +1389,9 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys): # POS_TIMESTAMP 256 # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_pos_fields_no_args(capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_pos_fields_no_args(capsys): # """Test --pos-fields no args (which shows settings)""" # sys.argv = ['', '--pos-fields'] # Globals.getInstance().set_args(sys.argv) @@ -1343,9 +1421,9 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_pos_fields_arg_of_zero(capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_pos_fields_arg_of_zero(capsys): # """Test --pos-fields an arg of 0 (which shows list)""" # sys.argv = ['', '--pos-fields', '0'] # Globals.getInstance().set_args(sys.argv) @@ -1378,9 +1456,9 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_pos_fields_valid_values(capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_pos_fields_valid_values(capsys): # """Test --pos-fields with valid values""" # sys.argv = ['', '--pos-fields', 'POS_GEO_SEP', 'POS_ALT_MSL'] # Globals.getInstance().set_args(sys.argv) @@ -1406,9 +1484,9 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_get_with_valid_values(capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_get_with_valid_values(capsys): # """Test --get with valid values (with string, number, boolean)""" # sys.argv = ['', '--get', 'ls_secs', '--get', 'wifi_ssid', '--get', 'fixed_position'] # Globals.getInstance().set_args(sys.argv) @@ -1435,14 +1513,13 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_get_with_valid_values_camel(capsys, caplog): """Test --get with valid values (with string, number, boolean)""" - sys.argv = ['', '--get', 'lsSecs', '--get', 'wifiSsid', '--get', 'fixedPosition'] + sys.argv = ["", "--get", "lsSecs", "--get", "wifiSsid", "--get", "fixedPosition"] Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_camel_case() with caplog.at_level(logging.DEBUG): - with patch('meshtastic.serial_interface.SerialInterface') as mo: - - mo().getNode().radioConfig.preferences.wifi_ssid = 'foo' + with patch("meshtastic.serial_interface.SerialInterface") as mo: + mo().getNode().radioConfig.preferences.wifi_ssid = "foo" mo().getNode().radioConfig.preferences.ls_secs = 300 mo().getNode().radioConfig.preferences.fixed_position = False @@ -1451,36 +1528,36 @@ def test_main_get_with_valid_values_camel(capsys, caplog): mo.assert_called() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'lsSecs: 300', out, re.MULTILINE) - assert re.search(r'wifiSsid: foo', out, re.MULTILINE) - assert re.search(r'fixedPosition: False', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"lsSecs: 300", out, re.MULTILINE) + assert re.search(r"wifiSsid: foo", out, re.MULTILINE) + assert re.search(r"fixedPosition: False", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_get_with_invalid(capsys): """Test --get with invalid field""" - sys.argv = ['', '--get', 'foo'] + sys.argv = ["", "--get", "foo"] Globals.getInstance().set_args(sys.argv) mocked_user_prefs = MagicMock() mocked_user_prefs.DESCRIPTOR.fields_by_name.get.return_value = None mocked_node = MagicMock(autospec=Node) - mocked_node.radioConfig.preferences = ( mocked_user_prefs ) + mocked_node.radioConfig.preferences = mocked_user_prefs iface = MagicMock(autospec=SerialInterface) iface.getNode.return_value = mocked_node - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'does not have an attribute called foo', out, re.MULTILINE) - assert re.search(r'Choices in sorted order are', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"does not have an attribute called foo", out, re.MULTILINE) + assert re.search(r"Choices in sorted order are", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1494,10 +1571,12 @@ def test_main_onReceive_empty(caplog, capsys): packet = {} with caplog.at_level(logging.DEBUG): onReceive(packet, iface) - assert re.search(r'in onReceive', caplog.text, re.MULTILINE) + assert re.search(r"in onReceive", caplog.text, re.MULTILINE) out, err = capsys.readouterr() - assert re.search(r"Warning: There is no field 'to' in the packet.", out, re.MULTILINE) - assert err == '' + assert re.search( + r"Warning: There is no field 'to' in the packet.", out, re.MULTILINE + ) + assert err == "" # TODO: use this captured position app message (might want/need in the future) @@ -1511,99 +1590,95 @@ def test_main_onReceive_empty(caplog, capsys): # 'hop_limit': 3 # } + @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_onReceive_with_sendtext(caplog, capsys): """Test onReceive with sendtext - The entire point of this test is to make sure the interface.close() call - is made in onReceive(). + The entire point of this test is to make sure the interface.close() call + is made in onReceive(). """ - sys.argv = ['', '--sendtext', 'hello'] + sys.argv = ["", "--sendtext", "hello"] Globals.getInstance().set_args(sys.argv) # Note: 'TEXT_MESSAGE_APP' value is 1 packet = { - 'to': 4294967295, - 'decoded': { - 'portnum': 1, - 'payload': "hello" - }, - 'id': 334776977, - 'hop_limit': 3, - 'want_ack': True - } + "to": 4294967295, + "decoded": {"portnum": 1, "payload": "hello"}, + "id": 334776977, + "hop_limit": 3, + "want_ack": True, + } iface = MagicMock(autospec=SerialInterface) iface.myInfo.my_node_num = 4294967295 - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with caplog.at_level(logging.DEBUG): main() onReceive(packet, iface) - assert re.search(r'in onReceive', caplog.text, re.MULTILINE) + assert re.search(r"in onReceive", caplog.text, re.MULTILINE) mo.assert_called() out, err = capsys.readouterr() - assert re.search(r'Sending text message hello to', out, re.MULTILINE) - assert err == '' + assert re.search(r"Sending text message hello to", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_onReceive_with_text(caplog, capsys): - """Test onReceive with text - """ + """Test onReceive with text""" args = MagicMock() - args.sendtext.return_value = 'foo' + args.sendtext.return_value = "foo" Globals.getInstance().set_args(args) # Note: 'TEXT_MESSAGE_APP' value is 1 # Note: Some of this is faked below. packet = { - 'to': 4294967295, - 'decoded': { - 'portnum': 1, - 'payload': "hello", - 'text': "faked" - }, - 'id': 334776977, - 'hop_limit': 3, - 'want_ack': True, - 'rxSnr': 6.0, - 'hopLimit': 3, - 'raw': 'faked', - 'fromId': '!28b5465c', - 'toId': '^all' - } + "to": 4294967295, + "decoded": {"portnum": 1, "payload": "hello", "text": "faked"}, + "id": 334776977, + "hop_limit": 3, + "want_ack": True, + "rxSnr": 6.0, + "hopLimit": 3, + "raw": "faked", + "fromId": "!28b5465c", + "toId": "^all", + } iface = MagicMock(autospec=SerialInterface) iface.myInfo.my_node_num = 4294967295 - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): with caplog.at_level(logging.DEBUG): onReceive(packet, iface) - assert re.search(r'in onReceive', caplog.text, re.MULTILINE) + assert re.search(r"in onReceive", caplog.text, re.MULTILINE) out, err = capsys.readouterr() - assert re.search(r'Sending reply', out, re.MULTILINE) - assert err == '' + assert re.search(r"Sending reply", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_onConnection(capsys): """Test onConnection""" - sys.argv = [''] + sys.argv = [""] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) + class TempTopic: - """ temp class for topic """ + """temp class for topic""" + def getName(self): - """ return the fake name of a topic""" - return 'foo' + """return the fake name of a topic""" + return "foo" + mytopic = TempTopic() onConnection(iface, mytopic) out, err = capsys.readouterr() - assert re.search(r'Connection changed: foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connection changed: foo", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -1611,13 +1686,18 @@ def test_main_onConnection(capsys): def test_main_export_config(capsys): """Test export_config() function directly""" iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: - mo.getLongName.return_value = 'foo' - mo.getShortName.return_value = 'oof' - mo.localNode.getURL.return_value = 'bar' - mo.getMyNodeInfo().get.return_value = { 'latitudeI': 1100000000, 'longitudeI': 1200000000, - 'altitude': 100, 'batteryLevel': 34, 'latitude': 110.0, - 'longitude': 120.0} + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: + mo.getLongName.return_value = "foo" + mo.getShortName.return_value = "oof" + mo.localNode.getURL.return_value = "bar" + mo.getMyNodeInfo().get.return_value = { + "latitudeI": 1100000000, + "longitudeI": 1200000000, + "altitude": 100, + "batteryLevel": 34, + "latitude": 110.0, + "longitude": 120.0, + } mo.localNode.radioConfig.preferences = """phone_timeout_secs: 900 ls_secs: 300 position_broadcast_smart: true @@ -1627,22 +1707,22 @@ position_flags: 35""" out, err = capsys.readouterr() # ensure we do not output this line - assert not re.search(r'Connected to radio', out, re.MULTILINE) + assert not re.search(r"Connected to radio", out, re.MULTILINE) - assert re.search(r'owner: foo', out, re.MULTILINE) - assert re.search(r'owner_short: oof', out, re.MULTILINE) - assert re.search(r'channel_url: bar', out, re.MULTILINE) - assert re.search(r'location:', out, re.MULTILINE) - assert re.search(r'lat: 110.0', out, re.MULTILINE) - assert re.search(r'lon: 120.0', out, re.MULTILINE) - assert re.search(r'alt: 100', out, re.MULTILINE) - assert re.search(r'user_prefs:', out, re.MULTILINE) - assert re.search(r'phone_timeout_secs: 900', out, re.MULTILINE) - assert re.search(r'ls_secs: 300', out, re.MULTILINE) + assert re.search(r"owner: foo", out, re.MULTILINE) + assert re.search(r"owner_short: oof", out, re.MULTILINE) + assert re.search(r"channel_url: bar", out, re.MULTILINE) + assert re.search(r"location:", out, re.MULTILINE) + assert re.search(r"lat: 110.0", out, re.MULTILINE) + assert re.search(r"lon: 120.0", out, re.MULTILINE) + assert re.search(r"alt: 100", out, re.MULTILINE) + assert re.search(r"user_prefs:", out, re.MULTILINE) + assert re.search(r"phone_timeout_secs: 900", out, re.MULTILINE) + assert re.search(r"ls_secs: 300", out, re.MULTILINE) assert re.search(r"position_broadcast_smart: 'true'", out, re.MULTILINE) assert re.search(r"fixed_position: 'true'", out, re.MULTILINE) assert re.search(r"position_flags: 35", out, re.MULTILINE) - assert err == '' + assert err == "" @pytest.mark.unit @@ -1651,12 +1731,17 @@ def test_main_export_config_use_camel(capsys): """Test export_config() function directly""" Globals.getInstance().set_camel_case() iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: - mo.getLongName.return_value = 'foo' - mo.localNode.getURL.return_value = 'bar' - mo.getMyNodeInfo().get.return_value = { 'latitudeI': 1100000000, 'longitudeI': 1200000000, - 'altitude': 100, 'batteryLevel': 34, 'latitude': 110.0, - 'longitude': 120.0} + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: + mo.getLongName.return_value = "foo" + mo.localNode.getURL.return_value = "bar" + mo.getMyNodeInfo().get.return_value = { + "latitudeI": 1100000000, + "longitudeI": 1200000000, + "altitude": 100, + "batteryLevel": 34, + "latitude": 110.0, + "longitude": 120.0, + } mo.localNode.radioConfig.preferences = """phone_timeout_secs: 900 ls_secs: 300 position_broadcast_smart: true @@ -1666,38 +1751,38 @@ position_flags: 35""" out, err = capsys.readouterr() # ensure we do not output this line - assert not re.search(r'Connected to radio', out, re.MULTILINE) + assert not re.search(r"Connected to radio", out, re.MULTILINE) - assert re.search(r'owner: foo', out, re.MULTILINE) - assert re.search(r'channelUrl: bar', out, re.MULTILINE) - assert re.search(r'location:', out, re.MULTILINE) - assert re.search(r'lat: 110.0', out, re.MULTILINE) - assert re.search(r'lon: 120.0', out, re.MULTILINE) - assert re.search(r'alt: 100', out, re.MULTILINE) - assert re.search(r'userPrefs:', out, re.MULTILINE) - assert re.search(r'phoneTimeoutSecs: 900', out, re.MULTILINE) - assert re.search(r'lsSecs: 300', out, re.MULTILINE) + assert re.search(r"owner: foo", out, re.MULTILINE) + assert re.search(r"channelUrl: bar", out, re.MULTILINE) + assert re.search(r"location:", out, re.MULTILINE) + assert re.search(r"lat: 110.0", out, re.MULTILINE) + assert re.search(r"lon: 120.0", out, re.MULTILINE) + assert re.search(r"alt: 100", out, re.MULTILINE) + assert re.search(r"userPrefs:", out, re.MULTILINE) + assert re.search(r"phoneTimeoutSecs: 900", out, re.MULTILINE) + assert re.search(r"lsSecs: 300", out, re.MULTILINE) # TODO: should True be capitalized here? assert re.search(r"positionBroadcastSmart: 'True'", out, re.MULTILINE) assert re.search(r"fixedPosition: 'True'", out, re.MULTILINE) assert re.search(r"positionFlags: 35", out, re.MULTILINE) - assert err == '' + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_export_config_called_from_main(capsys): """Test --export-config""" - sys.argv = ['', '--export-config'] + sys.argv = ["", "--export-config"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert not re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'# start of Meshtastic configure yaml', out, re.MULTILINE) - assert err == '' + assert not re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"# start of Meshtastic configure yaml", out, re.MULTILINE) + assert err == "" mo.assert_called() @@ -1705,49 +1790,49 @@ def test_main_export_config_called_from_main(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_gpio_rd_no_gpio_channel(capsys): """Test --gpio_rd with no named gpio channel""" - sys.argv = ['', '--gpio-rd', '0x10', '--dest', '!foo'] + sys.argv = ["", "--gpio-rd", "0x10", "--dest", "!foo"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=SerialInterface) iface.localNode.getChannelByName.return_value = None - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No channel named', out) - assert err == '' + assert re.search(r"Warning: No channel named", out) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_gpio_rd_no_dest(capsys): """Test --gpio_rd with a named gpio channel but no dest was specified""" - sys.argv = ['', '--gpio-rd', '0x2000'] + sys.argv = ["", "--gpio-rd", "0x2000"] Globals.getInstance().set_args(sys.argv) channel = Channel(index=2, role=2) - channel.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84' - channel.settings.name = 'gpio' + channel.settings.psk = b"\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84" + channel.settings.name = "gpio" iface = MagicMock(autospec=SerialInterface) iface.localNode.getChannelByName.return_value = channel - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: Must use a destination node ID', out) - assert err == '' + assert re.search(r"Warning: Must use a destination node ID", out) + assert err == "" # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#@patch('time.sleep') -#def test_main_gpio_rd(caplog, capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# @patch('time.sleep') +# def test_main_gpio_rd(caplog, capsys): # """Test --gpio_rd with a named gpio channel""" # # Note: On the Heltec v2.1, there is a GPIO pin GPIO 13 that does not have a # # red arrow (meaning ok to use for our purposes) @@ -1801,10 +1886,10 @@ def test_main_gpio_rd_no_dest(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#@patch('time.sleep') -#def test_main_gpio_rd_with_no_gpioMask(caplog, capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# @patch('time.sleep') +# def test_main_gpio_rd_with_no_gpioMask(caplog, capsys): # """Test --gpio_rd with a named gpio channel""" # sys.argv = ['', '--gpio-rd', '0x1000', '--dest', '!1234'] # Globals.getInstance().set_args(sys.argv) @@ -1849,9 +1934,9 @@ def test_main_gpio_rd_no_dest(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_gpio_watch(caplog, capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_gpio_watch(caplog, capsys): # """Test --gpio_watch with a named gpio channel""" # sys.argv = ['', '--gpio-watch', '0x1000', '--dest', '!1234'] # Globals.getInstance().set_args(sys.argv) @@ -1903,9 +1988,9 @@ def test_main_gpio_rd_no_dest(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_gpio_wrb(caplog, capsys): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_gpio_wrb(caplog, capsys): # """Test --gpio_wrb with a named gpio channel""" # sys.argv = ['', '--gpio-wrb', '4', '1', '--dest', '!1234'] # Globals.getInstance().set_args(sys.argv) @@ -1955,15 +2040,15 @@ def test_main_gpio_rd_no_dest(capsys): def test_main_getPref_valid_field(capsys): """Test getPref() with a valid field""" prefs = MagicMock() - prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs' - prefs.wifi_ssid = 'foo' + prefs.DESCRIPTOR.fields_by_name.get.return_value = "ls_secs" + prefs.wifi_ssid = "foo" prefs.ls_secs = 300 prefs.fixed_position = False - getPref(prefs, 'ls_secs') + getPref(prefs, "ls_secs") out, err = capsys.readouterr() - assert re.search(r'ls_secs: 300', out, re.MULTILINE) - assert err == '' + assert re.search(r"ls_secs: 300", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -1972,30 +2057,31 @@ def test_main_getPref_valid_field_camel(capsys): """Test getPref() with a valid field""" Globals.getInstance().set_camel_case() prefs = MagicMock() - prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs' - prefs.wifi_ssid = 'foo' + prefs.DESCRIPTOR.fields_by_name.get.return_value = "ls_secs" + prefs.wifi_ssid = "foo" prefs.ls_secs = 300 prefs.fixed_position = False - getPref(prefs, 'ls_secs') + getPref(prefs, "ls_secs") out, err = capsys.readouterr() - assert re.search(r'lsSecs: 300', out, re.MULTILINE) - assert err == '' + assert re.search(r"lsSecs: 300", out, re.MULTILINE) + assert err == "" + @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_getPref_valid_field_string(capsys): """Test getPref() with a valid field and value as a string""" prefs = MagicMock() - prefs.DESCRIPTOR.fields_by_name.get.return_value = 'wifi_ssid' - prefs.wifi_ssid = 'foo' + prefs.DESCRIPTOR.fields_by_name.get.return_value = "wifi_ssid" + prefs.wifi_ssid = "foo" prefs.ls_secs = 300 prefs.fixed_position = False - getPref(prefs, 'wifi_ssid') + getPref(prefs, "wifi_ssid") out, err = capsys.readouterr() - assert re.search(r'wifi_ssid: foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"wifi_ssid: foo", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2004,15 +2090,15 @@ def test_main_getPref_valid_field_string_camel(capsys): """Test getPref() with a valid field and value as a string""" Globals.getInstance().set_camel_case() prefs = MagicMock() - prefs.DESCRIPTOR.fields_by_name.get.return_value = 'wifi_ssid' - prefs.wifi_ssid = 'foo' + prefs.DESCRIPTOR.fields_by_name.get.return_value = "wifi_ssid" + prefs.wifi_ssid = "foo" prefs.ls_secs = 300 prefs.fixed_position = False - getPref(prefs, 'wifi_ssid') + getPref(prefs, "wifi_ssid") out, err = capsys.readouterr() - assert re.search(r'wifiSsid: foo', out, re.MULTILINE) - assert err == '' + assert re.search(r"wifiSsid: foo", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2020,15 +2106,15 @@ def test_main_getPref_valid_field_string_camel(capsys): def test_main_getPref_valid_field_bool(capsys): """Test getPref() with a valid field and value as a bool""" prefs = MagicMock() - prefs.DESCRIPTOR.fields_by_name.get.return_value = 'fixed_position' - prefs.wifi_ssid = 'foo' + prefs.DESCRIPTOR.fields_by_name.get.return_value = "fixed_position" + prefs.wifi_ssid = "foo" prefs.ls_secs = 300 prefs.fixed_position = False - getPref(prefs, 'fixed_position') + getPref(prefs, "fixed_position") out, err = capsys.readouterr() - assert re.search(r'fixed_position: False', out, re.MULTILINE) - assert err == '' + assert re.search(r"fixed_position: False", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2037,15 +2123,15 @@ def test_main_getPref_valid_field_bool_camel(capsys): """Test getPref() with a valid field and value as a bool""" Globals.getInstance().set_camel_case() prefs = MagicMock() - prefs.DESCRIPTOR.fields_by_name.get.return_value = 'fixed_position' - prefs.wifi_ssid = 'foo' + prefs.DESCRIPTOR.fields_by_name.get.return_value = "fixed_position" + prefs.wifi_ssid = "foo" prefs.ls_secs = 300 prefs.fixed_position = False - getPref(prefs, 'fixed_position') + getPref(prefs, "fixed_position") out, err = capsys.readouterr() - assert re.search(r'fixedPosition: False', out, re.MULTILINE) - assert err == '' + assert re.search(r"fixedPosition: False", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2064,20 +2150,20 @@ def test_main_getPref_invalid_field(capsys): prefs.DESCRIPTOR.fields_by_name.get.return_value = None # Note: This is a subset of the real fields - ls_secs_field = Field('ls_secs') - is_router = Field('is_router') - fixed_position = Field('fixed_position') + ls_secs_field = Field("ls_secs") + is_router = Field("is_router") + fixed_position = Field("fixed_position") - fields = [ ls_secs_field, is_router, fixed_position ] + fields = [ls_secs_field, is_router, fixed_position] prefs.DESCRIPTOR.fields = fields - getPref(prefs, 'foo') + getPref(prefs, "foo") out, err = capsys.readouterr() - assert re.search(r'does not have an attribute called foo', out, re.MULTILINE) + assert re.search(r"does not have an attribute called foo", out, re.MULTILINE) # ensure they are sorted - assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE) - assert err == '' + assert re.search(r"fixed_position\s+is_router\s+ls_secs", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2097,20 +2183,20 @@ def test_main_getPref_invalid_field_camel(capsys): prefs.DESCRIPTOR.fields_by_name.get.return_value = None # Note: This is a subset of the real fields - ls_secs_field = Field('ls_secs') - is_router = Field('is_router') - fixed_position = Field('fixed_position') + ls_secs_field = Field("ls_secs") + is_router = Field("is_router") + fixed_position = Field("fixed_position") - fields = [ ls_secs_field, is_router, fixed_position ] + fields = [ls_secs_field, is_router, fixed_position] prefs.DESCRIPTOR.fields = fields - getPref(prefs, 'foo') + getPref(prefs, "foo") out, err = capsys.readouterr() - assert re.search(r'does not have an attribute called foo', out, re.MULTILINE) + assert re.search(r"does not have an attribute called foo", out, re.MULTILINE) # ensure they are sorted - assert re.search(r'fixedPosition\s+isRouter\s+lsSecs', out, re.MULTILINE) - assert err == '' + assert re.search(r"fixedPosition\s+isRouter\s+lsSecs", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2126,20 +2212,20 @@ def test_main_setPref_valid_field_int_as_string(capsys): self.name = name self.enum_type = enum_type - ls_secs_field = Field('ls_secs', 'int') + ls_secs_field = Field("ls_secs", "int") prefs = MagicMock() prefs.DESCRIPTOR.fields_by_name.get.return_value = ls_secs_field - setPref(prefs, 'ls_secs', '300') + setPref(prefs, "ls_secs", "300") out, err = capsys.readouterr() - assert re.search(r'Set ls_secs to 300', out, re.MULTILINE) - assert err == '' + assert re.search(r"Set ls_secs to 300", out, re.MULTILINE) + assert err == "" # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_setPref_valid_field_invalid_enum(capsys, caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_setPref_valid_field_invalid_enum(capsys, caplog): # """Test setPref() with a valid field but invalid enum value""" # # radioConfig = RadioConfig() @@ -2156,9 +2242,9 @@ def test_main_setPref_valid_field_int_as_string(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_setPref_valid_field_invalid_enum_where_enums_are_camel_cased_values(capsys, caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_setPref_valid_field_invalid_enum_where_enums_are_camel_cased_values(capsys, caplog): # """Test setPref() with a valid field but invalid enum value""" # # radioConfig = RadioConfig() @@ -2175,9 +2261,9 @@ def test_main_setPref_valid_field_int_as_string(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_setPref_valid_field_invalid_enum_camel(capsys, caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_setPref_valid_field_invalid_enum_camel(capsys, caplog): # """Test setPref() with a valid field but invalid enum value""" # Globals.getInstance().set_camel_case() # @@ -2192,9 +2278,9 @@ def test_main_setPref_valid_field_int_as_string(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_setPref_valid_field_valid_enum(capsys, caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_setPref_valid_field_valid_enum(capsys, caplog): # """Test setPref() with a valid field and valid enum value""" # # # charge_current @@ -2211,9 +2297,9 @@ def test_main_setPref_valid_field_int_as_string(capsys): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_main_setPref_valid_field_valid_enum_camel(capsys, caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_main_setPref_valid_field_valid_enum_camel(capsys, caplog): # """Test setPref() with a valid field and valid enum value""" # Globals.getInstance().set_camel_case() # @@ -2235,7 +2321,6 @@ def test_main_setPref_valid_field_int_as_string(capsys): def test_main_setPref_invalid_field(capsys): """Test setPref() with a invalid field""" - class Field: """Simple class for testing.""" @@ -2247,19 +2332,19 @@ def test_main_setPref_invalid_field(capsys): prefs.DESCRIPTOR.fields_by_name.get.return_value = None # Note: This is a subset of the real fields - ls_secs_field = Field('ls_secs') - is_router = Field('is_router') - fixed_position = Field('fixed_position') + ls_secs_field = Field("ls_secs") + is_router = Field("is_router") + fixed_position = Field("fixed_position") - fields = [ ls_secs_field, is_router, fixed_position ] + fields = [ls_secs_field, is_router, fixed_position] prefs.DESCRIPTOR.fields = fields - setPref(prefs, 'foo', '300') + setPref(prefs, "foo", "300") out, err = capsys.readouterr() - assert re.search(r'does not have an attribute called foo', out, re.MULTILINE) + assert re.search(r"does not have an attribute called foo", out, re.MULTILINE) # ensure they are sorted - assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE) - assert err == '' + assert re.search(r"fixed_position\s+is_router\s+ls_secs", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2279,19 +2364,19 @@ def test_main_setPref_invalid_field_camel(capsys): prefs.DESCRIPTOR.fields_by_name.get.return_value = None # Note: This is a subset of the real fields - ls_secs_field = Field('ls_secs') - is_router = Field('is_router') - fixed_position = Field('fixed_position') + ls_secs_field = Field("ls_secs") + is_router = Field("is_router") + fixed_position = Field("fixed_position") - fields = [ ls_secs_field, is_router, fixed_position ] + fields = [ls_secs_field, is_router, fixed_position] prefs.DESCRIPTOR.fields = fields - setPref(prefs, 'foo', '300') + setPref(prefs, "foo", "300") out, err = capsys.readouterr() - assert re.search(r'does not have an attribute called foo', out, re.MULTILINE) + assert re.search(r"does not have an attribute called foo", out, re.MULTILINE) # ensure they are sorted - assert re.search(r'fixedPosition\s+isRouter\s+lsSecs', out, re.MULTILINE) - assert err == '' + assert re.search(r"fixedPosition\s+isRouter\s+lsSecs", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2307,15 +2392,15 @@ def test_main_setPref_ignore_incoming_123(capsys): self.name = name self.enum_type = enum_type - ignore_incoming_field = Field('ignore_incoming', 'list') + ignore_incoming_field = Field("ignore_incoming", "list") prefs = MagicMock() prefs.DESCRIPTOR.fields_by_name.get.return_value = ignore_incoming_field - setPref(prefs, 'ignore_incoming', '123') + setPref(prefs, "ignore_incoming", "123") out, err = capsys.readouterr() assert re.search(r"Adding '123' to the ignore_incoming list", out, re.MULTILINE) - assert re.search(r'Set ignore_incoming to 123', out, re.MULTILINE) - assert err == '' + assert re.search(r"Set ignore_incoming to 123", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -2331,32 +2416,32 @@ def test_main_setPref_ignore_incoming_0(capsys): self.name = name self.enum_type = enum_type - ignore_incoming_field = Field('ignore_incoming', 'list') + ignore_incoming_field = Field("ignore_incoming", "list") prefs = MagicMock() prefs.DESCRIPTOR.fields_by_name.get.return_value = ignore_incoming_field - setPref(prefs, 'ignore_incoming', '0') + setPref(prefs, "ignore_incoming", "0") out, err = capsys.readouterr() - assert re.search(r'Clearing ignore_incoming list', out, re.MULTILINE) - assert re.search(r'Set ignore_incoming to 0', out, re.MULTILINE) - assert err == '' + assert re.search(r"Clearing ignore_incoming list", out, re.MULTILINE) + assert re.search(r"Set ignore_incoming to 0", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_ch_set_psk_no_ch_index(capsys): - """Test --ch-set psk """ - sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local'] + """Test --ch-set psk""" + sys.argv = ["", "--ch-set", "psk", "foo", "--host", "meshtastic.local"] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=TCPInterface) - with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo: + with patch("meshtastic.tcp_interface.TCPInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Warning: Need to specify '--ch-index'", out, re.MULTILINE) - assert err == '' + assert err == "" assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 mo.assert_called() @@ -2365,17 +2450,26 @@ def test_main_ch_set_psk_no_ch_index(capsys): @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_main_ch_set_psk_with_ch_index(capsys): - """Test --ch-set psk """ - sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local', '--ch-index', '0'] + """Test --ch-set psk""" + sys.argv = [ + "", + "--ch-set", + "psk", + "foo", + "--host", + "meshtastic.local", + "--ch-index", + "0", + ] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=TCPInterface) - with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo: + with patch("meshtastic.tcp_interface.TCPInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Writing modified channels to device", out, re.MULTILINE) - assert err == '' + assert err == "" mo.assert_called() @@ -2383,17 +2477,26 @@ def test_main_ch_set_psk_with_ch_index(capsys): @pytest.mark.usefixtures("reset_globals") def test_main_ch_set_name_with_ch_index(capsys): """Test --ch-set setting other than psk""" - sys.argv = ['', '--ch-set', 'name', 'foo', '--host', 'meshtastic.local', '--ch-index', '0'] + sys.argv = [ + "", + "--ch-set", + "name", + "foo", + "--host", + "meshtastic.local", + "--ch-index", + "0", + ] Globals.getInstance().set_args(sys.argv) iface = MagicMock(autospec=TCPInterface) - with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo: + with patch("meshtastic.tcp_interface.TCPInterface", return_value=iface) as mo: main() out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert re.search(r'Set name to foo', out, re.MULTILINE) + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Set name to foo", out, re.MULTILINE) assert re.search(r"Writing modified channels to device", out, re.MULTILINE) - assert err == '' + assert err == "" mo.assert_called() @@ -2401,38 +2504,38 @@ def test_main_ch_set_name_with_ch_index(capsys): @pytest.mark.usefixtures("reset_globals") def test_onNode(capsys): """Test onNode""" - onNode('foo') + onNode("foo") out, err = capsys.readouterr() - assert re.search(r'Node changed', out, re.MULTILINE) - assert err == '' + assert re.search(r"Node changed", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_tunnel_no_args(capsys): """Test tunnel no arguments""" - sys.argv = [''] + sys.argv = [""] Globals.getInstance().set_args(sys.argv) with pytest.raises(SystemExit) as pytest_wrapped_e: tunnelMain() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 _, err = capsys.readouterr() - assert re.search(r'usage: ', err, re.MULTILINE) + assert re.search(r"usage: ", err, re.MULTILINE) @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.util.findPorts', return_value=[]) -@patch('platform.system') +@patch("meshtastic.util.findPorts", return_value=[]) +@patch("platform.system") def test_tunnel_tunnel_arg_with_no_devices(mock_platform_system, caplog, capsys): """Test tunnel with tunnel arg (act like we are on a linux system)""" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock - sys.argv = ['', '--tunnel'] + sys.argv = ["", "--tunnel"] Globals.getInstance().set_args(sys.argv) - print(f'platform.system():{platform.system()}') + print(f"platform.system():{platform.system()}") with caplog.at_level(logging.DEBUG): with pytest.raises(SystemExit) as pytest_wrapped_e: tunnelMain() @@ -2440,22 +2543,22 @@ def test_tunnel_tunnel_arg_with_no_devices(mock_platform_system, caplog, capsys) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: No Meshtastic devices detected", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('meshtastic.util.findPorts', return_value=[]) -@patch('platform.system') +@patch("meshtastic.util.findPorts", return_value=[]) +@patch("platform.system") def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, caplog, capsys): """Test tunnel with subnet arg (act like we are on a linux system)""" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock - sys.argv = ['', '--subnet', 'foo'] + sys.argv = ["", "--subnet", "foo"] Globals.getInstance().set_args(sys.argv) - print(f'platform.system():{platform.system()}') + print(f"platform.system():{platform.system()}") with caplog.at_level(logging.DEBUG): with pytest.raises(SystemExit) as pytest_wrapped_e: tunnelMain() @@ -2463,38 +2566,39 @@ def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, caplog, capsys) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: No Meshtastic devices detected", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") -@patch('platform.system') +@patch("platform.system") def test_tunnel_tunnel_arg(mock_platform_system, caplog, iface_with_nodes, capsys): """Test tunnel with tunnel arg (act like we are on a linux system)""" + # Override the time.sleep so there is no loop def my_sleep(amount): - print(f'{amount}') + print(f"{amount}") sys.exit(3) a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock - sys.argv = ['', '--tunnel'] + sys.argv = ["", "--tunnel"] Globals.getInstance().set_args(sys.argv) iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 with caplog.at_level(logging.DEBUG): - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): - with patch('time.sleep', side_effect=my_sleep): + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): + with patch("time.sleep", side_effect=my_sleep): with pytest.raises(SystemExit) as pytest_wrapped_e: tunnelMain() mock_platform_system.assert_called() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 3 - assert re.search(r'Not starting Tunnel', caplog.text, re.MULTILINE) + assert re.search(r"Not starting Tunnel", caplog.text, re.MULTILINE) out, err = capsys.readouterr() - assert re.search(r'Connected to radio', out, re.MULTILINE) - assert err == '' + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert err == "" diff --git a/meshtastic/tests/test_mesh_interface.py b/meshtastic/tests/test_mesh_interface.py index 5eac37e..75b2baa 100644 --- a/meshtastic/tests/test_mesh_interface.py +++ b/meshtastic/tests/test_mesh_interface.py @@ -1,17 +1,18 @@ """Meshtastic unit tests for mesh_interface.py""" -import re import logging +import re +from unittest.mock import MagicMock, patch -from unittest.mock import patch, MagicMock import pytest +from .. import mesh_pb2 +from ..__init__ import BROADCAST_ADDR, LOCAL_ADDR from ..mesh_interface import MeshInterface from ..node import Node -from .. import mesh_pb2 -from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR + # TODO -#from ..config import Config +# from ..config import Config from ..util import Timeout @@ -20,24 +21,24 @@ from ..util import Timeout def test_MeshInterface(capsys): """Test that we can instantiate a MeshInterface""" iface = MeshInterface(noProto=True) - anode = Node('foo', 'bar') + anode = Node("foo", "bar") nodes = { - '!9388f81c': { - 'num': 2475227164, - 'user': { - 'id': '!9388f81c', - 'longName': 'Unknown f81c', - 'shortName': '?1C', - 'macaddr': 'RBeTiPgc', - 'hwModel': 'TBEAM' + "!9388f81c": { + "num": 2475227164, + "user": { + "id": "!9388f81c", + "longName": "Unknown f81c", + "shortName": "?1C", + "macaddr": "RBeTiPgc", + "hwModel": "TBEAM", }, - 'position': {}, - 'lastHeard': 1640204888 + "position": {}, + "lastHeard": 1640204888, } } - iface.nodesByNum = {1: anode } + iface.nodesByNum = {1: anode} iface.nodes = nodes myInfo = MagicMock() @@ -46,15 +47,15 @@ def test_MeshInterface(capsys): iface.showInfo() iface.localNode.showInfo() iface.showNodes() - iface.sendText('hello') + iface.sendText("hello") iface.close() out, err = capsys.readouterr() - assert re.search(r'Owner: None \(None\)', out, re.MULTILINE) - assert re.search(r'Nodes', out, re.MULTILINE) - assert re.search(r'Preferences', out, re.MULTILINE) - assert re.search(r'Channels', out, re.MULTILINE) - assert re.search(r'Primary channel URL', out, re.MULTILINE) - assert err == '' + assert re.search(r"Owner: None \(None\)", out, re.MULTILINE) + assert re.search(r"Nodes", out, re.MULTILINE) + assert re.search(r"Preferences", out, re.MULTILINE) + assert re.search(r"Channels", out, re.MULTILINE) + assert re.search(r"Primary channel URL", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -65,7 +66,7 @@ def test_getMyUser(iface_with_nodes): iface.myInfo.my_node_num = 2475227164 myuser = iface.getMyUser() assert myuser is not None - assert myuser["id"] == '!9388f81c' + assert myuser["id"] == "!9388f81c" @pytest.mark.unit @@ -75,7 +76,7 @@ def test_getLongName(iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 mylongname = iface.getLongName() - assert mylongname == 'Unknown f81c' + assert mylongname == "Unknown f81c" @pytest.mark.unit @@ -85,7 +86,7 @@ def test_getShortName(iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 myshortname = iface.getShortName() - assert myshortname == '?1C' + assert myshortname == "?1C" @pytest.mark.unit @@ -96,24 +97,24 @@ def test_handlePacketFromRadio_no_from(capsys): meshPacket = mesh_pb2.MeshPacket() iface._handlePacketFromRadio(meshPacket) out, err = capsys.readouterr() - assert re.search(r'Device returned a packet we sent, ignoring', out, re.MULTILINE) - assert err == '' + assert re.search(r"Device returned a packet we sent, ignoring", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @pytest.mark.usefixtures("reset_globals") def test_handlePacketFromRadio_with_a_portnum(caplog): """Test _handlePacketFromRadio with a portnum - Since we have an attribute called 'from', we cannot simply 'set' it. - Had to implement a hack just to be able to test some code. + Since we have an attribute called 'from', we cannot simply 'set' it. + Had to implement a hack just to be able to test some code. """ iface = MeshInterface(noProto=True) meshPacket = mesh_pb2.MeshPacket() - meshPacket.decoded.payload = b'' + meshPacket.decoded.payload = b"" meshPacket.decoded.portnum = 1 with caplog.at_level(logging.WARNING): iface._handlePacketFromRadio(meshPacket, hack=True) - assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE) + assert re.search(r"Not populating fromId", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -122,10 +123,10 @@ def test_handlePacketFromRadio_no_portnum(caplog): """Test _handlePacketFromRadio without a portnum""" iface = MeshInterface(noProto=True) meshPacket = mesh_pb2.MeshPacket() - meshPacket.decoded.payload = b'' + meshPacket.decoded.payload = b"" with caplog.at_level(logging.WARNING): iface._handlePacketFromRadio(meshPacket, hack=True) - assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE) + assert re.search(r"Not populating fromId", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -144,10 +145,10 @@ def test_getNode_not_local(caplog): iface = MeshInterface(noProto=True) anode = MagicMock(autospec=Node) with caplog.at_level(logging.DEBUG): - with patch('meshtastic.node.Node', return_value=anode): - another_node = iface.getNode('bar2') + with patch("meshtastic.node.Node", return_value=anode): + another_node = iface.getNode("bar2") assert another_node != iface.localNode - assert re.search(r'About to requestConfig', caplog.text, re.MULTILINE) + assert re.search(r"About to requestConfig", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -157,14 +158,14 @@ def test_getNode_not_local_timeout(capsys): iface = MeshInterface(noProto=True) anode = MagicMock(autospec=Node) anode.waitForConfig.return_value = False - with patch('meshtastic.node.Node', return_value=anode): + with patch("meshtastic.node.Node", return_value=anode): with pytest.raises(SystemExit) as pytest_wrapped_e: - iface.getNode('bar2') + iface.getNode("bar2") assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.match(r'Error: Timed out waiting for node config', out) - assert err == '' + assert re.match(r"Error: Timed out waiting for node config", out) + assert err == "" @pytest.mark.unit @@ -175,13 +176,13 @@ def test_sendPosition(caplog): with caplog.at_level(logging.DEBUG): iface.sendPosition() iface.close() - assert re.search(r'p.time:', caplog.text, re.MULTILINE) + assert re.search(r"p.time:", caplog.text, re.MULTILINE) # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_close_with_heartbeatTimer(caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_close_with_heartbeatTimer(caplog): # """Test close() with heartbeatTimer""" # iface = MeshInterface(noProto=True) # anode = Node('foo', 'bar') @@ -197,9 +198,9 @@ def test_sendPosition(caplog): # TODO -#@pytest.mark.unit -#@pytest.mark.usefixtures("reset_globals") -#def test_handleFromRadio_empty_payload(caplog): +# @pytest.mark.unit +# @pytest.mark.usefixtures("reset_globals") +# def test_handleFromRadio_empty_payload(caplog): # """Test _handleFromRadio""" # iface = MeshInterface(noProto=True) # with caplog.at_level(logging.DEBUG): @@ -224,13 +225,13 @@ def test_handleFromRadio_with_my_info(caplog): # max_channels: 8 # has_wifi: true # } - from_radio_bytes = b'\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01' + from_radio_bytes = b"\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01" iface = MeshInterface(noProto=True) with caplog.at_level(logging.DEBUG): iface._handleFromRadio(from_radio_bytes) iface.close() - assert re.search(r'Received myinfo', caplog.text, re.MULTILINE) - assert re.search(r'max_channels: 8', caplog.text, re.MULTILINE) + assert re.search(r"Received myinfo", caplog.text, re.MULTILINE) + assert re.search(r"max_channels: 8", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -257,16 +258,16 @@ def test_handleFromRadio_with_node_info(caplog, capsys): with caplog.at_level(logging.DEBUG): iface._startConfig() iface._handleFromRadio(from_radio_bytes) - assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE) - assert re.search(r'682584012', caplog.text, re.MULTILINE) - assert re.search(r'HELTEC_V2_1', caplog.text, re.MULTILINE) + assert re.search(r"Received nodeinfo", caplog.text, re.MULTILINE) + assert re.search(r"682584012", caplog.text, re.MULTILINE) + assert re.search(r"HELTEC_V2_1", caplog.text, re.MULTILINE) # validate some of showNodes() output iface.showNodes() out, err = capsys.readouterr() - assert re.search(r' 1 ', out, re.MULTILINE) - assert re.search(r'│ Unknown 67cc │ ', out, re.MULTILINE) - assert re.search(r'│ !28af67cc │ N/A │ N/A │ N/A', out, re.MULTILINE) - assert err == '' + assert re.search(r" 1 ", out, re.MULTILINE) + assert re.search(r"│ Unknown 67cc │ ", out, re.MULTILINE) + assert re.search(r"│ !28af67cc │ N/A │ N/A │ N/A", out, re.MULTILINE) + assert err == "" iface.close() @@ -281,16 +282,16 @@ def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys): with caplog.at_level(logging.DEBUG): iface._startConfig() iface._handleFromRadio(from_radio_bytes) - assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE) - assert re.search(r'TBeam 1', caplog.text, re.MULTILINE) - assert re.search(r'2127707136', caplog.text, re.MULTILINE) + assert re.search(r"Received nodeinfo", caplog.text, re.MULTILINE) + assert re.search(r"TBeam 1", caplog.text, re.MULTILINE) + assert re.search(r"2127707136", caplog.text, re.MULTILINE) # validate some of showNodes() output iface.showNodes() out, err = capsys.readouterr() - assert re.search(r' 1 ', out, re.MULTILINE) - assert re.search(r'│ TBeam 1 │ ', out, re.MULTILINE) - assert re.search(r'│ !7ed23c00 │', out, re.MULTILINE) - assert err == '' + assert re.search(r" 1 ", out, re.MULTILINE) + assert re.search(r"│ TBeam 1 │ ", out, re.MULTILINE) + assert re.search(r"│ !7ed23c00 │", out, re.MULTILINE) + assert err == "" iface.close() @@ -312,8 +313,8 @@ def test_MeshInterface_sendToRadioImpl(caplog): """Test _sendToRadioImp()""" iface = MeshInterface(noProto=True) with caplog.at_level(logging.DEBUG): - iface._sendToRadioImpl('foo') - assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE) + iface._sendToRadioImpl("foo") + assert re.search(r"Subclass must provide toradio", caplog.text, re.MULTILINE) iface.close() @@ -323,8 +324,8 @@ def test_MeshInterface_sendToRadio_no_proto(caplog): """Test sendToRadio()""" iface = MeshInterface() with caplog.at_level(logging.DEBUG): - iface._sendToRadioImpl('foo') - assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE) + iface._sendToRadioImpl("foo") + assert re.search(r"Subclass must provide toradio", caplog.text, re.MULTILINE) iface.close() @@ -333,22 +334,22 @@ def test_MeshInterface_sendToRadio_no_proto(caplog): def test_sendData_too_long(caplog): """Test when data payload is too big""" iface = MeshInterface(noProto=True) - some_large_text = b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' - some_large_text += b'This is a long text that will be too long for send text.' + some_large_text = b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." + some_large_text += b"This is a long text that will be too long for send text." with caplog.at_level(logging.DEBUG): with pytest.raises(Exception) as pytest_wrapped_e: iface.sendData(some_large_text) - assert re.search('Data payload too big', caplog.text, re.MULTILINE) + assert re.search("Data payload too big", caplog.text, re.MULTILINE) assert pytest_wrapped_e.type == Exception iface.close() @@ -359,10 +360,10 @@ def test_sendData_unknown_app(capsys): """Test sendData when unknown app""" iface = MeshInterface(noProto=True) with pytest.raises(SystemExit) as pytest_wrapped_e: - iface.sendData(b'hello', portNum=0) + iface.sendData(b"hello", portNum=0) out, err = capsys.readouterr() - assert re.search(r'Warning: A non-zero port number', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: A non-zero port number", out, re.MULTILINE) + assert err == "" assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 @@ -374,9 +375,9 @@ def test_sendPosition_with_a_position(caplog): iface = MeshInterface(noProto=True) with caplog.at_level(logging.DEBUG): iface.sendPosition(latitude=40.8, longitude=-111.86, altitude=201) - assert re.search(r'p.latitude_i:408', caplog.text, re.MULTILINE) - assert re.search(r'p.longitude_i:-11186', caplog.text, re.MULTILINE) - assert re.search(r'p.altitude:201', caplog.text, re.MULTILINE) + assert re.search(r"p.latitude_i:408", caplog.text, re.MULTILINE) + assert re.search(r"p.longitude_i:-11186", caplog.text, re.MULTILINE) + assert re.search(r"p.altitude:201", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -385,10 +386,10 @@ def test_sendPacket_with_no_destination(capsys): """Test _sendPacket()""" iface = MeshInterface(noProto=True) with pytest.raises(SystemExit) as pytest_wrapped_e: - iface._sendPacket(b'', destinationId=None) + iface._sendPacket(b"", destinationId=None) out, err = capsys.readouterr() - assert re.search(r'Warning: destinationId must not be None', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: destinationId must not be None", out, re.MULTILINE) + assert err == "" assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 @@ -401,7 +402,7 @@ def test_sendPacket_with_destination_as_int(caplog): with caplog.at_level(logging.DEBUG): meshPacket = mesh_pb2.MeshPacket() iface._sendPacket(meshPacket, destinationId=123) - assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) + assert re.search(r"Not sending packet", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -411,8 +412,8 @@ def test_sendPacket_with_destination_starting_with_a_bang(caplog): iface = MeshInterface(noProto=True) with caplog.at_level(logging.DEBUG): meshPacket = mesh_pb2.MeshPacket() - iface._sendPacket(meshPacket, destinationId='!1234') - assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) + iface._sendPacket(meshPacket, destinationId="!1234") + assert re.search(r"Not sending packet", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -423,7 +424,7 @@ def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog): with caplog.at_level(logging.DEBUG): meshPacket = mesh_pb2.MeshPacket() iface._sendPacket(meshPacket, destinationId=BROADCAST_ADDR) - assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) + assert re.search(r"Not sending packet", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -435,8 +436,8 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys): meshPacket = mesh_pb2.MeshPacket() iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR) out, err = capsys.readouterr() - assert re.search(r'Warning: No myInfo', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: No myInfo", out, re.MULTILINE) + assert err == "" assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 @@ -452,7 +453,7 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog): with caplog.at_level(logging.DEBUG): meshPacket = mesh_pb2.MeshPacket() iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR) - assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) + assert re.search(r"Not sending packet", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -462,12 +463,12 @@ def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_node iface = iface_with_nodes meshPacket = mesh_pb2.MeshPacket() with pytest.raises(SystemExit) as pytest_wrapped_e: - iface._sendPacket(meshPacket, destinationId='') + iface._sendPacket(meshPacket, destinationId="") assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.match(r'Warning: NodeId not found in DB', out, re.MULTILINE) - assert err == '' + assert re.match(r"Warning: NodeId not found in DB", out, re.MULTILINE) + assert err == "" @pytest.mark.unit @@ -478,8 +479,8 @@ def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_n iface.nodes = None meshPacket = mesh_pb2.MeshPacket() with caplog.at_level(logging.WARNING): - iface._sendPacket(meshPacket, destinationId='') - assert re.search(r'Warning: There were no self.nodes.', caplog.text, re.MULTILINE) + iface._sendPacket(meshPacket, destinationId="") + assert re.search(r"Warning: There were no self.nodes.", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -488,7 +489,7 @@ def test_getMyNodeInfo(): """Test getMyNodeInfo()""" iface = MeshInterface(noProto=True) anode = iface.getNode(LOCAL_ADDR) - iface.nodesByNum = {1: anode } + iface.nodesByNum = {1: anode} assert iface.nodesByNum.get(1) == anode myInfo = MagicMock() iface.myInfo = myInfo @@ -508,8 +509,10 @@ def test_generatePacketId(capsys): with pytest.raises(Exception) as pytest_wrapped_e: iface._generatePacketId() out, err = capsys.readouterr() - assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE) - assert err == '' + assert re.search( + r"Not connected yet, can not generate packet", out, re.MULTILINE + ) + assert err == "" assert pytest_wrapped_e.type == Exception @@ -540,10 +543,12 @@ def test_fixupPosition(): iface = MeshInterface(noProto=True) pos = {"latitudeI": 1010000000, "longitudeI": 1020000000} newpos = iface._fixupPosition(pos) - assert newpos == {"latitude": 101.0, - "latitudeI": 1010000000, - "longitude": 102.0, - "longitudeI": 1020000000} + assert newpos == { + "latitude": 101.0, + "latitudeI": 1010000000, + "longitude": 102.0, + "longitudeI": 1020000000, + } @pytest.mark.unit @@ -553,7 +558,7 @@ def test_nodeNumToId(iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 someid = iface._nodeNumToId(2475227164) - assert someid == '!9388f81c' + assert someid == "!9388f81c" @pytest.mark.unit @@ -572,8 +577,8 @@ def test_nodeNumToId_to_all(iface_with_nodes): """Test _nodeNumToId()""" iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 - someid = iface._nodeNumToId(0xffffffff) - assert someid == '^all' + someid = iface._nodeNumToId(0xFFFFFFFF) + assert someid == "^all" @pytest.mark.unit @@ -583,7 +588,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 tmp = iface._getOrCreateByNum(123) - assert tmp == {'num': 123} + assert tmp == {"num": 123} @pytest.mark.unit @@ -593,7 +598,7 @@ def test_getOrCreateByNum_not_found(iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 with pytest.raises(Exception) as pytest_wrapped_e: - iface._getOrCreateByNum(0xffffffff) + iface._getOrCreateByNum(0xFFFFFFFF) assert pytest_wrapped_e.type == Exception @@ -604,12 +609,12 @@ def test_getOrCreateByNum(iface_with_nodes): iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 tmp = iface._getOrCreateByNum(2475227164) - assert tmp['num'] == 2475227164 + assert tmp["num"] == 2475227164 # TODO -#@pytest.mark.unit -#def test_enter(): +# @pytest.mark.unit +# def test_enter(): # """Test __enter__()""" # iface = MeshInterface(noProto=True) # assert iface == iface.__enter__() @@ -620,9 +625,13 @@ def test_exit_with_exception(caplog): """Test __exit__()""" iface = MeshInterface(noProto=True) with caplog.at_level(logging.ERROR): - iface.__exit__('foo', 'bar', 'baz') - assert re.search(r'An exception of type foo with value bar has occurred', caplog.text, re.MULTILINE) - assert re.search(r'Traceback: baz', caplog.text, re.MULTILINE) + iface.__exit__("foo", "bar", "baz") + assert re.search( + r"An exception of type foo with value bar has occurred", + caplog.text, + re.MULTILINE, + ) + assert re.search(r"Traceback: baz", caplog.text, re.MULTILINE) @pytest.mark.unit @@ -646,8 +655,10 @@ def test_waitForConfig(capsys): iface.waitForConfig() assert pytest_wrapped_e.type == Exception out, err = capsys.readouterr() - assert re.search(r'Exception: Timed out waiting for interface config', err, re.MULTILINE) - assert out == '' + assert re.search( + r"Exception: Timed out waiting for interface config", err, re.MULTILINE + ) + assert out == "" @pytest.mark.unit @@ -659,8 +670,8 @@ def test_waitConnected_raises_an_exception(capsys): iface._waitConnected(0.01) assert pytest_wrapped_e.type == Exception out, err = capsys.readouterr() - assert re.search(r'warn about something', err, re.MULTILINE) - assert out == '' + assert re.search(r"warn about something", err, re.MULTILINE) + assert out == "" @pytest.mark.unit @@ -671,5 +682,5 @@ def test_waitConnected_isConnected_timeout(capsys): iface._waitConnected(0.01) assert pytest_wrapped_e.type == Exception out, err = capsys.readouterr() - assert re.search(r'warn about something', err, re.MULTILINE) - assert out == '' + assert re.search(r"warn about something", err, re.MULTILINE) + assert out == "" diff --git a/meshtastic/tests/test_node.py b/meshtastic/tests/test_node.py index b432d86..8500ecd 100644 --- a/meshtastic/tests/test_node.py +++ b/meshtastic/tests/test_node.py @@ -1,25 +1,26 @@ """Meshtastic unit tests for node.py""" -import re import logging +import re +from unittest.mock import MagicMock, patch -from unittest.mock import patch, MagicMock import pytest +# from ..admin_pb2 import AdminMessage +from ..channel_pb2 import Channel from ..node import Node from ..serial_interface import SerialInterface -#from ..admin_pb2 import AdminMessage -from ..channel_pb2 import Channel -#from ..config_pb2 import Config -#from ..cannedmessages_pb2 import (CannedMessagePluginMessagePart1, CannedMessagePluginMessagePart2, + +# from ..config_pb2 import Config +# from ..cannedmessages_pb2 import (CannedMessagePluginMessagePart1, CannedMessagePluginMessagePart2, # CannedMessagePluginMessagePart3, CannedMessagePluginMessagePart4, # CannedMessagePluginMessagePart5) -#from ..util import Timeout +# from ..util import Timeout # TODO -#@pytest.mark.unit -#def test_node(capsys): +# @pytest.mark.unit +# def test_node(capsys): # """Test that we can instantiate a Node""" # anode = Node('foo', 'bar') # radioConfig = RadioConfig() @@ -34,8 +35,8 @@ from ..channel_pb2 import Channel # TODO -#@pytest.mark.unit -#def test_node_requestConfig(capsys): +# @pytest.mark.unit +# def test_node_requestConfig(capsys): # """Test run requestConfig""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -48,8 +49,8 @@ from ..channel_pb2 import Channel # assert err == '' -#@pytest.mark.unit -#def test_node_get_canned_message_with_all_parts(capsys): +# @pytest.mark.unit +# def test_node_get_canned_message_with_all_parts(capsys): # """Test run get_canned_message()""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -69,8 +70,8 @@ from ..channel_pb2 import Channel # assert err == '' # # -#@pytest.mark.unit -#def test_node_get_canned_message_with_some_parts(capsys): +# @pytest.mark.unit +# def test_node_get_canned_message_with_some_parts(capsys): # """Test run get_canned_message()""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -86,8 +87,8 @@ from ..channel_pb2 import Channel # assert err == '' # # -#@pytest.mark.unit -#def test_node_set_canned_message_one_part(caplog): +# @pytest.mark.unit +# def test_node_set_canned_message_one_part(caplog): # """Test run set_canned_message()""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -100,8 +101,8 @@ from ..channel_pb2 import Channel # assert not re.search(r"Setting canned message '' part 2", caplog.text, re.MULTILINE) # # -#@pytest.mark.unit -#def test_node_set_canned_message_200(caplog): +# @pytest.mark.unit +# def test_node_set_canned_message_200(caplog): # """Test run set_canned_message() 200 characters long""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -115,8 +116,8 @@ from ..channel_pb2 import Channel # assert not re.search(r"Setting canned message '' part 2", caplog.text, re.MULTILINE) # # -#@pytest.mark.unit -#def test_node_set_canned_message_201(caplog): +# @pytest.mark.unit +# def test_node_set_canned_message_201(caplog): # """Test run set_canned_message() 201 characters long""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -130,8 +131,8 @@ from ..channel_pb2 import Channel # assert re.search(r"Setting canned message 'a' part 2", caplog.text, re.MULTILINE) # # -#@pytest.mark.unit -#def test_node_set_canned_message_1000(caplog): +# @pytest.mark.unit +# def test_node_set_canned_message_1000(caplog): # """Test run set_canned_message() 1000 characters long""" # iface = MagicMock(autospec=SerialInterface) # amesg = MagicMock(autospec=AdminMessage) @@ -148,8 +149,8 @@ from ..channel_pb2 import Channel # assert re.search(r" part 5", caplog.text, re.MULTILINE) # # -#@pytest.mark.unit -#def test_node_set_canned_message_1001(capsys): +# @pytest.mark.unit +# def test_node_set_canned_message_1001(capsys): # """Test run set_canned_message() 1001 characters long""" # iface = MagicMock(autospec=SerialInterface) # with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -165,8 +166,8 @@ from ..channel_pb2 import Channel # TODO -#@pytest.mark.unit -#def test_setOwnerShort(caplog): +# @pytest.mark.unit +# def test_setOwnerShort(caplog): # """Test setOwner""" # anode = Node('foo', 'bar', noProto=True) # with caplog.at_level(logging.DEBUG): @@ -175,8 +176,8 @@ from ..channel_pb2 import Channel # TODO -#@pytest.mark.unit -#def test_setOwner_no_short_name(caplog): +# @pytest.mark.unit +# def test_setOwner_no_short_name(caplog): # """Test setOwner""" # anode = Node('foo', 'bar', noProto=True) # with caplog.at_level(logging.DEBUG): @@ -187,8 +188,8 @@ from ..channel_pb2 import Channel # TODO -#@pytest.mark.unit -#def test_setOwner_no_short_name_and_long_name_is_short(caplog): +# @pytest.mark.unit +# def test_setOwner_no_short_name_and_long_name_is_short(caplog): # """Test setOwner""" # anode = Node('foo', 'bar', noProto=True) # with caplog.at_level(logging.DEBUG): @@ -199,8 +200,8 @@ from ..channel_pb2 import Channel # TODO -#@pytest.mark.unit -#def test_setOwner_no_short_name_and_long_name_has_words(caplog): +# @pytest.mark.unit +# def test_setOwner_no_short_name_and_long_name_has_words(caplog): # """Test setOwner""" # anode = Node('foo', 'bar', noProto=True) # with caplog.at_level(logging.DEBUG): @@ -211,8 +212,8 @@ from ..channel_pb2 import Channel # TODO -#@pytest.mark.unit -#def test_setOwner_long_name_no_short(caplog): +# @pytest.mark.unit +# def test_setOwner_long_name_no_short(caplog): # """Test setOwner""" # anode = Node('foo', 'bar', noProto=True) # with caplog.at_level(logging.DEBUG): @@ -224,46 +225,46 @@ from ..channel_pb2 import Channel @pytest.mark.unit def test_exitSimulator(caplog): """Test exitSimulator""" - anode = Node('foo', 'bar', noProto=True) + anode = Node("foo", "bar", noProto=True) with caplog.at_level(logging.DEBUG): anode.exitSimulator() - assert re.search(r'in exitSimulator', caplog.text, re.MULTILINE) + assert re.search(r"in exitSimulator", caplog.text, re.MULTILINE) @pytest.mark.unit def test_reboot(caplog): """Test reboot""" - anode = Node('foo', 'bar', noProto=True) + anode = Node("foo", "bar", noProto=True) with caplog.at_level(logging.DEBUG): anode.reboot() - assert re.search(r'Telling node to reboot', caplog.text, re.MULTILINE) + assert re.search(r"Telling node to reboot", caplog.text, re.MULTILINE) @pytest.mark.unit def test_shutdown(caplog): """Test shutdown""" - anode = Node('foo', 'bar', noProto=True) + anode = Node("foo", "bar", noProto=True) with caplog.at_level(logging.DEBUG): anode.shutdown() - assert re.search(r'Telling node to shutdown', caplog.text, re.MULTILINE) + assert re.search(r"Telling node to shutdown", caplog.text, re.MULTILINE) @pytest.mark.unit def test_setURL_empty_url(capsys): """Test reboot""" - anode = Node('foo', 'bar', noProto=True) + anode = Node("foo", "bar", noProto=True) with pytest.raises(SystemExit) as pytest_wrapped_e: - anode.setURL('') + anode.setURL("") assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No RadioConfig has been read', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: No RadioConfig has been read", out, re.MULTILINE) + assert err == "" # TODO -#@pytest.mark.unit -#def test_setURL_valid_URL(caplog): +# @pytest.mark.unit +# def test_setURL_valid_URL(caplog): # """Test setURL""" # iface = MagicMock(autospec=SerialInterface) # url = "https://www.meshtastic.org/d/#CgUYAyIBAQ" @@ -285,19 +286,19 @@ def test_setURL_valid_URL_but_no_settings(capsys): iface = MagicMock(autospec=SerialInterface) url = "https://www.meshtastic.org/d/#" with pytest.raises(SystemExit) as pytest_wrapped_e: - anode = Node(iface, 'bar', noProto=True) - anode.radioConfig = 'baz' + anode = Node(iface, "bar", noProto=True) + anode.radioConfig = "baz" anode.setURL(url) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: There were no settings', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: There were no settings", out, re.MULTILINE) + assert err == "" # TODO -#@pytest.mark.unit -#def test_showChannels(capsys): +# @pytest.mark.unit +# def test_showChannels(capsys): # """Test showChannels""" # anode = Node('foo', 'bar') # @@ -340,10 +341,10 @@ def test_setURL_valid_URL_but_no_settings(capsys): @pytest.mark.unit def test_getChannelByChannelIndex(): """Test getChannelByChannelIndex()""" - anode = Node('foo', 'bar') + anode = Node("foo", "bar") - channel1 = Channel(index=1, role=1) # primary channel - channel2 = Channel(index=2, role=2) # secondary channel + channel1 = Channel(index=1, role=1) # primary channel + channel2 = Channel(index=2, role=2) # secondary channel channel3 = Channel(index=3, role=0) channel4 = Channel(index=4, role=0) channel5 = Channel(index=5, role=0) @@ -351,7 +352,16 @@ def test_getChannelByChannelIndex(): channel7 = Channel(index=7, role=0) channel8 = Channel(index=8, role=0) - channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ] + channels = [ + channel1, + channel2, + channel3, + channel4, + channel5, + channel6, + channel7, + channel8, + ] anode.channels = channels @@ -367,8 +377,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_deleteChannel_try_to_delete_primary_channel(capsys): +# @pytest.mark.unit +# def test_deleteChannel_try_to_delete_primary_channel(capsys): # """Try to delete primary channel.""" # anode = Node('foo', 'bar') # @@ -398,8 +408,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_deleteChannel_secondary(): +# @pytest.mark.unit +# def test_deleteChannel_secondary(): # """Try to delete a secondary channel.""" # # channel1 = Channel(index=1, role=1) @@ -451,8 +461,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_deleteChannel_secondary_with_admin_channel_after_testing(): +# @pytest.mark.unit +# def test_deleteChannel_secondary_with_admin_channel_after_testing(): # """Try to delete a secondary channel where there is an admin channel.""" # # channel1 = Channel(index=1, role=1) @@ -511,8 +521,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_deleteChannel_secondary_with_admin_channel_before_testing(): +# @pytest.mark.unit +# def test_deleteChannel_secondary_with_admin_channel_before_testing(): # """Try to delete a secondary channel where there is an admin channel.""" # # channel1 = Channel(index=1, role=1) @@ -565,8 +575,8 @@ def test_getChannelByChannelIndex(): # assert channels[7].settings.name == '' # # -#@pytest.mark.unit -#def test_getChannelByName(): +# @pytest.mark.unit +# def test_getChannelByName(): # """Get a channel by the name.""" # anode = Node('foo', 'bar') # @@ -593,8 +603,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_getChannelByName_invalid_name(): +# @pytest.mark.unit +# def test_getChannelByName_invalid_name(): # """Get a channel by the name but one that is not present.""" # anode = Node('foo', 'bar') # @@ -620,8 +630,8 @@ def test_getChannelByChannelIndex(): # assert ch is None # # -#@pytest.mark.unit -#def test_getDisabledChannel(): +# @pytest.mark.unit +# def test_getDisabledChannel(): # """Get the first disabled channel.""" # anode = Node('foo', 'bar') # @@ -651,8 +661,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_getDisabledChannel_where_all_channels_are_used(): +# @pytest.mark.unit +# def test_getDisabledChannel_where_all_channels_are_used(): # """Get the first disabled channel.""" # anode = Node('foo', 'bar') # @@ -676,8 +686,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_getAdminChannelIndex(): +# @pytest.mark.unit +# def test_getAdminChannelIndex(): # """Get the 'admin' channel index.""" # anode = Node('foo', 'bar') # @@ -704,8 +714,8 @@ def test_getChannelByChannelIndex(): # TODO -#@pytest.mark.unit -#def test_getAdminChannelIndex_when_no_admin_named_channel(): +# @pytest.mark.unit +# def test_getAdminChannelIndex_when_no_admin_named_channel(): # """Get the 'admin' channel when there is not one.""" # anode = Node('foo', 'bar') # @@ -730,8 +740,8 @@ def test_getChannelByChannelIndex(): # TODO # TODO: should we check if we need to turn it off? -#@pytest.mark.unit -#def test_turnOffEncryptionOnPrimaryChannel(capsys): +# @pytest.mark.unit +# def test_turnOffEncryptionOnPrimaryChannel(capsys): # """Turn off encryption when there is a psk.""" # anode = Node('foo', 'bar', noProto=True) # @@ -760,20 +770,20 @@ def test_getChannelByChannelIndex(): @pytest.mark.unit def test_writeConfig_with_no_radioConfig(capsys): """Test writeConfig with no radioConfig.""" - anode = Node('foo', 'bar', noProto=True) + anode = Node("foo", "bar", noProto=True) with pytest.raises(SystemExit) as pytest_wrapped_e: - anode.writeConfig() + anode.writeConfig('foo') assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Error: No RadioConfig has been read', out) - assert err == '' + assert re.search(r"Error: No RadioConfig has been read", out) + assert err == "" # TODO -#@pytest.mark.unit -#def test_writeConfig(caplog): +# @pytest.mark.unit +# def test_writeConfig(caplog): # """Test writeConfig""" # anode = Node('foo', 'bar', noProto=True) # radioConfig = RadioConfig() @@ -788,38 +798,40 @@ def test_writeConfig_with_no_radioConfig(capsys): def test_requestChannel_not_localNode(caplog, capsys): """Test _requestChannel()""" iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: mo.localNode.getChannelByName.return_value = None mo.myInfo.max_channels = 8 - anode = Node(mo, 'bar', noProto=True) + anode = Node(mo, "bar", noProto=True) with caplog.at_level(logging.DEBUG): anode._requestChannel(0) - assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE) + assert re.search( + r"Requesting channel 0 info from remote node", caplog.text, re.MULTILINE + ) out, err = capsys.readouterr() - assert re.search(r'Requesting channel 0 info', out, re.MULTILINE) - assert err == '' + assert re.search(r"Requesting channel 0 info", out, re.MULTILINE) + assert err == "" @pytest.mark.unit def test_requestChannel_localNode(caplog): """Test _requestChannel()""" iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: mo.localNode.getChannelByName.return_value = None mo.myInfo.max_channels = 8 - anode = Node(mo, 'bar', noProto=True) + anode = Node(mo, "bar", noProto=True) # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock mo.localNode = anode with caplog.at_level(logging.DEBUG): anode._requestChannel(0) - assert re.search(r'Requesting channel 0', caplog.text, re.MULTILINE) - assert not re.search(r'from remote node', caplog.text, re.MULTILINE) + assert re.search(r"Requesting channel 0", caplog.text, re.MULTILINE) + assert not re.search(r"from remote node", caplog.text, re.MULTILINE) -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart1(caplog): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart1(caplog): # """Test onResponseRequestCannedMessagePluginMessagePart1()""" # # part1 = CannedMessagePluginMessagePart1() @@ -861,8 +873,8 @@ def test_requestChannel_localNode(caplog): # assert anode.cannedPluginMessagePart1 == 'foo1' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart2(caplog): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart2(caplog): # """Test onResponseRequestCannedMessagePluginMessagePart2()""" # # part2 = CannedMessagePluginMessagePart2() @@ -904,8 +916,8 @@ def test_requestChannel_localNode(caplog): # assert anode.cannedPluginMessagePart2 == 'foo2' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart3(caplog): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart3(caplog): # """Test onResponseRequestCannedMessagePluginMessagePart3()""" # # part3 = CannedMessagePluginMessagePart3() @@ -947,8 +959,8 @@ def test_requestChannel_localNode(caplog): # assert anode.cannedPluginMessagePart3 == 'foo3' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart4(caplog): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart4(caplog): # """Test onResponseRequestCannedMessagePluginMessagePart4()""" # # part4 = CannedMessagePluginMessagePart4() @@ -990,8 +1002,8 @@ def test_requestChannel_localNode(caplog): # assert anode.cannedPluginMessagePart4 == 'foo4' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart5(caplog): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart5(caplog): # """Test onResponseRequestCannedMessagePluginMessagePart5()""" # # part5 = CannedMessagePluginMessagePart5() @@ -1034,8 +1046,8 @@ def test_requestChannel_localNode(caplog): # assert anode.cannedPluginMessagePart5 == 'foo5' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart1_error(caplog, capsys): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart1_error(caplog, capsys): # """Test onResponseRequestCannedMessagePluginMessagePart1() with error""" # # packet = { @@ -1060,8 +1072,8 @@ def test_requestChannel_localNode(caplog): # assert err == '' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart2_error(caplog, capsys): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart2_error(caplog, capsys): # """Test onResponseRequestCannedMessagePluginMessagePart2() with error""" # # packet = { @@ -1086,8 +1098,8 @@ def test_requestChannel_localNode(caplog): # assert err == '' -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart3_error(caplog, capsys): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart3_error(caplog, capsys): # """Test onResponseRequestCannedMessagePluginMessagePart3() with error""" # # packet = { @@ -1112,8 +1124,8 @@ def test_requestChannel_localNode(caplog): # assert err == '' # # -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart4_error(caplog, capsys): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart4_error(caplog, capsys): # """Test onResponseRequestCannedMessagePluginMessagePart4() with error""" # # packet = { @@ -1138,8 +1150,8 @@ def test_requestChannel_localNode(caplog): # assert err == '' # # -#@pytest.mark.unit -#def test_onResponseRequestCannedMessagePluginMesagePart5_error(caplog, capsys): +# @pytest.mark.unit +# def test_onResponseRequestCannedMessagePluginMesagePart5_error(caplog, capsys): # """Test onResponseRequestCannedMessagePluginMessagePart5() with error""" # # packet = { @@ -1165,8 +1177,8 @@ def test_requestChannel_localNode(caplog): # TODO -#@pytest.mark.unit -#def test_onResponseRequestChannel(caplog): +# @pytest.mark.unit +# def test_onResponseRequestChannel(caplog): # """Test onResponseRequestChannel()""" # # channel1 = Channel(index=1, role=1) @@ -1264,8 +1276,8 @@ def test_requestChannel_localNode(caplog): # TODO -#@pytest.mark.unit -#def test_onResponseRequestSetting(caplog): +# @pytest.mark.unit +# def test_onResponseRequestSetting(caplog): # """Test onResponseRequestSetting()""" # # Note: Split out the get_radio_response to a MagicMock # # so it could be "returned" (not really sure how to do that @@ -1278,7 +1290,7 @@ def test_requestChannel_localNode(caplog): # position_broadcast_smart: true # position_flags: 35 # } -#}""" +# }""" # packet = { # 'from': 2475227164, # 'to': 2475227164, @@ -1324,8 +1336,8 @@ def test_requestChannel_localNode(caplog): # TODO -#@pytest.mark.unit -#def test_onResponseRequestSetting_with_error(capsys): +# @pytest.mark.unit +# def test_onResponseRequestSetting_with_error(capsys): # """Test onResponseRequestSetting() with an error""" # packet = { # 'from': 2475227164, @@ -1374,8 +1386,8 @@ def test_requestChannel_localNode(caplog): # TODO -#@pytest.mark.unitslow -#def test_waitForConfig(): +# @pytest.mark.unitslow +# def test_waitForConfig(): # """Test waitForConfig()""" # anode = Node('foo', 'bar') # radioConfig = RadioConfig() diff --git a/meshtastic/tests/test_remote_hardware.py b/meshtastic/tests/test_remote_hardware.py index 1521037..7d4cf6f 100644 --- a/meshtastic/tests/test_remote_hardware.py +++ b/meshtastic/tests/test_remote_hardware.py @@ -2,8 +2,8 @@ import logging import re +from unittest.mock import MagicMock, patch -from unittest.mock import patch, MagicMock import pytest from ..remote_hardware import RemoteHardwareClient, onGPIOreceive @@ -23,25 +23,25 @@ def test_RemoteHardwareClient(): def test_onGPIOreceive(capsys): """Test onGPIOreceive""" iface = MagicMock(autospec=SerialInterface) - packet = {'decoded': {'remotehw': {'type': 'foo', 'gpioValue': '4096' }}} + packet = {"decoded": {"remotehw": {"type": "foo", "gpioValue": "4096"}}} onGPIOreceive(packet, iface) out, err = capsys.readouterr() - assert re.search(r'Received RemoteHardware', out) - assert err == '' + assert re.search(r"Received RemoteHardware", out) + assert err == "" @pytest.mark.unit def test_RemoteHardwareClient_no_gpio_channel(capsys): """Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'""" iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: mo.localNode.getChannelByName.return_value = None with pytest.raises(SystemExit) as pytest_wrapped_e: RemoteHardwareClient(mo) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No channel named', out) + assert re.search(r"Warning: No channel named", out) assert err == "" @@ -51,8 +51,8 @@ def test_readGPIOs(caplog): iface = MagicMock(autospec=SerialInterface) rhw = RemoteHardwareClient(iface) with caplog.at_level(logging.DEBUG): - rhw.readGPIOs('0x10', 123) - assert re.search(r'readGPIOs', caplog.text, re.MULTILINE) + rhw.readGPIOs("0x10", 123) + assert re.search(r"readGPIOs", caplog.text, re.MULTILINE) iface.close() @@ -62,8 +62,8 @@ def test_writeGPIOs(caplog): iface = MagicMock(autospec=SerialInterface) rhw = RemoteHardwareClient(iface) with caplog.at_level(logging.DEBUG): - rhw.writeGPIOs('0x10', 123, 1) - assert re.search(r'writeGPIOs', caplog.text, re.MULTILINE) + rhw.writeGPIOs("0x10", 123, 1) + assert re.search(r"writeGPIOs", caplog.text, re.MULTILINE) iface.close() @@ -73,8 +73,8 @@ def test_watchGPIOs(caplog): iface = MagicMock(autospec=SerialInterface) rhw = RemoteHardwareClient(iface) with caplog.at_level(logging.DEBUG): - rhw.watchGPIOs('0x10', 123) - assert re.search(r'watchGPIOs', caplog.text, re.MULTILINE) + rhw.watchGPIOs("0x10", 123) + assert re.search(r"watchGPIOs", caplog.text, re.MULTILINE) iface.close() @@ -82,11 +82,11 @@ def test_watchGPIOs(caplog): def test_sendHardware_no_nodeid(capsys): """Test sending no nodeid to _sendHardware()""" iface = MagicMock(autospec=SerialInterface) - with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with pytest.raises(SystemExit) as pytest_wrapped_e: rhw = RemoteHardwareClient(mo) rhw._sendHardware(None, None) assert pytest_wrapped_e.type == SystemExit out, err = capsys.readouterr() - assert re.search(r'Warning: Must use a destination node ID', out) - assert err == '' + assert re.search(r"Warning: Must use a destination node ID", out) + assert err == "" diff --git a/meshtastic/tests/test_serial_interface.py b/meshtastic/tests/test_serial_interface.py index ac16cba..bda4738 100644 --- a/meshtastic/tests/test_serial_interface.py +++ b/meshtastic/tests/test_serial_interface.py @@ -1,21 +1,23 @@ """Meshtastic unit tests for serial_interface.py""" import re +from unittest.mock import mock_open, patch - -from unittest.mock import patch, mock_open import pytest from ..serial_interface import SerialInterface + @pytest.mark.unit @patch("time.sleep") @patch("termios.tcsetattr") @patch("termios.tcgetattr") @patch("builtins.open", new_callable=mock_open, read_data="data") -@patch('serial.Serial') -@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake']) -def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys): +@patch("serial.Serial") +@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"]) +def test_SerialInterface_single_port( + mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys +): """Test that we can instantiate a SerialInterface with a single port""" iface = SerialInterface(noProto=True) iface.showInfo() @@ -28,15 +30,15 @@ def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, mocked_ope mock_set.assert_called() mock_sleep.assert_called() out, err = capsys.readouterr() - assert re.search(r'Nodes in mesh', out, re.MULTILINE) - assert re.search(r'Preferences', out, re.MULTILINE) - assert re.search(r'Channels', out, re.MULTILINE) - assert re.search(r'Primary channel', out, re.MULTILINE) - assert err == '' + assert re.search(r"Nodes in mesh", out, re.MULTILINE) + assert re.search(r"Preferences", out, re.MULTILINE) + assert re.search(r"Channels", out, re.MULTILINE) + assert re.search(r"Primary channel", out, re.MULTILINE) + assert err == "" @pytest.mark.unit -@patch('meshtastic.util.findPorts', return_value=[]) +@patch("meshtastic.util.findPorts", return_value=[]) def test_SerialInterface_no_ports(mocked_findPorts, capsys): """Test that we can instantiate a SerialInterface with no ports""" with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -45,12 +47,14 @@ def test_SerialInterface_no_ports(mocked_findPorts, capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: No Meshtastic devices detected", out, re.MULTILINE) + assert err == "" @pytest.mark.unit -@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake1', '/dev/ttyUSBfake2']) +@patch( + "meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake1", "/dev/ttyUSBfake2"] +) def test_SerialInterface_multiple_ports(mocked_findPorts, capsys): """Test that we can instantiate a SerialInterface with two ports""" with pytest.raises(SystemExit) as pytest_wrapped_e: @@ -59,5 +63,5 @@ def test_SerialInterface_multiple_ports(mocked_findPorts, capsys): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 out, err = capsys.readouterr() - assert re.search(r'Warning: Multiple serial ports were detected', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: Multiple serial ports were detected", out, re.MULTILINE) + assert err == "" diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index fbb6e7e..7ee7c33 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -1,9 +1,9 @@ """Meshtastic smoke tests with a single device via USB""" +import os +import platform import re import subprocess import time -import platform -import os # Do not like using hard coded sleeps, but it probably makes # sense to pause for the radio at apprpriate times @@ -19,7 +19,7 @@ PAUSE_AFTER_REBOOT = 7 @pytest.mark.smoke1 def test_smoke1_reboot(): """Test reboot""" - return_value, _ = subprocess.getstatusoutput('meshtastic --reboot') + return_value, _ = subprocess.getstatusoutput("meshtastic --reboot") assert return_value == 0 # pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up) time.sleep(18) @@ -28,94 +28,100 @@ def test_smoke1_reboot(): @pytest.mark.smoke1 def test_smoke1_info(): """Test --info""" - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Owner', out, re.MULTILINE) - assert re.search(r'^My info', out, re.MULTILINE) - assert re.search(r'^Nodes in mesh', out, re.MULTILINE) - assert re.search(r'^Preferences', out, re.MULTILINE) - assert re.search(r'^Channels', out, re.MULTILINE) - assert re.search(r'^ PRIMARY', out, re.MULTILINE) - assert re.search(r'^Primary channel URL', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Owner", out, re.MULTILINE) + assert re.search(r"^My info", out, re.MULTILINE) + assert re.search(r"^Nodes in mesh", out, re.MULTILINE) + assert re.search(r"^Preferences", out, re.MULTILINE) + assert re.search(r"^Channels", out, re.MULTILINE) + assert re.search(r"^ PRIMARY", out, re.MULTILINE) + assert re.search(r"^Primary channel URL", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_sendping(): """Test --sendping""" - return_value, out = subprocess.getstatusoutput('meshtastic --sendping') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Sending ping message', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --sendping") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Sending ping message", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_get_with_invalid_setting(): """Test '--get a_bad_setting'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --get a_bad_setting') - assert re.search(r'Choices in sorted order', out) + return_value, out = subprocess.getstatusoutput("meshtastic --get a_bad_setting") + assert re.search(r"Choices in sorted order", out) assert return_value == 0 @pytest.mark.smoke1 def test_set_with_invalid_setting(): """Test '--set a_bad_setting'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --set a_bad_setting foo') - assert re.search(r'Choices in sorted order', out) + return_value, out = subprocess.getstatusoutput("meshtastic --set a_bad_setting foo") + assert re.search(r"Choices in sorted order", out) assert return_value == 0 @pytest.mark.smoke1 def test_ch_set_with_invalid_settingpatch_find_ports(): """Test '--ch-set with a_bad_setting'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set invalid_setting foo --ch-index 0') - assert re.search(r'Choices in sorted order', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set invalid_setting foo --ch-index 0" + ) + assert re.search(r"Choices in sorted order", out) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_pos_fields(): """Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)""" - return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Setting position fields to 35', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Setting position fields to 35", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields') - assert re.match(r'Connected to radio', out) - assert re.search(r'POS_ALTITUDE', out, re.MULTILINE) - assert re.search(r'POS_ALT_MSL', out, re.MULTILINE) - assert re.search(r'POS_BATTERY', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --pos-fields") + assert re.match(r"Connected to radio", out) + assert re.search(r"POS_ALTITUDE", out, re.MULTILINE) + assert re.search(r"POS_ALT_MSL", out, re.MULTILINE) + assert re.search(r"POS_BATTERY", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_test_with_arg_but_no_hardware(): """Test --test - Note: Since only one device is connected, it will not do much. + Note: Since only one device is connected, it will not do much. """ - return_value, out = subprocess.getstatusoutput('meshtastic --test') - assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --test") + assert re.search(r"^Warning: Must have at least two devices", out, re.MULTILINE) assert return_value == 1 @pytest.mark.smoke1 def test_smoke1_debug(): """Test --debug""" - return_value, out = subprocess.getstatusoutput('meshtastic --info --debug') - assert re.search(r'^Owner', out, re.MULTILINE) - assert re.search(r'^DEBUG file', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info --debug") + assert re.search(r"^Owner", out, re.MULTILINE) + assert re.search(r"^DEBUG file", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_seriallog_to_file(): """Test --seriallog to a file creates a file""" - filename = 'tmpoutput.txt' + filename = "tmpoutput.txt" if os.path.exists(f"{filename}"): os.remove(f"{filename}") - return_value, _ = subprocess.getstatusoutput(f'meshtastic --info --seriallog {filename}') + return_value, _ = subprocess.getstatusoutput( + f"meshtastic --info --seriallog {filename}" + ) assert os.path.exists(f"{filename}") assert return_value == 0 os.remove(f"{filename}") @@ -124,10 +130,10 @@ def test_smoke1_seriallog_to_file(): @pytest.mark.smoke1 def test_smoke1_qr(): """Test --qr""" - filename = 'tmpqr' + filename = "tmpqr" if os.path.exists(f"{filename}"): os.remove(f"{filename}") - return_value, _ = subprocess.getstatusoutput(f'meshtastic --qr > {filename}') + return_value, _ = subprocess.getstatusoutput(f"meshtastic --qr > {filename}") assert os.path.exists(f"{filename}") # not really testing that a valid qr code is created, just that the file size # is reasonably big enough for a qr code @@ -139,20 +145,20 @@ def test_smoke1_qr(): @pytest.mark.smoke1 def test_smoke1_nodes(): """Test --nodes""" - return_value, out = subprocess.getstatusoutput('meshtastic --nodes') - assert re.match(r'Connected to radio', out) - if platform.system() != 'Windows': - assert re.search(r' User ', out, re.MULTILINE) - assert re.search(r' 1 ', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --nodes") + assert re.match(r"Connected to radio", out) + if platform.system() != "Windows": + assert re.search(r" User ", out, re.MULTILINE) + assert re.search(r" 1 ", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_send_hello(): """Test --sendtext hello""" - return_value, out = subprocess.getstatusoutput('meshtastic --sendtext hello') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --sendtext hello") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Sending text message hello to \^all", out, re.MULTILINE) assert return_value == 0 @@ -164,27 +170,29 @@ def test_smoke1_port(): # hopefully there is just one assert len(ports) == 1 port = ports[0] - return_value, out = subprocess.getstatusoutput(f'meshtastic --port {port} --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Owner', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput(f"meshtastic --port {port} --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Owner", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_set_location_info(): - """Test --setlat, --setlon and --setalt """ - return_value, out = subprocess.getstatusoutput('meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Fixing altitude', out, re.MULTILINE) - assert re.search(r'^Fixing latitude', out, re.MULTILINE) - assert re.search(r'^Fixing longitude', out, re.MULTILINE) + """Test --setlat, --setlon and --setalt""" + return_value, out = subprocess.getstatusoutput( + "meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Fixing altitude", out, re.MULTILINE) + assert re.search(r"^Fixing latitude", out, re.MULTILINE) + assert re.search(r"^Fixing longitude", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out2 = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'1337', out2, re.MULTILINE) - assert re.search(r'32.7767', out2, re.MULTILINE) - assert re.search(r'-96.797', out2, re.MULTILINE) + return_value, out2 = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"1337", out2, re.MULTILINE) + assert re.search(r"32.7767", out2, re.MULTILINE) + assert re.search(r"-96.797", out2, re.MULTILINE) assert return_value == 0 @@ -192,76 +200,80 @@ def test_smoke1_set_location_info(): def test_smoke1_set_owner(): """Test --set-owner name""" # make sure the owner is not Joe - return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Bob') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --set-owner Bob") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Setting device owner to Bob", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert not re.search(r'Owner: Joe', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert not re.search(r"Owner: Joe", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Joe') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --set-owner Joe") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Setting device owner to Joe", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'Owner: Joe', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"Owner: Joe", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_ch_set_modem_config(): """Test --ch-set modem_config""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config MedFast') - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set modem_config MedFast" + ) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert not re.search(r'MedFast', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert not re.search(r"MedFast", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config MedFast --ch-index 0') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set modem_config to MedFast', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set modem_config MedFast --ch-index 0" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set modem_config to MedFast", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'MedFast', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"MedFast", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_ch_values(): """Test --ch-vlongslow --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast, - --ch-shortslow, and --ch-shortfast arguments + --ch-shortslow, and --ch-shortfast arguments """ exp = { - '--ch-vlongslow': '{ "psk": "AQ==" }', - '--ch-longslow': 'LongSlow', - '--ch-longfast': 'LongFast', - '--ch-medslow': 'MedSlow', - '--ch-medfast': 'MedFast', - '--ch-shortslow': 'ShortSlow', - '--ch-shortfast': 'ShortFast' - } + "--ch-vlongslow": '{ "psk": "AQ==" }', + "--ch-longslow": "LongSlow", + "--ch-longfast": "LongFast", + "--ch-medslow": "MedSlow", + "--ch-medfast": "MedFast", + "--ch-shortslow": "ShortSlow", + "--ch-shortfast": "ShortFast", + } for key, val in exp.items(): print(key, val) - return_value, out = subprocess.getstatusoutput(f'meshtastic {key}') - assert re.match(r'Connected to radio', out) - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput(f"meshtastic {key}") + assert re.match(r"Connected to radio", out) + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio (might reboot) time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --info') + return_value, out = subprocess.getstatusoutput("meshtastic --info") assert re.search(val, out, re.MULTILINE) assert return_value == 0 # pause for the radio @@ -271,132 +283,144 @@ def test_smoke1_ch_values(): @pytest.mark.smoke1 def test_smoke1_ch_set_name(): """Test --ch-set name""" - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert not re.search(r'MyChannel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert not re.search(r"MyChannel", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel') - assert re.match(r'Connected to radio', out) - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-set name MyChannel") + assert re.match(r"Connected to radio", out) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel --ch-index 0') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set name to MyChannel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set name MyChannel --ch-index 0" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set name to MyChannel", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'MyChannel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"MyChannel", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_ch_set_downlink_and_uplink(): """Test -ch-set downlink_enabled X and --ch-set uplink_enabled X""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false') - assert re.match(r'Connected to radio', out) - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert not re.search(r'uplinkEnabled', out, re.MULTILINE) - assert not re.search(r'downlinkEnabled', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert not re.search(r"uplinkEnabled", out, re.MULTILINE) + assert not re.search(r"downlinkEnabled", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE) - assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set downlink_enabled to true", out, re.MULTILINE) + assert re.search(r"^Set uplink_enabled to true", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'uplinkEnabled', out, re.MULTILINE) - assert re.search(r'downlinkEnabled', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"uplinkEnabled", out, re.MULTILINE) + assert re.search(r"downlinkEnabled", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_ch_add_and_ch_del(): """Test --ch-add""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing") + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-index 1 --ch-del') - assert re.search(r'Deleting channel 1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-index 1 --ch-del") + assert re.search(r"Deleting channel 1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) # make sure the secondar channel is not there - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert not re.search(r'SECONDARY', out, re.MULTILINE) - assert not re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert not re.search(r"SECONDARY", out, re.MULTILINE) + assert not re.search(r"testing", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_ch_enable_and_disable(): """Test --ch-enable and --ch-disable""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing") + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # ensure they need to specify a --ch-index - return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable') + return_value, out = subprocess.getstatusoutput("meshtastic --ch-disable") assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1') + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-disable --ch-index 1" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'DISABLED', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"DISABLED", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 1') + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-enable --ch-index 1" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1") assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -405,32 +429,32 @@ def test_smoke1_ch_enable_and_disable(): @pytest.mark.smoke1 def test_smoke1_ch_del_a_disabled_non_primary_channel(): """Test --ch-del will work on a disabled non-primary channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing") + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # ensure they need to specify a --ch-index - return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable') + return_value, out = subprocess.getstatusoutput("meshtastic --ch-disable") assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1") assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert not re.search(r'DISABLED', out, re.MULTILINE) - assert not re.search(r'SECONDARY', out, re.MULTILINE) - assert not re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert not re.search(r"DISABLED", out, re.MULTILINE) + assert not re.search(r"SECONDARY", out, re.MULTILINE) + assert not re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -439,8 +463,8 @@ def test_smoke1_ch_del_a_disabled_non_primary_channel(): @pytest.mark.smoke1 def test_smoke1_attempt_to_delete_primary_channel(): """Test that we cannot delete the PRIMARY channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 0') - assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 0") + assert re.search(r"Warning: Cannot delete primary channel", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -449,8 +473,10 @@ def test_smoke1_attempt_to_delete_primary_channel(): @pytest.mark.smoke1 def test_smoke1_attempt_to_disable_primary_channel(): """Test that we cannot disable the PRIMARY channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 0') - assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-disable --ch-index 0" + ) + assert re.search(r"Warning: Cannot enable", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -459,8 +485,10 @@ def test_smoke1_attempt_to_disable_primary_channel(): @pytest.mark.smoke1 def test_smoke1_attempt_to_enable_primary_channel(): """Test that we cannot enable the PRIMARY channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 0') - assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-enable --ch-index 0" + ) + assert re.search(r"Warning: Cannot enable", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -469,42 +497,42 @@ def test_smoke1_attempt_to_enable_primary_channel(): @pytest.mark.smoke1 def test_smoke1_ensure_ch_del_second_of_three_channels(): """Test that when we delete the 2nd of 3 channels, that it deletes the correct channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing1") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing2") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing2', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing2", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing2', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing2", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -513,42 +541,42 @@ def test_smoke1_ensure_ch_del_second_of_three_channels(): @pytest.mark.smoke1 def test_smoke1_ensure_ch_del_third_of_three_channels(): """Test that when we delete the 3rd of 3 channels, that it deletes the correct channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing1") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing2") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing2', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing2", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 2') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 2") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1") + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -558,22 +586,24 @@ def test_smoke1_ensure_ch_del_third_of_three_channels(): def test_smoke1_seturl_default(): """Test --seturl with default value""" # set some channel value so we no longer have a default channel - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo --ch-index 0') + return_value, out = subprocess.getstatusoutput( + "meshtastic --ch-set name foo --ch-index 0" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # ensure we no longer have a default primary channel - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert not re.search('CgUYAyIBAQ', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert not re.search("CgUYAyIBAQ", out, re.MULTILINE) assert return_value == 0 url = "https://www.meshtastic.org/d/#CgUYAyIBAQ" return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}") - assert re.match(r'Connected to radio', out) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search('CgUYAyIBAQ', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search("CgUYAyIBAQ", out, re.MULTILINE) assert return_value == 0 @@ -583,8 +613,8 @@ def test_smoke1_seturl_invalid_url(): # Note: This url is no longer a valid url. url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ=" return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}") - assert re.match(r'Connected to radio', out) - assert re.search('Warning: There were no settings', out, re.MULTILINE) + assert re.match(r"Connected to radio", out) + assert re.search("Warning: There were no settings", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -593,18 +623,18 @@ def test_smoke1_seturl_invalid_url(): @pytest.mark.smoke1 def test_smoke1_configure(): """Test --configure""" - _ , out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml") - assert re.match(r'Connected to radio', out) - assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE) - assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE) - assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE) - assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE) - assert re.search('^Setting device position', out, re.MULTILINE) - assert re.search('^Set region to 1', out, re.MULTILINE) - assert re.search('^Set is_always_powered to true', out, re.MULTILINE) - assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE) - assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE) - assert re.search('^Writing modified preferences to device', out, re.MULTILINE) + _, out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml") + assert re.match(r"Connected to radio", out) + assert re.search("^Setting device owner to Bob TBeam", out, re.MULTILINE) + assert re.search("^Fixing altitude at 304 meters", out, re.MULTILINE) + assert re.search("^Fixing latitude at 35.8", out, re.MULTILINE) + assert re.search("^Fixing longitude at -93.8", out, re.MULTILINE) + assert re.search("^Setting device position", out, re.MULTILINE) + assert re.search("^Set region to 1", out, re.MULTILINE) + assert re.search("^Set is_always_powered to true", out, re.MULTILINE) + assert re.search("^Set screen_on_secs to 31536000", out, re.MULTILINE) + assert re.search("^Set wait_bluetooth_secs to 31536000", out, re.MULTILINE) + assert re.search("^Writing modified preferences to device", out, re.MULTILINE) # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) @@ -612,41 +642,47 @@ def test_smoke1_configure(): @pytest.mark.smoke1 def test_smoke1_set_ham(): """Test --set-ham - Note: Do a factory reset after this setting so it is very short-lived. + Note: Do a factory reset after this setting so it is very short-lived. """ - return_value, out = subprocess.getstatusoutput('meshtastic --set-ham KI1234') - assert re.search(r'Setting Ham ID', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --set-ham KI1234") + assert re.search(r"Setting Ham ID", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'Owner: KI1234', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"Owner: KI1234", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_set_wifi_settings(): """Test --set wifi_ssid and --set wifi_password""" - return_value, out = subprocess.getstatusoutput('meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE) - assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + 'meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"' + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set wifi_ssid to some_ssid", out, re.MULTILINE) + assert re.search(r"^Set wifi_password to temp1234", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --get wifi_ssid --get wifi_password') - assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE) - assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --get wifi_ssid --get wifi_password" + ) + assert re.search(r"^wifi_ssid: some_ssid", out, re.MULTILINE) + assert re.search(r"^wifi_password: sekrit", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smoke1 def test_smoke1_factory_reset(): """Test factory reset""" - return_value, out = subprocess.getstatusoutput('meshtastic --set factory_reset true') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set factory_reset to true', out, re.MULTILINE) - assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --set factory_reset true" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set factory_reset to true", out, re.MULTILINE) + assert re.search(r"^Writing modified preferences to device", out, re.MULTILINE) assert return_value == 0 # NOTE: The radio may not be responsive after this, may need to do a manual reboot # by pressing the button diff --git a/meshtastic/tests/test_smoke2.py b/meshtastic/tests/test_smoke2.py index 4788a2e..ff4e6a0 100644 --- a/meshtastic/tests/test_smoke2.py +++ b/meshtastic/tests/test_smoke2.py @@ -8,16 +8,16 @@ import pytest @pytest.mark.smoke2 def test_smoke2_info(): """Test --info with 2 devices connected serially""" - return_value, out = subprocess.getstatusoutput('meshtastic --info') - assert re.search(r'Warning: Multiple', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --info") + assert re.search(r"Warning: Multiple", out, re.MULTILINE) assert return_value == 1 @pytest.mark.smoke2 def test_smoke2_test(): """Test --test""" - return_value, out = subprocess.getstatusoutput('meshtastic --test') - assert re.search(r'Writing serial debugging', out, re.MULTILINE) - assert re.search(r'Ports opened', out, re.MULTILINE) - assert re.search(r'Running 5 tests', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --test") + assert re.search(r"Writing serial debugging", out, re.MULTILINE) + assert re.search(r"Ports opened", out, re.MULTILINE) + assert re.search(r"Running 5 tests", out, re.MULTILINE) assert return_value == 0 diff --git a/meshtastic/tests/test_smoke_wifi.py b/meshtastic/tests/test_smoke_wifi.py index de37de8..eed8e68 100644 --- a/meshtastic/tests/test_smoke_wifi.py +++ b/meshtastic/tests/test_smoke_wifi.py @@ -12,12 +12,14 @@ import pytest @pytest.mark.smokewifi def test_smokewifi_info(): """Test --info""" - return_value, out = subprocess.getstatusoutput('meshtastic --info --host meshtastic.local') - assert re.search(r'^Owner', out, re.MULTILINE) - assert re.search(r'^My info', out, re.MULTILINE) - assert re.search(r'^Nodes in mesh', out, re.MULTILINE) - assert re.search(r'^Preferences', out, re.MULTILINE) - assert re.search(r'^Channels', out, re.MULTILINE) - assert re.search(r'^ PRIMARY', out, re.MULTILINE) - assert re.search(r'^Primary channel URL', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --info --host meshtastic.local" + ) + assert re.search(r"^Owner", out, re.MULTILINE) + assert re.search(r"^My info", out, re.MULTILINE) + assert re.search(r"^Nodes in mesh", out, re.MULTILINE) + assert re.search(r"^Preferences", out, re.MULTILINE) + assert re.search(r"^Channels", out, re.MULTILINE) + assert re.search(r"^ PRIMARY", out, re.MULTILINE) + assert re.search(r"^Primary channel URL", out, re.MULTILINE) assert return_value == 0 diff --git a/meshtastic/tests/test_smokevirt.py b/meshtastic/tests/test_smokevirt.py index 2e307e0..6e91468 100644 --- a/meshtastic/tests/test_smokevirt.py +++ b/meshtastic/tests/test_smokevirt.py @@ -7,11 +7,11 @@ This smoke test runs against that localhost. """ +import os +import platform import re import subprocess import time -import platform -import os # Do not like using hard coded sleeps, but it probably makes # sense to pause for the radio at apprpriate times @@ -24,10 +24,10 @@ PAUSE_AFTER_COMMAND = 0.1 PAUSE_AFTER_REBOOT = 0.2 -#TODO: need to fix the virtual device to have a reboot. When you issue the command +# TODO: need to fix the virtual device to have a reboot. When you issue the command # below, you get "FIXME implement reboot for this platform" -#@pytest.mark.smokevirt -#def test_smokevirt_reboot(): +# @pytest.mark.smokevirt +# def test_smokevirt_reboot(): # """Test reboot""" # return_value, _ = subprocess.getstatusoutput('meshtastic --host localhost --reboot') # assert return_value == 0 @@ -38,94 +38,110 @@ PAUSE_AFTER_REBOOT = 0.2 @pytest.mark.smokevirt def test_smokevirt_info(): """Test --info""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Owner', out, re.MULTILINE) - assert re.search(r'^My info', out, re.MULTILINE) - assert re.search(r'^Nodes in mesh', out, re.MULTILINE) - assert re.search(r'^Preferences', out, re.MULTILINE) - assert re.search(r'^Channels', out, re.MULTILINE) - assert re.search(r'^ PRIMARY', out, re.MULTILINE) - assert re.search(r'^Primary channel URL', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"^Owner", out, re.MULTILINE) + assert re.search(r"^My info", out, re.MULTILINE) + assert re.search(r"^Nodes in mesh", out, re.MULTILINE) + assert re.search(r"^Preferences", out, re.MULTILINE) + assert re.search(r"^Channels", out, re.MULTILINE) + assert re.search(r"^ PRIMARY", out, re.MULTILINE) + assert re.search(r"^Primary channel URL", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_sendping(): """Test --sendping""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --sendping') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Sending ping message', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --sendping" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Sending ping message", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_get_with_invalid_setting(): """Test '--get a_bad_setting'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --get a_bad_setting') - assert re.search(r'Choices in sorted order', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --get a_bad_setting" + ) + assert re.search(r"Choices in sorted order", out) assert return_value == 0 @pytest.mark.smokevirt def test_set_with_invalid_setting(): """Test '--set a_bad_setting'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set a_bad_setting foo') - assert re.search(r'Choices in sorted order', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --set a_bad_setting foo" + ) + assert re.search(r"Choices in sorted order", out) assert return_value == 0 @pytest.mark.smokevirt def test_ch_set_with_invalid_settingpatch_find_ports(): """Test '--ch-set with a_bad_setting'.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set invalid_setting foo --ch-index 0') - assert re.search(r'Choices in sorted order', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set invalid_setting foo --ch-index 0" + ) + assert re.search(r"Choices in sorted order", out) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_pos_fields(): """Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Setting position fields to 35', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Setting position fields to 35", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --pos-fields') - assert re.match(r'Connected to radio', out) - assert re.search(r'POS_ALTITUDE', out, re.MULTILINE) - assert re.search(r'POS_ALT_MSL', out, re.MULTILINE) - assert re.search(r'POS_BATTERY', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --pos-fields" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"POS_ALTITUDE", out, re.MULTILINE) + assert re.search(r"POS_ALT_MSL", out, re.MULTILINE) + assert re.search(r"POS_BATTERY", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_test_with_arg_but_no_hardware(): """Test --test - Note: Since only one device is connected, it will not do much. + Note: Since only one device is connected, it will not do much. """ - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --test') - assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --test") + assert re.search(r"^Warning: Must have at least two devices", out, re.MULTILINE) assert return_value == 1 @pytest.mark.smokevirt def test_smokevirt_debug(): """Test --debug""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info --debug') - assert re.search(r'^Owner', out, re.MULTILINE) - assert re.search(r'^DEBUG file', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --info --debug" + ) + assert re.search(r"^Owner", out, re.MULTILINE) + assert re.search(r"^DEBUG file", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_seriallog_to_file(): """Test --seriallog to a file creates a file""" - filename = 'tmpoutput.txt' + filename = "tmpoutput.txt" if os.path.exists(f"{filename}"): os.remove(f"{filename}") - return_value, _ = subprocess.getstatusoutput(f'meshtastic --host localhost --info --seriallog {filename}') + return_value, _ = subprocess.getstatusoutput( + f"meshtastic --host localhost --info --seriallog {filename}" + ) assert os.path.exists(f"{filename}") assert return_value == 0 os.remove(f"{filename}") @@ -134,10 +150,12 @@ def test_smokevirt_seriallog_to_file(): @pytest.mark.smokevirt def test_smokevirt_qr(): """Test --qr""" - filename = 'tmpqr' + filename = "tmpqr" if os.path.exists(f"{filename}"): os.remove(f"{filename}") - return_value, _ = subprocess.getstatusoutput(f'meshtastic --host localhost --qr > {filename}') + return_value, _ = subprocess.getstatusoutput( + f"meshtastic --host localhost --qr > {filename}" + ) assert os.path.exists(f"{filename}") # not really testing that a valid qr code is created, just that the file size # is reasonably big enough for a qr code @@ -149,20 +167,24 @@ def test_smokevirt_qr(): @pytest.mark.smokevirt def test_smokevirt_nodes(): """Test --nodes""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --nodes') - assert re.match(r'Connected to radio', out) - if platform.system() != 'Windows': - assert re.search(r' User ', out, re.MULTILINE) - assert re.search(r' 1 ', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --nodes" + ) + assert re.match(r"Connected to radio", out) + if platform.system() != "Windows": + assert re.search(r" User ", out, re.MULTILINE) + assert re.search(r" 1 ", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_send_hello(): """Test --sendtext hello""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --sendtext hello') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --sendtext hello" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Sending text message hello to \^all", out, re.MULTILINE) assert return_value == 0 @@ -177,19 +199,23 @@ def test_smokevirt_port(): @pytest.mark.smokevirt def test_smokevirt_set_location_info(): - """Test --setlat, --setlon and --setalt """ - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --setlat 32.7767 --setlon -96.7970 --setalt 1337') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Fixing altitude', out, re.MULTILINE) - assert re.search(r'^Fixing latitude', out, re.MULTILINE) - assert re.search(r'^Fixing longitude', out, re.MULTILINE) + """Test --setlat, --setlon and --setalt""" + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --setlat 32.7767 --setlon -96.7970 --setalt 1337" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Fixing altitude", out, re.MULTILINE) + assert re.search(r"^Fixing latitude", out, re.MULTILINE) + assert re.search(r"^Fixing longitude", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out2 = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search(r'1337', out2, re.MULTILINE) - assert re.search(r'32.7767', out2, re.MULTILINE) - assert re.search(r'-96.797', out2, re.MULTILINE) + return_value, out2 = subprocess.getstatusoutput( + "meshtastic --host localhost --info" + ) + assert re.search(r"1337", out2, re.MULTILINE) + assert re.search(r"32.7767", out2, re.MULTILINE) + assert re.search(r"-96.797", out2, re.MULTILINE) assert return_value == 0 @@ -197,50 +223,58 @@ def test_smokevirt_set_location_info(): def test_smokevirt_set_owner(): """Test --set-owner name""" # make sure the owner is not Joe - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-owner Bob') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --set-owner Bob" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Setting device owner to Bob", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert not re.search(r'Owner: Joe', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert not re.search(r"Owner: Joe", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-owner Joe') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --set-owner Joe" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Setting device owner to Joe", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search(r'Owner: Joe', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.search(r"Owner: Joe", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_ch_values(): """Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast, - --ch-shortslow, and --ch-shortfast arguments + --ch-shortslow, and --ch-shortfast arguments """ exp = { - '--ch-longslow': 'LongSlow', - '--ch-longfast': 'LongFast', - '--ch-medslow': 'MedSlow', - '--ch-medfast': 'MedFast', - '--ch-shortslow': 'ShortSlow', - '--ch-shortfast': 'ShortFast' - } + "--ch-longslow": "LongSlow", + "--ch-longfast": "LongFast", + "--ch-medslow": "MedSlow", + "--ch-medfast": "MedFast", + "--ch-shortslow": "ShortSlow", + "--ch-shortfast": "ShortFast", + } for key, val in exp.items(): - return_value, out = subprocess.getstatusoutput(f'meshtastic --host localhost {key}') - assert re.match(r'Connected to radio', out) - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + f"meshtastic --host localhost {key}" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio (might reboot) time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --info" + ) assert re.search(val, out, re.MULTILINE) assert return_value == 0 # pause for the radio @@ -250,144 +284,172 @@ def test_smokevirt_ch_values(): @pytest.mark.smokevirt def test_smokevirt_ch_set_name(): """Test --ch-set name""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert not re.search(r'MyChannel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert not re.search(r"MyChannel", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set name MyChannel') - assert re.match(r'Connected to radio', out) - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set name MyChannel" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set name MyChannel --ch-index 0') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set name to MyChannel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set name MyChannel --ch-index 0" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set name to MyChannel", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search(r'MyChannel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.search(r"MyChannel", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_ch_set_downlink_and_uplink(): """Test -ch-set downlink_enabled X and --ch-set uplink_enabled X""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set downlink_enabled false --ch-set uplink_enabled false') - assert re.match(r'Connected to radio', out) - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set downlink_enabled false --ch-set uplink_enabled false" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # pylint: disable=C0301 - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert not re.search(r'uplinkEnabled', out, re.MULTILINE) - assert not re.search(r'downlinkEnabled', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert not re.search(r"uplinkEnabled", out, re.MULTILINE) + assert not re.search(r"downlinkEnabled", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # pylint: disable=C0301 - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE) - assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set downlink_enabled to true", out, re.MULTILINE) + assert re.search(r"^Set uplink_enabled to true", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search(r'uplinkEnabled', out, re.MULTILINE) - assert re.search(r'downlinkEnabled', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.search(r"uplinkEnabled", out, re.MULTILINE) + assert re.search(r"downlinkEnabled", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_ch_add_and_ch_del(): """Test --ch-add""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-index 1 --ch-del') - assert re.search(r'Deleting channel 1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-index 1 --ch-del" + ) + assert re.search(r"Deleting channel 1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing') - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing" + ) + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-index 1 --ch-del') - assert re.search(r'Deleting channel 1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-index 1 --ch-del" + ) + assert re.search(r"Deleting channel 1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) # make sure the secondary channel is not there - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert not re.search(r'SECONDARY', out, re.MULTILINE) - assert not re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert not re.search(r"SECONDARY", out, re.MULTILINE) + assert not re.search(r"testing", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_ch_enable_and_disable(): """Test --ch-enable and --ch-disable""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-index 1 --ch-del') - assert re.search(r'Deleting channel 1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-index 1 --ch-del" + ) + assert re.search(r"Deleting channel 1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing') - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing" + ) + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # ensure they need to specify a --ch-index - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-disable" + ) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable --ch-index 1') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-disable --ch-index 1" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'DISABLED', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"DISABLED", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-enable --ch-index 1') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-enable --ch-index 1" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 1" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -396,37 +458,45 @@ def test_smokevirt_ch_enable_and_disable(): @pytest.mark.smokevirt def test_smokevirt_ch_del_a_disabled_non_primary_channel(): """Test --ch-del will work on a disabled non-primary channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-index 1 --ch-del') - assert re.search(r'Deleting channel 1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-index 1 --ch-del" + ) + assert re.search(r"Deleting channel 1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing') - assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing" + ) + assert re.search(r"Writing modified channels to device", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # ensure they need to specify a --ch-index - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-disable" + ) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 1" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert not re.search(r'DISABLED', out, re.MULTILINE) - assert not re.search(r'SECONDARY', out, re.MULTILINE) - assert not re.search(r'testing', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert not re.search(r"DISABLED", out, re.MULTILINE) + assert not re.search(r"SECONDARY", out, re.MULTILINE) + assert not re.search(r"testing", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -435,8 +505,10 @@ def test_smokevirt_ch_del_a_disabled_non_primary_channel(): @pytest.mark.smokevirt def test_smokevirt_attempt_to_delete_primary_channel(): """Test that we cannot delete the PRIMARY channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 0') - assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 0" + ) + assert re.search(r"Warning: Cannot delete primary channel", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -445,8 +517,10 @@ def test_smokevirt_attempt_to_delete_primary_channel(): @pytest.mark.smokevirt def test_smokevirt_attempt_to_disable_primary_channel(): """Test that we cannot disable the PRIMARY channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-disable --ch-index 0') - assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-disable --ch-index 0" + ) + assert re.search(r"Warning: Cannot enable", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -455,8 +529,10 @@ def test_smokevirt_attempt_to_disable_primary_channel(): @pytest.mark.smokevirt def test_smokevirt_attempt_to_enable_primary_channel(): """Test that we cannot enable the PRIMARY channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-enable --ch-index 0') - assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-enable --ch-index 0" + ) + assert re.search(r"Warning: Cannot enable", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -465,42 +541,50 @@ def test_smokevirt_attempt_to_enable_primary_channel(): @pytest.mark.smokevirt def test_smokevirt_ensure_ch_del_second_of_three_channels(): """Test that when we delete the 2nd of 3 channels, that it deletes the correct channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing1" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing2') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing2" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing2', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing2", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 1" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing2', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing2", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 1" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -509,42 +593,50 @@ def test_smokevirt_ensure_ch_del_second_of_three_channels(): @pytest.mark.smokevirt def test_smokevirt_ensure_ch_del_third_of_three_channels(): """Test that when we delete the 3rd of 3 channels, that it deletes the correct channel.""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing1" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'SECONDARY', out, re.MULTILINE) - assert re.search(r'testing1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"SECONDARY", out, re.MULTILINE) + assert re.search(r"testing1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-add testing2') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-add testing2" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing2', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing2", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 2') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 2" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.match(r'Connected to radio', out) - assert re.search(r'testing1', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.match(r"Connected to radio", out) + assert re.search(r"testing1", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-del --ch-index 1') - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-del --ch-index 1" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -553,24 +645,28 @@ def test_smokevirt_ensure_ch_del_third_of_three_channels(): @pytest.mark.smokevirt def test_smokevirt_ch_set_modem_config(): """Test --ch-set modem_config""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set modem_config Bw31_25Cr48Sf512') - assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set modem_config Bw31_25Cr48Sf512" + ) + assert re.search(r"Warning: Need to specify", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert not re.search(r"Bw31_25Cr48Sf512", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set modem_config MidSlow --ch-index 0') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set modem_config to MidSlow', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set modem_config MidSlow --ch-index 0" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set modem_config to MidSlow", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search(r'MidSlow', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.search(r"MidSlow", out, re.MULTILINE) assert return_value == 0 @@ -578,22 +674,26 @@ def test_smokevirt_ch_set_modem_config(): def test_smokevirt_seturl_default(): """Test --seturl with default value""" # set some channel value so we no longer have a default channel - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --ch-set name foo --ch-index 0') + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --ch-set name foo --ch-index 0" + ) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) # ensure we no longer have a default primary channel - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert not re.search('CgUYAyIBAQ', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert not re.search("CgUYAyIBAQ", out, re.MULTILINE) assert return_value == 0 url = "https://www.meshtastic.org/d/#CgUYAyIBAQ" - return_value, out = subprocess.getstatusoutput(f"meshtastic --host localhost --seturl {url}") - assert re.match(r'Connected to radio', out) + return_value, out = subprocess.getstatusoutput( + f"meshtastic --host localhost --seturl {url}" + ) + assert re.match(r"Connected to radio", out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search('CgUYAyIBAQ', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.search("CgUYAyIBAQ", out, re.MULTILINE) assert return_value == 0 @@ -602,9 +702,11 @@ def test_smokevirt_seturl_invalid_url(): """Test --seturl with invalid url""" # Note: This url is no longer a valid url. url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ=" - return_value, out = subprocess.getstatusoutput(f"meshtastic --host localhost --seturl {url}") - assert re.match(r'Connected to radio', out) - assert re.search('Warning: There were no settings', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + f"meshtastic --host localhost --seturl {url}" + ) + assert re.match(r"Connected to radio", out) + assert re.search("Warning: There were no settings", out, re.MULTILINE) assert return_value == 1 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -613,19 +715,21 @@ def test_smokevirt_seturl_invalid_url(): @pytest.mark.smokevirt def test_smokevirt_configure(): """Test --configure""" - _ , out = subprocess.getstatusoutput(f"meshtastic --host localhost --configure example_config.yaml") - assert re.match(r'Connected to radio', out) - assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE) - assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE) - assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE) - assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE) - assert re.search('^Setting device position', out, re.MULTILINE) - assert re.search('^Set region to 1', out, re.MULTILINE) - assert re.search('^Set is_always_powered to true', out, re.MULTILINE) - assert re.search('^Set send_owner_interval to 2', out, re.MULTILINE) - assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE) - assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE) - assert re.search('^Writing modified preferences to device', out, re.MULTILINE) + _, out = subprocess.getstatusoutput( + f"meshtastic --host localhost --configure example_config.yaml" + ) + assert re.match(r"Connected to radio", out) + assert re.search("^Setting device owner to Bob TBeam", out, re.MULTILINE) + assert re.search("^Fixing altitude at 304 meters", out, re.MULTILINE) + assert re.search("^Fixing latitude at 35.8", out, re.MULTILINE) + assert re.search("^Fixing longitude at -93.8", out, re.MULTILINE) + assert re.search("^Setting device position", out, re.MULTILINE) + assert re.search("^Set region to 1", out, re.MULTILINE) + assert re.search("^Set is_always_powered to true", out, re.MULTILINE) + assert re.search("^Set send_owner_interval to 2", out, re.MULTILINE) + assert re.search("^Set screen_on_secs to 31536000", out, re.MULTILINE) + assert re.search("^Set wait_bluetooth_secs to 31536000", out, re.MULTILINE) + assert re.search("^Writing modified preferences to device", out, re.MULTILINE) # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) @@ -633,41 +737,49 @@ def test_smokevirt_configure(): @pytest.mark.smokevirt def test_smokevirt_set_ham(): """Test --set-ham - Note: Do a factory reset after this setting so it is very short-lived. + Note: Do a factory reset after this setting so it is very short-lived. """ - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-ham KI1234') - assert re.search(r'Setting Ham ID', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --set-ham KI1234" + ) + assert re.search(r"Setting Ham ID", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_REBOOT) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info') - assert re.search(r'Owner: KI1234', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput("meshtastic --host localhost --info") + assert re.search(r"Owner: KI1234", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_set_wifi_settings(): """Test --set wifi_ssid and --set wifi_password""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set wifi_ssid "some_ssid" --set wifi_password "temp1234"') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE) - assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + 'meshtastic --host localhost --set wifi_ssid "some_ssid" --set wifi_password "temp1234"' + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set wifi_ssid to some_ssid", out, re.MULTILINE) + assert re.search(r"^Set wifi_password to temp1234", out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --get wifi_ssid --get wifi_password') - assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE) - assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --get wifi_ssid --get wifi_password" + ) + assert re.search(r"^wifi_ssid: some_ssid", out, re.MULTILINE) + assert re.search(r"^wifi_password: sekrit", out, re.MULTILINE) assert return_value == 0 @pytest.mark.smokevirt def test_smokevirt_factory_reset(): """Test factory reset""" - return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set factory_reset true') - assert re.match(r'Connected to radio', out) - assert re.search(r'^Set factory_reset to true', out, re.MULTILINE) - assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE) + return_value, out = subprocess.getstatusoutput( + "meshtastic --host localhost --set factory_reset true" + ) + assert re.match(r"Connected to radio", out) + assert re.search(r"^Set factory_reset to true", out, re.MULTILINE) + assert re.search(r"^Writing modified preferences to device", out, re.MULTILINE) assert return_value == 0 # NOTE: The virtual radio will not respond well after this command. Need to re-start the virtual program at this point. # TODO: fix? diff --git a/meshtastic/tests/test_stream_interface.py b/meshtastic/tests/test_stream_interface.py index 6815810..7520559 100644 --- a/meshtastic/tests/test_stream_interface.py +++ b/meshtastic/tests/test_stream_interface.py @@ -1,13 +1,14 @@ """Meshtastic unit tests for stream_interface.py""" import logging -#import re - from unittest.mock import MagicMock + import pytest from ..stream_interface import StreamInterface +# import re + @pytest.mark.unit def test_StreamInterface(): @@ -22,10 +23,10 @@ def test_StreamInterface(): @pytest.mark.usefixtures("reset_globals") def test_StreamInterface_with_noProto(caplog): """Test that we can instantiate a StreamInterface based on nonProto - and we can read/write bytes from a mocked stream + and we can read/write bytes from a mocked stream """ stream = MagicMock() - test_data = b'hello' + test_data = b"hello" stream.read.return_value = test_data with caplog.at_level(logging.DEBUG): iface = StreamInterface(noProto=True, connectNow=False) @@ -39,9 +40,9 @@ def test_StreamInterface_with_noProto(caplog): ### Note: This takes a bit, so moving from unit to slow ### Tip: If you want to see the print output, run with '-s' flag: ### pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl -#@pytest.mark.unitslow -#@pytest.mark.usefixtures("reset_globals") -#def test_sendToRadioImpl(caplog): +# @pytest.mark.unitslow +# @pytest.mark.usefixtures("reset_globals") +# def test_sendToRadioImpl(caplog): # """Test _sendToRadioImpl()""" # ## def add_header(b): diff --git a/meshtastic/tests/test_tcp_interface.py b/meshtastic/tests/test_tcp_interface.py index 6d49359..9084e1d 100644 --- a/meshtastic/tests/test_tcp_interface.py +++ b/meshtastic/tests/test_tcp_interface.py @@ -1,8 +1,8 @@ """Meshtastic unit tests for tcp_interface.py""" import re - from unittest.mock import patch + import pytest from ..tcp_interface import TCPInterface @@ -11,18 +11,18 @@ from ..tcp_interface import TCPInterface @pytest.mark.unit def test_TCPInterface(capsys): """Test that we can instantiate a TCPInterface""" - with patch('socket.socket') as mock_socket: - iface = TCPInterface(hostname='localhost', noProto=True) + with patch("socket.socket") as mock_socket: + iface = TCPInterface(hostname="localhost", noProto=True) iface.myConnect() iface.showInfo() iface.localNode.showInfo() out, err = capsys.readouterr() - assert re.search(r'Owner: None \(None\)', out, re.MULTILINE) - assert re.search(r'Nodes', out, re.MULTILINE) - assert re.search(r'Preferences', out, re.MULTILINE) - assert re.search(r'Channels', out, re.MULTILINE) - assert re.search(r'Primary channel URL', out, re.MULTILINE) - assert err == '' + assert re.search(r"Owner: None \(None\)", out, re.MULTILINE) + assert re.search(r"Nodes", out, re.MULTILINE) + assert re.search(r"Preferences", out, re.MULTILINE) + assert re.search(r"Channels", out, re.MULTILINE) + assert re.search(r"Primary channel URL", out, re.MULTILINE) + assert err == "" assert mock_socket.called iface.close() @@ -34,10 +34,12 @@ def test_TCPInterface_exception(): def throw_an_exception(): raise ValueError("Fake exception.") - with patch('meshtastic.tcp_interface.TCPInterface._socket_shutdown') as mock_shutdown: + with patch( + "meshtastic.tcp_interface.TCPInterface._socket_shutdown" + ) as mock_shutdown: mock_shutdown.side_effect = throw_an_exception - with patch('socket.socket') as mock_socket: - iface = TCPInterface(hostname='localhost', noProto=True) + with patch("socket.socket") as mock_socket: + iface = TCPInterface(hostname="localhost", noProto=True) iface.myConnect() iface.close() assert mock_socket.called @@ -47,6 +49,6 @@ def test_TCPInterface_exception(): @pytest.mark.unit def test_TCPInterface_without_connecting(): """Test that we can instantiate a TCPInterface with connectNow as false""" - with patch('socket.socket'): - iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False) + with patch("socket.socket"): + iface = TCPInterface(hostname="localhost", noProto=True, connectNow=False) assert iface.socket is None diff --git a/meshtastic/tests/test_tunnel.py b/meshtastic/tests/test_tunnel.py index 14b9dd4..a5a3e7b 100644 --- a/meshtastic/tests/test_tunnel.py +++ b/meshtastic/tests/test_tunnel.py @@ -1,38 +1,38 @@ """Meshtastic unit tests for tunnel.py""" +import logging import re import sys -import logging +from unittest.mock import MagicMock, patch -from unittest.mock import patch, MagicMock import pytest +from ..globals import Globals from ..tcp_interface import TCPInterface from ..tunnel import Tunnel, onTunnelReceive -from ..globals import Globals @pytest.mark.unit -@patch('platform.system') +@patch("platform.system") def test_Tunnel_on_non_linux_system(mock_platform_system): """Test that we cannot instantiate a Tunnel on a non Linux system""" a_mock = MagicMock() - a_mock.return_value = 'notLinux' + a_mock.return_value = "notLinux" mock_platform_system.side_effect = a_mock - with patch('socket.socket') as mock_socket: + with patch("socket.socket") as mock_socket: with pytest.raises(Exception) as pytest_wrapped_e: - iface = TCPInterface(hostname='localhost', noProto=True) + iface = TCPInterface(hostname="localhost", noProto=True) Tunnel(iface) assert pytest_wrapped_e.type == Exception assert mock_socket.called @pytest.mark.unit -@patch('platform.system') +@patch("platform.system") def test_Tunnel_without_interface(mock_platform_system): """Test that we can not instantiate a Tunnel without a valid interface""" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with pytest.raises(Exception) as pytest_wrapped_e: Tunnel(None) @@ -40,227 +40,235 @@ def test_Tunnel_without_interface(mock_platform_system): @pytest.mark.unitslow -@patch('platform.system') +@patch("platform.system") def test_Tunnel_with_interface(mock_platform_system, caplog, iface_with_nodes): """Test that we can not instantiate a Tunnel without a valid interface""" iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.WARNING): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) assert tun == Globals.getInstance().get_tunnelInstance() iface.close() - assert re.search(r'Not creating a TapDevice()', caplog.text, re.MULTILINE) - assert re.search(r'Not starting TUN reader', caplog.text, re.MULTILINE) - assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) + assert re.search(r"Not creating a TapDevice()", caplog.text, re.MULTILINE) + assert re.search(r"Not starting TUN reader", caplog.text, re.MULTILINE) + assert re.search(r"Not sending packet", caplog.text, re.MULTILINE) @pytest.mark.unitslow -@patch('platform.system') +@patch("platform.system") def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with_nodes): """Test onTunnelReceive""" iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 - sys.argv = [''] + sys.argv = [""] Globals.getInstance().set_args(sys.argv) - packet = {'decoded': { 'payload': 'foo'}, 'from': 2475227164} + packet = {"decoded": {"payload": "foo"}, "from": 2475227164} a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) Globals.getInstance().set_tunnelInstance(tun) onTunnelReceive(packet, iface) - assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE) - assert re.search(r'Ignoring message we sent', caplog.text, re.MULTILINE) + assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE) + assert re.search(r"Ignoring message we sent", caplog.text, re.MULTILINE) @pytest.mark.unit -@patch('platform.system') -def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, iface_with_nodes): +@patch("platform.system") +def test_onTunnelReceive_from_someone_else( + mock_platform_system, caplog, iface_with_nodes +): """Test onTunnelReceive""" iface = iface_with_nodes iface.myInfo.my_node_num = 2475227164 - sys.argv = [''] + sys.argv = [""] Globals.getInstance().set_args(sys.argv) - packet = {'decoded': { 'payload': 'foo'}, 'from': 123} + packet = {"decoded": {"payload": "foo"}, "from": 123} a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) Globals.getInstance().set_tunnelInstance(tun) onTunnelReceive(packet, iface) - assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE) + assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE) @pytest.mark.unitslow -@patch('platform.system') +@patch("platform.system") def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_nodes): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # random packet - packet = b'1234567890123456789012345678901234567890' + packet = b"1234567890123456789012345678901234567890" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) assert not ignore @pytest.mark.unitslow -@patch('platform.system') -def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, iface_with_nodes): +@patch("platform.system") +def test_shouldFilterPacket_in_blacklist( + mock_platform_system, caplog, iface_with_nodes +): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # faked IGMP - packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) assert ignore @pytest.mark.unitslow -@patch('platform.system') +@patch("platform.system") def test_shouldFilterPacket_icmp(mock_platform_system, caplog, iface_with_nodes): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # faked ICMP - packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) - assert re.search(r'forwarding ICMP message', caplog.text, re.MULTILINE) + assert re.search(r"forwarding ICMP message", caplog.text, re.MULTILINE) assert not ignore @pytest.mark.unit -@patch('platform.system') +@patch("platform.system") def test_shouldFilterPacket_udp(mock_platform_system, caplog, iface_with_nodes): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # faked UDP - packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) - assert re.search(r'forwarding udp', caplog.text, re.MULTILINE) + assert re.search(r"forwarding udp", caplog.text, re.MULTILINE) assert not ignore @pytest.mark.unitslow -@patch('platform.system') -def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, iface_with_nodes): +@patch("platform.system") +def test_shouldFilterPacket_udp_blacklisted( + mock_platform_system, caplog, iface_with_nodes +): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # faked UDP - packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x6c\x07\x6c\x00\x00\x00' + packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x6c\x07\x6c\x00\x00\x00" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock # Note: custom logging level LOG_TRACE = 5 with caplog.at_level(LOG_TRACE): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) - assert re.search(r'ignoring blacklisted UDP', caplog.text, re.MULTILINE) + assert re.search(r"ignoring blacklisted UDP", caplog.text, re.MULTILINE) assert ignore @pytest.mark.unit -@patch('platform.system') +@patch("platform.system") def test_shouldFilterPacket_tcp(mock_platform_system, caplog, iface_with_nodes): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # faked TCP - packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) - assert re.search(r'forwarding tcp', caplog.text, re.MULTILINE) + assert re.search(r"forwarding tcp", caplog.text, re.MULTILINE) assert not ignore @pytest.mark.unitslow -@patch('platform.system') -def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, iface_with_nodes): +@patch("platform.system") +def test_shouldFilterPacket_tcp_blacklisted( + mock_platform_system, caplog, iface_with_nodes +): """Test _shouldFilterPacket()""" iface = iface_with_nodes iface.noProto = True # faked TCP - packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x0c\x17\x0c\x00\x00\x00' + packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x0c\x17\x0c\x00\x00\x00" a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock # Note: custom logging level LOG_TRACE = 5 with caplog.at_level(LOG_TRACE): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) ignore = tun._shouldFilterPacket(packet) - assert re.search(r'ignoring blacklisted TCP', caplog.text, re.MULTILINE) + assert re.search(r"ignoring blacklisted TCP", caplog.text, re.MULTILINE) assert ignore @pytest.mark.unitslow -@patch('platform.system') +@patch("platform.system") def test_ipToNodeId_none(mock_platform_system, caplog, iface_with_nodes): """Test _ipToNodeId()""" iface = iface_with_nodes iface.noProto = True a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) - nodeid = tun._ipToNodeId('something not useful') + nodeid = tun._ipToNodeId("something not useful") assert nodeid is None @pytest.mark.unitslow -@patch('platform.system') +@patch("platform.system") def test_ipToNodeId_all(mock_platform_system, caplog, iface_with_nodes): """Test _ipToNodeId()""" iface = iface_with_nodes iface.noProto = True a_mock = MagicMock() - a_mock.return_value = 'Linux' + a_mock.return_value = "Linux" mock_platform_system.side_effect = a_mock with caplog.at_level(logging.DEBUG): - with patch('socket.socket'): + with patch("socket.socket"): tun = Tunnel(iface) - nodeid = tun._ipToNodeId(b'\x00\x00\xff\xff') - assert nodeid == '^all' + nodeid = tun._ipToNodeId(b"\x00\x00\xff\xff") + assert nodeid == "^all" diff --git a/meshtastic/tests/test_util.py b/meshtastic/tests/test_util.py index 48061ea..df760d1 100644 --- a/meshtastic/tests/test_util.py +++ b/meshtastic/tests/test_util.py @@ -1,114 +1,131 @@ """Meshtastic unit tests for util.py""" -import re import logging - +import re from unittest.mock import patch + import pytest -from meshtastic.util import (fixme, stripnl, pskToString, our_exit, - support_info, genPSK256, fromStr, fromPSK, - quoteBooleans, catchAndIgnore, - remove_keys_from_dict, Timeout, hexstr, - ipstr, readnet_u16, findPorts, convert_mac_addr, - snake_to_camel, camel_to_snake, eliminate_duplicate_port, - is_windows11, active_ports_on_supported_devices) - from meshtastic.supported_device import SupportedDevice +from meshtastic.util import ( + Timeout, + active_ports_on_supported_devices, + camel_to_snake, + catchAndIgnore, + convert_mac_addr, + eliminate_duplicate_port, + findPorts, + fixme, + fromPSK, + fromStr, + genPSK256, + hexstr, + ipstr, + is_windows11, + our_exit, + pskToString, + quoteBooleans, + readnet_u16, + remove_keys_from_dict, + snake_to_camel, + stripnl, + support_info, +) @pytest.mark.unit def test_genPSK256(): """Test genPSK256""" - assert genPSK256() != '' + assert genPSK256() != "" @pytest.mark.unit def test_fromStr(): """Test fromStr""" - assert fromStr('') == b'' - assert fromStr('0x12') == b'\x12' - assert fromStr('t') - assert fromStr('T') - assert fromStr('true') - assert fromStr('True') - assert fromStr('yes') - assert fromStr('Yes') - assert fromStr('f') is False - assert fromStr('F') is False - assert fromStr('false') is False - assert fromStr('False') is False - assert fromStr('no') is False - assert fromStr('No') is False - assert fromStr('100.01') == 100.01 - assert fromStr('123') == 123 - assert fromStr('abc') == 'abc' - assert fromStr('123456789') == 123456789 + assert fromStr("") == b"" + assert fromStr("0x12") == b"\x12" + assert fromStr("t") + assert fromStr("T") + assert fromStr("true") + assert fromStr("True") + assert fromStr("yes") + assert fromStr("Yes") + assert fromStr("f") is False + assert fromStr("F") is False + assert fromStr("false") is False + assert fromStr("False") is False + assert fromStr("no") is False + assert fromStr("No") is False + assert fromStr("100.01") == 100.01 + assert fromStr("123") == 123 + assert fromStr("abc") == "abc" + assert fromStr("123456789") == 123456789 @pytest.mark.unitslow def test_quoteBooleans(): """Test quoteBooleans""" - assert quoteBooleans('') == '' - assert quoteBooleans('foo') == 'foo' - assert quoteBooleans('true') == 'true' - assert quoteBooleans('false') == 'false' - assert quoteBooleans(': true') == ": 'true'" - assert quoteBooleans(': false') == ": 'false'" + assert quoteBooleans("") == "" + assert quoteBooleans("foo") == "foo" + assert quoteBooleans("true") == "true" + assert quoteBooleans("false") == "false" + assert quoteBooleans(": true") == ": 'true'" + assert quoteBooleans(": false") == ": 'false'" + @pytest.mark.unit def test_fromPSK(): """Test fromPSK""" - assert fromPSK('random') != '' - assert fromPSK('none') == b'\x00' - assert fromPSK('default') == b'\x01' - assert fromPSK('simple22') == b'\x17' - assert fromPSK('trash') == 'trash' + assert fromPSK("random") != "" + assert fromPSK("none") == b"\x00" + assert fromPSK("default") == b"\x01" + assert fromPSK("simple22") == b"\x17" + assert fromPSK("trash") == "trash" @pytest.mark.unit def test_stripnl(): """Test stripnl""" - assert stripnl('') == '' - assert stripnl('a\n') == 'a' - assert stripnl(' a \n ') == 'a' - assert stripnl('a\nb') == 'a b' + assert stripnl("") == "" + assert stripnl("a\n") == "a" + assert stripnl(" a \n ") == "a" + assert stripnl("a\nb") == "a b" @pytest.mark.unit def test_pskToString_empty_string(): """Test pskToString empty string""" - assert pskToString('') == 'unencrypted' + assert pskToString("") == "unencrypted" @pytest.mark.unit def test_pskToString_string(): """Test pskToString string""" - assert pskToString('hunter123') == 'secret' + assert pskToString("hunter123") == "secret" @pytest.mark.unit def test_pskToString_one_byte_zero_value(): """Test pskToString one byte that is value of 0""" - assert pskToString(bytes([0x00])) == 'unencrypted' + assert pskToString(bytes([0x00])) == "unencrypted" @pytest.mark.unitslow def test_pskToString_one_byte_non_zero_value(): """Test pskToString one byte that is non-zero""" - assert pskToString(bytes([0x01])) == 'default' + assert pskToString(bytes([0x01])) == "default" @pytest.mark.unitslow def test_pskToString_many_bytes(): """Test pskToString many bytes""" - assert pskToString(bytes([0x02, 0x01])) == 'secret' + assert pskToString(bytes([0x02, 0x01])) == "secret" @pytest.mark.unit def test_pskToString_simple(): """Test pskToString simple""" - assert pskToString(bytes([0x03])) == 'simple2' + assert pskToString(bytes([0x03])) == "simple2" @pytest.mark.unitslow @@ -117,8 +134,8 @@ def test_our_exit_zero_return_value(capsys): with pytest.raises(SystemExit) as pytest_wrapped_e: our_exit("Warning: Some message", 0) out, err = capsys.readouterr() - assert re.search(r'Warning: Some message', out, re.MULTILINE) - assert err == '' + assert re.search(r"Warning: Some message", out, re.MULTILINE) + assert err == "" assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 @@ -129,8 +146,8 @@ def test_our_exit_non_zero_return_value(capsys): with pytest.raises(SystemExit) as pytest_wrapped_e: our_exit("Error: Some message", 1) out, err = capsys.readouterr() - assert re.search(r'Error: Some message', out, re.MULTILINE) - assert err == '' + assert re.search(r"Error: Some message", out, re.MULTILINE) + assert err == "" assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 @@ -148,21 +165,23 @@ def test_support_info(capsys): """Test support_info""" support_info() out, err = capsys.readouterr() - assert re.search(r'System', out, re.MULTILINE) - assert re.search(r'Platform', out, re.MULTILINE) - assert re.search(r'Machine', out, re.MULTILINE) - assert re.search(r'Executable', out, re.MULTILINE) - assert err == '' + assert re.search(r"System", out, re.MULTILINE) + assert re.search(r"Platform", out, re.MULTILINE) + assert re.search(r"Machine", out, re.MULTILINE) + assert re.search(r"Executable", out, re.MULTILINE) + assert err == "" @pytest.mark.unit def test_catchAndIgnore(caplog): """Test catchAndIgnore() does not actually throw an exception, but just logs""" + def some_closure(): - raise Exception('foo') + raise Exception("foo") + with caplog.at_level(logging.DEBUG): catchAndIgnore("something", some_closure) - assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE) + assert re.search(r"Exception thrown in something", caplog.text, re.MULTILINE) @pytest.mark.unitslow @@ -174,35 +193,35 @@ def test_remove_keys_from_dict_empty_keys_empty_dict(): @pytest.mark.unitslow def test_remove_keys_from_dict_empty_dict(): """Test when dict is empty""" - assert not remove_keys_from_dict(('a'), {}) + assert not remove_keys_from_dict(("a"), {}) @pytest.mark.unit def test_remove_keys_from_dict_empty_keys(): """Test when keys is empty""" - assert remove_keys_from_dict((), {'a':1}) == {'a':1} + assert remove_keys_from_dict((), {"a": 1}) == {"a": 1} @pytest.mark.unitslow def test_remove_keys_from_dict(): """Test remove_keys_from_dict()""" - assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1} + assert remove_keys_from_dict(("b"), {"a": 1, "b": 2}) == {"a": 1} @pytest.mark.unitslow def test_remove_keys_from_dict_multiple_keys(): """Test remove_keys_from_dict()""" - keys = ('a', 'b') - adict = {'a': 1, 'b': 2, 'c': 3} - assert remove_keys_from_dict(keys, adict) == {'c':3} + keys = ("a", "b") + adict = {"a": 1, "b": 2, "c": 3} + assert remove_keys_from_dict(keys, adict) == {"c": 3} @pytest.mark.unit def test_remove_keys_from_dict_nested(): """Test remove_keys_from_dict()""" - keys = ('b') - adict = {'a': {'b': 1}, 'b': 2, 'c': 3} - exp = {'a': {}, 'c': 3} + keys = "b" + adict = {"a": {"b": 1}, "b": 2, "c": 3} + exp = {"a": {}, "c": 3} assert remove_keys_from_dict(keys, adict) == exp @@ -210,8 +229,8 @@ def test_remove_keys_from_dict_nested(): def test_Timeout_not_found(): """Test Timeout()""" to = Timeout(0.2) - attrs = ('foo') - to.waitForSet('bar', attrs) + attrs = "foo" + to.waitForSet("bar", attrs) @pytest.mark.unitslow @@ -219,31 +238,31 @@ def test_Timeout_found(): """Test Timeout()""" to = Timeout(0.2) attrs = () - to.waitForSet('bar', attrs) + to.waitForSet("bar", attrs) @pytest.mark.unitslow def test_hexstr(): """Test hexstr()""" - assert hexstr(b'123') == '31:32:33' - assert hexstr(b'') == '' + assert hexstr(b"123") == "31:32:33" + assert hexstr(b"") == "" @pytest.mark.unitslow def test_ipstr(): """Test ipstr()""" - assert ipstr(b'1234') == '49.50.51.52' - assert ipstr(b'') == '' + assert ipstr(b"1234") == "49.50.51.52" + assert ipstr(b"") == "" @pytest.mark.unitslow def test_readnet_u16(): """Test readnet_u16()""" - assert readnet_u16(b'123456', 2) == 13108 + assert readnet_u16(b"123456", 2) == 13108 @pytest.mark.unitslow -@patch('serial.tools.list_ports.comports', return_value=[]) +@patch("serial.tools.list_ports.comports", return_value=[]) def test_findPorts_when_none_found(patch_comports): """Test findPorts()""" assert not findPorts() @@ -251,99 +270,134 @@ def test_findPorts_when_none_found(patch_comports): @pytest.mark.unitslow -@patch('serial.tools.list_ports.comports') +@patch("serial.tools.list_ports.comports") def test_findPorts_when_duplicate_found_and_duplicate_option_used(patch_comports): """Test findPorts()""" + class TempPort: - """ temp class for port""" + """temp class for port""" + def __init__(self, device=None, vid=None): self.device = device self.vid = vid - fake1 = TempPort('/dev/cu.usbserial-1430', vid='fake1') - fake2 = TempPort('/dev/cu.wchusbserial1430', vid='fake2') + + fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1") + fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2") patch_comports.return_value = [fake1, fake2] - assert findPorts(eliminate_duplicates=True) == ['/dev/cu.wchusbserial1430'] + assert findPorts(eliminate_duplicates=True) == ["/dev/cu.wchusbserial1430"] patch_comports.assert_called() @pytest.mark.unitslow -@patch('serial.tools.list_ports.comports') -def test_findPorts_when_duplicate_found_and_duplicate_option_used_ports_reversed(patch_comports): +@patch("serial.tools.list_ports.comports") +def test_findPorts_when_duplicate_found_and_duplicate_option_used_ports_reversed( + patch_comports, +): """Test findPorts()""" + class TempPort: - """ temp class for port""" + """temp class for port""" + def __init__(self, device=None, vid=None): self.device = device self.vid = vid - fake1 = TempPort('/dev/cu.usbserial-1430', vid='fake1') - fake2 = TempPort('/dev/cu.wchusbserial1430', vid='fake2') + + fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1") + fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2") patch_comports.return_value = [fake2, fake1] - assert findPorts(eliminate_duplicates=True) == ['/dev/cu.wchusbserial1430'] + assert findPorts(eliminate_duplicates=True) == ["/dev/cu.wchusbserial1430"] patch_comports.assert_called() @pytest.mark.unitslow -@patch('serial.tools.list_ports.comports') +@patch("serial.tools.list_ports.comports") def test_findPorts_when_duplicate_found_and_duplicate_option_not_used(patch_comports): """Test findPorts()""" + class TempPort: - """ temp class for port""" + """temp class for port""" + def __init__(self, device=None, vid=None): self.device = device self.vid = vid - fake1 = TempPort('/dev/cu.usbserial-1430', vid='fake1') - fake2 = TempPort('/dev/cu.wchusbserial1430', vid='fake2') + + fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1") + fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2") patch_comports.return_value = [fake1, fake2] - assert findPorts() == ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430'] + assert findPorts() == ["/dev/cu.usbserial-1430", "/dev/cu.wchusbserial1430"] patch_comports.assert_called() @pytest.mark.unitslow def test_convert_mac_addr(): """Test convert_mac_addr()""" - assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b' - assert convert_mac_addr('fd:cd:20:17:28:5b') == 'fd:cd:20:17:28:5b' - assert convert_mac_addr('') == '' + assert convert_mac_addr("/c0gFyhb") == "fd:cd:20:17:28:5b" + assert convert_mac_addr("fd:cd:20:17:28:5b") == "fd:cd:20:17:28:5b" + assert convert_mac_addr("") == "" @pytest.mark.unit def test_snake_to_camel(): """Test snake_to_camel""" - assert snake_to_camel('') == '' - assert snake_to_camel('foo') == 'foo' - assert snake_to_camel('foo_bar') == 'fooBar' - assert snake_to_camel('fooBar') == 'fooBar' + assert snake_to_camel("") == "" + assert snake_to_camel("foo") == "foo" + assert snake_to_camel("foo_bar") == "fooBar" + assert snake_to_camel("fooBar") == "fooBar" @pytest.mark.unit def test_camel_to_snake(): """Test camel_to_snake""" - assert camel_to_snake('') == '' - assert camel_to_snake('foo') == 'foo' - assert camel_to_snake('Foo') == 'foo' - assert camel_to_snake('fooBar') == 'foo_bar' - assert camel_to_snake('fooBarBaz') == 'foo_bar_baz' + assert camel_to_snake("") == "" + assert camel_to_snake("foo") == "foo" + assert camel_to_snake("Foo") == "foo" + assert camel_to_snake("fooBar") == "foo_bar" + assert camel_to_snake("fooBarBaz") == "foo_bar_baz" @pytest.mark.unit def test_eliminate_duplicate_port(): """Test eliminate_duplicate_port()""" assert not eliminate_duplicate_port([]) - assert eliminate_duplicate_port(['/dev/fake']) == ['/dev/fake'] - assert eliminate_duplicate_port(['/dev/fake', '/dev/fake1']) == ['/dev/fake', '/dev/fake1'] - assert eliminate_duplicate_port(['/dev/fake', '/dev/fake1', '/dev/fake2']) == ['/dev/fake', '/dev/fake1', '/dev/fake2'] - assert eliminate_duplicate_port(['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430']) == ['/dev/cu.wchusbserial1430'] - assert eliminate_duplicate_port(['/dev/cu.wchusbserial1430', '/dev/cu.usbserial-1430']) == ['/dev/cu.wchusbserial1430'] - assert eliminate_duplicate_port(['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001']) == ['/dev/cu.usbserial-0001'] - assert eliminate_duplicate_port(['/dev/cu.usbserial-0001', '/dev/cu.SLAB_USBtoUART']) == ['/dev/cu.usbserial-0001'] - assert eliminate_duplicate_port(['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301']) == ['/dev/cu.wchusbserial11301'] - assert eliminate_duplicate_port(['/dev/cu.wchusbserial11301', '/dev/cu.usbmodem11301']) == ['/dev/cu.wchusbserial11301'] - assert eliminate_duplicate_port(['/dev/cu.usbmodem53230051441', '/dev/cu.wchusbserial53230051441']) == ['/dev/cu.wchusbserial53230051441'] - assert eliminate_duplicate_port(['/dev/cu.wchusbserial53230051441', '/dev/cu.usbmodem53230051441']) == ['/dev/cu.wchusbserial53230051441'] + assert eliminate_duplicate_port(["/dev/fake"]) == ["/dev/fake"] + assert eliminate_duplicate_port(["/dev/fake", "/dev/fake1"]) == [ + "/dev/fake", + "/dev/fake1", + ] + assert eliminate_duplicate_port(["/dev/fake", "/dev/fake1", "/dev/fake2"]) == [ + "/dev/fake", + "/dev/fake1", + "/dev/fake2", + ] + assert eliminate_duplicate_port( + ["/dev/cu.usbserial-1430", "/dev/cu.wchusbserial1430"] + ) == ["/dev/cu.wchusbserial1430"] + assert eliminate_duplicate_port( + ["/dev/cu.wchusbserial1430", "/dev/cu.usbserial-1430"] + ) == ["/dev/cu.wchusbserial1430"] + assert eliminate_duplicate_port( + ["/dev/cu.SLAB_USBtoUART", "/dev/cu.usbserial-0001"] + ) == ["/dev/cu.usbserial-0001"] + assert eliminate_duplicate_port( + ["/dev/cu.usbserial-0001", "/dev/cu.SLAB_USBtoUART"] + ) == ["/dev/cu.usbserial-0001"] + assert eliminate_duplicate_port( + ["/dev/cu.usbmodem11301", "/dev/cu.wchusbserial11301"] + ) == ["/dev/cu.wchusbserial11301"] + assert eliminate_duplicate_port( + ["/dev/cu.wchusbserial11301", "/dev/cu.usbmodem11301"] + ) == ["/dev/cu.wchusbserial11301"] + assert eliminate_duplicate_port( + ["/dev/cu.usbmodem53230051441", "/dev/cu.wchusbserial53230051441"] + ) == ["/dev/cu.wchusbserial53230051441"] + assert eliminate_duplicate_port( + ["/dev/cu.wchusbserial53230051441", "/dev/cu.usbmodem53230051441"] + ) == ["/dev/cu.wchusbserial53230051441"] -@patch('platform.version', return_value='10.0.22000.194') -@patch('platform.release', return_value='10') -@patch('platform.system', return_value='Windows') + +@patch("platform.version", return_value="10.0.22000.194") +@patch("platform.release", return_value="10") +@patch("platform.system", return_value="Windows") def test_is_windows11_true(patched_platform, patched_release, patched_version): """Test is_windows11()""" assert is_windows11() is True @@ -352,9 +406,9 @@ def test_is_windows11_true(patched_platform, patched_release, patched_version): patched_version.assert_called() -@patch('platform.version', return_value='10.0.a2200.foo') # made up -@patch('platform.release', return_value='10') -@patch('platform.system', return_value='Windows') +@patch("platform.version", return_value="10.0.a2200.foo") # made up +@patch("platform.release", return_value="10") +@patch("platform.system", return_value="Windows") def test_is_windows11_true2(patched_platform, patched_release, patched_version): """Test is_windows11()""" assert is_windows11() is False @@ -363,9 +417,9 @@ def test_is_windows11_true2(patched_platform, patched_release, patched_version): patched_version.assert_called() -@patch('platform.version', return_value='10.0.17763') # windows 10 home -@patch('platform.release', return_value='10') -@patch('platform.system', return_value='Windows') +@patch("platform.version", return_value="10.0.17763") # windows 10 home +@patch("platform.release", return_value="10") +@patch("platform.system", return_value="Windows") def test_is_windows11_false(patched_platform, patched_release, patched_version): """Test is_windows11()""" assert is_windows11() is False @@ -374,8 +428,8 @@ def test_is_windows11_false(patched_platform, patched_release, patched_version): patched_version.assert_called() -@patch('platform.release', return_value='8.1') -@patch('platform.system', return_value='Windows') +@patch("platform.release", return_value="8.1") +@patch("platform.system", return_value="Windows") def test_is_windows11_false_win8_1(patched_platform, patched_release): """Test is_windows11()""" assert is_windows11() is False @@ -384,7 +438,7 @@ def test_is_windows11_false_win8_1(patched_platform, patched_release): @pytest.mark.unit -@patch('platform.system', return_value='Linux') +@patch("platform.system", return_value="Linux") def test_active_ports_on_supported_devices_empty(mock_platform): """Test active_ports_on_supported_devices()""" sds = set() @@ -393,66 +447,101 @@ def test_active_ports_on_supported_devices_empty(mock_platform): @pytest.mark.unit -@patch('subprocess.getstatusoutput') -@patch('platform.system', return_value='Linux') +@patch("subprocess.getstatusoutput") +@patch("platform.system", return_value="Linux") def test_active_ports_on_supported_devices_linux(mock_platform, mock_sp): """Test active_ports_on_supported_devices()""" - mock_sp.return_value = (None, 'crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/ttyUSBfake') - fake_device = SupportedDevice(name='a', for_firmware='heltec-v2.1', baseport_on_linux='ttyUSB') + mock_sp.return_value = ( + None, + "crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/ttyUSBfake", + ) + fake_device = SupportedDevice( + name="a", for_firmware="heltec-v2.1", baseport_on_linux="ttyUSB" + ) fake_supported_devices = [fake_device] - assert active_ports_on_supported_devices(fake_supported_devices) == {'/dev/ttyUSBfake'} + assert active_ports_on_supported_devices(fake_supported_devices) == { + "/dev/ttyUSBfake" + } mock_platform.assert_called() mock_sp.assert_called() @pytest.mark.unit -@patch('subprocess.getstatusoutput') -@patch('platform.system', return_value='Darwin') +@patch("subprocess.getstatusoutput") +@patch("platform.system", return_value="Darwin") def test_active_ports_on_supported_devices_mac(mock_platform, mock_sp): """Test active_ports_on_supported_devices()""" - mock_sp.return_value = (None, 'crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/cu.usbserial-foo') - fake_device = SupportedDevice(name='a', for_firmware='heltec-v2.1', baseport_on_linux='cu.usbserial-') + mock_sp.return_value = ( + None, + "crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/cu.usbserial-foo", + ) + fake_device = SupportedDevice( + name="a", for_firmware="heltec-v2.1", baseport_on_linux="cu.usbserial-" + ) fake_supported_devices = [fake_device] - assert active_ports_on_supported_devices(fake_supported_devices) == {'/dev/cu.usbserial-foo'} + assert active_ports_on_supported_devices(fake_supported_devices) == { + "/dev/cu.usbserial-foo" + } mock_platform.assert_called() mock_sp.assert_called() @pytest.mark.unit -@patch('meshtastic.util.detect_windows_port', return_value={'COM2'}) -@patch('platform.system', return_value='Windows') +@patch("meshtastic.util.detect_windows_port", return_value={"COM2"}) +@patch("platform.system", return_value="Windows") def test_active_ports_on_supported_devices_win(mock_platform, mock_dwp): """Test active_ports_on_supported_devices()""" - fake_device = SupportedDevice(name='a', for_firmware='heltec-v2.1') + fake_device = SupportedDevice(name="a", for_firmware="heltec-v2.1") fake_supported_devices = [fake_device] - assert active_ports_on_supported_devices(fake_supported_devices) == {'COM2'} + assert active_ports_on_supported_devices(fake_supported_devices) == {"COM2"} mock_platform.assert_called() mock_dwp.assert_called() @pytest.mark.unit -@patch('subprocess.getstatusoutput') -@patch('platform.system', return_value='Darwin') -def test_active_ports_on_supported_devices_mac_no_duplicates_check(mock_platform, mock_sp): +@patch("subprocess.getstatusoutput") +@patch("platform.system", return_value="Darwin") +def test_active_ports_on_supported_devices_mac_no_duplicates_check( + mock_platform, mock_sp +): """Test active_ports_on_supported_devices()""" - mock_sp.return_value = (None, ('crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n' - 'crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441')) - fake_device = SupportedDevice(name='a', for_firmware='tbeam', baseport_on_mac='cu.usbmodem') + mock_sp.return_value = ( + None, + ( + "crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n" + "crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441" + ), + ) + fake_device = SupportedDevice( + name="a", for_firmware="tbeam", baseport_on_mac="cu.usbmodem" + ) fake_supported_devices = [fake_device] - assert active_ports_on_supported_devices(fake_supported_devices, False) == {'/dev/cu.usbmodem53230051441', '/dev/cu.wchusbserial53230051441'} + assert active_ports_on_supported_devices(fake_supported_devices, False) == { + "/dev/cu.usbmodem53230051441", + "/dev/cu.wchusbserial53230051441", + } mock_platform.assert_called() mock_sp.assert_called() @pytest.mark.unit -@patch('subprocess.getstatusoutput') -@patch('platform.system', return_value='Darwin') +@patch("subprocess.getstatusoutput") +@patch("platform.system", return_value="Darwin") def test_active_ports_on_supported_devices_mac_duplicates_check(mock_platform, mock_sp): """Test active_ports_on_supported_devices()""" - mock_sp.return_value = (None, ('crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n' - 'crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441')) - fake_device = SupportedDevice(name='a', for_firmware='tbeam', baseport_on_mac='cu.usbmodem') + mock_sp.return_value = ( + None, + ( + "crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n" + "crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441" + ), + ) + fake_device = SupportedDevice( + name="a", for_firmware="tbeam", baseport_on_mac="cu.usbmodem" + ) fake_supported_devices = [fake_device] - assert active_ports_on_supported_devices(fake_supported_devices, True) == {'/dev/cu.wchusbserial53230051441'} + assert active_ports_on_supported_devices(fake_supported_devices, True) == { + "/dev/cu.wchusbserial53230051441" + } mock_platform.assert_called() mock_sp.assert_called() diff --git a/meshtastic/tunnel.py b/meshtastic/tunnel.py index 8f9ff87..65c5f5c 100644 --- a/meshtastic/tunnel.py +++ b/meshtastic/tunnel.py @@ -16,20 +16,20 @@ """ import logging -import threading import platform -from pubsub import pub +import threading +from pubsub import pub from pytap2 import TapDevice from meshtastic import portnums_pb2 -from meshtastic.util import ipstr, readnet_u16 from meshtastic.globals import Globals +from meshtastic.util import ipstr, readnet_u16 -def onTunnelReceive(packet, interface): # pylint: disable=W0613 +def onTunnelReceive(packet, interface): # pylint: disable=W0613 """Callback for received tunneled messages from mesh.""" - logging.debug(f'in onTunnelReceive()') + logging.debug(f"in onTunnelReceive()") our_globals = Globals.getInstance() tunnelInstance = our_globals.get_tunnelInstance() tunnelInstance.onReceive(packet) @@ -38,7 +38,7 @@ def onTunnelReceive(packet, interface): # pylint: disable=W0613 class Tunnel: """A TUN based IP tunnel over meshtastic""" - def __init__(self, iface, subnet='10.115', netmask="255.255.0.0"): + def __init__(self, iface, subnet="10.115", netmask="255.255.0.0"): """ Constructor @@ -52,7 +52,7 @@ class Tunnel: self.iface = iface self.subnetPrefix = subnet - if platform.system() != 'Linux': + if platform.system() != "Linux": raise Exception("Tunnel() can only be run instantiated on a Linux system") our_globals = Globals.getInstance() @@ -80,8 +80,10 @@ class Tunnel: self.LOG_TRACE = 5 # TODO: check if root? - logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\ - "feature to work). Mesh members:") + logging.info( + "Starting IP to mesh tunnel (you must be root for this *pre-alpha* " + "feature to work). Mesh members:" + ) pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP") myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num) @@ -96,7 +98,9 @@ class Tunnel: # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data self.tun = None if self.iface.noProto: - logging.warning(f"Not creating a TapDevice() because it is disabled by noProto") + logging.warning( + f"Not creating a TapDevice() because it is disabled by noProto" + ) else: self.tun = TapDevice(name="mesh") self.tun.up() @@ -104,10 +108,14 @@ class Tunnel: self._rxThread = None if self.iface.noProto: - logging.warning(f"Not starting TUN reader because it is disabled by noProto") + logging.warning( + f"Not starting TUN reader because it is disabled by noProto" + ) else: logging.debug(f"starting TUN reader, our IP address is {myAddr}") - self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True) + self._rxThread = threading.Thread( + target=self.__tunReader, args=(), daemon=True + ) self._rxThread.start() def onReceive(self, packet): @@ -132,15 +140,19 @@ class Tunnel: ignore = False # Assume we will be forwarding the packet if protocol in self.protocolBlacklist: ignore = True - logging.log(self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}") + logging.log( + self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}" + ) elif protocol == 0x01: # ICMP icmpType = p[20] icmpCode = p[21] checksum = p[22:24] # pylint: disable=line-too-long - logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}") + logging.debug( + f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}" + ) # reply to pings (swap src and dest but keep rest of packet unchanged) - #pingback = p[:12]+p[16:20]+p[12:16]+p[20:] + # pingback = p[:12]+p[16:20]+p[12:16]+p[20:] # tap.write(pingback) elif protocol == 0x11: # UDP srcport = readnet_u16(p, subheader) @@ -159,8 +171,10 @@ class Tunnel: else: logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}") else: - logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, "\ - "src={ipstr(srcaddr)}, dest={ipstr(destAddr)}") + logging.warning( + f"forwarding unexpected protocol 0x{protocol:02x}, " + "src={ipstr(srcaddr)}, dest={ipstr(destAddr)}" + ) return ignore @@ -169,7 +183,7 @@ class Tunnel: logging.debug("TUN reader running") while True: p = tap.read() - #logging.debug(f"IP packet received on TUN interface, type={type(p)}") + # logging.debug(f"IP packet received on TUN interface, type={type(p)}") destAddr = p[16:20] if not self._shouldFilterPacket(p): @@ -179,11 +193,11 @@ class Tunnel: # We only consider the last 16 bits of the nodenum for IP address matching ipBits = ipAddr[2] * 256 + ipAddr[3] - if ipBits == 0xffff: + if ipBits == 0xFFFF: return "^all" for node in self.iface.nodes.values(): - nodeNum = node["num"] & 0xffff + nodeNum = node["num"] & 0xFFFF # logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}") if (nodeNum) == ipBits: return node["user"]["id"] @@ -196,11 +210,14 @@ class Tunnel: """Forward the provided IP packet into the mesh""" nodeId = self._ipToNodeId(destAddr) if nodeId is not None: - logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}") - self.iface.sendData( - p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False) + logging.debug( + f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}" + ) + self.iface.sendData(p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False) else: - logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}") + logging.warning( + f"Dropping packet because no node found for destIP={ipstr(destAddr)}" + ) def close(self): """Close""" diff --git a/meshtastic/util.py b/meshtastic/util.py index 9d4bcc5..083e9dd 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -1,21 +1,21 @@ """Utility functions. """ +import base64 +import logging +import os +import platform +import re +import subprocess +import sys +import threading +import time import traceback from queue import Queue -import os -import re -import sys -import base64 -import time -import platform -import logging -import threading -import subprocess -import serial -import serial.tools.list_ports + import pkg_resources import requests - +import serial +import serial.tools.list_ports from meshtastic.supported_device import supported_devices @@ -25,12 +25,13 @@ blacklistVids = dict.fromkeys([0x1366]) def quoteBooleans(a_string): """Quote booleans - given a string that contains ": true", replace with ": 'true'" (or false) + given a string that contains ": true", replace with ": 'true'" (or false) """ tmp = a_string.replace(": true", ": 'true'") tmp = tmp.replace(": false", ": 'false'") return tmp + def genPSK256(): """Generate a random preshared key""" return os.urandom(32) @@ -63,10 +64,10 @@ def fromStr(valstr): """ if len(valstr) == 0: # Treat an emptystring as an empty bytes val = bytes() - elif valstr.startswith('0x'): + elif valstr.startswith("0x"): # if needed convert to string with asBytes.decode('utf-8') val = bytes.fromhex(valstr[2:]) - elif valstr.startswith('base64:'): + elif valstr.startswith("base64:"): val = base64.b64decode(valstr[7:]) elif valstr.lower() in {"t", "true", "yes"}: val = True @@ -102,7 +103,7 @@ def pskToString(psk: bytes): def stripnl(s): """Remove newlines from a string (and remove extra whitespace)""" s = str(s).replace("\n", " ") - return ' '.join(s.split()) + return " ".join(s.split()) def fixme(message): @@ -125,9 +126,15 @@ def findPorts(eliminate_duplicates=False): Returns: list -- a list of device paths """ - l = list(map(lambda port: port.device, - filter(lambda port: port.vid is not None and port.vid not in blacklistVids, - serial.tools.list_ports.comports()))) + l = list( + map( + lambda port: port.device, + filter( + lambda port: port.vid is not None and port.vid not in blacklistVids, + serial.tools.list_ports.comports(), + ), + ) + ) l.sort() if eliminate_duplicates: l = eliminate_duplicate_port(l) @@ -136,6 +143,7 @@ def findPorts(eliminate_duplicates=False): class dotdict(dict): """dot.notation access to dictionary attributes""" + __getattr__ = dict.get __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ @@ -143,6 +151,7 @@ class dotdict(dict): class Timeout: """Timeout class""" + def __init__(self, maxSecs=20): self.expireTime = 0 self.sleepInterval = 0.1 @@ -161,7 +170,9 @@ class Timeout: time.sleep(self.sleepInterval) return False - def waitForAckNak(self, acknowledgment, attrs=('receivedAck', 'receivedNak', 'receivedImplAck')): + def waitForAckNak( + self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck") + ): """Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received.""" self.reset() while time.time() < self.expireTime: @@ -171,7 +182,7 @@ class Timeout: time.sleep(self.sleepInterval) return False - def waitForTraceRoute(self, waitFactor, acknowledgment, attr='receivedTraceRoute'): + def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute"): """Block until traceroute response is received. Returns True if traceroute response has been received.""" self.expireTimeout *= waitFactor self.reset() @@ -182,8 +193,10 @@ class Timeout: time.sleep(self.sleepInterval) return False + class Acknowledgment: "A class that records which type of acknowledgment was just received, if any." + def __init__(self): """initialize""" self.receivedAck = False @@ -198,7 +211,8 @@ class Acknowledgment: self.receivedImplAck = False self.receivedTraceRoute = False -class DeferredExecution(): + +class DeferredExecution: """A thread that accepts closures to run, and runs them as they are received""" def __init__(self, name=None): @@ -208,7 +222,7 @@ class DeferredExecution(): self.thread.start() def queueWork(self, runnable): - """ Queue up the work""" + """Queue up the work""" self.queue.put(runnable) def _run(self): @@ -217,13 +231,15 @@ class DeferredExecution(): o = self.queue.get() o() except: - logging.error(f"Unexpected error in deferred execution {sys.exc_info()[0]}") + logging.error( + f"Unexpected error in deferred execution {sys.exc_info()[0]}" + ) print(traceback.format_exc()) -def our_exit(message, return_value = 1): +def our_exit(message, return_value=1): """Print the message and return a value. - return_value defaults to 1 (non-successful) + return_value defaults to 1 (non-successful) """ print(message) sys.exit(return_value) @@ -231,32 +247,36 @@ def our_exit(message, return_value = 1): def support_info(): """Print out info that helps troubleshooting of the cli.""" - print('') - print('If having issues with meshtastic cli or python library') - print('or wish to make feature requests, visit:') - print('https://github.com/meshtastic/python/issues') - print('When adding an issue, be sure to include the following info:') - print(f' System: {platform.system()}') - print(f' Platform: {platform.platform()}') - print(f' Release: {platform.uname().release}') - print(f' Machine: {platform.uname().machine}') - print(f' Encoding (stdin): {sys.stdin.encoding}') - print(f' Encoding (stdout): {sys.stdout.encoding}') + print("") + print("If having issues with meshtastic cli or python library") + print("or wish to make feature requests, visit:") + print("https://github.com/meshtastic/python/issues") + print("When adding an issue, be sure to include the following info:") + print(f" System: {platform.system()}") + print(f" Platform: {platform.platform()}") + print(f" Release: {platform.uname().release}") + print(f" Machine: {platform.uname().machine}") + print(f" Encoding (stdin): {sys.stdin.encoding}") + print(f" Encoding (stdout): {sys.stdout.encoding}") the_version = pkg_resources.get_distribution("meshtastic").version pypi_version = check_if_newer_version() if pypi_version: - print(f' meshtastic: v{the_version} (*** newer version v{pypi_version} available ***)') + 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('') - print('Please add the output from the command: meshtastic --info') + 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("") + print("Please add the output from the command: meshtastic --info") def remove_keys_from_dict(keys, adict): """Return a dictionary without some keys in it. - Will removed nested keys. + Will removed nested keys. """ for key in keys: try: @@ -271,12 +291,12 @@ def remove_keys_from_dict(keys, adict): def hexstr(barray): """Print a string of hex digits""" - return ":".join(f'{x:02x}' for x in barray) + return ":".join(f"{x:02x}" for x in barray) def ipstr(barray): """Print a string of ip digits""" - return ".".join(f'{x}' for x in barray) + return ".".join(f"{x}" for x in barray) def readnet_u16(p, offset): @@ -286,8 +306,8 @@ def readnet_u16(p, offset): def convert_mac_addr(val): """Convert the base 64 encoded value to a mac address - val - base64 encoded value (ex: '/c0gFyhb')) - returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b') + val - base64 encoded value (ex: '/c0gFyhb')) + returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b') """ if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val): val_as_bytes = base64.b64decode(val) @@ -298,21 +318,23 @@ def convert_mac_addr(val): def snake_to_camel(a_string): """convert snake_case to camelCase""" # split underscore using split - temp = a_string.split('_') + temp = a_string.split("_") # joining result - result = temp[0] + ''.join(ele.title() for ele in temp[1:]) + result = temp[0] + "".join(ele.title() for ele in temp[1:]) return result def camel_to_snake(a_string): """convert camelCase to snake_case""" - return ''.join(['_'+i.lower() if i.isupper() else i for i in a_string]).lstrip('_') + return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip( + "_" + ) def detect_supported_devices(): """detect supported devices based on vendor id""" system = platform.system() - #print(f'system:{system}') + # print(f'system:{system}') possible_devices = set() if system == "Linux": @@ -320,31 +342,33 @@ def detect_supported_devices(): # linux: use lsusb # Bus 001 Device 091: ID 10c4:ea60 Silicon Labs CP210x UART Bridge - _, lsusb_output = subprocess.getstatusoutput('lsusb') + _, lsusb_output = subprocess.getstatusoutput("lsusb") vids = get_unique_vendor_ids() for vid in vids: - #print(f'looking for {vid}...') - search = f' {vid}:' - #print(f'search:"{search}"') + # print(f'looking for {vid}...') + search = f" {vid}:" + # print(f'search:"{search}"') if re.search(search, lsusb_output, re.MULTILINE): - #print(f'Found vendor id that matches') + # print(f'Found vendor id that matches') devices = get_devices_with_vendor_id(vid) for device in devices: possible_devices.add(device) elif system == "Windows": # if windows, run Get-PnpDevice - _, sp_output = subprocess.getstatusoutput('powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;' - 'Get-PnpDevice -PresentOnly | Format-List"') - #print(f'sp_output:{sp_output}') + _, sp_output = subprocess.getstatusoutput( + 'powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;' + 'Get-PnpDevice -PresentOnly | Format-List"' + ) + # print(f'sp_output:{sp_output}') vids = get_unique_vendor_ids() for vid in vids: - #print(f'looking for {vid.upper()}...') - search = f'DeviceID.*{vid.upper()}&' - #search = f'{vid.upper()}' - #print(f'search:"{search}"') + # print(f'looking for {vid.upper()}...') + search = f"DeviceID.*{vid.upper()}&" + # search = f'{vid.upper()}' + # print(f'search:"{search}"') if re.search(search, sp_output, re.MULTILINE): - #print(f'Found vendor id that matches') + # print(f'Found vendor id that matches') devices = get_devices_with_vendor_id(vid) for device in devices: possible_devices.add(device) @@ -353,14 +377,14 @@ def detect_supported_devices(): # run: system_profiler SPUSBDataType # Note: If in boot mode, the 19003 reports same product ID as 5005. - _, sp_output = subprocess.getstatusoutput('system_profiler SPUSBDataType') + _, sp_output = subprocess.getstatusoutput("system_profiler SPUSBDataType") vids = get_unique_vendor_ids() for vid in vids: - #print(f'looking for {vid}...') - search = f'Vendor ID: 0x{vid}' - #print(f'search:"{search}"') + # print(f'looking for {vid}...') + search = f"Vendor ID: 0x{vid}" + # print(f'search:"{search}"') if re.search(search, sp_output, re.MULTILINE): - #print(f'Found vendor id that matches') + # print(f'Found vendor id that matches') devices = get_devices_with_vendor_id(vid) for device in devices: possible_devices.add(device) @@ -373,7 +397,7 @@ def detect_windows_needs_driver(sd, print_reason=False): if sd: system = platform.system() - #print(f'in detect_windows_needs_driver system:{system}') + # print(f'in detect_windows_needs_driver system:{system}') if system == "Windows": # if windows, see if we can find a DeviceId with the vendor id @@ -382,11 +406,11 @@ def detect_windows_needs_driver(sd, print_reason=False): command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'" command += ')} | Format-List"' - #print(f'command:{command}') + # print(f'command:{command}') _, sp_output = subprocess.getstatusoutput(command) - #print(f'sp_output:{sp_output}') - search = f'CM_PROB_FAILED_INSTALL' - #print(f'search:"{search}"') + # print(f'sp_output:{sp_output}') + search = f"CM_PROB_FAILED_INSTALL" + # print(f'search:"{search}"') if re.search(search, sp_output, re.MULTILINE): need_to_install_driver = True # if the want to see the reason @@ -398,30 +422,30 @@ def detect_windows_needs_driver(sd, print_reason=False): def eliminate_duplicate_port(ports): """Sometimes we detect 2 serial ports, but we really only need to use one of the ports. - ports is a list of ports - return a list with a single port to use, if it meets the duplicate port conditions + ports is a list of ports + return a list with a single port to use, if it meets the duplicate port conditions - examples: - Ports: ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430'] => ['/dev/cu.wchusbserial1430'] - Ports: ['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301'] => ['/dev/cu.wchusbserial11301'] - Ports: ['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001'] => ['/dev/cu.usbserial-0001'] + examples: + Ports: ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430'] => ['/dev/cu.wchusbserial1430'] + Ports: ['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301'] => ['/dev/cu.wchusbserial11301'] + Ports: ['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001'] => ['/dev/cu.usbserial-0001'] """ new_ports = [] if len(ports) != 2: new_ports = ports else: ports.sort() - if 'usbserial' in ports[0] and 'wchusbserial' in ports[1]: + if "usbserial" in ports[0] and "wchusbserial" in ports[1]: first = ports[0].replace("usbserial-", "") second = ports[1].replace("wchusbserial", "") if first == second: new_ports.append(ports[1]) - elif 'usbmodem' in ports[0] and 'wchusbserial' in ports[1]: + elif "usbmodem" in ports[0] and "wchusbserial" in ports[1]: first = ports[0].replace("usbmodem", "") second = ports[1].replace("wchusbserial", "") if first == second: new_ports.append(ports[1]) - elif 'SLAB_USBtoUART' in ports[0] and 'usbserial' in ports[1]: + elif "SLAB_USBtoUART" in ports[0] and "usbserial" in ports[1]: new_ports.append(ports[1]) else: new_ports = ports @@ -433,14 +457,14 @@ def is_windows11(): is_win11 = False if platform.system() == "Windows": if float(platform.release()) >= 10.0: - patch = platform.version().split('.')[2] + patch = platform.version().split(".")[2] # in case they add some number suffix later, just get first 5 chars of patch patch = patch[:5] try: if int(patch) >= 22000: is_win11 = True except Exception as e: - print(f'problem detecting win11 e:{e}') + print(f"problem detecting win11 e:{e}") return is_win11 @@ -480,46 +504,46 @@ def active_ports_on_supported_devices(sds, eliminate_duplicates=False): for bp in baseports: if system == "Linux": # see if we have any devices (ignoring any stderr output) - command = f'ls -al /dev/{bp}* 2> /dev/null' - #print(f'command:{command}') + command = f"ls -al /dev/{bp}* 2> /dev/null" + # print(f'command:{command}') _, ls_output = subprocess.getstatusoutput(command) - #print(f'ls_output:{ls_output}') + # print(f'ls_output:{ls_output}') # if we got output, there are ports if len(ls_output) > 0: - #print('got output') + # print('got output') # for each line of output - lines = ls_output.split('\n') - #print(f'lines:{lines}') + lines = ls_output.split("\n") + # print(f'lines:{lines}') for line in lines: - parts = line.split(' ') - #print(f'parts:{parts}') + parts = line.split(" ") + # print(f'parts:{parts}') port = parts[-1] - #print(f'port:{port}') + # print(f'port:{port}') ports.add(port) elif system == "Darwin": # see if we have any devices (ignoring any stderr output) - command = f'ls -al /dev/{bp}* 2> /dev/null' - #print(f'command:{command}') + command = f"ls -al /dev/{bp}* 2> /dev/null" + # print(f'command:{command}') _, ls_output = subprocess.getstatusoutput(command) - #print(f'ls_output:{ls_output}') + # print(f'ls_output:{ls_output}') # if we got output, there are ports if len(ls_output) > 0: - #print('got output') + # print('got output') # for each line of output - lines = ls_output.split('\n') - #print(f'lines:{lines}') + lines = ls_output.split("\n") + # print(f'lines:{lines}') for line in lines: - parts = line.split(' ') - #print(f'parts:{parts}') + parts = line.split(" ") + # print(f'parts:{parts}') port = parts[-1] - #print(f'port:{port}') + # print(f'port:{port}') ports.add(port) elif system == "Windows": # for each device in supported devices found for d in sds: # find the port(s) com_ports = detect_windows_port(d) - #print(f'com_ports:{com_ports}') + # print(f'com_ports:{com_ports}') # add all ports for com_port in com_ports: ports.add(com_port) @@ -538,18 +562,20 @@ def detect_windows_port(sd): system = platform.system() if system == "Windows": - command = ('powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;' - 'Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like ') + command = ( + 'powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;' + "Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like " + ) command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'" command += ')} | Format-List"' - #print(f'command:{command}') + # print(f'command:{command}') _, sp_output = subprocess.getstatusoutput(command) - #print(f'sp_output:{sp_output}') - p = re.compile(r'\(COM(.*)\)') + # print(f'sp_output:{sp_output}') + p = re.compile(r"\(COM(.*)\)") for x in p.findall(sp_output): - #print(f'x:{x}') - ports.add(f'COM{x}') + # print(f'x:{x}') + ports.add(f"COM{x}") return ports @@ -558,12 +584,13 @@ def check_if_newer_version(): pypi_version = None try: url = "https://pypi.org/pypi/meshtastic/json" - data = requests.get(url).json() + data = requests.get(url, timeout=5).json() pypi_version = data["info"]["version"] - except Exception as e: - #print(f"could not get version from pypi e:{e}") + except Exception: 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): + if pypi_version and pkg_resources.parse_version( + pypi_version + ) <= pkg_resources.parse_version(act_version): return None return pypi_version diff --git a/setup.py b/setup.py index c24aa8a..6e6d198 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ # Note: you shouldn't need to run this script manually. It is run implicitly by the pip3 install command. import pathlib + from setuptools import setup # The directory containing this file @@ -30,18 +31,25 @@ setup( ], packages=["meshtastic"], include_package_data=True, - 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'"], - extras_require={ - 'tunnel': ["pytap2>=2.0.0"] - }, - python_requires='>=3.7', + 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'", + ], + extras_require={"tunnel": ["pytap2>=2.0.0"]}, + python_requires=">=3.7", entry_points={ "console_scripts": [ "meshtastic=meshtastic.__main__:main", - "mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]" + "mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]", ] }, ) diff --git a/tests/close-bug.py b/tests/close-bug.py index d9c10f4..1e84048 100644 --- a/tests/close-bug.py +++ b/tests/close-bug.py @@ -1,11 +1,14 @@ +import datetime +import logging import sys -import meshtastic -import datetime, logging + from pubsub import pub -#logging.basicConfig(level=logging.DEBUG) +import meshtastic + +# logging.basicConfig(level=logging.DEBUG) print(str(datetime.datetime.now()) + ": start") interface = meshtastic.TCPInterface(sys.argv[1]) print(str(datetime.datetime.now()) + ": middle") interface.close() -print(str(datetime.datetime.now()) + ": after close") \ No newline at end of file +print(str(datetime.datetime.now()) + ": after close") diff --git a/tests/hello_world.py b/tests/hello_world.py index b938ec2..8fd8f79 100644 --- a/tests/hello_world.py +++ b/tests/hello_world.py @@ -1,6 +1,9 @@ -import meshtastic import time -interface = meshtastic.SerialInterface() # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0 +import meshtastic + +interface = ( + meshtastic.SerialInterface() +) # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0 interface.sendText("hello mesh") interface.close() diff --git a/tests/tcp-test.py b/tests/tcp-test.py index 37411b2..34e356f 100644 --- a/tests/tcp-test.py +++ b/tests/tcp-test.py @@ -1,10 +1,18 @@ - # reported by @ScriptBlock -import meshtastic, sys +import sys + from pubsub import pub -def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio + +import meshtastic + + +def onConnection( + interface, topic=pub.AUTO_TOPIC +): # called when we (re)connect to the radio print(interface.myInfo) interface.close() + + pub.subscribe(onConnection, "meshtastic.connection.established") interface = meshtastic.TCPInterface(sys.argv[1]) diff --git a/tests/tuntest.py b/tests/tuntest.py index c8cd301..38f6231 100644 --- a/tests/tuntest.py +++ b/tests/tuntest.py @@ -8,15 +8,16 @@ # select local ip address based on nodeid # print known node ids as IP addresses -from pytap2 import TapDevice import logging from _thread import start_new_thread +from pytap2 import TapDevice + """A list of chatty UDP services we should never accidentally forward to our slow network""" udpBlacklist = { - 1900, # SSDP - 5353, # multicast DNS + 1900, # SSDP + 5353, # multicast DNS } """A list of TCP services to block""" @@ -24,22 +25,26 @@ tcpBlacklist = {} """A list of protocols we ignore""" protocolBlacklist = { - 0x02, # IGMP - 0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment + 0x02, # IGMP + 0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment } + def hexstr(barray): """Print a string of hex digits""" - return ":".join('{:02x}'.format(x) for x in barray) + return ":".join("{:02x}".format(x) for x in barray) + def ipstr(barray): """Print a string of ip digits""" - return ".".join('{}'.format(x) for x in barray) + return ".".join("{}".format(x) for x in barray) + def readnet_u16(p, offset): """Read big endian u16 (network byte order)""" return p[offset] * 256 + p[offset + 1] + def readtest(tap): while True: p = tap.read() @@ -48,23 +53,23 @@ def readtest(tap): srcaddr = p[12:16] destaddr = p[16:20] subheader = 20 - ignore = False # Assume we will be forwarding the packet + ignore = False # Assume we will be forwarding the packet if protocol in protocolBlacklist: ignore = True logging.debug(f"Ignoring blacklisted protocol 0x{protocol:02x}") - elif protocol == 0x01: # ICMP + elif protocol == 0x01: # ICMP logging.warn("Generating fake ping reply") # reply to pings (swap src and dest but keep rest of packet unchanged) - pingback = p[:12]+p[16:20]+p[12:16]+p[20:] + pingback = p[:12] + p[16:20] + p[12:16] + p[20:] tap.write(pingback) - elif protocol == 0x11: # UDP + elif protocol == 0x11: # UDP srcport = readnet_u16(p, subheader) destport = readnet_u16(p, subheader + 2) logging.debug(f"udp srcport={srcport}, destport={destport}") if destport in udpBlacklist: ignore = True logging.debug(f"ignoring blacklisted UDP port {destport}") - elif protocol == 0x06: # TCP + elif protocol == 0x06: # TCP srcport = readnet_u16(p, subheader) destport = readnet_u16(p, subheader + 2) logging.debug(f"tcp srcport={srcport}, destport={destport}") @@ -72,22 +77,23 @@ def readtest(tap): ignore = True logging.debug(f"ignoring blacklisted TCP port {destport}") else: - logging.warning(f"unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destaddr)}") + logging.warning( + f"unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destaddr)}" + ) if not ignore: - logging.debug(f"Forwarding packet bytelen={len(p)} src={ipstr(srcaddr)}, dest={ipstr(destaddr)}") + logging.debug( + f"Forwarding packet bytelen={len(p)} src={ipstr(srcaddr)}, dest={ipstr(destaddr)}" + ) - logging.basicConfig(level=logging.DEBUG) tun = TapDevice(mtu=200) # tun.create() tun.up() -tun.ifconfig(address="10.115.1.2",netmask="255.255.0.0") +tun.ifconfig(address="10.115.1.2", netmask="255.255.0.0") -start_new_thread(readtest,(tun,)) +start_new_thread(readtest, (tun,)) input("press return key to quit!") tun.close() - - diff --git a/vercel.json b/vercel.json index 0cae358..7ae9a3d 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,5 @@ { - "github": { - "silent": true - } + "github": { + "silent": true + } }