Compare commits

..

61 Commits

Author SHA1 Message Date
mkinney
52834e9966 Update setup.py 2022-01-12 17:25:00 -08:00
mkinney
63c60d4cea Merge pull request #229 from mkinney/check_for_multiple_mac_conversions
suggested fix from MitchConner912 for not converting mac address more…
2022-01-12 17:13:14 -08:00
Mike Kinney
6a2a9d2093 suggested fix from MitchConner912 for not converting mac address more than once 2022-01-12 17:10:51 -08:00
mkinney
1410448808 Merge pull request #228 from mkinney/more_unit_tests
add more unit tests
2022-01-12 17:06:24 -08:00
Mike Kinney
ad8f2222db cover a few more lines 2022-01-12 16:50:29 -08:00
Mike Kinney
48265e73b1 no need to import pygatt here 2022-01-12 15:54:07 -08:00
Mike Kinney
f3139a8aa0 add more unit tests 2022-01-12 15:50:16 -08:00
mkinney
8d68e36703 Merge pull request #227 from mkinney/pylint_fixes
fix the consider-using-f-string warnings
2022-01-12 14:57:50 -08:00
Mike Kinney
d2d93fbe80 no longer need to disable this pylint warning 2022-01-12 14:50:10 -08:00
Mike Kinney
ed8510468d no need to disable these pylint warnings anymore 2022-01-12 14:47:49 -08:00
Mike Kinney
e7680e07c2 fix pylint global-statement warnings 2022-01-12 14:46:21 -08:00
Mike Kinney
0f89baa36e fix the pylint unused-argument warnings 2022-01-12 14:41:49 -08:00
Mike Kinney
48ed7690af fix the consider-using-f-string warnings 2022-01-12 13:46:01 -08:00
mkinney
59b94ea650 Merge pull request #226 from mkinney/add_get_hw_example
add example how you can get the hwModel using api
2022-01-12 10:59:43 -08:00
Mike Kinney
5b992734fb add example how you can get the hwModel using api 2022-01-12 10:57:03 -08:00
mkinney
3b74b911f8 Merge pull request #224 from mkinney/add_set_owner_example
simple example showing how you can set the long and short name
2022-01-12 10:26:22 -08:00
Mike Kinney
b6570e3c27 simple example showing how you can set the long and short name 2022-01-12 10:23:51 -08:00
mkinney
e1e1664b96 Merge pull request #223 from mkinney/make_pygatt_linux_only
make pygatt linux only in requirements.txt
2022-01-12 10:23:06 -08:00
Mike Kinney
cb1913dfc3 make pygatt linux only in requirements.txt 2022-01-12 10:15:06 -08:00
Sacha Weatherstone
b813a6f8c5 Update requirements.txt 2022-01-12 14:39:20 +11:00
mkinney
0b662318e1 Merge pull request #222 from mkinney/testing_on_mac_air
fixes for working on mac air
2022-01-11 16:48:01 -08:00
Mike Kinney
a6ccc1a246 add conditional lib if linux 2022-01-11 16:42:25 -08:00
Mike Kinney
bc17e9b389 fixes for working on mac air 2022-01-11 16:36:39 -08:00
mkinney
b08e96b66a Update setup.py
bump version for release
2022-01-11 11:02:26 -08:00
mkinney
9e74ead54e Merge pull request #219 from mkinney/smoke_virt
add smoke test for virtual device
2022-01-11 10:56:33 -08:00
Mike Kinney
2cf52f3df6 add a convenience target in Makefile for running the smokevirt test 2022-01-11 18:50:18 +00:00
Mike Kinney
50e9a0a9f7 exclude smokevirt from ci 2022-01-11 18:44:39 +00:00
Mike Kinney
841c44e05c add smoke test for virtual device 2022-01-11 18:39:49 +00:00
mkinney
465bafeb30 Merge pull request #218 from mkinney/fix_remote_admin_message
refactor code to only call local node when necessary; fix tests
2022-01-10 16:51:04 -08:00
Mike Kinney
cb8dafbd31 add more coverage 2022-01-10 16:40:47 -08:00
Mike Kinney
52db617b06 refactor code to only call local node when necessary; fix tests 2022-01-10 16:20:11 -08:00
Jm Casler
ec622590da updating proto submodule to latest 2022-01-09 22:23:07 -08:00
mkinney
345cb1bdc4 Merge pull request #217 from mkinney/fix_remote_admin_message
Fix remote admin message
2022-01-06 12:36:03 -08:00
Mike Kinney
8ca692a26e cannot call os.getlogin() on github instances 2022-01-06 12:33:30 -08:00
Mike Kinney
d1ea68d7dc add code coverage to recently added code 2022-01-06 12:17:42 -08:00
Mike Kinney
b56440a4e8 should not need to talk with remote node if just doing sendtext 2022-01-06 11:55:36 -08:00
github-actions
1401b949a3 Update protobuf submodule 2022-01-06 17:09:45 +00:00
mkinney
97f3ce6198 Merge pull request #215 from mkinney/combine_build_single_executables
combine the building of single executables into one action
2022-01-06 00:25:46 -08:00
Mike Kinney
8019391914 combine the building of single executables into one action 2022-01-06 00:24:59 -08:00
mkinney
8d10010ab1 Merge pull request #214 from mkinney/more_testing_stuff
move slower unit tests to unitslow
2022-01-06 00:00:42 -08:00
mkinney
a2b4d2a96a Update build_mac.yml 2022-01-05 23:47:02 -08:00
mkinney
4de558bdf6 Update build_mac.yml 2022-01-05 23:40:49 -08:00
mkinney
8ff06a0de1 Update build_mac.yml 2022-01-05 23:37:35 -08:00
mkinney
f8ad6061c1 Update build_mac.yml
revert
2022-01-05 23:06:49 -08:00
mkinney
aa4cdb6aea Update build_mac.yml 2022-01-05 23:01:13 -08:00
Mike Kinney
cba424fde0 move slower unit tests to unitslow 2022-01-05 21:12:50 -08:00
mkinney
6c7a870645 Merge pull request #213 from mkinney/add_python_310
check python v3.10 as well
2022-01-05 18:20:55 -08:00
Mike Kinney
f42b1ad4e0 check python v3.10 as well 2022-01-05 18:18:31 -08:00
mkinney
30a51952e0 Merge pull request #212 from mkinney/serial_perms
improve the permission error on linux
2022-01-05 15:08:24 -08:00
Mike Kinney
5de754c5ab improve the permission error on linux 2022-01-05 23:03:06 +00:00
mkinney
bda446c7b5 Update build_mac.yml 2022-01-05 14:13:42 -08:00
mkinney
f79540f197 Update build_mac.yml 2022-01-05 14:09:17 -08:00
mkinney
d507697e56 Merge pull request #210 from mkinney/work_on_single_executable
install meshtastic before building single executable
2022-01-05 13:58:47 -08:00
Mike Kinney
acbc1f2e30 install meshtastic before building single executable 2022-01-05 13:56:07 -08:00
mkinney
7b3c68119c Merge pull request #209 from mkinney/add_modules_for_building_single_executables
collect the meshtastic libs
2022-01-05 13:36:25 -08:00
Mike Kinney
39f97166b0 collect the meshtastic libs 2022-01-05 13:34:28 -08:00
mkinney
541d19cafb Merge pull request #208 from mkinney/create_actions_for_building_single_executables
Create actions for building single executables
2022-01-05 13:26:47 -08:00
Mike Kinney
969a81b779 Merge remote-tracking branch 'upstream/master' into create_actions_for_building_single_executables 2022-01-05 13:24:33 -08:00
Mike Kinney
3f76c1efb0 refactor version info so pyinstaller will work; add build mac and ubuntu standalone executables 2022-01-05 13:22:37 -08:00
mkinney
774849189f Merge pull request #207 from mkinney/create_build_windows_action
add a build windows action
2022-01-05 12:46:10 -08:00
Mike Kinney
53d626aa72 add a build windows action 2022-01-05 12:44:05 -08:00
34 changed files with 1487 additions and 377 deletions

90
.github/workflows/build_executables.yml vendored Normal file
View File

@@ -0,0 +1,90 @@
name: Build and publish standalone executables
on: workflow_dispatch
jobs:
build-and-publish-mac:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Setup code signing
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
run: |
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_KEYCHAIN_PASSWORD" meshtastic.keychain
security default-keychain -s meshtastic.keychain
security unlock-keychain -p "$MACOS_KEYCHAIN_PASSWORD" meshtastic.keychain
security import certificate.p12 -k meshtastic.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PASSWORD" meshtastic.keychain
- name: Build
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
run: |
pip install pyinstaller
pip install -r requirements.txt
pip install .
pyinstaller -F -n meshtastic --collect-all meshtastic --codesign-identity "$MACOS_SIGNING_IDENTITY" meshtastic/__main__.py
- uses: actions/upload-artifact@v2
with:
name: meshtastic_mac
path: dist
build-and-publish-ubuntu:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Build
run: |
pip install pyinstaller
pip install -r requirements.txt
pip install .
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
- uses: actions/upload-artifact@v2
with:
name: meshtastic_ubuntu
path: dist
build-and-publish-windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Build
run: |
pip install pyinstaller
pip install -r requirements.txt
pip install .
pyinstaller -F -n meshtastic --collect-all meshtastic meshtastic/__main__.py
- uses: actions/upload-artifact@v2
with:
name: meshtastic_windows
path: dist

View File

@@ -17,6 +17,7 @@ jobs:
- "3.7" - "3.7"
- "3.8" - "3.8"
- "3.9" - "3.9"
- "3.10"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3
@@ -58,6 +59,7 @@ jobs:
- "3.7" - "3.7"
- "3.8" - "3.8"
- "3.9" - "3.9"
- "3.10"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ venv/
.DS_Store .DS_Store
__pycache__ __pycache__
examples/__pycache__ examples/__pycache__
meshtastic.spec

View File

@@ -23,7 +23,7 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admi
# no Warning level messages displayed, use"--disable=all --enable=classes # no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W" # --disable=W"
# #
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,consider-using-f-string,broad-except,no-else-return,unused-argument,global-statement,global-variable-not-assigned,too-many-boolean-expressions,no-else-raise,bare-except,c-extension-no-member disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except
[BASIC] [BASIC]

View File

@@ -2,6 +2,10 @@
test: test:
pytest -m unit pytest -m unit
# only run the smoke tests against the virtual device
virt:
pytest -m smokevirt
# local install # local install
install: install:
pip install . pip install .
@@ -16,7 +20,7 @@ lint:
# show the slowest unit tests # show the slowest unit tests
slow: slow:
pytest --durations=5 pytest -m unit --durations=5
# run the coverage report and open results in a browser # run the coverage report and open results in a browser
cov: cov:

20
examples/get_hw.py Normal file
View File

@@ -0,0 +1,20 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/get_hw.py
"""
import sys
import meshtastic
import meshtastic.serial_interface
# simple arg check
if len(sys.argv) != 1:
print(f"usage: {sys.argv[0]}")
print("Print the hardware model for the local node.")
sys.exit(3)
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'])
iface.close()

20
examples/set_owner.py Normal file
View File

@@ -0,0 +1,20 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/set_owner.py Bobby 333
"""
import sys
import meshtastic
import meshtastic.serial_interface
# simple arg check
if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} long_name [short_name]")
sys.exit(3)
iface = meshtastic.serial_interface.SerialInterface()
long_name = sys.argv[1]
short_name = None
if len(sys.argv) > 2:
short_name = sys.argv[2]
iface.localNode.setOwner(long_name, short_name)
iface.close()

View File

@@ -72,7 +72,6 @@ from typing import *
import serial import serial
import timeago import timeago
import google.protobuf.json_format import google.protobuf.json_format
import pygatt
from pubsub import pub from pubsub import pub
from dotmap import DotMap from dotmap import DotMap
from tabulate import tabulate from tabulate import tabulate
@@ -83,6 +82,7 @@ from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2,
environmental_measurement_pb2, remote_hardware_pb2, environmental_measurement_pb2, remote_hardware_pb2,
channel_pb2, radioconfig_pb2, util) channel_pb2, radioconfig_pb2, util)
# Note: To follow PEP224, comments should be after the module variable. # Note: To follow PEP224, comments should be after the module variable.
LOCAL_ADDR = "^local" LOCAL_ADDR = "^local"
@@ -129,6 +129,7 @@ def _onTextReceive(iface, asDict):
# #
# Usually btw this problem is caused by apps sending binary data but setting the payload type to # Usually btw this problem is caused by apps sending binary data but setting the payload type to
# text. # text.
logging.debug(f'in _onTextReceive() asDict:{asDict}')
try: try:
asBytes = asDict["decoded"]["payload"] asBytes = asDict["decoded"]["payload"]
asDict["decoded"]["text"] = asBytes.decode("utf-8") asDict["decoded"]["text"] = asBytes.decode("utf-8")
@@ -139,22 +140,30 @@ def _onTextReceive(iface, asDict):
def _onPositionReceive(iface, asDict): def _onPositionReceive(iface, asDict):
"""Special auto parsing for received messages""" """Special auto parsing for received messages"""
p = asDict["decoded"]["position"] logging.debug(f'in _onPositionReceive() asDict:{asDict}')
iface._fixupPosition(p) if 'decoded' in asDict:
# update node DB as needed if 'position' in asDict['decoded'] and 'from' in asDict:
iface._getOrCreateByNum(asDict["from"])["position"] = p p = asDict["decoded"]["position"]
logging.debug(f'p:{p}')
p = iface._fixupPosition(p)
logging.debug(f'after fixup p:{p}')
# update node DB as needed
iface._getOrCreateByNum(asDict["from"])["position"] = p
def _onNodeInfoReceive(iface, asDict): def _onNodeInfoReceive(iface, asDict):
"""Special auto parsing for received messages""" """Special auto parsing for received messages"""
p = asDict["decoded"]["user"] logging.debug(f'in _onNodeInfoReceive() asDict:{asDict}')
# decode user protobufs and update nodedb, provide decoded version as "position" in the published msg if 'decoded' in asDict:
# update node DB as needed if 'user' in asDict['decoded'] and 'from' in asDict:
n = iface._getOrCreateByNum(asDict["from"]) p = asDict["decoded"]["user"]
n["user"] = p # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg
# We now have a node ID, make sure it is uptodate in that table # update node DB as needed
iface.nodes[p["id"]] = n n = iface._getOrCreateByNum(asDict["from"])
_receiveInfoUpdate(iface, asDict) n["user"] = p
# We now have a node ID, make sure it is uptodate in that table
iface.nodes[p["id"]] = n
_receiveInfoUpdate(iface, asDict)
def _receiveInfoUpdate(iface, asDict): def _receiveInfoUpdate(iface, asDict):

View File

@@ -5,6 +5,7 @@
import argparse import argparse
import platform import platform
import logging import logging
import os
import sys import sys
import time import time
import yaml import yaml
@@ -17,6 +18,7 @@ from meshtastic import remote_hardware
from meshtastic.ble_interface import BLEInterface from meshtastic.ble_interface import BLEInterface
from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2 from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2
from meshtastic.globals import Globals from meshtastic.globals import Globals
from meshtastic.__init__ import BROADCAST_ADDR
def onReceive(packet, interface): def onReceive(packet, interface):
@@ -38,7 +40,7 @@ def onReceive(packet, interface):
rxSnr = packet['rxSnr'] rxSnr = packet['rxSnr']
hopLimit = packet['hopLimit'] hopLimit = packet['hopLimit']
print(f"message: {msg}") print(f"message: {msg}")
reply = "got msg \'{}\' with rxSnr: {} and hopLimit: {}".format(msg, rxSnr, hopLimit) reply = f"got msg \'{msg}\' with rxSnr: {rxSnr} and hopLimit: {hopLimit}"
print("Sending reply: ", reply) print("Sending reply: ", reply)
interface.sendText(reply) interface.sendText(reply)
@@ -46,7 +48,7 @@ def onReceive(packet, interface):
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): def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613
"""Callback invoked when we connect/disconnect from a radio""" """Callback invoked when we connect/disconnect from a radio"""
print(f"Connection changed: {topic.getName()}") print(f"Connection changed: {topic.getName()}")
@@ -139,14 +141,6 @@ def onConnected(interface):
if not args.export_config: if not args.export_config:
print("Connected to radio") print("Connected to radio")
def getNode():
"""This operation could be expensive, so we try to cache the results"""
targetNode = our_globals.get_target_node()
if not targetNode:
targetNode = interface.getNode(args.destOrLocal)
our_globals.set_target_node(targetNode)
return targetNode
if args.setlat or args.setlon or args.setalt: if args.setlat or args.setlon or args.setalt:
closeNow = True closeNow = True
@@ -178,12 +172,12 @@ def onConnected(interface):
if args.set_owner: if args.set_owner:
closeNow = True closeNow = True
print(f"Setting device owner to {args.set_owner}") print(f"Setting device owner to {args.set_owner}")
getNode().setOwner(args.set_owner) interface.getNode(args.dest).setOwner(args.set_owner)
if args.pos_fields: if args.pos_fields:
# If --pos-fields invoked with args, set position fields # If --pos-fields invoked with args, set position fields
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
allFields = 0 allFields = 0
try: try:
@@ -198,14 +192,14 @@ def onConnected(interface):
else: else:
print(f"Setting position fields to {allFields}") print(f"Setting position fields to {allFields}")
setPref(prefs, 'position_flags', ('%d' % allFields)) setPref(prefs, 'position_flags', f'{allFields:d}')
print("Writing modified preferences to device") print("Writing modified preferences to device")
getNode().writeConfig() interface.getNode(args.dest).writeConfig()
elif args.pos_fields is not None: elif args.pos_fields is not None:
# If --pos-fields invoked without args, read and display current value # If --pos-fields invoked without args, read and display current value
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
fieldNames = [] fieldNames = []
for bit in radioconfig_pb2.PositionFlags.values(): for bit in radioconfig_pb2.PositionFlags.values():
@@ -224,81 +218,85 @@ def onConnected(interface):
print(sorted(meshtastic.mesh_pb2.Team.keys())) print(sorted(meshtastic.mesh_pb2.Team.keys()))
else: else:
print(f"Setting team to {meshtastic.mesh_pb2.Team.Name(v_team)}") print(f"Setting team to {meshtastic.mesh_pb2.Team.Name(v_team)}")
getNode().setOwner(team=v_team) interface.getNode(args.dest).setOwner(team=v_team)
if args.set_ham: if args.set_ham:
closeNow = True closeNow = True
print(f"Setting Ham ID to {args.set_ham} and turning off encryption") print(f"Setting Ham ID to {args.set_ham} and turning off encryption")
getNode().setOwner(args.set_ham, is_licensed=True) interface.getNode(args.dest).setOwner(args.set_ham, is_licensed=True)
# Must turn off encryption on primary channel # Must turn off encryption on primary channel
getNode().turnOffEncryptionOnPrimaryChannel() interface.getNode(args.dest).turnOffEncryptionOnPrimaryChannel()
if args.reboot: if args.reboot:
closeNow = True closeNow = True
getNode().reboot() interface.getNode(args.dest).reboot()
if args.sendtext: if args.sendtext:
closeNow = True closeNow = True
channelIndex = 0 channelIndex = 0
if args.ch_index is not None: if args.ch_index is not None:
channelIndex = int(args.ch_index) channelIndex = int(args.ch_index)
ch = getNode().getChannelByChannelIndex(channelIndex) ch = interface.localNode.getChannelByChannelIndex(channelIndex)
logging.debug(f'ch:{ch}')
if ch and ch.role != channel_pb2.Channel.Role.DISABLED: if ch and ch.role != channel_pb2.Channel.Role.DISABLED:
print(f"Sending text message {args.sendtext} to {args.destOrAll} on channelIndex:{channelIndex}") print(f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}")
interface.sendText(args.sendtext, args.destOrAll, wantAck=True, channelIndex=channelIndex) interface.sendText(args.sendtext, args.dest, wantAck=True, channelIndex=channelIndex)
else: 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: if args.sendping:
payload = str.encode("test string") payload = str.encode("test string")
print(f"Sending ping message to {args.destOrAll}") print(f"Sending ping message to {args.dest}")
interface.sendData(payload, args.destOrAll, portNum=portnums_pb2.PortNum.REPLY_APP, interface.sendData(payload, args.dest, portNum=portnums_pb2.PortNum.REPLY_APP,
wantAck=True, wantResponse=True) wantAck=True, wantResponse=True)
if args.gpio_wrb or args.gpio_rd or args.gpio_watch: if args.gpio_wrb or args.gpio_rd or args.gpio_watch:
rhc = remote_hardware.RemoteHardwareClient(interface) if args.dest == BROADCAST_ADDR:
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
else:
rhc = remote_hardware.RemoteHardwareClient(interface)
if args.gpio_wrb: if args.gpio_wrb:
bitmask = 0 bitmask = 0
bitval = 0 bitval = 0
for wrpair in (args.gpio_wrb or []): for wrpair in (args.gpio_wrb or []):
bitmask |= 1 << int(wrpair[0]) bitmask |= 1 << int(wrpair[0])
bitval |= int(wrpair[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) rhc.writeGPIOs(args.dest, bitmask, bitval)
closeNow = True closeNow = True
if args.gpio_rd: if args.gpio_rd:
bitmask = int(args.gpio_rd, 16) bitmask = int(args.gpio_rd, 16)
print(f"Reading GPIO mask 0x{bitmask:x} from {args.dest}") print(f"Reading GPIO mask 0x{bitmask:x} from {args.dest}")
interface.mask = bitmask interface.mask = bitmask
rhc.readGPIOs(args.dest, bitmask, None) rhc.readGPIOs(args.dest, bitmask, None)
if not interface.noProto: if not interface.noProto:
# wait up to X seconds for a response # wait up to X seconds for a response
for _ in range(10): for _ in range(10):
time.sleep(1)
if interface.gotResponse:
break
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")
while True:
rhc.watchGPIOs(args.dest, bitmask)
time.sleep(1) time.sleep(1)
if interface.gotResponse:
break
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")
while True:
rhc.watchGPIOs(args.dest, bitmask)
time.sleep(1)
# handle settings # handle settings
if args.set: if args.set:
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
# Handle the int/float/bool arguments # Handle the int/float/bool arguments
for pref in args.set: for pref in args.set:
setPref(prefs, pref[0], pref[1]) setPref(prefs, pref[0], pref[1])
print("Writing modified preferences to device") print("Writing modified preferences to device")
getNode().writeConfig() interface.getNode(args.dest).writeConfig()
if args.configure: if args.configure:
with open(args.configure[0], encoding='utf8') as file: with open(args.configure[0], encoding='utf8') as file:
@@ -307,11 +305,11 @@ def onConnected(interface):
if 'owner' in configuration: if 'owner' in configuration:
print(f"Setting device owner to {configuration['owner']}") print(f"Setting device owner to {configuration['owner']}")
getNode().setOwner(configuration['owner']) interface.getNode(args.dest).setOwner(configuration['owner'])
if 'channel_url' in configuration: if 'channel_url' in configuration:
print("Setting channel url to", configuration['channel_url']) print("Setting channel url to", configuration['channel_url'])
getNode().setURL(configuration['channel_url']) interface.getNode(args.dest).setURL(configuration['channel_url'])
if 'location' in configuration: if 'location' in configuration:
alt = 0 alt = 0
@@ -336,11 +334,11 @@ def onConnected(interface):
interface.localNode.writeConfig() interface.localNode.writeConfig()
if 'user_prefs' in configuration: if 'user_prefs' in configuration:
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
for pref in configuration['user_prefs']: for pref in configuration['user_prefs']:
setPref(prefs, pref, str(configuration['user_prefs'][pref])) setPref(prefs, pref, str(configuration['user_prefs'][pref]))
print("Writing modified preferences to device") print("Writing modified preferences to device")
getNode().writeConfig() interface.getNode(args.dest).writeConfig()
if args.export_config: if args.export_config:
# export the configuration (the opposite of '--configure') # export the configuration (the opposite of '--configure')
@@ -349,7 +347,7 @@ def onConnected(interface):
if args.seturl: if args.seturl:
closeNow = True closeNow = True
getNode().setURL(args.seturl) interface.getNode(args.dest).setURL(args.seturl)
# handle changing channels # handle changing channels
@@ -357,7 +355,7 @@ def onConnected(interface):
closeNow = True closeNow = True
if len(args.ch_add) > 10: 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 = getNode() n = interface.getNode(args.dest)
ch = n.getChannelByName(args.ch_add) ch = n.getChannelByName(args.ch_add)
if ch: 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.")
@@ -385,7 +383,7 @@ def onConnected(interface):
meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1) meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1)
else: else:
print(f"Deleting channel {channelIndex}") print(f"Deleting channel {channelIndex}")
ch = getNode().deleteChannel(channelIndex) ch = interface.getNode(args.dest).deleteChannel(channelIndex)
ch_changes = [args.ch_longslow, args.ch_longfast, ch_changes = [args.ch_longslow, args.ch_longfast,
args.ch_mediumslow, args.ch_mediumfast, args.ch_mediumslow, args.ch_mediumfast,
@@ -401,7 +399,7 @@ def onConnected(interface):
channelIndex = 0 channelIndex = 0
else: else:
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1) meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
ch = getNode().channels[channelIndex] ch = interface.getNode(args.dest).channels[channelIndex]
if any_primary_channel_changes or args.ch_enable or args.ch_disable: if any_primary_channel_changes or args.ch_enable or args.ch_disable:
@@ -462,21 +460,22 @@ def onConnected(interface):
ch.role = channel_pb2.Channel.Role.DISABLED ch.role = channel_pb2.Channel.Role.DISABLED
print(f"Writing modified channels to device") print(f"Writing modified channels to device")
getNode().writeChannel(channelIndex) interface.getNode(args.dest).writeChannel(channelIndex)
if args.info: if args.info:
print("") print("")
if not args.dest: # If we aren't trying to talk to our local node, don't show it # If we aren't trying to talk to our local node, don't show it
if args.dest == BROADCAST_ADDR:
interface.showInfo() interface.showInfo()
print("") print("")
getNode().showInfo() interface.getNode(args.dest).showInfo()
closeNow = True # FIXME, for now we leave the link up while talking to remote nodes closeNow = True # FIXME, for now we leave the link up while talking to remote nodes
print("") print("")
if args.get: if args.get:
closeNow = True closeNow = True
prefs = getNode().radioConfig.preferences prefs = interface.getNode(args.dest).radioConfig.preferences
# Handle the int/float/bool arguments # Handle the int/float/bool arguments
for pref in args.get: for pref in args.get:
@@ -590,13 +589,8 @@ def common():
channelIndex = int(args.ch_index) channelIndex = int(args.ch_index)
our_globals.set_channel_index(channelIndex) our_globals.set_channel_index(channelIndex)
# Some commands require dest to be set, so we now use destOrAll/destOrLocal for more lenient commands
if not args.dest: if not args.dest:
args.destOrAll = "^all" args.dest = BROADCAST_ADDR
args.destOrLocal = "^local"
else:
args.destOrAll = args.dest
args.destOrLocal = args.dest # FIXME, temp hack for debugging remove
if not args.seriallog: if not args.seriallog:
if args.noproto: if args.noproto:
@@ -635,12 +629,19 @@ def common():
elif args.host: 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: else:
client = meshtastic.serial_interface.SerialInterface(args.port, debugOut=logfile, noProto=args.noproto) try:
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 += 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}"
meshtastic.util.our_exit(message)
# We assume client is fully connected now # We assume client is fully connected now
onConnected(client) onConnected(client)
#if logfile:
#logfile.close()
have_tunnel = platform.system() == 'Linux' have_tunnel = platform.system() == 'Linux'
if args.noproto or args.reply or (have_tunnel and args.tunnel): # loop until someone presses ctrlc if args.noproto or args.reply or (have_tunnel and args.tunnel): # loop until someone presses ctrlc
@@ -818,8 +819,8 @@ def initParser():
parser.set_defaults(deprecated=None) parser.set_defaults(deprecated=None)
parser.add_argument('--version', action='version', the_version = pkg_resources.get_distribution("meshtastic").version
version=f"{pkg_resources.require('meshtastic')[0].version}") parser.add_argument('--version', action='version', version=f"{the_version}")
parser.add_argument( 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)")

View File

@@ -1,10 +1,16 @@
"""Bluetooth interface """Bluetooth interface
""" """
import logging import logging
import pygatt import platform
from meshtastic.mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import our_exit
if platform.system() == 'Linux':
# pylint: disable=E0401
import pygatt
# Our standard BLE characteristics # Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7" TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
@@ -16,6 +22,8 @@ class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices""" """A not quite ready - FIXME - BLE interface to devices"""
def __init__(self, address, noProto=False, debugOut=None): def __init__(self, address, noProto=False, debugOut=None):
if platform.system() != 'Linux':
our_exit("Linux is the only platform with experimental BLE support.", 1)
self.address = address self.address = address
if not noProto: if not noProto:
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend() self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
@@ -31,7 +39,7 @@ class BLEInterface(MeshInterface):
self._readFromRadio() # read the initial responses self._readFromRadio() # read the initial responses
def handle_data(handle, data): def handle_data(handle, data): # pylint: disable=W0613
self._handleFromRadio(data) self._handleFromRadio(data)
if self.device: if self.device:

View File

@@ -27,7 +27,6 @@ class Globals:
Globals.__instance = self Globals.__instance = self
self.args = None self.args = None
self.parser = None self.parser = None
self.target_node = None
self.channel_index = None self.channel_index = None
self.logfile = None self.logfile = None
self.tunnelInstance = None self.tunnelInstance = None
@@ -36,7 +35,6 @@ class Globals:
"""Reset all of our globals. If you add a member, add it to this method, too.""" """Reset all of our globals. If you add a member, add it to this method, too."""
self.args = None self.args = None
self.parser = None self.parser = None
self.target_node = None
self.channel_index = None self.channel_index = None
self.logfile = None self.logfile = None
self.tunnelInstance = None self.tunnelInstance = None
@@ -50,10 +48,6 @@ class Globals:
"""Set the parser""" """Set the parser"""
self.parser = parser self.parser = parser
def set_target_node(self, target_node):
"""Set the target_node"""
self.target_node = target_node
def set_channel_index(self, channel_index): def set_channel_index(self, channel_index):
"""Set the channel_index""" """Set the channel_index"""
self.channel_index = channel_index self.channel_index = channel_index
@@ -75,10 +69,6 @@ class Globals:
"""Get parser""" """Get parser"""
return self.parser return self.parser
def get_target_node(self):
"""Get target_node"""
return self.target_node
def get_channel_index(self): def get_channel_index(self):
"""Get channel_index""" """Get channel_index"""
return self.channel_index return self.channel_index

View File

@@ -73,7 +73,7 @@ class MeshInterface:
logging.error(f'Traceback: {traceback}') logging.error(f'Traceback: {traceback}')
self.close() self.close()
def showInfo(self, file=sys.stdout): def showInfo(self, file=sys.stdout): # pylint: disable=W0613
"""Show human readable summary about this object""" """Show human readable summary about this object"""
owner = f"Owner: {self.getLongName()} ({self.getShortName()})" owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
myinfo = '' myinfo = ''
@@ -100,7 +100,7 @@ class MeshInterface:
print(infos) print(infos)
return infos return infos
def showNodes(self, includeSelf=True, file=sys.stdout): def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
"""Show table summary of nodes in mesh""" """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.""" """Format a float value with precsion."""
@@ -159,7 +159,7 @@ class MeshInterface:
def getNode(self, nodeId): def getNode(self, nodeId):
"""Return a node object which contains device settings and channel info""" """Return a node object which contains device settings and channel info"""
if nodeId == LOCAL_ADDR: if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
return self.localNode return self.localNode
else: else:
n = meshtastic.node.Node(self, nodeId) n = meshtastic.node.Node(self, nodeId)

View File

@@ -257,6 +257,7 @@ class Node:
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
p.get_radio_request = True p.get_radio_request = True
# TODO: should we check that localNode has an 'admin' channel?
# Show progress message for super slow operations # Show progress message for super slow operations
if self != self.iface.localNode: if self != self.iface.localNode:
print("Requesting preferences from remote node.") print("Requesting preferences from remote node.")

View File

@@ -40,28 +40,25 @@ class SerialInterface(StreamInterface):
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR # 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 # see https://github.com/pyserial/pyserial/issues/124
if not self.noProto: if platform.system() != 'Windows':
if platform.system() != 'Windows': with open(devPath, encoding='utf8') as f:
with open(devPath, encoding='utf8') as f: attrs = termios.tcgetattr(f)
attrs = termios.tcgetattr(f) attrs[2] = attrs[2] & ~termios.HUPCL
attrs[2] = attrs[2] & ~termios.HUPCL termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
termios.tcsetattr(f, termios.TCSAFLUSH, attrs) f.close()
f.close() time.sleep(0.1)
time.sleep(0.1)
self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0) self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0)
if not self.noProto: self.stream.flush()
self.stream.flush() time.sleep(0.1)
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): def close(self):
"""Close a connection to the device""" """Close a connection to the device"""
if not self.noProto: self.stream.flush()
self.stream.flush() time.sleep(0.1)
time.sleep(0.1) self.stream.flush()
self.stream.flush() time.sleep(0.1)
time.sleep(0.1)
logging.debug("Closing Serial stream") logging.debug("Closing Serial stream")
StreamInterface.close(self) StreamInterface.close(self)

View File

@@ -62,8 +62,7 @@ class StreamInterface(MeshInterface):
# because we want to ensure it is looking for START1) # because we want to ensure it is looking for START1)
p = bytearray([START2] * 32) p = bytearray([START2] * 32)
self._writeBytes(p) self._writeBytes(p)
if not self.noProto: time.sleep(0.1) # wait 100ms to give device time to start running
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start() self._rxThread.start()
@@ -90,8 +89,7 @@ class StreamInterface(MeshInterface):
self.stream.write(b) self.stream.write(b)
self.stream.flush() self.stream.flush()
# we sleep here to give the TBeam a chance to work # we sleep here to give the TBeam a chance to work
if not self.noProto: time.sleep(0.1)
time.sleep(0.1)
def _readBytes(self, length): def _readBytes(self, length):
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""

View File

@@ -33,6 +33,12 @@ class TCPInterface(StreamInterface):
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
connectNow=connectNow) connectNow=connectNow)
def _socket_shutdown(self):
"""Shutdown the socket.
Note: Broke out this line so the exception could be unit tested.
"""
self.socket.shutdown(socket.SHUT_RDWR)
def myConnect(self): def myConnect(self):
"""Connect to socket""" """Connect to socket"""
server_address = (self.hostname, self.portNumber) server_address = (self.hostname, self.portNumber)
@@ -48,7 +54,7 @@ class TCPInterface(StreamInterface):
self._wantExit = True self._wantExit = True
if not self.socket is None: if not self.socket is None:
try: try:
self.socket.shutdown(socket.SHUT_RDWR) self._socket_shutdown()
except: except:
pass # Ignore errors in shutdown, because we might have a race with the server pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close() self.socket.close()

View File

@@ -63,6 +63,7 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
Returns: Returns:
boolean -- True for success boolean -- True for success
""" """
# pylint: disable=W0603
global receivedPackets global receivedPackets
receivedPackets = [] receivedPackets = []
fromNode = fromInterface.myInfo.my_node_num fromNode = fromInterface.myInfo.my_node_num
@@ -74,6 +75,7 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
logging.debug( logging.debug(
f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}") f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
# pylint: disable=W0603
global sendingInterface global sendingInterface
sendingInterface = fromInterface sendingInterface = fromInterface
if not asBinary: if not asBinary:
@@ -94,6 +96,7 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
numFail = 0 numFail = 0
numSuccess = 0 numSuccess = 0
for _ in range(numTests): for _ in range(numTests):
# pylint: disable=W0603
global testNumber global testNumber
testNumber = testNumber + 1 testNumber = testNumber + 1
isBroadcast = True isBroadcast = True
@@ -152,6 +155,7 @@ def testAll(numTests=5):
pub.subscribe(onConnection, "meshtastic.connection") pub.subscribe(onConnection, "meshtastic.connection")
pub.subscribe(onReceive, "meshtastic.receive") pub.subscribe(onReceive, "meshtastic.receive")
# pylint: disable=W0603
global interfaces global interfaces
interfaces = list(map(lambda port: SerialInterface( interfaces = list(map(lambda port: SerialInterface(
port, debugOut=openDebugLog(port), connectNow=True), ports)) port, debugOut=openDebugLog(port), connectNow=True), ports))

View File

@@ -1,12 +1,15 @@
"""Meshtastic unit tests for ble_interface.py""" """Meshtastic unit tests for ble_interface.py"""
from unittest.mock import patch
import pytest import pytest
from ..ble_interface import BLEInterface from ..ble_interface import BLEInterface
@pytest.mark.unit @pytest.mark.unit
def test_BLEInterface(): @patch('platform.system', return_value='Linux')
def test_BLEInterface(mock_platform):
"""Test that we can instantiate a BLEInterface""" """Test that we can instantiate a BLEInterface"""
iface = BLEInterface('foo', debugOut=True, noProto=True) iface = BLEInterface('foo', debugOut=True, noProto=True)
iface.close() iface.close()
mock_platform.assert_called()

View File

@@ -0,0 +1,61 @@
"""Meshtastic unit tests for __init__.py"""
import re
import logging
from unittest.mock import MagicMock
import pytest
from meshtastic.__init__ import _onTextReceive, _onPositionReceive, _onNodeInfoReceive
from ..serial_interface import SerialInterface
from ..globals import Globals
@pytest.mark.unit
def test_init_onTextReceive_with_exception(caplog):
"""Test _onTextReceive"""
args = MagicMock()
Globals.getInstance().set_args(args)
iface = MagicMock(autospec=SerialInterface)
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)
@pytest.mark.unit
def test_init_onPositionReceive(caplog):
"""Test _onPositionReceive"""
args = MagicMock()
Globals.getInstance().set_args(args)
iface = MagicMock(autospec=SerialInterface)
packet = {
'from': 'foo',
'decoded': {
'position': {}
}
}
with caplog.at_level(logging.DEBUG):
_onPositionReceive(iface, packet)
assert re.search(r'in _onPositionReceive', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
"""Test _onNodeInfoReceive"""
args = MagicMock()
Globals.getInstance().set_args(args)
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
packet = {
'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)

View File

@@ -22,7 +22,8 @@ from ..remote_hardware import onGPIOreceive
@pytest.mark.unit @pytest.mark.unit
def test_main_init_parser_no_args(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_init_parser_no_args(capsys):
"""Test no arguments""" """Test no arguments"""
sys.argv = [''] sys.argv = ['']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -33,7 +34,8 @@ def test_main_init_parser_no_args(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_init_parser_version(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_init_parser_version(capsys):
"""Test --version""" """Test --version"""
sys.argv = ['', '--version'] sys.argv = ['', '--version']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -48,7 +50,8 @@ def test_main_init_parser_version(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_main_version(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_main_version(capsys):
"""Test --version""" """Test --version"""
sys.argv = ['', '--version'] sys.argv = ['', '--version']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -63,7 +66,8 @@ def test_main_main_version(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_main_no_args(reset_globals, capsys): @pytest.mark.usefixtures("reset_globals")
def test_main_main_no_args(capsys):
"""Test with no args""" """Test with no args"""
sys.argv = [''] sys.argv = ['']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -77,7 +81,8 @@ def test_main_main_no_args(reset_globals, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_main_support(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_support(capsys):
"""Test --support""" """Test --support"""
sys.argv = ['', '--support'] sys.argv = ['', '--support']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -95,8 +100,9 @@ def test_main_support(capsys, reset_globals):
@pytest.mark.unit @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, reset_globals): def test_main_ch_index_no_devices(patched_find_ports, capsys):
"""Test --ch-index 1""" """Test --ch-index 1"""
sys.argv = ['', '--ch-index', '1'] sys.argv = ['', '--ch-index', '1']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -113,13 +119,13 @@ def test_main_ch_index_no_devices(patched_find_ports, capsys, reset_globals):
@pytest.mark.unit @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, reset_globals, capsys): def test_main_test_no_ports(patched_find_ports, capsys):
"""Test --test with no hardware""" """Test --test with no hardware"""
sys.argv = ['', '--test'] sys.argv = ['', '--test']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
assert Globals.getInstance().get_target_node() is None
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
main() main()
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
@@ -131,13 +137,13 @@ def test_main_test_no_ports(patched_find_ports, reset_globals, capsys):
@pytest.mark.unit @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, reset_globals, capsys): def test_main_test_one_port(patched_find_ports, capsys):
"""Test --test with one fake port""" """Test --test with one fake port"""
sys.argv = ['', '--test'] sys.argv = ['', '--test']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
assert Globals.getInstance().get_target_node() is None
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
main() main()
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
@@ -149,8 +155,9 @@ def test_main_test_one_port(patched_find_ports, reset_globals, capsys):
@pytest.mark.unit @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, reset_globals, capsys): def test_main_test_two_ports_success(patched_test_all, capsys):
"""Test --test two fake ports and testAll() is a simulated success""" """Test --test two fake ports and testAll() is a simulated success"""
sys.argv = ['', '--test'] sys.argv = ['', '--test']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -166,8 +173,9 @@ def test_main_test_two_ports_success(patched_test_all, reset_globals, capsys):
@pytest.mark.unit @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, reset_globals, capsys): def test_main_test_two_ports_fails(patched_test_all, capsys):
"""Test --test two fake ports and testAll() is a simulated failure""" """Test --test two fake ports and testAll() is a simulated failure"""
sys.argv = ['', '--test'] sys.argv = ['', '--test']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -183,7 +191,8 @@ def test_main_test_two_ports_fails(patched_test_all, reset_globals, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_main_info(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_info(capsys, caplog):
"""Test --info""" """Test --info"""
sys.argv = ['', '--info'] sys.argv = ['', '--info']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -192,17 +201,43 @@ def test_main_info(capsys, reset_globals):
def mock_showInfo(): def mock_showInfo():
print('inside mocked showInfo') print('inside mocked showInfo')
iface.showInfo.side_effect = mock_showInfo iface.showInfo.side_effect = mock_showInfo
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with caplog.at_level(logging.DEBUG):
main() with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
out, err = capsys.readouterr() main()
assert re.search(r'Connected to radio', out, re.MULTILINE) out, err = capsys.readouterr()
assert re.search(r'inside mocked showInfo', out, re.MULTILINE) assert re.search(r'Connected to radio', out, re.MULTILINE)
assert err == '' assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
mo.assert_called() assert err == ''
mo.assert_called()
@pytest.mark.unit @pytest.mark.unit
def test_main_info_with_tcp_interface(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
@patch('os.getlogin')
def test_main_info_with_permission_error(patched_getlogin, capsys, caplog):
"""Test --info"""
sys.argv = ['', '--info']
Globals.getInstance().set_args(sys.argv)
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')
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 == ''
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_main_info_with_tcp_interface(capsys):
"""Test --info""" """Test --info"""
sys.argv = ['', '--info', '--host', 'meshtastic.local'] sys.argv = ['', '--info', '--host', 'meshtastic.local']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -222,7 +257,7 @@ def test_main_info_with_tcp_interface(capsys, reset_globals):
# TODO: comment out ble (for now) # TODO: comment out ble (for now)
#@pytest.mark.unit #@pytest.mark.unit
#def test_main_info_with_ble_interface(capsys, reset_globals): #def test_main_info_with_ble_interface(capsys):
# """Test --info""" # """Test --info"""
# sys.argv = ['', '--info', '--ble', 'foo'] # sys.argv = ['', '--info', '--ble', 'foo']
# Globals.getInstance().set_args(sys.argv) # Globals.getInstance().set_args(sys.argv)
@@ -241,7 +276,8 @@ def test_main_info_with_tcp_interface(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_no_proto(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_no_proto(capsys):
"""Test --noproto (using --info for output)""" """Test --noproto (using --info for output)"""
sys.argv = ['', '--info', '--noproto'] sys.argv = ['', '--info', '--noproto']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -253,6 +289,7 @@ def test_main_no_proto(capsys, reset_globals):
# Override the time.sleep so there is no loop # Override the time.sleep so there is no loop
def my_sleep(amount): def my_sleep(amount):
print(f'amount:{amount}')
sys.exit(0) sys.exit(0)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface): with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
@@ -268,7 +305,8 @@ def test_main_no_proto(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_info_with_seriallog_stdout(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_info_with_seriallog_stdout(capsys):
"""Test --info""" """Test --info"""
sys.argv = ['', '--info', '--seriallog', 'stdout'] sys.argv = ['', '--info', '--seriallog', 'stdout']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -287,7 +325,8 @@ def test_main_info_with_seriallog_stdout(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_info_with_seriallog_output_txt(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_info_with_seriallog_output_txt(capsys):
"""Test --info""" """Test --info"""
sys.argv = ['', '--info', '--seriallog', 'output.txt'] sys.argv = ['', '--info', '--seriallog', 'output.txt']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -308,7 +347,8 @@ def test_main_info_with_seriallog_output_txt(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_qr(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_qr(capsys):
"""Test --qr""" """Test --qr"""
sys.argv = ['', '--qr'] sys.argv = ['', '--qr']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -327,7 +367,8 @@ def test_main_qr(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_nodes(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_nodes(capsys):
"""Test --nodes""" """Test --nodes"""
sys.argv = ['', '--nodes'] sys.argv = ['', '--nodes']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -346,7 +387,8 @@ def test_main_nodes(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_set_owner_to_bob(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_set_owner_to_bob(capsys):
"""Test --set-owner bob""" """Test --set-owner bob"""
sys.argv = ['', '--set-owner', 'bob'] sys.argv = ['', '--set-owner', 'bob']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -362,7 +404,8 @@ def test_main_set_owner_to_bob(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_set_ham_to_KI123(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_set_ham_to_KI123(capsys):
"""Test --set-ham KI123""" """Test --set-ham KI123"""
sys.argv = ['', '--set-ham', 'KI123'] sys.argv = ['', '--set-ham', 'KI123']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -371,7 +414,7 @@ def test_main_set_ham_to_KI123(capsys, reset_globals):
def mock_turnOffEncryptionOnPrimaryChannel(): def mock_turnOffEncryptionOnPrimaryChannel():
print('inside mocked turnOffEncryptionOnPrimaryChannel') print('inside mocked turnOffEncryptionOnPrimaryChannel')
def mock_setOwner(name, is_licensed): def mock_setOwner(name, is_licensed):
print('inside mocked setOwner') print(f'inside mocked setOwner name:{name} is_licensed:{is_licensed}')
mocked_node.turnOffEncryptionOnPrimaryChannel.side_effect = mock_turnOffEncryptionOnPrimaryChannel mocked_node.turnOffEncryptionOnPrimaryChannel.side_effect = mock_turnOffEncryptionOnPrimaryChannel
mocked_node.setOwner.side_effect = mock_setOwner mocked_node.setOwner.side_effect = mock_setOwner
@@ -390,7 +433,8 @@ def test_main_set_ham_to_KI123(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_reboot(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_reboot(capsys):
"""Test --reboot""" """Test --reboot"""
sys.argv = ['', '--reboot'] sys.argv = ['', '--reboot']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -413,7 +457,8 @@ def test_main_reboot(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_sendtext(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_sendtext(capsys):
"""Test --sendtext""" """Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello'] sys.argv = ['', '--sendtext', 'hello']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -421,6 +466,7 @@ def test_main_sendtext(capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
def mock_sendText(text, dest, wantAck, channelIndex): def mock_sendText(text, dest, wantAck, channelIndex):
print('inside mocked sendText') print('inside mocked sendText')
print(f'{text} {dest} {wantAck} {channelIndex}')
iface.sendText.side_effect = mock_sendText 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:
@@ -434,7 +480,8 @@ def test_main_sendtext(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_sendtext_with_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_sendtext_with_channel(capsys):
"""Test --sendtext""" """Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1'] sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -442,6 +489,7 @@ def test_main_sendtext_with_channel(capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
def mock_sendText(text, dest, wantAck, channelIndex): def mock_sendText(text, dest, wantAck, channelIndex):
print('inside mocked sendText') print('inside mocked sendText')
print(f'{text} {dest} {wantAck} {channelIndex}')
iface.sendText.side_effect = mock_sendText 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:
@@ -456,67 +504,79 @@ def test_main_sendtext_with_channel(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_sendtext_with_invalid_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_sendtext_with_invalid_channel(caplog, capsys):
"""Test --sendtext""" """Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1'] sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.getChannelByChannelIndex.return_value = None iface.localNode.getChannelByChannelIndex.return_value = None
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
iface.getNode.return_value.getChannelByChannelIndex.return_value = None 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:
main() with pytest.raises(SystemExit) as pytest_wrapped_e:
assert pytest_wrapped_e.type == SystemExit main()
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.type == SystemExit
out, err = capsys.readouterr() assert pytest_wrapped_e.value.code == 1
assert re.search(r'is not a valid channel', out, re.MULTILINE) out, err = capsys.readouterr()
assert err == '' assert re.search(r'is not a valid channel', out, re.MULTILINE)
mo.assert_called() assert err == ''
mo.assert_called()
@pytest.mark.unit @pytest.mark.unit
def test_main_sendtext_with_invalid_channel_nine(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_sendtext_with_invalid_channel_nine(caplog, capsys):
"""Test --sendtext""" """Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '9'] sys.argv = ['', '--sendtext', 'hello', '--ch-index', '9']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: iface.localNode.getChannelByChannelIndex.return_value = None
iface.getNode.return_value.getChannelByChannelIndex.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e: with caplog.at_level(logging.DEBUG):
main() with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
assert pytest_wrapped_e.type == SystemExit with pytest.raises(SystemExit) as pytest_wrapped_e:
assert pytest_wrapped_e.value.code == 1 main()
out, err = capsys.readouterr() assert pytest_wrapped_e.type == SystemExit
assert re.search(r'is not a valid channel', out, re.MULTILINE) assert pytest_wrapped_e.value.code == 1
assert err == '' out, err = capsys.readouterr()
mo.assert_called() assert re.search(r'is not a valid channel', out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit @pytest.mark.unit
def test_main_sendtext_with_dest(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_sendtext_with_dest(capsys, caplog, iface_with_nodes):
"""Test --sendtext with --dest""" """Test --sendtext with --dest"""
sys.argv = ['', '--sendtext', 'hello', '--dest', 'foo'] sys.argv = ['', '--sendtext', 'hello', '--dest', 'foo']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface) iface = iface_with_nodes
def mock_sendText(text, dest, wantAck, channelIndex): iface.myInfo.my_node_num = 2475227164
print('inside mocked sendText') mocked_channel = MagicMock(autospec=Channel)
iface.sendText.side_effect = mock_sendText iface.localNode.getChannelByChannelIndex = mocked_channel
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
main() with caplog.at_level(logging.DEBUG):
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE) with pytest.raises(SystemExit) as pytest_wrapped_e:
assert re.search(r'Sending text message', out, re.MULTILINE) main()
assert re.search(r'inside mocked sendText', out, re.MULTILINE) assert pytest_wrapped_e.type == SystemExit
assert err == '' assert pytest_wrapped_e.value.code == 1
mo.assert_called() 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 == ''
@pytest.mark.unit @pytest.mark.unit
def test_main_sendping(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_sendping(capsys):
"""Test --sendping""" """Test --sendping"""
sys.argv = ['', '--sendping'] sys.argv = ['', '--sendping']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -524,6 +584,7 @@ def test_main_sendping(capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
def mock_sendData(payload, dest, portNum, wantAck, wantResponse): def mock_sendData(payload, dest, portNum, wantAck, wantResponse):
print('inside mocked sendData') print('inside mocked sendData')
print(f'{payload} {dest} {portNum} {wantAck} {wantResponse}')
iface.sendData.side_effect = mock_sendData 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:
@@ -537,7 +598,8 @@ def test_main_sendping(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setlat(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_setlat(capsys):
"""Test --sendlat""" """Test --sendlat"""
sys.argv = ['', '--setlat', '37.5'] sys.argv = ['', '--setlat', '37.5']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -550,6 +612,7 @@ def test_main_setlat(capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
def mock_sendPosition(lat, lon, alt): def mock_sendPosition(lat, lon, alt):
print('inside mocked sendPosition') print('inside mocked sendPosition')
print(f'{lat} {lon} {alt}')
iface.sendPosition.side_effect = mock_sendPosition iface.sendPosition.side_effect = mock_sendPosition
iface.localNode.return_value = mocked_node iface.localNode.return_value = mocked_node
@@ -566,7 +629,8 @@ def test_main_setlat(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setlon(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_setlon(capsys):
"""Test --setlon""" """Test --setlon"""
sys.argv = ['', '--setlon', '-122.1'] sys.argv = ['', '--setlon', '-122.1']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -579,6 +643,7 @@ def test_main_setlon(capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
def mock_sendPosition(lat, lon, alt): def mock_sendPosition(lat, lon, alt):
print('inside mocked sendPosition') print('inside mocked sendPosition')
print(f'{lat} {lon} {alt}')
iface.sendPosition.side_effect = mock_sendPosition iface.sendPosition.side_effect = mock_sendPosition
iface.localNode.return_value = mocked_node iface.localNode.return_value = mocked_node
@@ -595,7 +660,8 @@ def test_main_setlon(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setalt(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_setalt(capsys):
"""Test --setalt""" """Test --setalt"""
sys.argv = ['', '--setalt', '51'] sys.argv = ['', '--setalt', '51']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -608,6 +674,7 @@ def test_main_setalt(capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
def mock_sendPosition(lat, lon, alt): def mock_sendPosition(lat, lon, alt):
print('inside mocked sendPosition') print('inside mocked sendPosition')
print(f'{lat} {lon} {alt}')
iface.sendPosition.side_effect = mock_sendPosition iface.sendPosition.side_effect = mock_sendPosition
iface.localNode.return_value = mocked_node iface.localNode.return_value = mocked_node
@@ -624,7 +691,8 @@ def test_main_setalt(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_set_team_valid(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_set_team_valid(capsys):
"""Test --set-team""" """Test --set-team"""
sys.argv = ['', '--set-team', 'CYAN'] sys.argv = ['', '--set-team', 'CYAN']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -632,6 +700,7 @@ def test_main_set_team_valid(capsys, reset_globals):
mocked_node = MagicMock(autospec=Node) mocked_node = MagicMock(autospec=Node)
def mock_setOwner(team): def mock_setOwner(team):
print('inside mocked setOwner') print('inside mocked setOwner')
print(f'{team}')
mocked_node.setOwner.side_effect = mock_setOwner mocked_node.setOwner.side_effect = mock_setOwner
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
@@ -652,7 +721,8 @@ def test_main_set_team_valid(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_set_team_invalid(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_set_team_invalid(capsys):
"""Test --set-team using an invalid team name""" """Test --set-team using an invalid team name"""
sys.argv = ['', '--set-team', 'NOTCYAN'] sys.argv = ['', '--set-team', 'NOTCYAN']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -675,7 +745,8 @@ def test_main_set_team_invalid(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_seturl(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_seturl(capsys):
"""Test --seturl (url used below is what is generated after a factory_reset)""" """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) Globals.getInstance().set_args(sys.argv)
@@ -690,7 +761,8 @@ def test_main_seturl(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_set_valid(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_set_valid(capsys):
"""Test --set with valid field""" """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_args(sys.argv)
@@ -710,7 +782,8 @@ def test_main_set_valid(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_set_with_invalid(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_set_with_invalid(capsys):
"""Test --set with invalid field""" """Test --set with invalid field"""
sys.argv = ['', '--set', 'foo', 'foo'] sys.argv = ['', '--set', 'foo', 'foo']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -735,7 +808,8 @@ def test_main_set_with_invalid(capsys, reset_globals):
# TODO: write some negative --configure tests # TODO: write some negative --configure tests
@pytest.mark.unit @pytest.mark.unit
def test_main_configure(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_configure(capsys):
"""Test --configure with valid file""" """Test --configure with valid file"""
sys.argv = ['', '--configure', 'example_config.yaml'] sys.argv = ['', '--configure', 'example_config.yaml']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -760,7 +834,8 @@ def test_main_configure(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_add_valid(capsys, reset_globals): @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""" """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) Globals.getInstance().set_args(sys.argv)
@@ -787,7 +862,8 @@ def test_main_ch_add_valid(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_add_invalid_name_too_long(capsys, reset_globals): @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""" """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) Globals.getInstance().set_args(sys.argv)
@@ -817,7 +893,8 @@ def test_main_ch_add_invalid_name_too_long(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_add_but_name_already_exists(capsys, reset_globals): @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""" """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) Globals.getInstance().set_args(sys.argv)
@@ -842,7 +919,8 @@ def test_main_ch_add_but_name_already_exists(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_add_but_no_more_channels(capsys, reset_globals): @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""" """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) Globals.getInstance().set_args(sys.argv)
@@ -869,7 +947,8 @@ def test_main_ch_add_but_no_more_channels(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_del(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_del(capsys):
"""Test --ch-del with valid secondary channel to be deleted""" """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) Globals.getInstance().set_args(sys.argv)
@@ -889,7 +968,8 @@ def test_main_ch_del(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_del_no_ch_index_specified(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_del_no_ch_index_specified(capsys):
"""Test --ch-del without a valid ch-index""" """Test --ch-del without a valid ch-index"""
sys.argv = ['', '--ch-del'] sys.argv = ['', '--ch-del']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -912,7 +992,8 @@ def test_main_ch_del_no_ch_index_specified(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_del_primary_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_del_primary_channel(capsys):
"""Test --ch-del on ch-index=0""" """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_args(sys.argv)
@@ -936,7 +1017,8 @@ def test_main_ch_del_primary_channel(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_enable_valid_secondary_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_enable_valid_secondary_channel(capsys):
"""Test --ch-enable with --ch-index""" """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) Globals.getInstance().set_args(sys.argv)
@@ -957,7 +1039,8 @@ def test_main_ch_enable_valid_secondary_channel(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_disable_valid_secondary_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_disable_valid_secondary_channel(capsys):
"""Test --ch-disable with --ch-index""" """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) Globals.getInstance().set_args(sys.argv)
@@ -978,7 +1061,8 @@ def test_main_ch_disable_valid_secondary_channel(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_enable_without_a_ch_index(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_enable_without_a_ch_index(capsys):
"""Test --ch-enable without --ch-index""" """Test --ch-enable without --ch-index"""
sys.argv = ['', '--ch-enable'] sys.argv = ['', '--ch-enable']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1002,7 +1086,8 @@ def test_main_ch_enable_without_a_ch_index(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_enable_primary_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_enable_primary_channel(capsys):
"""Test --ch-enable with --ch-index = 0""" """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) Globals.getInstance().set_args(sys.argv)
@@ -1026,7 +1111,8 @@ def test_main_ch_enable_primary_channel(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_range_options(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_range_options(capsys):
"""Test changing the various range options.""" """Test changing the various range options."""
range_options = ['--ch-longslow', '--ch-longfast', '--ch-mediumslow', range_options = ['--ch-longslow', '--ch-longfast', '--ch-mediumslow',
'--ch-mediumfast', '--ch-shortslow', '--ch-shortfast'] '--ch-mediumfast', '--ch-shortslow', '--ch-shortfast']
@@ -1049,7 +1135,8 @@ def test_main_ch_range_options(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_longsfast_on_non_primary_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_longsfast_on_non_primary_channel(capsys):
"""Test --ch-longfast --ch-index 1""" """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) Globals.getInstance().set_args(sys.argv)
@@ -1086,7 +1173,8 @@ def test_main_ch_longsfast_on_non_primary_channel(capsys, reset_globals):
# POS_TIMESTAMP 256 # POS_TIMESTAMP 256
@pytest.mark.unit @pytest.mark.unit
def test_main_pos_fields_no_args(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_pos_fields_no_args(capsys):
"""Test --pos-fields no args (which shows settings)""" """Test --pos-fields no args (which shows settings)"""
sys.argv = ['', '--pos-fields'] sys.argv = ['', '--pos-fields']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1094,18 +1182,14 @@ def test_main_pos_fields_no_args(capsys, reset_globals):
pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags) pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
with patch('meshtastic.serial_interface.SerialInterface') as mo: with patch('meshtastic.serial_interface.SerialInterface') as mo:
mo().getNode().radioConfig.preferences.position_flags = 35
with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc: with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
# kind of cheating here, we are setting up the node
mocked_node = MagicMock(autospec=Node)
anode = mocked_node()
anode.radioConfig.preferences.position_flags = 35
Globals.getInstance().set_target_node(anode)
mrc.values.return_value = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256] mrc.values.return_value = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256]
# Note: When you use side_effect and a list, each call will use a value from the front of the list then # Note: When you use side_effect and a list, each call will use a value from the front of the list then
# remove that value from the list. If there are three values in the list, we expect it to be called # remove that value from the list. If there are three values in the list, we expect it to be called
# three times. # three times.
mrc.Name.side_effect = [ 'POS_ALTITUDE', 'POS_ALT_MSL', 'POS_BATTERY' ] mrc.Name.side_effect = ['POS_ALTITUDE', 'POS_ALT_MSL', 'POS_BATTERY']
main() main()
@@ -1120,7 +1204,8 @@ def test_main_pos_fields_no_args(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_pos_fields_arg_of_zero(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_pos_fields_arg_of_zero(capsys):
"""Test --pos-fields an arg of 0 (which shows list)""" """Test --pos-fields an arg of 0 (which shows list)"""
sys.argv = ['', '--pos-fields', '0'] sys.argv = ['', '--pos-fields', '0']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1153,7 +1238,8 @@ def test_main_pos_fields_arg_of_zero(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_pos_fields_valid_values(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_pos_fields_valid_values(capsys):
"""Test --pos-fields with valid values""" """Test --pos-fields with valid values"""
sys.argv = ['', '--pos-fields', 'POS_GEO_SEP', 'POS_ALT_MSL'] sys.argv = ['', '--pos-fields', 'POS_GEO_SEP', 'POS_ALT_MSL']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1179,20 +1265,17 @@ def test_main_pos_fields_valid_values(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_get_with_valid_values(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_get_with_valid_values(capsys):
"""Test --get with valid values (with string, number, boolean)""" """Test --get with valid values (with string, number, boolean)"""
sys.argv = ['', '--get', 'ls_secs', '--get', 'wifi_ssid', '--get', 'fixed_position'] sys.argv = ['', '--get', 'ls_secs', '--get', 'wifi_ssid', '--get', 'fixed_position']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
with patch('meshtastic.serial_interface.SerialInterface') as mo: with patch('meshtastic.serial_interface.SerialInterface') as mo:
# kind of cheating here, we are setting up the node mo().getNode().radioConfig.preferences.wifi_ssid = 'foo'
mocked_node = MagicMock(autospec=Node) mo().getNode().radioConfig.preferences.ls_secs = 300
anode = mocked_node() mo().getNode().radioConfig.preferences.fixed_position = False
anode.radioConfig.preferences.wifi_ssid = 'foo'
anode.radioConfig.preferences.ls_secs = 300
anode.radioConfig.preferences.fixed_position = False
Globals.getInstance().set_target_node(anode)
main() main()
@@ -1207,7 +1290,8 @@ def test_main_get_with_valid_values(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_get_with_invalid(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_get_with_invalid(capsys):
"""Test --get with invalid field""" """Test --get with invalid field"""
sys.argv = ['', '--get', 'foo'] sys.argv = ['', '--get', 'foo']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1232,7 +1316,8 @@ def test_main_get_with_invalid(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setchan(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_setchan(capsys):
"""Test --setchan (deprecated)""" """Test --setchan (deprecated)"""
sys.argv = ['', '--setchan', 'a', 'b'] sys.argv = ['', '--setchan', 'a', 'b']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1249,7 +1334,8 @@ def test_main_setchan(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_onReceive_empty(caplog, reset_globals, capsys): @pytest.mark.usefixtures("reset_globals")
def test_main_onReceive_empty(caplog, capsys):
"""Test onReceive""" """Test onReceive"""
args = MagicMock() args = MagicMock()
Globals.getInstance().set_args(args) Globals.getInstance().set_args(args)
@@ -1275,7 +1361,8 @@ def test_main_onReceive_empty(caplog, reset_globals, capsys):
# } # }
@pytest.mark.unit @pytest.mark.unit
def test_main_onReceive_with_sendtext(caplog, capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_onReceive_with_sendtext(caplog, capsys):
"""Test onReceive with sendtext """Test onReceive with sendtext
The entire point of this test is to make sure the interface.close() call The entire point of this test is to make sure the interface.close() call
is made in onReceive(). is made in onReceive().
@@ -1310,7 +1397,8 @@ def test_main_onReceive_with_sendtext(caplog, capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_onReceive_with_text(caplog, capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_onReceive_with_text(caplog, capsys):
"""Test onReceive with text """Test onReceive with text
""" """
args = MagicMock() args = MagicMock()
@@ -1349,7 +1437,8 @@ def test_main_onReceive_with_text(caplog, capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_onConnection(reset_globals, capsys): @pytest.mark.usefixtures("reset_globals")
def test_main_onConnection(capsys):
"""Test onConnection""" """Test onConnection"""
sys.argv = [''] sys.argv = ['']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1367,7 +1456,8 @@ def test_main_onConnection(reset_globals, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_main_export_config(reset_globals, capsys): @pytest.mark.usefixtures("reset_globals")
def test_main_export_config(capsys):
"""Test export_config() function directly""" """Test export_config() function directly"""
iface = MagicMock(autospec=SerialInterface) 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:
@@ -1403,7 +1493,8 @@ position_flags: 35"""
@pytest.mark.unit @pytest.mark.unit
def test_main_export_config_called_from_main(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_export_config_called_from_main(capsys):
"""Test --export-config""" """Test --export-config"""
sys.argv = ['', '--export-config'] sys.argv = ['', '--export-config']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1419,9 +1510,10 @@ def test_main_export_config_called_from_main(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_gpio_rd_no_gpio_channel(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_gpio_rd_no_gpio_channel(capsys):
"""Test --gpio_rd with no named gpio channel""" """Test --gpio_rd with no named gpio channel"""
sys.argv = ['', '--gpio-rd', '0x10'] sys.argv = ['', '--gpio-rd', '0x10', '--dest', '!foo']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
@@ -1437,14 +1529,15 @@ def test_main_gpio_rd_no_gpio_channel(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_gpio_rd_no_dest(capsys, reset_globals): @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""" """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) Globals.getInstance().set_args(sys.argv)
channel = Channel(index=1, role=1) channel = Channel(index=2, role=2)
channel.settings.modem_config = 3 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.psk = b'\x01' channel.settings.name = 'gpio'
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode.getChannelByName.return_value = channel iface.localNode.getChannelByName.return_value = channel
@@ -1459,7 +1552,8 @@ def test_main_gpio_rd_no_dest(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_gpio_rd(caplog, capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_gpio_rd(caplog, capsys):
"""Test --gpio_rd with a named gpio channel""" """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 # 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) # red arrow (meaning ok to use for our purposes)
@@ -1516,7 +1610,8 @@ def test_main_gpio_rd(caplog, capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_getPref_valid_field(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_getPref_valid_field(capsys):
"""Test getPref() with a valid field""" """Test getPref() with a valid field"""
prefs = MagicMock() prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs' prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs'
@@ -1531,7 +1626,8 @@ def test_main_getPref_valid_field(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_getPref_valid_field_string(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_getPref_valid_field_string(capsys):
"""Test getPref() with a valid field and value as a string""" """Test getPref() with a valid field and value as a string"""
prefs = MagicMock() prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'wifi_ssid' prefs.DESCRIPTOR.fields_by_name.get.return_value = 'wifi_ssid'
@@ -1546,7 +1642,8 @@ def test_main_getPref_valid_field_string(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_getPref_valid_field_bool(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_getPref_valid_field_bool(capsys):
"""Test getPref() with a valid field and value as a bool""" """Test getPref() with a valid field and value as a bool"""
prefs = MagicMock() prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'fixed_position' prefs.DESCRIPTOR.fields_by_name.get.return_value = 'fixed_position'
@@ -1561,7 +1658,8 @@ def test_main_getPref_valid_field_bool(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_getPref_invalid_field(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_getPref_invalid_field(capsys):
"""Test getPref() with an invalid field""" """Test getPref() with an invalid field"""
class Field: class Field:
@@ -1592,7 +1690,8 @@ def test_main_getPref_invalid_field(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setPref_valid_field_int(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_setPref_valid_field_int(capsys):
"""Test setPref() with a valid field""" """Test setPref() with a valid field"""
class Field: class Field:
@@ -1614,7 +1713,8 @@ def test_main_setPref_valid_field_int(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setPref_invalid_field(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_setPref_invalid_field(capsys):
"""Test setPref() with a invalid field""" """Test setPref() with a invalid field"""
@@ -1645,7 +1745,8 @@ def test_main_setPref_invalid_field(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_set_psk_no_ch_index(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_set_psk_no_ch_index(capsys):
"""Test --ch-set psk """ """Test --ch-set psk """
sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local'] sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1664,7 +1765,8 @@ def test_main_ch_set_psk_no_ch_index(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_set_psk_with_ch_index(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_set_psk_with_ch_index(capsys):
"""Test --ch-set psk """ """Test --ch-set psk """
sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local', '--ch-index', '0'] sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local', '--ch-index', '0']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1680,7 +1782,8 @@ def test_main_ch_set_psk_with_ch_index(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_ch_set_name_with_ch_index(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_main_ch_set_name_with_ch_index(capsys):
"""Test --ch-set setting other than psk""" """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) Globals.getInstance().set_args(sys.argv)
@@ -1697,7 +1800,8 @@ def test_main_ch_set_name_with_ch_index(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_onNode(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_onNode(capsys):
"""Test onNode""" """Test onNode"""
onNode('foo') onNode('foo')
out, err = capsys.readouterr() out, err = capsys.readouterr()
@@ -1706,7 +1810,8 @@ def test_onNode(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_tunnel_no_args(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_tunnel_no_args(capsys):
"""Test tunnel no arguments""" """Test tunnel no arguments"""
sys.argv = [''] sys.argv = ['']
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
@@ -1719,9 +1824,10 @@ def test_tunnel_no_args(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
@patch('meshtastic.util.findPorts', return_value=[]) @patch('meshtastic.util.findPorts', return_value=[])
@patch('platform.system') @patch('platform.system')
def test_tunnel_tunnel_arg_with_no_devices(mock_platform_system, patched_find_ports, caplog, capsys, reset_globals): 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)""" """Test tunnel with tunnel arg (act like we are on a linux system)"""
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = 'Linux'
@@ -1741,9 +1847,10 @@ def test_tunnel_tunnel_arg_with_no_devices(mock_platform_system, patched_find_po
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
@patch('meshtastic.util.findPorts', return_value=[]) @patch('meshtastic.util.findPorts', return_value=[])
@patch('platform.system') @patch('platform.system')
def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, patched_find_ports, caplog, capsys, reset_globals): 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)""" """Test tunnel with subnet arg (act like we are on a linux system)"""
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = 'Linux'
@@ -1763,11 +1870,13 @@ def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, patched_find_po
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
@patch('platform.system') @patch('platform.system')
def test_tunnel_tunnel_arg(mock_platform_system, caplog, reset_globals, iface_with_nodes, capsys): 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)""" """Test tunnel with tunnel arg (act like we are on a linux system)"""
# Override the time.sleep so there is no loop # Override the time.sleep so there is no loop
def my_sleep(amount): def my_sleep(amount):
print(f'{amount}')
sys.exit(3) sys.exit(3)
a_mock = MagicMock() a_mock = MagicMock()

View File

@@ -15,7 +15,8 @@ from ..util import Timeout
@pytest.mark.unit @pytest.mark.unit
def test_MeshInterface(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_MeshInterface(capsys):
"""Test that we can instantiate a MeshInterface""" """Test that we can instantiate a MeshInterface"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -56,7 +57,8 @@ def test_MeshInterface(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_getMyUser(reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getMyUser(iface_with_nodes):
"""Test getMyUser()""" """Test getMyUser()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -66,7 +68,8 @@ def test_getMyUser(reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_getLongName(reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getLongName(iface_with_nodes):
"""Test getLongName()""" """Test getLongName()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -75,7 +78,8 @@ def test_getLongName(reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_getShortName(reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getShortName(iface_with_nodes):
"""Test getShortName().""" """Test getShortName()."""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -84,7 +88,8 @@ def test_getShortName(reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_handlePacketFromRadio_no_from(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_no_from(capsys):
"""Test _handlePacketFromRadio with no 'from' in the mesh packet.""" """Test _handlePacketFromRadio with no 'from' in the mesh packet."""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
@@ -95,7 +100,8 @@ def test_handlePacketFromRadio_no_from(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_with_a_portnum(caplog):
"""Test _handlePacketFromRadio with a portnum """Test _handlePacketFromRadio with a portnum
Since we have an attribute called 'from', we cannot simply 'set' it. 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. Had to implement a hack just to be able to test some code.
@@ -110,7 +116,8 @@ def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_handlePacketFromRadio_no_portnum(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_no_portnum(caplog):
"""Test _handlePacketFromRadio without a portnum""" """Test _handlePacketFromRadio without a portnum"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
@@ -121,7 +128,8 @@ def test_handlePacketFromRadio_no_portnum(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_getNode_with_local(reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_getNode_with_local():
"""Test getNode""" """Test getNode"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR) anode = iface.getNode(LOCAL_ADDR)
@@ -129,7 +137,8 @@ def test_getNode_with_local(reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_getNode_not_local(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_getNode_not_local(caplog):
"""Test getNode not local""" """Test getNode not local"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node) anode = MagicMock(autospec=Node)
@@ -141,7 +150,8 @@ def test_getNode_not_local(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_getNode_not_local_timeout(reset_globals, capsys): @pytest.mark.usefixtures("reset_globals")
def test_getNode_not_local_timeout(capsys):
"""Test getNode not local, simulate timeout""" """Test getNode not local, simulate timeout"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node) anode = MagicMock(autospec=Node)
@@ -157,7 +167,8 @@ def test_getNode_not_local_timeout(reset_globals, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_sendPosition(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_sendPosition(caplog):
"""Test sendPosition""" """Test sendPosition"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -167,7 +178,8 @@ def test_sendPosition(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_close_with_heartbeatTimer(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_close_with_heartbeatTimer(caplog):
"""Test close() with heartbeatTimer""" """Test close() with heartbeatTimer"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -183,7 +195,8 @@ def test_close_with_heartbeatTimer(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_empty_payload(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_empty_payload(caplog):
"""Test _handleFromRadio""" """Test _handleFromRadio"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -193,7 +206,8 @@ def test_handleFromRadio_empty_payload(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_my_info(reset_globals, caplog): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_my_info(caplog):
"""Test _handleFromRadio with my_info""" """Test _handleFromRadio with my_info"""
# Note: I captured the '--debug --info' for the bytes below. # Note: I captured the '--debug --info' for the bytes below.
# It "translates" to this: # It "translates" to this:
@@ -218,7 +232,8 @@ def test_handleFromRadio_with_my_info(reset_globals, caplog):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_node_info(caplog, capsys):
"""Test _handleFromRadio with node_info""" """Test _handleFromRadio with node_info"""
# Note: I captured the '--debug --info' for the bytes below. # Note: I captured the '--debug --info' for the bytes below.
# It "translates" to this: # It "translates" to this:
@@ -254,7 +269,8 @@ def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
"""Test _handleFromRadio with node_info""" """Test _handleFromRadio with node_info"""
# Note: Captured the '--debug --info' for the bytes below. # Note: Captured the '--debug --info' for the bytes below.
# pylint: disable=C0301 # pylint: disable=C0301
@@ -277,7 +293,8 @@ def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplog, capsys): @pytest.mark.usefixtures("reset_globals")
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(caplog):
"""Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception""" """Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
# Note: Captured the '--debug --info' for the bytes below. # Note: Captured the '--debug --info' for the bytes below.
from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@' from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@'
@@ -288,7 +305,8 @@ def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplo
@pytest.mark.unit @pytest.mark.unit
def test_MeshInterface_sendToRadioImpl(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_MeshInterface_sendToRadioImpl(caplog):
"""Test _sendToRadioImp()""" """Test _sendToRadioImp()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -298,7 +316,8 @@ def test_MeshInterface_sendToRadioImpl(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_MeshInterface_sendToRadio_no_proto(caplog):
"""Test sendToRadio()""" """Test sendToRadio()"""
iface = MeshInterface() iface = MeshInterface()
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -308,7 +327,8 @@ def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendData_too_long(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendData_too_long(caplog):
"""Test when data payload is too big""" """Test when data payload is too big"""
iface = MeshInterface(noProto=True) 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.'
@@ -332,7 +352,8 @@ def test_sendData_too_long(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendData_unknown_app(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendData_unknown_app(capsys):
"""Test sendData when unknown app""" """Test sendData when unknown app"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -345,7 +366,8 @@ def test_sendData_unknown_app(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPosition_with_a_position(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPosition_with_a_position(caplog):
"""Test sendPosition when lat/long/alt""" """Test sendPosition when lat/long/alt"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -356,7 +378,8 @@ def test_sendPosition_with_a_position(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_no_destination(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_no_destination(capsys):
"""Test _sendPacket()""" """Test _sendPacket()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -369,7 +392,8 @@ def test_sendPacket_with_no_destination(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_int(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_int(caplog):
"""Test _sendPacket() with int as a destination""" """Test _sendPacket() with int as a destination"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -379,7 +403,8 @@ def test_sendPacket_with_destination_as_int(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_starting_with_a_bang(caplog):
"""Test _sendPacket() with int as a destination""" """Test _sendPacket() with int as a destination"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -389,7 +414,8 @@ def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals)
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
"""Test _sendPacket() with BROADCAST_ADDR as a destination""" """Test _sendPacket() with BROADCAST_ADDR as a destination"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@@ -399,7 +425,8 @@ def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
"""Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo""" """Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -413,11 +440,13 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globa
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
"""Test _sendPacket() with LOCAL_ADDR as a destination with myInfo""" """Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
myInfo = MagicMock() myInfo = MagicMock()
iface.myInfo = myInfo iface.myInfo = myInfo
iface.myInfo.my_node_num = 1
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR) iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
@@ -425,7 +454,8 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_glo
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_nodes):
"""Test _sendPacket() with '' as a destination with myInfo""" """Test _sendPacket() with '' as a destination with myInfo"""
iface = iface_with_nodes iface = iface_with_nodes
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
@@ -439,7 +469,8 @@ def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals,
@pytest.mark.unit @pytest.mark.unit
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_nodes):
"""Test _sendPacket() with '' as a destination with myInfo""" """Test _sendPacket() with '' as a destination with myInfo"""
iface = iface_with_nodes iface = iface_with_nodes
iface.nodes = None iface.nodes = None
@@ -450,7 +481,8 @@ def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_global
@pytest.mark.unit @pytest.mark.unit
def test_getMyNodeInfo(reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_getMyNodeInfo():
"""Test getMyNodeInfo()""" """Test getMyNodeInfo()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR) anode = iface.getNode(LOCAL_ADDR)
@@ -464,7 +496,8 @@ def test_getMyNodeInfo(reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_generatePacketId(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_generatePacketId(capsys):
"""Test _generatePacketId() when no currentPacketId (not connected)""" """Test _generatePacketId() when no currentPacketId (not connected)"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
# not sure when this condition would ever happen... but we can simulate it # not sure when this condition would ever happen... but we can simulate it
@@ -479,7 +512,8 @@ def test_generatePacketId(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_fixupPosition_empty_pos(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_fixupPosition_empty_pos():
"""Test _fixupPosition()""" """Test _fixupPosition()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
pos = {} pos = {}
@@ -488,7 +522,8 @@ def test_fixupPosition_empty_pos(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_fixupPosition_no_changes_needed(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_fixupPosition_no_changes_needed():
"""Test _fixupPosition()""" """Test _fixupPosition()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
pos = {"latitude": 101, "longitude": 102} pos = {"latitude": 101, "longitude": 102}
@@ -497,7 +532,8 @@ def test_fixupPosition_no_changes_needed(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_fixupPosition(capsys, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_fixupPosition():
"""Test _fixupPosition()""" """Test _fixupPosition()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
pos = {"latitudeI": 1010000000, "longitudeI": 1020000000} pos = {"latitudeI": 1010000000, "longitudeI": 1020000000}
@@ -509,7 +545,8 @@ def test_fixupPosition(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_nodeNumToId(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_nodeNumToId(iface_with_nodes):
"""Test _nodeNumToId()""" """Test _nodeNumToId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -518,7 +555,8 @@ def test_nodeNumToId(capsys, reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_nodeNumToId_not_found(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_nodeNumToId_not_found(iface_with_nodes):
"""Test _nodeNumToId()""" """Test _nodeNumToId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -527,7 +565,8 @@ def test_nodeNumToId_not_found(capsys, reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_nodeNumToId_to_all(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_nodeNumToId_to_all(iface_with_nodes):
"""Test _nodeNumToId()""" """Test _nodeNumToId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -536,7 +575,8 @@ def test_nodeNumToId_to_all(capsys, reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_getOrCreateByNum_minimal(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getOrCreateByNum_minimal(iface_with_nodes):
"""Test _getOrCreateByNum()""" """Test _getOrCreateByNum()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -545,7 +585,8 @@ def test_getOrCreateByNum_minimal(capsys, reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_getOrCreateByNum_not_found(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getOrCreateByNum_not_found(iface_with_nodes):
"""Test _getOrCreateByNum()""" """Test _getOrCreateByNum()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -555,7 +596,8 @@ def test_getOrCreateByNum_not_found(capsys, reset_globals, iface_with_nodes):
@pytest.mark.unit @pytest.mark.unit
def test_getOrCreateByNum(capsys, reset_globals, iface_with_nodes): @pytest.mark.usefixtures("reset_globals")
def test_getOrCreateByNum(iface_with_nodes):
"""Test _getOrCreateByNum()""" """Test _getOrCreateByNum()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -581,7 +623,7 @@ def test_exit_with_exception(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_showNodes_exclude_self(capsys, caplog, reset_globals, iface_with_nodes): def test_showNodes_exclude_self(capsys, caplog, iface_with_nodes):
"""Test that we hit that continue statement""" """Test that we hit that continue statement"""
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface = iface_with_nodes iface = iface_with_nodes
@@ -591,8 +633,8 @@ def test_showNodes_exclude_self(capsys, caplog, reset_globals, iface_with_nodes)
capsys.readouterr() capsys.readouterr()
@pytest.mark.unit @pytest.mark.unitslow
def test_waitForConfig(caplog, capsys): def test_waitForConfig(capsys):
"""Test waitForConfig()""" """Test waitForConfig()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
# override how long to wait # override how long to wait
@@ -606,7 +648,7 @@ def test_waitForConfig(caplog, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_waitConnected_raises_an_exception(caplog, capsys): def test_waitConnected_raises_an_exception(capsys):
"""Test waitConnected()""" """Test waitConnected()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
@@ -619,7 +661,7 @@ def test_waitConnected_raises_an_exception(caplog, capsys):
@pytest.mark.unit @pytest.mark.unit
def test_waitConnected_isConnected_timeout(caplog, capsys): def test_waitConnected_isConnected_timeout(capsys):
"""Test waitConnected()""" """Test waitConnected()"""
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
iface = MeshInterface() iface = MeshInterface()

View File

@@ -150,7 +150,7 @@ def test_setURL_valid_URL(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_setURL_valid_URL_but_no_settings(caplog, capsys): def test_setURL_valid_URL_but_no_settings(capsys):
"""Test setURL""" """Test setURL"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
url = "https://www.meshtastic.org/d/#" url = "https://www.meshtastic.org/d/#"
@@ -430,7 +430,7 @@ def test_deleteChannel_secondary_with_admin_channel_before_testing():
@pytest.mark.unit @pytest.mark.unit
def test_getChannelByName(capsys): def test_getChannelByName():
"""Get a channel by the name.""" """Get a channel by the name."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -457,7 +457,7 @@ def test_getChannelByName(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getChannelByName_invalid_name(capsys): def test_getChannelByName_invalid_name():
"""Get a channel by the name but one that is not present.""" """Get a channel by the name but one that is not present."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -484,7 +484,7 @@ def test_getChannelByName_invalid_name(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getDisabledChannel(capsys): def test_getDisabledChannel():
"""Get the first disabled channel.""" """Get the first disabled channel."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -514,7 +514,7 @@ def test_getDisabledChannel(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getDisabledChannel_where_all_channels_are_used(capsys): def test_getDisabledChannel_where_all_channels_are_used():
"""Get the first disabled channel.""" """Get the first disabled channel."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -538,7 +538,7 @@ def test_getDisabledChannel_where_all_channels_are_used(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getAdminChannelIndex(capsys): def test_getAdminChannelIndex():
"""Get the 'admin' channel index.""" """Get the 'admin' channel index."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -565,7 +565,7 @@ def test_getAdminChannelIndex(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_getAdminChannelIndex_when_no_admin_named_channel(capsys): def test_getAdminChannelIndex_when_no_admin_named_channel():
"""Get the 'admin' channel when there is not one.""" """Get the 'admin' channel when there is not one."""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')
@@ -882,7 +882,7 @@ def test_onResponseRequestSetting_with_error(capsys):
assert err == '' assert err == ''
@pytest.mark.unit @pytest.mark.unitslow
def test_waitForConfig(): def test_waitForConfig():
"""Test waitForConfig()""" """Test waitForConfig()"""
anode = Node('foo', 'bar') anode = Node('foo', 'bar')

View File

@@ -3,15 +3,19 @@
import re import re
from unittest.mock import patch from unittest.mock import patch, mock_open
import pytest import pytest
from ..serial_interface import SerialInterface from ..serial_interface import SerialInterface
@pytest.mark.unit @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('serial.Serial')
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake']) @patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake'])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, capsys): 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""" """Test that we can instantiate a SerialInterface with a single port"""
iface = SerialInterface(noProto=True) iface = SerialInterface(noProto=True)
iface.showInfo() iface.showInfo()
@@ -19,6 +23,10 @@ def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, capsys):
iface.close() iface.close()
mocked_findPorts.assert_called() mocked_findPorts.assert_called()
mocked_serial.assert_called() mocked_serial.assert_called()
mocked_open.assert_called()
mock_get.assert_called()
mock_set.assert_called()
mock_sleep.assert_called()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Nodes in mesh', 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'Preferences', out, re.MULTILINE)

View File

@@ -0,0 +1,706 @@
"""Meshtastic smoke tests with a single virtual device via localhost.
During the CI build of the Meshtastic-device, a build.zip file is created.
Inside that build.zip is a standalone executable meshtasticd_linux_amd64.
That linux executable will simulate a Meshtastic device listening on localhost.
This smoke test runs against that localhost.
"""
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
import pytest
from ..util import findPorts
# seconds to pause after running a meshtastic command
PAUSE_AFTER_COMMAND = 0.1
PAUSE_AFTER_REBOOT = 0.1
#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():
# """Test reboot"""
# return_value, _ = subprocess.getstatusoutput('meshtastic --host localhost --reboot')
# assert return_value == 0
# # pause for the radio to reset
# time.sleep(8)
@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)
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)
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)
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)
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)
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)
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)
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.
"""
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)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_seriallog_to_file():
"""Test --seriallog to a file creates a file"""
filename = 'tmpoutput.txt'
if os.path.exists(f"{filename}"):
os.remove(f"{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}")
@pytest.mark.smokevirt
def test_smokevirt_qr():
"""Test --qr"""
filename = 'tmpqr'
if os.path.exists(f"{filename}"):
os.remove(f"{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
assert os.stat(f"{filename}").st_size > 20000
assert return_value == 0
os.remove(f"{filename}")
@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)
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)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_port():
"""Test --port"""
# first, get the ports
ports = findPorts()
# hopefully there is none
assert len(ports) == 0
@pytest.mark.smokevirt
def test_smokevirt_set_is_router_true():
"""Test --set is_router true"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set is_router true')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router 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 --get is_router')
assert re.search(r'^is_router: True', out, re.MULTILINE)
assert return_value == 0
@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)
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)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_is_router_false():
"""Test --set is_router false"""
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set is_router false')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to false', 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 is_router')
assert re.search(r'^is_router: False', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
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)
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)
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)
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)
assert return_value == 0
@pytest.mark.smokevirt
def test_smokevirt_set_team():
"""Test --set-team """
# unset the team
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-team CLEAR')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Setting team to CLEAR', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --set-team CYAN')
assert re.search(r'Setting team to CYAN', 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'CYAN', 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
"""
exp = {
'--ch-longslow': 'Bw125Cr48Sf4096',
'--ch-longfast': 'Bw31_25Cr48Sf512',
'--ch-mediumslow': 'Bw250Cr46Sf2048',
'--ch-mediumfast': 'Bw250Cr47Sf1024',
'--ch-shortslow': '{ "psk',
'--ch-shortfast': 'Bw500Cr45Sf128'
}
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)
assert return_value == 0
# pause for the radio (might reboot)
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --host localhost --info')
assert re.search(val, out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
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)
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)
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)
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)
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)
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)
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)
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)
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-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)
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)
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 --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-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)
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')
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')
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)
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')
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)
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 return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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-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)
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')
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')
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)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
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)
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)
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)
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)
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)
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)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
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)
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)
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)
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)
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)
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)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
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)
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 Bw31_25Cr48Sf512 --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set modem_config to 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 --info')
assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smokevirt
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')
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)
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)
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)
assert return_value == 0
@pytest.mark.smokevirt
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)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
@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)
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
@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.
"""
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)
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)
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)
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)
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?

View File

@@ -19,7 +19,8 @@ def test_StreamInterface():
# Note: This takes a bit, so moving from unit to slow # Note: This takes a bit, so moving from unit to slow
@pytest.mark.unitslow @pytest.mark.unitslow
def test_StreamInterface_with_noProto(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_StreamInterface_with_noProto(caplog):
"""Test that we can instantiate a StreamInterface based on nonProto """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
""" """
@@ -38,7 +39,8 @@ def test_StreamInterface_with_noProto(caplog, reset_globals):
## Tip: If you want to see the print output, run with '-s' flag: ## Tip: If you want to see the print output, run with '-s' flag:
## pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl ## pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
@pytest.mark.unitslow @pytest.mark.unitslow
def test_sendToRadioImpl(caplog, reset_globals): @pytest.mark.usefixtures("reset_globals")
def test_sendToRadioImpl(caplog):
"""Test _sendToRadioImpl()""" """Test _sendToRadioImpl()"""
# def add_header(b): # def add_header(b):

View File

@@ -28,7 +28,24 @@ def test_TCPInterface(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_TCPInterface_without_connecting(capsys): def test_TCPInterface_exception():
"""Test that we can instantiate a TCPInterface"""
def throw_an_exception():
raise ValueError("Fake exception.")
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)
iface.myConnect()
iface.close()
assert mock_socket.called
assert mock_shutdown.called
@pytest.mark.unit
def test_TCPInterface_without_connecting():
"""Test that we can instantiate a TCPInterface with connectNow as false""" """Test that we can instantiate a TCPInterface with connectNow as false"""
with patch('socket.socket'): with patch('socket.socket'):
iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False) iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False)

View File

@@ -14,7 +14,7 @@ from ..globals import Globals
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch('platform.system')
def test_Tunnel_on_non_linux_system(mock_platform_system, reset_globals): def test_Tunnel_on_non_linux_system(mock_platform_system):
"""Test that we cannot instantiate a Tunnel on a non Linux system""" """Test that we cannot instantiate a Tunnel on a non Linux system"""
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'notLinux' a_mock.return_value = 'notLinux'
@@ -29,7 +29,7 @@ def test_Tunnel_on_non_linux_system(mock_platform_system, reset_globals):
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch('platform.system')
def test_Tunnel_without_interface(mock_platform_system, reset_globals): def test_Tunnel_without_interface(mock_platform_system):
"""Test that we can not instantiate a Tunnel without a valid interface""" """Test that we can not instantiate a Tunnel without a valid interface"""
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = 'Linux'
@@ -41,7 +41,7 @@ def test_Tunnel_without_interface(mock_platform_system, reset_globals):
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_Tunnel_with_interface(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_Tunnel_with_interface(mock_platform_system, caplog, iface_with_nodes):
"""Test that we can not instantiate a Tunnel without a valid interface""" """Test that we can not instantiate a Tunnel without a valid interface"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -60,7 +60,7 @@ def test_Tunnel_with_interface(mock_platform_system, caplog, reset_globals, ifac
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with_nodes):
"""Test onTunnelReceive""" """Test onTunnelReceive"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -81,7 +81,7 @@ def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, reset_glob
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch('platform.system')
def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, iface_with_nodes):
"""Test onTunnelReceive""" """Test onTunnelReceive"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
@@ -99,9 +99,9 @@ def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, reset_g
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE) assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_random(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -117,9 +117,9 @@ def test_shouldFilterPacket_random(mock_platform_system, caplog, reset_globals,
assert not ignore assert not ignore
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -135,9 +135,9 @@ def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, reset_glo
assert ignore assert ignore
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_icmp(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_icmp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -156,7 +156,7 @@ def test_shouldFilterPacket_icmp(mock_platform_system, caplog, reset_globals, if
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_udp(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_udp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -173,9 +173,9 @@ def test_shouldFilterPacket_udp(mock_platform_system, caplog, reset_globals, ifa
assert not ignore assert not ignore
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -196,7 +196,7 @@ def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, reset_
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_tcp(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_tcp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -213,9 +213,9 @@ def test_shouldFilterPacket_tcp(mock_platform_system, caplog, reset_globals, ifa
assert not ignore assert not ignore
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -234,9 +234,9 @@ def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, reset_
assert ignore assert ignore
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_ipToNodeId_none(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_ipToNodeId_none(mock_platform_system, caplog, iface_with_nodes):
"""Test _ipToNodeId()""" """Test _ipToNodeId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
@@ -250,9 +250,9 @@ def test_ipToNodeId_none(mock_platform_system, caplog, reset_globals, iface_with
assert nodeid is None assert nodeid is None
@pytest.mark.unit @pytest.mark.unitslow
@patch('platform.system') @patch('platform.system')
def test_ipToNodeId_all(mock_platform_system, caplog, reset_globals, iface_with_nodes): def test_ipToNodeId_all(mock_platform_system, caplog, iface_with_nodes):
"""Test _ipToNodeId()""" """Test _ipToNodeId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True

View File

@@ -88,7 +88,7 @@ def test_pskToString_one_byte_zero_value():
assert pskToString(bytes([0x00])) == 'unencrypted' assert pskToString(bytes([0x00])) == 'unencrypted'
@pytest.mark.unit @pytest.mark.unitslow
def test_pskToString_one_byte_non_zero_value(): def test_pskToString_one_byte_non_zero_value():
"""Test pskToString one byte that is non-zero""" """Test pskToString one byte that is non-zero"""
assert pskToString(bytes([0x01])) == 'default' assert pskToString(bytes([0x01])) == 'default'
@@ -118,7 +118,7 @@ def test_our_exit_zero_return_value(capsys):
assert pytest_wrapped_e.value.code == 0 assert pytest_wrapped_e.value.code == 0
@pytest.mark.unit @pytest.mark.unitslow
def test_our_exit_non_zero_return_value(capsys): def test_our_exit_non_zero_return_value(capsys):
"""Test our_exit with a non-zero return value""" """Test our_exit with a non-zero return value"""
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -130,7 +130,7 @@ def test_our_exit_non_zero_return_value(capsys):
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@pytest.mark.unit @pytest.mark.unitslow
def test_fixme(): def test_fixme():
"""Test fixme()""" """Test fixme()"""
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
@@ -166,7 +166,7 @@ def test_remove_keys_from_dict_empty_keys_empty_dict():
assert not remove_keys_from_dict((), {}) assert not remove_keys_from_dict((), {})
@pytest.mark.unit @pytest.mark.unitslow
def test_remove_keys_from_dict_empty_dict(): def test_remove_keys_from_dict_empty_dict():
"""Test when dict is empty""" """Test when dict is empty"""
assert not remove_keys_from_dict(('a'), {}) assert not remove_keys_from_dict(('a'), {})
@@ -178,13 +178,13 @@ def test_remove_keys_from_dict_empty_keys():
assert remove_keys_from_dict((), {'a':1}) == {'a':1} assert remove_keys_from_dict((), {'a':1}) == {'a':1}
@pytest.mark.unit @pytest.mark.unitslow
def test_remove_keys_from_dict(): def test_remove_keys_from_dict():
"""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.unit @pytest.mark.unitslow
def test_remove_keys_from_dict_multiple_keys(): def test_remove_keys_from_dict_multiple_keys():
"""Test remove_keys_from_dict()""" """Test remove_keys_from_dict()"""
keys = ('a', 'b') keys = ('a', 'b')
@@ -224,7 +224,7 @@ def test_hexstr():
assert hexstr(b'') == '' assert hexstr(b'') == ''
@pytest.mark.unit @pytest.mark.unitslow
def test_ipstr(): def test_ipstr():
"""Test ipstr()""" """Test ipstr()"""
assert ipstr(b'1234') == '49.50.51.52' assert ipstr(b'1234') == '49.50.51.52'
@@ -237,15 +237,17 @@ def test_readnet_u16():
assert readnet_u16(b'123456', 2) == 13108 assert readnet_u16(b'123456', 2) == 13108
@pytest.mark.unit @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): def test_findPorts_when_none_found(patch_comports):
"""Test findPorts()""" """Test findPorts()"""
assert not findPorts() assert not findPorts()
patch_comports.assert_called()
@pytest.mark.unit @pytest.mark.unitslow
def test_convert_mac_addr(): def test_convert_mac_addr():
"""Test convert_mac_addr()""" """Test convert_mac_addr()"""
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b' 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('') == ''

View File

@@ -27,7 +27,7 @@ from meshtastic.util import ipstr, readnet_u16
from meshtastic.globals import Globals from meshtastic.globals import Globals
def onTunnelReceive(packet, interface): def onTunnelReceive(packet, interface): # pylint: disable=W0613
"""Callback for received tunneled messages from mesh.""" """Callback for received tunneled messages from mesh."""
logging.debug(f'in onTunnelReceive()') logging.debug(f'in onTunnelReceive()')
our_globals = Globals.getInstance() our_globals = Globals.getInstance()

View File

@@ -3,6 +3,7 @@
import traceback import traceback
from queue import Queue from queue import Queue
import os import os
import re
import sys import sys
import base64 import base64
import time import time
@@ -189,16 +190,16 @@ def support_info():
print('or wish to make feature requests, visit:') print('or wish to make feature requests, visit:')
print('https://github.com/meshtastic/Meshtastic-python/issues') print('https://github.com/meshtastic/Meshtastic-python/issues')
print('When adding an issue, be sure to include the following info:') print('When adding an issue, be sure to include the following info:')
print(' System: {0}'.format(platform.system())) print(f' System: {platform.system()}')
print(' Platform: {0}'.format(platform.platform())) print(f' Platform: {platform.platform()}')
print(' Release: {0}'.format(platform.uname().release)) print(f' Release: {platform.uname().release}')
print(' Machine: {0}'.format(platform.uname().machine)) print(f' Machine: {platform.uname().machine}')
print(' Encoding (stdin): {0}'.format(sys.stdin.encoding)) print(f' Encoding (stdin): {sys.stdin.encoding}')
print(' Encoding (stdout): {0}'.format(sys.stdout.encoding)) print(f' Encoding (stdout): {sys.stdout.encoding}')
print(' meshtastic: v{0}'.format(pkg_resources.require('meshtastic')[0].version)) the_version = pkg_resources.get_distribution("meshtastic").version
print(' Executable: {0}'.format(sys.argv[0])) print(f' meshtastic: v{the_version}')
print(' Python: {0} {1} {2}'.format(platform.python_version(), print(f' Executable: {sys.argv[0]}')
platform.python_implementation(), platform.python_compiler())) print(f' Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}')
print('') print('')
print('Please add the output from the command: meshtastic --info') print('Please add the output from the command: meshtastic --info')
@@ -220,12 +221,12 @@ def remove_keys_from_dict(keys, adict):
def hexstr(barray): def hexstr(barray):
"""Print a string of hex digits""" """Print a string of hex digits"""
return ":".join('{:02x}'.format(x) for x in barray) return ":".join(f'{x:02x}' for x in barray)
def ipstr(barray): def ipstr(barray):
"""Print a string of ip digits""" """Print a string of ip digits"""
return ".".join('{}'.format(x) for x in barray) return ".".join(f'{x}' for x in barray)
def readnet_u16(p, offset): def readnet_u16(p, offset):
@@ -238,5 +239,7 @@ def convert_mac_addr(val):
val - base64 encoded value (ex: '/c0gFyhb')) val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b') returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
""" """
val_as_bytes = base64.b64decode(val) if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val):
return hexstr(val_as_bytes) val_as_bytes = base64.b64decode(val)
return hexstr(val_as_bytes)
return val

2
proto

Submodule proto updated: 7b80bde421...18fc4cdb52

View File

@@ -1,11 +1,15 @@
[pytest] [pytest]
addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples" addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples and not smokevirt"
filterwarnings =
ignore::DeprecationWarning
markers = markers =
unit: marks tests as unit tests unit: marks tests as unit tests
unitslow: marks slow unit tests unitslow: marks slow unit tests
int: marks tests as integration tests int: marks tests as integration tests
smokevirt: marks tests as smoke tests against virtual device
smoke1: runs smoke tests on a single device connected via USB smoke1: runs smoke tests on a single device connected via USB
smoke2: runs smoke tests on a two devices connected via USB smoke2: runs smoke tests on a two devices connected via USB
smokewifi: runs smoke test on an esp32 device setup with wifi smokewifi: runs smoke test on an esp32 device setup with wifi

View File

@@ -4,7 +4,6 @@ protobuf
dotmap dotmap
pexpect pexpect
pyqrcode pyqrcode
pygatt
tabulate tabulate
timeago timeago
webencodings webencodings
@@ -18,3 +17,4 @@ pyyaml
pytap2 pytap2
pdoc3 pdoc3
pypubsub pypubsub
pygatt; platform_system == "Linux"

View File

@@ -12,7 +12,7 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work # This call to setup() does all the work
setup( setup(
name="meshtastic", name="meshtastic",
version="1.2.52", version="1.2.54",
description="Python API & client shell for talking to Meshtastic devices", description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
@@ -27,12 +27,14 @@ setup(
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
], ],
packages=["meshtastic"], packages=["meshtastic"],
include_package_data=True, include_package_data=True,
install_requires=["pyserial>=3.4", "protobuf>=3.13.0", install_requires=["pyserial>=3.4", "protobuf>=3.13.0",
"pypubsub>=4.0.3", "dotmap>=1.3.14", "pexpect>=4.6.0", "pyqrcode>=1.2.1", "pypubsub>=4.0.3", "dotmap>=1.3.14", "pexpect>=4.6.0", "pyqrcode>=1.2.1",
"pygatt>=4.0.5", "tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml"], "tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml",
"pygatt>=4.0.5 ; platform_system=='Linux'"],
extras_require={ extras_require={
'tunnel': ["pytap2>=2.0.0"] 'tunnel': ["pytap2>=2.0.0"]
}, },