Compare commits

..

64 Commits

Author SHA1 Message Date
mkinney
0a655ac8df Merge pull request #203 from mkinney/minor_changes
do not print line for export; comment out ble test; do not send decoded
2022-01-01 09:51:34 -08:00
Mike Kinney
0b6676c5b3 do not print line for export; comment out ble test; do not send decoded 2022-01-01 09:49:21 -08:00
mkinney
e5ecba7ec0 Merge pull request #202 from mkinney/format_mac_address
if mac address is in nodes, format it like a valid mac address
2021-12-31 20:04:19 -08:00
Mike Kinney
a1809f5b84 if mac address is in nodes, format it like a valid mac address 2021-12-31 20:01:14 -08:00
mkinney
9c66447913 Merge pull request #201 from mkinney/more_testing
added tests for _getOrCreateByNum(), nodeNumToId(), and _fixupPositio…
2021-12-31 14:26:37 -08:00
Mike Kinney
65960fb982 added tests for _getOrCreateByNum(), nodeNumToId(), and _fixupPosition(); found/fixed bug on _fixupPosition 2021-12-31 13:43:37 -08:00
mkinney
9d0bc09e0f Merge pull request #200 from mkinney/tuning_tests
Tuning tests
2021-12-31 12:30:00 -08:00
Mike Kinney
475ddcc8dd add tests for _ipToNodeId() 2021-12-31 12:28:14 -08:00
Mike Kinney
105276f98e add unit tests for _shouldFilterPacket() 2021-12-31 12:17:04 -08:00
Mike Kinney
4ee647403b fix output on tests using pytest -s option; fixed some tests 2021-12-31 10:55:13 -08:00
Mike Kinney
10f48f130f move some unit tests to unitslow 2021-12-31 09:59:22 -08:00
mkinney
bd697864e4 Merge pull request #199 from mkinney/unit_testing_continues
revert the stream interface change; fix tunnel tests
2021-12-31 09:40:58 -08:00
Mike Kinney
ab876c9efd add unit test for findPorts() 2021-12-31 09:38:44 -08:00
Mike Kinney
aba303c677 figured out issue; had device connected to serial port; needed to patch; fixed tunnel test in main 2021-12-31 09:28:17 -08:00
Mike Kinney
43d59ca8d8 temp comment out tests that pass locally but not when run from CI 2021-12-31 08:53:17 -08:00
Mike Kinney
177705aeff revert the stream interface change; fix tunnel tests 2021-12-31 08:49:13 -08:00
Sacha Weatherstone
b92fff0da6 Create vercel.json 2021-12-31 20:46:21 +11:00
mkinney
6a6b72a2ae Merge pull request #198 from mkinney/keep_working_on_unit_tests
start to add unit tests for tunnel
2021-12-30 23:01:05 -08:00
Mike Kinney
614a90c0eb unit test a few more lines 2021-12-30 22:59:01 -08:00
Mike Kinney
9adbed4be6 add unit tests for onTunnelReceive() 2021-12-30 22:52:49 -08:00
Mike Kinney
809f005f61 add unit tests for ipstr(), hexstr(), and readnet_u16() 2021-12-30 22:26:26 -08:00
Mike Kinney
d366e74e86 refactor of Tunnel() for unit testing; create unit tests for Tunnel() 2021-12-30 21:24:32 -08:00
Mike Kinney
3f307880f9 add unit tests for tunnel and subnet 2021-12-30 20:04:32 -08:00
Mike Kinney
50523ec1b1 start to add unit tests for tunnel 2021-12-30 19:37:38 -08:00
mkinney
684b2885aa Merge pull request #197 from mkinney/work_on_unit_tests
add more tests; do not need the old --reply test
2021-12-30 12:28:36 -08:00
Mike Kinney
f5eb8738fb added unit tests for --ch-set and onNode() 2021-12-30 12:20:24 -08:00
Mike Kinney
14941c742a add more tests; do not need the old --reply test 2021-12-30 11:28:03 -08:00
mkinney
217add3b00 Update README.md
add code coverage badge
2021-12-30 09:32:18 -08:00
mkinney
4bac85b6a9 Update ci.yml 2021-12-30 09:21:17 -08:00
mkinney
36bed11959 Update publish_to_pypi.yml 2021-12-30 09:12:53 -08:00
mkinney
6f9bcfaaff Merge pull request #196 from mkinney/bump_version
bump version for testing pub to pypi
2021-12-30 09:06:54 -08:00
Mike Kinney
f17b66c872 bump version for testing pub to pypi 2021-12-30 09:05:26 -08:00
Sacha Weatherstone
040cb9bf34 Update and rename publish_to_pypi.py to publish_to_pypi.yml 2021-12-31 04:00:03 +11:00
mkinney
3269b3018f Merge pull request #195 from mkinney/publish_to_pypi
add workflow to publish to pypi
2021-12-30 08:49:26 -08:00
Mike Kinney
aab10b0912 add workflow to publish to pypi 2021-12-30 08:46:34 -08:00
mkinney
e2e9b7d55e Merge pull request #194 from mkinney/remove_raw_from_nodes_display
remove the raw key from the nodes dict
2021-12-30 08:28:36 -08:00
Mike Kinney
cecc5c3b25 remove the raw key from the nodes dict 2021-12-30 08:26:13 -08:00
mkinney
54bb846d00 Merge pull request #193 from mkinney/temp_disable_reply_unittest
need to comment out unittest with change to --reply
2021-12-30 07:51:15 -08:00
Mike Kinney
1a5f525632 need to comment out unittest with change to --reply 2021-12-30 07:49:34 -08:00
mkinney
8ba3d26d63 Merge pull request #191 from Beiri22/patch-1
Update __main__.py
2021-12-30 07:46:40 -08:00
Beiri22
b341b6cfdb Update __main__.py
Main loop also in reply mode.
2021-12-30 10:31:27 +01:00
mkinney
38e7972191 Merge pull request #189 from mkinney/master
bump version; remove docs dir; no need to regen docs on release
2021-12-29 22:02:01 -08:00
mkinney
5be70328fe Merge branch 'meshtastic:master' into master 2021-12-29 21:58:15 -08:00
Mike Kinney
dfe798dbdf no longer gen docs 2021-12-29 21:57:16 -08:00
Mike Kinney
d98b23dba0 remove docs dir 2021-12-29 21:56:03 -08:00
Mike Kinney
4fbe5c7863 bump version 2021-12-29 21:55:41 -08:00
Sacha Weatherstone
69bb8bcca2 Fx protobuf path 2021-12-30 16:52:25 +11:00
Sacha Weatherstone
bb2ea17371 Fix action name 2021-12-30 16:49:55 +11:00
Sacha Weatherstone
b51af8a070 Create update_protobufs 2021-12-30 16:48:20 +11:00
mkinney
aede26694c Merge pull request #188 from mkinney/work_on_serial_issue
revamp the serial connection to avoid Tbeam reboots
2021-12-29 21:41:59 -08:00
Mike Kinney
8e1010e9f2 ignore two checks for Windows smoke1 testing 2021-12-29 21:21:24 -08:00
Mike Kinney
ce2d9f5728 yet another attempt 2021-12-29 21:13:20 -08:00
Mike Kinney
9879e9f2df try yet another way 2021-12-29 21:00:22 -08:00
Mike Kinney
e79faf93d0 try a different way 2021-12-29 20:57:57 -08:00
Mike Kinney
181c04716a accidentally dropped an arg 2021-12-29 20:55:31 -08:00
Mike Kinney
c7d981ec35 fix typo 2021-12-29 20:51:46 -08:00
Mike Kinney
75fe7622a4 deal with windows on the serial issue 2021-12-29 20:49:19 -08:00
Mike Kinney
5dc800f9a3 deal with windows on smoke1 test 2021-12-29 20:39:34 -08:00
Mike Kinney
c7d3f9f787 close seriallog if we have one 2021-12-29 20:35:50 -08:00
Mike Kinney
c70d36d2cd revamp the serial connection to avoid Tbeam reboots 2021-12-29 19:24:26 -08:00
Jm Casler
f239c23d9a Merge branch 'master' of https://github.com/meshtastic/Meshtastic-python 2021-12-29 14:18:35 -08:00
Jm Casler
ac5d729cdf Bumped to 1.2.47. 2021-12-29 14:18:34 -08:00
mkinney
047f43534f Merge pull request #187 from mkinney/bump_version
bump version
2021-12-29 13:29:28 -08:00
Mike Kinney
c26e030f1f bump version 2021-12-29 13:28:08 -08:00
33 changed files with 1288 additions and 304 deletions

View File

@@ -1,2 +1,6 @@
[run] [run]
omit = meshtastic/*_pb2.py,meshtastic/tests/*.py,meshtastic/test.py omit = meshtastic/*_pb2.py,meshtastic/tests/*.py,meshtastic/test.py
[report]
exclude_lines =
if __name__ == .__main__.:

View File

@@ -32,6 +32,18 @@ jobs:
run: pylint meshtastic run: pylint meshtastic
- name: Run tests with pytest - name: Run tests with pytest
run: pytest --cov=meshtastic run: pytest --cov=meshtastic
- name: Generate coverage report
run: |
pytest --cov=meshtastic --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
yml: ./codecov.yml
fail_ci_if_error: true
validate: validate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

35
.github/workflows/publish_to_pypi.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Publish PyPI
on: workflow_dispatch
jobs:
build-and-publish:
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: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
username: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

24
.github/workflows/update_protobufs.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: "Update protobufs"
on: workflow_dispatch
jobs:
update-protobufs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: true
- name: Update Submodule
run: |
git pull --recurse-submodules
git submodule update --remote --recursive
- name: Commit update
run: |
git config --global user.name 'github-actions'
git config --global user.email 'bot@noreply.github.com'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add proto
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"

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 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
[BASIC] [BASIC]

View File

@@ -16,7 +16,7 @@ lint:
# show the slowest unit tests # show the slowest unit tests
slow: slow:
pytest --durations=0 pytest --durations=5
# run the coverage report and open results in a browser # run the coverage report and open results in a browser
cov: cov:

View File

@@ -2,6 +2,7 @@
[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/meshtastic/Meshtastic-python) [![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/meshtastic/Meshtastic-python)
![Unit Tests](https://github.com/meshtastic/Meshtastic-python/actions/workflows/ci.yml/badge.svg) ![Unit Tests](https://github.com/meshtastic/Meshtastic-python/actions/workflows/ci.yml/badge.svg)
[![codecov](https://codecov.io/gh/meshtastic/Meshtastic-python/branch/master/graph/badge.svg?token=TIWPJL73KV)](https://codecov.io/gh/meshtastic/Meshtastic-python)
A python client for using [Meshtastic](https://www.meshtastic.org) devices. This small library (and example application) provides an easy API for sending and receiving messages over mesh radios. It also provides access to any of the operations/data available in the device user interface or the Android application. Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in. A python client for using [Meshtastic](https://www.meshtastic.org) devices. This small library (and example application) provides an easy API for sending and receiving messages over mesh radios. It also provides access to any of the operations/data available in the device user interface or the Android application. Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in.

View File

@@ -1,7 +1,5 @@
rm dist/* rm dist/*
set -e set -e
bin/regen-docs.sh
pandoc --from=markdown --to=rst --output=README README.md
python3 setup.py sdist bdist_wheel python3 setup.py sdist bdist_wheel
python3 -m twine upload dist/* python3 -m twine upload dist/*

View File

@@ -14,13 +14,11 @@ import pkg_resources
import meshtastic.util import meshtastic.util
import meshtastic.test import meshtastic.test
from . import remote_hardware from . import remote_hardware
from .ble_interface import BLEInterface
from . import portnums_pb2, channel_pb2, radioconfig_pb2 from . import portnums_pb2, channel_pb2, radioconfig_pb2
from .globals import Globals from .globals import Globals
have_tunnel = platform.system() == 'Linux'
"""We only import the tunnel code if we are on a platform that can run it. """
def onReceive(packet, interface): def onReceive(packet, interface):
"""Callback invoked when a packet arrives""" """Callback invoked when a packet arrives"""
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
@@ -45,7 +43,7 @@ def onReceive(packet, interface):
interface.sendText(reply) interface.sendText(reply)
except Exception as ex: except Exception as ex:
print(ex) 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):
@@ -137,7 +135,9 @@ def onConnected(interface):
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
args = our_globals.get_args() args = our_globals.get_args()
print("Connected to radio") # do not print this line if we are exporting the config
if not args.export_config:
print("Connected to radio")
def getNode(): def getNode():
"""This operation could be expensive, so we try to cache the results""" """This operation could be expensive, so we try to cache the results"""
@@ -495,12 +495,16 @@ def onConnected(interface):
qr = pyqrcode.create(url) qr = pyqrcode.create(url)
print(qr.terminal()) print(qr.terminal())
have_tunnel = platform.system() == 'Linux'
if have_tunnel and args.tunnel: if have_tunnel and args.tunnel:
# pylint: disable=C0415 # pylint: disable=C0415
from . import tunnel from . import tunnel
# Even if others said we could close, stay open if the user asked for a tunnel # Even if others said we could close, stay open if the user asked for a tunnel
closeNow = False closeNow = False
tunnel.Tunnel(interface, subnet=args.tunnel_net) if interface.noProto:
logging.warning(f"Not starting Tunnel - disabled by noProto")
else:
tunnel.Tunnel(interface, subnet=args.tunnel_net)
# if the user didn't ask for serial debugging output, we might want to exit after we've done our operation # if the user didn't ask for serial debugging output, we might want to exit after we've done our operation
if (not args.seriallog) and closeNow: if (not args.seriallog) and closeNow:
@@ -567,6 +571,7 @@ def export_config(interface):
def common(): def common():
"""Shared code for all of our command line wrappers""" """Shared code for all of our command line wrappers"""
logfile = None
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
args = our_globals.get_args() args = our_globals.get_args()
parser = our_globals.get_parser() parser = our_globals.get_parser()
@@ -621,23 +626,24 @@ def common():
logging.info(f"Logging serial output to {args.seriallog}") logging.info(f"Logging serial output to {args.seriallog}")
# Note: using "line buffering" # Note: using "line buffering"
# pylint: disable=R1732 # pylint: disable=R1732
logfile = open(args.seriallog, 'w+', logfile = open(args.seriallog, 'w+', buffering=1, encoding='utf8')
buffering=1, encoding='utf8') our_globals.set_logfile(logfile)
subscribe() subscribe()
if args.ble: if args.ble:
client = meshtastic.ble_interface.BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto) client = BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto)
elif args.host: elif args.host:
client = meshtastic.tcp_interface.TCPInterface( client = meshtastic.tcp_interface.TCPInterface(args.host, debugOut=logfile, noProto=args.noproto)
args.host, debugOut=logfile, noProto=args.noproto)
else: else:
client = meshtastic.serial_interface.SerialInterface( client = meshtastic.serial_interface.SerialInterface(args.port, debugOut=logfile, noProto=args.noproto)
args.port, debugOut=logfile, noProto=args.noproto)
# We assume client is fully connected now # We assume client is fully connected now
onConnected(client) onConnected(client)
#if logfile:
#logfile.close()
if args.noproto or (have_tunnel and args.tunnel): # loop until someone presses ctrlc have_tunnel = platform.system() == 'Linux'
if args.noproto or args.reply or (have_tunnel and args.tunnel): # loop until someone presses ctrlc
while True: while True:
time.sleep(1000) time.sleep(1000)
@@ -802,11 +808,13 @@ def initParser():
parser.add_argument('--unset-router', dest='deprecated', parser.add_argument('--unset-router', dest='deprecated',
action='store_false', help='Deprecated, use "--set is_router false" instead') action='store_false', help='Deprecated, use "--set is_router false" instead')
have_tunnel = platform.system() == 'Linux'
if have_tunnel: if have_tunnel:
parser.add_argument('--tunnel', parser.add_argument('--tunnel', action='store_true',
action='store_true', help="Create a TUN tunnel device for forwarding IP packets over the mesh") help="Create a TUN tunnel device for forwarding IP packets over the mesh")
parser.add_argument( parser.add_argument("--subnet", dest='tunnel_net',
"--subnet", dest='tunnel_net', help="Sets the local-end subnet address for the TUN IP bridge", default=None) help="Sets the local-end subnet address for the TUN IP bridge. (ex: 10.115' which is the default)",
default=None)
parser.set_defaults(deprecated=None) parser.set_defaults(deprecated=None)
@@ -828,6 +836,10 @@ def main():
our_globals.set_parser(parser) our_globals.set_parser(parser)
initParser() initParser()
common() common()
logfile = our_globals.get_logfile()
if logfile:
logfile.close()
def tunnelMain(): def tunnelMain():

View File

@@ -12,8 +12,8 @@ _sym_db = _symbol_database.Default()
from . import channel_pb2 as channel__pb2 from . import channel_pb2 as channel__pb2
from . import mesh_pb2 as mesh__pb2
from . import radioconfig_pb2 as radioconfig__pb2 from . import radioconfig_pb2 as radioconfig__pb2
from . import mesh_pb2 as mesh__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor.FileDescriptor(
@@ -21,9 +21,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='', package='',
syntax='proto3', syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto', serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\xfb\x02\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\x11radioconfig.proto\x1a\nmesh.proto\"\xbd\x03\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
, ,
dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,]) dependencies=[channel__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,])
@@ -85,28 +85,42 @@ _ADMINMESSAGE = _descriptor.Descriptor(
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=7, name='get_owner_request', full_name='AdminMessage.get_owner_request', index=7,
number=8, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='get_owner_response', full_name='AdminMessage.get_owner_response', index=8,
number=9, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=9,
number=32, type=8, cpp_type=7, label=1, number=32, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=8, name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=10,
number=33, type=8, cpp_type=7, label=1, number=33, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='exit_simulator', full_name='AdminMessage.exit_simulator', index=9, name='exit_simulator', full_name='AdminMessage.exit_simulator', index=11,
number=34, type=8, cpp_type=7, label=1, number=34, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=10, name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=12,
number=35, type=5, cpp_type=1, label=1, number=35, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@@ -128,7 +142,7 @@ _ADMINMESSAGE = _descriptor.Descriptor(
index=0, containing_type=None, fields=[]), index=0, containing_type=None, fields=[]),
], ],
serialized_start=62, serialized_start=62,
serialized_end=441, serialized_end=507,
) )
_ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG _ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG
@@ -136,6 +150,7 @@ _ADMINMESSAGE.fields_by_name['set_owner'].message_type = mesh__pb2._USER
_ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL _ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG _ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG
_ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL _ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL
_ADMINMESSAGE.fields_by_name['get_owner_response'].message_type = mesh__pb2._USER
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append( _ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['set_radio']) _ADMINMESSAGE.fields_by_name['set_radio'])
_ADMINMESSAGE.fields_by_name['set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] _ADMINMESSAGE.fields_by_name['set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
@@ -157,6 +172,12 @@ _ADMINMESSAGE.fields_by_name['get_channel_request'].containing_oneof = _ADMINMES
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append( _ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_channel_response']) _ADMINMESSAGE.fields_by_name['get_channel_response'])
_ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] _ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_owner_request'])
_ADMINMESSAGE.fields_by_name['get_owner_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['get_owner_response'])
_ADMINMESSAGE.fields_by_name['get_owner_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append( _ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['confirm_set_channel']) _ADMINMESSAGE.fields_by_name['confirm_set_channel'])
_ADMINMESSAGE.fields_by_name['confirm_set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] _ADMINMESSAGE.fields_by_name['confirm_set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']

View File

@@ -29,6 +29,8 @@ class Globals:
self.parser = None self.parser = None
self.target_node = None self.target_node = None
self.channel_index = None self.channel_index = None
self.logfile = None
self.tunnelInstance = None
def reset(self): def reset(self):
"""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."""
@@ -36,7 +38,10 @@ class Globals:
self.parser = None self.parser = None
self.target_node = None self.target_node = None
self.channel_index = None self.channel_index = None
self.logfile = None
self.tunnelInstance = None
# setters
def set_args(self, args): def set_args(self, args):
"""Set the args""" """Set the args"""
self.args = args self.args = args
@@ -53,6 +58,15 @@ class Globals:
"""Set the channel_index""" """Set the channel_index"""
self.channel_index = channel_index self.channel_index = channel_index
def set_logfile(self, logfile):
"""Set the logfile"""
self.logfile = logfile
def set_tunnelInstance(self, tunnelInstance):
"""Set the tunnelInstance"""
self.tunnelInstance = tunnelInstance
# getters
def get_args(self): def get_args(self):
"""Get args""" """Get args"""
return self.args return self.args
@@ -68,3 +82,11 @@ class Globals:
def get_channel_index(self): def get_channel_index(self):
"""Get channel_index""" """Get channel_index"""
return self.channel_index return self.channel_index
def get_logfile(self):
"""Get logfile"""
return self.logfile
def get_tunnelInstance(self):
"""Get tunnelInstance"""
return self.tunnelInstance

View File

@@ -18,7 +18,7 @@ from google.protobuf.json_format import MessageToJson
import meshtastic.node import meshtastic.node
from . import portnums_pb2, mesh_pb2 from . import portnums_pb2, mesh_pb2
from .util import stripnl, Timeout, our_exit from .util import stripnl, Timeout, our_exit, remove_keys_from_dict, convert_mac_addr
from .__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols from .__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
class MeshInterface: class MeshInterface:
@@ -84,7 +84,19 @@ class MeshInterface:
nodes = "" nodes = ""
if self.nodes: if self.nodes:
for n in self.nodes.values(): for n in self.nodes.values():
nodes = nodes + f" {stripnl(n)}" # when the TBeam is first booted, it sometimes shows the 'raw' data
# so, we will just remove any raw keys
n2 = remove_keys_from_dict('raw', n)
n2 = remove_keys_from_dict('decode', n2)
# if we have 'macaddr', re-format it
if 'macaddr' in n2['user']:
val = n2['user']['macaddr']
# decode the base64 value
addr = convert_mac_addr(val)
n2['user']['macaddr'] = addr
nodes = nodes + f" {stripnl(n2)}"
infos = owner + myinfo + mesh + nodes infos = owner + myinfo + mesh + nodes
print(infos) print(infos)
return infos return infos
@@ -504,7 +516,8 @@ class MeshInterface:
elif fromRadio.HasField("node_info"): elif fromRadio.HasField("node_info"):
node = asDict["nodeInfo"] node = asDict["nodeInfo"]
try: try:
self._fixupPosition(node["position"]) newpos = self._fixupPosition(node["position"])
node["position"] = newpos
except: except:
logging.debug("Node without position") logging.debug("Node without position")
@@ -536,12 +549,14 @@ class MeshInterface:
"""Convert integer lat/lon into floats """Convert integer lat/lon into floats
Arguments: Arguments:
position {Position dictionary} -- object ot fix up position {Position dictionary} -- object to fix up
Returns the position with the updated keys
""" """
if "latitudeI" in position: if "latitudeI" in position:
position["latitude"] = position["latitudeI"] * 1e-7 position["latitude"] = position["latitudeI"] * 1e-7
if "longitudeI" in position: if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7 position["longitude"] = position["longitudeI"] * 1e-7
return position
def _nodeNumToId(self, num): def _nodeNumToId(self, num):
"""Map a node node number to a node ID """Map a node node number to a node ID

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,16 @@
""" Serial interface class """ Serial interface class
""" """
import logging import logging
import time
import platform import platform
import os
import stat
import serial import serial
import meshtastic.util import meshtastic.util
from .stream_interface import StreamInterface from .stream_interface import StreamInterface
if platform.system() != 'Windows':
import termios
class SerialInterface(StreamInterface): class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link""" """Interface class for meshtastic devices over a serial link"""
@@ -20,6 +22,7 @@ class SerialInterface(StreamInterface):
devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None}) devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None}) debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
""" """
self.noProto = noProto
if devPath is None: if devPath is None:
ports = meshtastic.util.findPorts() ports = meshtastic.util.findPorts()
@@ -35,43 +38,30 @@ class SerialInterface(StreamInterface):
logging.debug(f"Connecting to {devPath}") logging.debug(f"Connecting to {devPath}")
# Note: we provide None for port here, because we will be opening it later # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
self.stream = serial.Serial( # see https://github.com/pyserial/pyserial/issues/124
None, 921600, exclusive=True, timeout=0.5, write_timeout=0) if not self.noProto:
if platform.system() != 'Windows':
with open(devPath, encoding='utf8') as f:
attrs = termios.tcgetattr(f)
attrs[2] = attrs[2] & ~termios.HUPCL
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
f.close()
time.sleep(0.1)
# rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0)
self.stream.port = devPath if not self.noProto:
self.stream.flush()
time.sleep(0.1)
# HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
# mode, set RTS to false so that the device platform won't be reset spuriously.
# Linux does this properly, so don't apply this hack on Linux (because it makes the reset button not work).
if self._hostPlatformAlwaysDrivesUartRts():
self.stream.rts = False
self.stream.open()
StreamInterface.__init__( def close(self):
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) """Close a connection to the device"""
if not self.noProto:
"""true if platform driving the serial port is Windows Subsystem for Linux 1.""" self.stream.flush()
def _isWsl1(self): time.sleep(0.1)
# WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control, self.stream.flush()
# e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom time.sleep(0.1)
# serial driver for the CP21xx still exhibits the buggy behavior. logging.debug("Closing Serial stream")
# WSL2 is not covered here, as it does not (as of 2021-May-25) support the appropriate functionality to StreamInterface.close(self)
# share or pass-through serial ports.
try:
# Claims to be Linux, but has /dev/lxss; must be WSL 1
return platform.system() == 'Linux' and stat.S_ISCHR(os.stat('/dev/lxss').st_mode)
except:
# Couldn't stat /dev/lxss special device; not WSL1
return False
def _hostPlatformAlwaysDrivesUartRts(self):
# OS-X/Windows seems to have a bug in its CP21xx serial drivers. It ignores that we asked for no RTSCTS
# control and will always drive RTS either high or low (rather than letting the CP102 leave
# it as an open-collector floating pin).
# TODO: When WSL2 supports USB passthrough, this will get messier. If/when WSL2 gets virtual serial
# ports that "share" the Windows serial port (and thus the Windows drivers), this code will need to be
# updated to reflect that as well -- or if T-Beams get made with an alternate USB to UART bridge that has
# a less buggy driver.
return platform.system() != 'Linux' or self._isWsl1()

View File

@@ -18,7 +18,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='', package='',
syntax='proto3', syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto', serialized_options=b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto',
serialized_pb=b'\n\x12storeforward.proto\"\xe7\x04\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x1a\xc6\x01\n\nStatistics\x12\x15\n\rMessagesTotal\x18\x01 \x01(\r\x12\x15\n\rMessagesSaved\x18\x02 \x01(\r\x12\x13\n\x0bMessagesMax\x18\x03 \x01(\r\x12\x0e\n\x06UpTime\x18\x04 \x01(\r\x12\x10\n\x08Requests\x18\x05 \x01(\r\x12\x17\n\x0fRequestsHistory\x18\x06 \x01(\r\x12\x11\n\tHeartbeat\x18\x07 \x01(\x08\x12\x11\n\tReturnMax\x18\x08 \x01(\r\x12\x14\n\x0cReturnWindow\x18\t \x01(\r\x1a\x32\n\x07History\x12\x17\n\x0fHistoryMessages\x18\x01 \x01(\r\x12\x0e\n\x06Window\x18\x02 \x01(\r\"\xd1\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10iBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' serialized_pb=b'\n\x12storeforward.proto\"\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xf7\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
) )
@@ -54,30 +54,38 @@ _STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor(
serialized_options=None, serialized_options=None,
type=None), type=None),
_descriptor.EnumValueDescriptor( _descriptor.EnumValueDescriptor(
name='CLIENT_ERROR', index=6, number=101, name='ROUTER_HISTORY', index=6, number=6,
serialized_options=None, serialized_options=None,
type=None), type=None),
_descriptor.EnumValueDescriptor( _descriptor.EnumValueDescriptor(
name='CLIENT_HISTORY', index=7, number=102, name='CLIENT_ERROR', index=7, number=101,
serialized_options=None, serialized_options=None,
type=None), type=None),
_descriptor.EnumValueDescriptor( _descriptor.EnumValueDescriptor(
name='CLIENT_STATS', index=8, number=103, name='CLIENT_HISTORY', index=8, number=102,
serialized_options=None, serialized_options=None,
type=None), type=None),
_descriptor.EnumValueDescriptor( _descriptor.EnumValueDescriptor(
name='CLIENT_PING', index=9, number=104, name='CLIENT_STATS', index=9, number=103,
serialized_options=None, serialized_options=None,
type=None), type=None),
_descriptor.EnumValueDescriptor( _descriptor.EnumValueDescriptor(
name='CLIENT_PONG', index=10, number=105, name='CLIENT_PING', index=10, number=104,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_PONG', index=11, number=105,
serialized_options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='CLIENT_ABORT', index=12, number=106,
serialized_options=None, serialized_options=None,
type=None), type=None),
], ],
containing_type=None, containing_type=None,
serialized_options=None, serialized_options=None,
serialized_start=429, serialized_start=554,
serialized_end=638, serialized_end=801,
) )
_sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE) _sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE)
@@ -90,63 +98,63 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
containing_type=None, containing_type=None,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='MessagesTotal', full_name='StoreAndForward.Statistics.MessagesTotal', index=0, name='messages_total', full_name='StoreAndForward.Statistics.messages_total', index=0,
number=1, type=13, cpp_type=3, label=1, number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='MessagesSaved', full_name='StoreAndForward.Statistics.MessagesSaved', index=1, name='messages_saved', full_name='StoreAndForward.Statistics.messages_saved', index=1,
number=2, type=13, cpp_type=3, label=1, number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='MessagesMax', full_name='StoreAndForward.Statistics.MessagesMax', index=2, name='messages_max', full_name='StoreAndForward.Statistics.messages_max', index=2,
number=3, type=13, cpp_type=3, label=1, number=3, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='UpTime', full_name='StoreAndForward.Statistics.UpTime', index=3, name='up_time', full_name='StoreAndForward.Statistics.up_time', index=3,
number=4, type=13, cpp_type=3, label=1, number=4, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='Requests', full_name='StoreAndForward.Statistics.Requests', index=4, name='requests', full_name='StoreAndForward.Statistics.requests', index=4,
number=5, type=13, cpp_type=3, label=1, number=5, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='RequestsHistory', full_name='StoreAndForward.Statistics.RequestsHistory', index=5, name='requests_history', full_name='StoreAndForward.Statistics.requests_history', index=5,
number=6, type=13, cpp_type=3, label=1, number=6, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='Heartbeat', full_name='StoreAndForward.Statistics.Heartbeat', index=6, name='heartbeat', full_name='StoreAndForward.Statistics.heartbeat', index=6,
number=7, type=8, cpp_type=7, label=1, number=7, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='ReturnMax', full_name='StoreAndForward.Statistics.ReturnMax', index=7, name='return_max', full_name='StoreAndForward.Statistics.return_max', index=7,
number=8, type=13, cpp_type=3, label=1, number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='ReturnWindow', full_name='StoreAndForward.Statistics.ReturnWindow', index=8, name='return_window', full_name='StoreAndForward.Statistics.return_window', index=8,
number=9, type=13, cpp_type=3, label=1, number=9, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@@ -164,8 +172,8 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=176, serialized_start=223,
serialized_end=374, serialized_end=428,
) )
_STOREANDFORWARD_HISTORY = _descriptor.Descriptor( _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
@@ -176,14 +184,58 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
containing_type=None, containing_type=None,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='HistoryMessages', full_name='StoreAndForward.History.HistoryMessages', index=0, name='history_messages', full_name='StoreAndForward.History.history_messages', index=0,
number=1, type=13, cpp_type=3, label=1, number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='Window', full_name='StoreAndForward.History.Window', index=1, name='window', full_name='StoreAndForward.History.window', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='last_request', full_name='StoreAndForward.History.last_request', index=2,
number=3, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=430,
serialized_end=503,
)
_STOREANDFORWARD_HEARTBEAT = _descriptor.Descriptor(
name='Heartbeat',
full_name='StoreAndForward.Heartbeat',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='period', full_name='StoreAndForward.Heartbeat.period', index=0,
number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='secondary', full_name='StoreAndForward.Heartbeat.secondary', index=1,
number=2, type=13, cpp_type=3, label=1, number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@@ -201,8 +253,8 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=376, serialized_start=505,
serialized_end=426, serialized_end=551,
) )
_STOREANDFORWARD = _descriptor.Descriptor( _STOREANDFORWARD = _descriptor.Descriptor(
@@ -233,10 +285,17 @@ _STOREANDFORWARD = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR), serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='heartbeat', full_name='StoreAndForward.heartbeat', index=3,
number=4, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
], ],
extensions=[ extensions=[
], ],
nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, ], nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, _STOREANDFORWARD_HEARTBEAT, ],
enum_types=[ enum_types=[
_STOREANDFORWARD_REQUESTRESPONSE, _STOREANDFORWARD_REQUESTRESPONSE,
], ],
@@ -247,14 +306,16 @@ _STOREANDFORWARD = _descriptor.Descriptor(
oneofs=[ oneofs=[
], ],
serialized_start=23, serialized_start=23,
serialized_end=638, serialized_end=801,
) )
_STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD _STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD _STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD
_STOREANDFORWARD_HEARTBEAT.containing_type = _STOREANDFORWARD
_STOREANDFORWARD.fields_by_name['rr'].enum_type = _STOREANDFORWARD_REQUESTRESPONSE _STOREANDFORWARD.fields_by_name['rr'].enum_type = _STOREANDFORWARD_REQUESTRESPONSE
_STOREANDFORWARD.fields_by_name['stats'].message_type = _STOREANDFORWARD_STATISTICS _STOREANDFORWARD.fields_by_name['stats'].message_type = _STOREANDFORWARD_STATISTICS
_STOREANDFORWARD.fields_by_name['history'].message_type = _STOREANDFORWARD_HISTORY _STOREANDFORWARD.fields_by_name['history'].message_type = _STOREANDFORWARD_HISTORY
_STOREANDFORWARD.fields_by_name['heartbeat'].message_type = _STOREANDFORWARD_HEARTBEAT
_STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD _STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD
DESCRIPTOR.message_types_by_name['StoreAndForward'] = _STOREANDFORWARD DESCRIPTOR.message_types_by_name['StoreAndForward'] = _STOREANDFORWARD
_sym_db.RegisterFileDescriptor(DESCRIPTOR) _sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -274,6 +335,13 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
# @@protoc_insertion_point(class_scope:StoreAndForward.History) # @@protoc_insertion_point(class_scope:StoreAndForward.History)
}) })
, ,
'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT,
'__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
})
,
'DESCRIPTOR' : _STOREANDFORWARD, 'DESCRIPTOR' : _STOREANDFORWARD,
'__module__' : 'storeforward_pb2' '__module__' : 'storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward) # @@protoc_insertion_point(class_scope:StoreAndForward)
@@ -281,6 +349,7 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
_sym_db.RegisterMessage(StoreAndForward) _sym_db.RegisterMessage(StoreAndForward)
_sym_db.RegisterMessage(StoreAndForward.Statistics) _sym_db.RegisterMessage(StoreAndForward.Statistics)
_sym_db.RegisterMessage(StoreAndForward.History) _sym_db.RegisterMessage(StoreAndForward.History)
_sym_db.RegisterMessage(StoreAndForward.Heartbeat)
DESCRIPTOR._options = None DESCRIPTOR._options = None

View File

@@ -62,7 +62,8 @@ 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)
time.sleep(0.1) # wait 100ms to give device time to start running if not self.noProto:
time.sleep(0.1) # wait 100ms to give device time to start running
self._rxThread.start() self._rxThread.start()
@@ -88,6 +89,9 @@ class StreamInterface(MeshInterface):
if self.stream: # ignore writes when stream is closed if self.stream: # ignore writes when stream is closed
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
if not self.noProto:
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

@@ -17,8 +17,6 @@ class TCPInterface(StreamInterface):
hostname {string} -- Hostname/IP address of the device to connect to hostname {string} -- Hostname/IP address of the device to connect to
""" """
# Instead of wrapping as a stream, we use the native socket API
# self.stream = sock.makefile('rw')
self.stream = None self.stream = None
self.hostname = hostname self.hostname = hostname

View File

@@ -6,13 +6,21 @@ import pytest
@pytest.mark.int @pytest.mark.int
def test_int_no_args(): def test_int_meshtastic_no_args():
"""Test without any args""" """Test meshtastic without any args"""
return_value, out = subprocess.getstatusoutput('meshtastic') return_value, out = subprocess.getstatusoutput('meshtastic')
assert re.match(r'usage: meshtastic', out) assert re.match(r'usage: meshtastic', out)
assert return_value == 1 assert return_value == 1
@pytest.mark.int
def test_int_mesh_tunnel_no_args():
"""Test mesh-tunnel without any args"""
return_value, out = subprocess.getstatusoutput('mesh-tunnel')
assert re.match(r'usage: mesh-tunnel', out)
assert return_value == 1
@pytest.mark.int @pytest.mark.int
def test_int_version(): def test_int_version():
"""Test '--version'.""" """Test '--version'."""

View File

@@ -5,16 +5,17 @@ import sys
import os import os
import re import re
import logging import logging
import platform
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
import pytest import pytest
from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, export_config, getPref, setPref from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, export_config, getPref, setPref, onNode, tunnelMain
#from ..radioconfig_pb2 import UserPreferences #from ..radioconfig_pb2 import UserPreferences
import meshtastic.radioconfig_pb2 import meshtastic.radioconfig_pb2
from ..serial_interface import SerialInterface from ..serial_interface import SerialInterface
from ..tcp_interface import TCPInterface from ..tcp_interface import TCPInterface
from ..ble_interface import BLEInterface #from ..ble_interface import BLEInterface
from ..node import Node from ..node import Node
from ..channel_pb2 import Channel from ..channel_pb2 import Channel
from ..remote_hardware import onGPIOreceive from ..remote_hardware import onGPIOreceive
@@ -62,7 +63,7 @@ def test_main_main_version(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_main_no_args(reset_globals): def test_main_main_no_args(reset_globals, 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)
@@ -71,6 +72,8 @@ def test_main_main_no_args(reset_globals):
main() main()
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
_, err = capsys.readouterr()
assert re.search(r'usage:', err, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -111,7 +114,7 @@ def test_main_ch_index_no_devices(patched_find_ports, capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=[]) @patch('meshtastic.util.findPorts', return_value=[])
def test_main_test_no_ports(patched_find_ports, reset_globals): def test_main_test_no_ports(patched_find_ports, reset_globals, 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)
@@ -122,11 +125,14 @@ def test_main_test_no_ports(patched_find_ports, reset_globals):
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
patched_find_ports.assert_called() patched_find_ports.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Warning: Must have at least two devices connected to USB', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@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): def test_main_test_one_port(patched_find_ports, reset_globals, 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)
@@ -137,12 +143,14 @@ def test_main_test_one_port(patched_find_ports, reset_globals):
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
patched_find_ports.assert_called() patched_find_ports.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Warning: Must have at least two devices connected to USB', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@patch('meshtastic.test.testAll', return_value=True) @patch('meshtastic.test.testAll', return_value=True)
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1', '/dev/ttyFake2']) def test_main_test_two_ports_success(patched_test_all, reset_globals, capsys):
def test_main_test_two_ports_success(patched_find_ports, patched_test_all, reset_globals):
"""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)
@@ -151,14 +159,15 @@ def test_main_test_two_ports_success(patched_find_ports, patched_test_all, reset
main() main()
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0 assert pytest_wrapped_e.value.code == 0
# TODO: why does this fail? patched_find_ports.assert_called()
patched_test_all.assert_called() patched_test_all.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Test was a success.', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@patch('meshtastic.test.testAll', return_value=False) @patch('meshtastic.test.testAll', return_value=False)
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1', '/dev/ttyFake2']) def test_main_test_two_ports_fails(patched_test_all, reset_globals, capsys):
def test_main_test_two_ports_fails(patched_find_ports, patched_test_all, reset_globals):
"""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)
@@ -167,8 +176,10 @@ def test_main_test_two_ports_fails(patched_find_ports, patched_test_all, reset_g
main() main()
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
# TODO: why does this fail? patched_find_ports.assert_called()
patched_test_all.assert_called() patched_test_all.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Test was not successful.', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -209,23 +220,24 @@ def test_main_info_with_tcp_interface(capsys, reset_globals):
mo.assert_called() mo.assert_called()
@pytest.mark.unit # TODO: comment out ble (for now)
def test_main_info_with_ble_interface(capsys, reset_globals): #@pytest.mark.unit
"""Test --info""" #def test_main_info_with_ble_interface(capsys, reset_globals):
sys.argv = ['', '--info', '--ble', 'foo'] # """Test --info"""
Globals.getInstance().set_args(sys.argv) # sys.argv = ['', '--info', '--ble', 'foo']
# Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=BLEInterface) #
def mock_showInfo(): # iface = MagicMock(autospec=BLEInterface)
print('inside mocked showInfo') # def mock_showInfo():
iface.showInfo.side_effect = mock_showInfo # print('inside mocked showInfo')
with patch('meshtastic.ble_interface.BLEInterface', return_value=iface) as mo: # iface.showInfo.side_effect = mock_showInfo
main() # with patch('meshtastic.ble_interface.BLEInterface', 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
@@ -1232,18 +1244,23 @@ def test_main_setchan(capsys, reset_globals):
main() main()
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
_, err = capsys.readouterr()
assert re.search(r'usage:', err, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
def test_main_onReceive_empty(caplog, reset_globals): def test_main_onReceive_empty(caplog, reset_globals, capsys):
"""Test onReceive""" """Test onReceive"""
sys.argv = [''] args = MagicMock()
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(args)
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
packet = {'decoded': 'foo'} packet = {}
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
onReceive(packet, iface) onReceive(packet, iface)
assert re.search(r'in onReceive', caplog.text, re.MULTILINE) assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(r"Warning: There is no field 'to' in the packet.", out, re.MULTILINE)
assert err == ''
# TODO: use this captured position app message (might want/need in the future) # TODO: use this captured position app message (might want/need in the future)
@@ -1258,7 +1275,7 @@ def test_main_onReceive_empty(caplog, reset_globals):
# } # }
@pytest.mark.unit @pytest.mark.unit
def test_main_onReceive_with_sendtext(caplog, reset_globals): def test_main_onReceive_with_sendtext(caplog, capsys, reset_globals):
"""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().
@@ -1287,41 +1304,31 @@ def test_main_onReceive_with_sendtext(caplog, reset_globals):
onReceive(packet, iface) onReceive(packet, iface)
assert re.search(r'in onReceive', caplog.text, re.MULTILINE) assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
mo.assert_called() mo.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Sending text message hello to', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
def test_main_onReceive_with_reply(caplog, capsys, reset_globals): def test_main_onReceive_with_text(caplog, capsys, reset_globals):
"""Test onReceive with a reply """Test onReceive with text
To capture: on one device run '--sendtext aaa --reply' and on another
device run '--sendtext bbb --reply', then back to the first device and
run '--sendtext aaa2 --reply'. You should now see a "Sending reply" message.
""" """
sys.argv = ['', '--sendtext', 'hello', '--reply'] args = MagicMock()
Globals.getInstance().set_args(sys.argv) args.sendtext.return_value = 'foo'
Globals.getInstance().set_args(args)
# Note: 'TEXT_MESSAGE_APP' value is 1 # Note: 'TEXT_MESSAGE_APP' value is 1
# Note: Some of this is faked below.
send_packet = { packet = {
'to': 4294967295, 'to': 4294967295,
'decoded': { 'decoded': {
'portnum': 1, 'portnum': 1,
'payload': "hello" 'payload': "hello",
'text': "faked"
}, },
'id': 334776977, 'id': 334776977,
'hop_limit': 3, 'hop_limit': 3,
'want_ack': True 'want_ack': True,
}
reply_packet = {
'from': 682968668,
'to': 4294967295,
'decoded': {
'portnum': 'TEXT_MESSAGE_APP',
'payload': b'bbb',
'text': 'bbb'
},
'id': 1709936182,
'rxTime': 1640381999,
'rxSnr': 6.0, 'rxSnr': 6.0,
'hopLimit': 3, 'hopLimit': 3,
'raw': 'faked', 'raw': 'faked',
@@ -1332,16 +1339,13 @@ def test_main_onReceive_with_reply(caplog, capsys, reset_globals):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.myInfo.my_node_num = 4294967295 iface.myInfo.my_node_num = 4294967295
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
main() onReceive(packet, iface)
onReceive(send_packet, iface)
onReceive(reply_packet, iface)
assert re.search(r'in onReceive', caplog.text, re.MULTILINE) assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'got msg ', out, re.MULTILINE) assert re.search(r'Sending reply', out, re.MULTILINE)
assert err == '' assert err == ''
mo.assert_called()
@pytest.mark.unit @pytest.mark.unit
@@ -1379,6 +1383,10 @@ fixed_position: true
position_flags: 35""" position_flags: 35"""
export_config(mo) export_config(mo)
out, err = capsys.readouterr() out, err = capsys.readouterr()
# ensure we do not output this line
assert not re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'owner: foo', out, re.MULTILINE) assert re.search(r'owner: foo', out, re.MULTILINE)
assert re.search(r'channel_url: bar', out, re.MULTILINE) assert re.search(r'channel_url: bar', out, re.MULTILINE)
assert re.search(r'location:', out, re.MULTILINE) assert re.search(r'location:', out, re.MULTILINE)
@@ -1404,7 +1412,7 @@ def test_main_export_config_called_from_main(capsys, reset_globals):
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
main() main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE) assert not re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'# start of Meshtastic configure yaml', out, re.MULTILINE) assert re.search(r'# start of Meshtastic configure yaml', out, re.MULTILINE)
assert err == '' assert err == ''
mo.assert_called() mo.assert_called()
@@ -1522,6 +1530,36 @@ def test_main_getPref_valid_field(capsys, reset_globals):
assert err == '' assert err == ''
@pytest.mark.unit
def test_main_getPref_valid_field_string(capsys, reset_globals):
"""Test getPref() with a valid field and value as a string"""
prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'wifi_ssid'
prefs.wifi_ssid = 'foo'
prefs.ls_secs = 300
prefs.fixed_position = False
getPref(prefs, 'wifi_ssid')
out, err = capsys.readouterr()
assert re.search(r'wifi_ssid: foo', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_main_getPref_valid_field_bool(capsys, reset_globals):
"""Test getPref() with a valid field and value as a bool"""
prefs = MagicMock()
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'fixed_position'
prefs.wifi_ssid = 'foo'
prefs.ls_secs = 300
prefs.fixed_position = False
getPref(prefs, 'fixed_position')
out, err = capsys.readouterr()
assert re.search(r'fixed_position: False', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
def test_main_getPref_invalid_field(capsys, reset_globals): def test_main_getPref_invalid_field(capsys, reset_globals):
"""Test getPref() with an invalid field""" """Test getPref() with an invalid field"""
@@ -1554,7 +1592,7 @@ def test_main_getPref_invalid_field(capsys, reset_globals):
@pytest.mark.unit @pytest.mark.unit
def test_main_setPref_valid_field(capsys, reset_globals): def test_main_setPref_valid_field_int(capsys, reset_globals):
"""Test setPref() with a valid field""" """Test setPref() with a valid field"""
class Field: class Field:
@@ -1604,3 +1642,152 @@ def test_main_setPref_invalid_field(capsys, reset_globals):
# ensure they are sorted # ensure they are sorted
assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE) assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
assert err == '' assert err == ''
@pytest.mark.unit
def test_main_ch_set_psk_no_ch_index(capsys, reset_globals):
"""Test --ch-set psk """
sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=TCPInterface)
with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo:
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r"Warning: Need to specify '--ch-index'", out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
mo.assert_called()
@pytest.mark.unit
def test_main_ch_set_psk_with_ch_index(capsys, reset_globals):
"""Test --ch-set psk """
sys.argv = ['', '--ch-set', 'psk', 'foo', '--host', 'meshtastic.local', '--ch-index', '0']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=TCPInterface)
with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo:
main()
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
def test_main_ch_set_name_with_ch_index(capsys, reset_globals):
"""Test --ch-set setting other than psk"""
sys.argv = ['', '--ch-set', 'name', 'foo', '--host', 'meshtastic.local', '--ch-index', '0']
Globals.getInstance().set_args(sys.argv)
iface = MagicMock(autospec=TCPInterface)
with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo:
main()
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert re.search(r'Set name to foo', out, re.MULTILINE)
assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
assert err == ''
mo.assert_called()
@pytest.mark.unit
def test_onNode(capsys, reset_globals):
"""Test onNode"""
onNode('foo')
out, err = capsys.readouterr()
assert re.search(r'Node changed', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
def test_tunnel_no_args(capsys, reset_globals):
"""Test tunnel no arguments"""
sys.argv = ['']
Globals.getInstance().set_args(sys.argv)
with pytest.raises(SystemExit) as pytest_wrapped_e:
tunnelMain()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
_, err = capsys.readouterr()
assert re.search(r'usage: ', err, re.MULTILINE)
@pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=[])
@patch('platform.system')
def test_tunnel_tunnel_arg_with_no_devices(mock_platform_system, patched_find_ports, caplog, capsys, reset_globals):
"""Test tunnel with tunnel arg (act like we are on a linux system)"""
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
sys.argv = ['', '--tunnel']
Globals.getInstance().set_args(sys.argv)
print(f'platform.system():{platform.system()}')
with caplog.at_level(logging.DEBUG):
with pytest.raises(SystemExit) as pytest_wrapped_e:
tunnelMain()
mock_platform_system.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=[])
@patch('platform.system')
def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, patched_find_ports, caplog, capsys, reset_globals):
"""Test tunnel with subnet arg (act like we are on a linux system)"""
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
sys.argv = ['', '--subnet', 'foo']
Globals.getInstance().set_args(sys.argv)
print(f'platform.system():{platform.system()}')
with caplog.at_level(logging.DEBUG):
with pytest.raises(SystemExit) as pytest_wrapped_e:
tunnelMain()
mock_platform_system.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit
@patch('platform.system')
def test_tunnel_tunnel_arg(mock_platform_system, caplog, reset_globals, iface_with_nodes, capsys):
"""Test tunnel with tunnel arg (act like we are on a linux system)"""
# Override the time.sleep so there is no loop
def my_sleep(amount):
sys.exit(3)
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
sys.argv = ['', '--tunnel']
Globals.getInstance().set_args(sys.argv)
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
with caplog.at_level(logging.DEBUG):
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
with patch('time.sleep', side_effect=my_sleep):
with pytest.raises(SystemExit) as pytest_wrapped_e:
tunnelMain()
mock_platform_system.assert_called()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 3
assert re.search(r'Not starting Tunnel', caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(r'Connected to radio', out, re.MULTILINE)
assert err == ''

View File

@@ -57,10 +57,8 @@ def test_MeshInterface(capsys, reset_globals):
def test_getMyUser(reset_globals, iface_with_nodes): def test_getMyUser(reset_globals, 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
myuser = iface.getMyUser() myuser = iface.getMyUser()
print(f'myuser:{myuser}')
assert myuser is not None assert myuser is not None
assert myuser["id"] == '!9388f81c' assert myuser["id"] == '!9388f81c'
@@ -460,3 +458,88 @@ def test_generatePacketId(capsys, reset_globals):
assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE) assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE)
assert err == '' assert err == ''
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
@pytest.mark.unit
def test_fixupPosition_empty_pos(capsys, reset_globals):
"""Test _fixupPosition()"""
iface = MeshInterface(noProto=True)
pos = {}
newpos = iface._fixupPosition(pos)
assert newpos == pos
@pytest.mark.unit
def test_fixupPosition_no_changes_needed(capsys, reset_globals):
"""Test _fixupPosition()"""
iface = MeshInterface(noProto=True)
pos = {"latitude": 101, "longitude": 102}
newpos = iface._fixupPosition(pos)
assert newpos == pos
@pytest.mark.unit
def test_fixupPosition(capsys, reset_globals):
"""Test _fixupPosition()"""
iface = MeshInterface(noProto=True)
pos = {"latitudeI": 1010000000, "longitudeI": 1020000000}
newpos = iface._fixupPosition(pos)
assert newpos == {"latitude": 101.0,
"latitudeI": 1010000000,
"longitude": 102.0,
"longitudeI": 1020000000}
@pytest.mark.unit
def test_nodeNumToId(capsys, reset_globals, iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(2475227164)
assert someid == '!9388f81c'
@pytest.mark.unit
def test_nodeNumToId_not_found(capsys, reset_globals, iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(123)
assert someid is None
@pytest.mark.unit
def test_nodeNumToId_to_all(capsys, reset_globals, iface_with_nodes):
"""Test _nodeNumToId()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(0xffffffff)
assert someid == '^all'
@pytest.mark.unit
def test_getOrCreateByNum_minimal(capsys, reset_globals, iface_with_nodes):
"""Test _getOrCreateByNum()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(123)
assert tmp == {'num': 123}
@pytest.mark.unit
def test_getOrCreateByNum_not_found(capsys, reset_globals, iface_with_nodes):
"""Test _getOrCreateByNum()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
with pytest.raises(Exception) as pytest_wrapped_e:
iface._getOrCreateByNum(0xffffffff)
assert pytest_wrapped_e.type == Exception
@pytest.mark.unit
def test_getOrCreateByNum(capsys, reset_globals, iface_with_nodes):
"""Test _getOrCreateByNum()"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(2475227164)
assert tmp['num'] == 2475227164

View File

@@ -29,7 +29,7 @@ def test_node(capsys):
@pytest.mark.unit @pytest.mark.unit
def test_node_reqquestConfig(): def test_node_requestConfig(capsys):
"""Test run requestConfig""" """Test run requestConfig"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
amesg = MagicMock(autospec=AdminMessage) amesg = MagicMock(autospec=AdminMessage)
@@ -37,6 +37,9 @@ def test_node_reqquestConfig():
with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg): with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg):
anode = Node(mo, 'bar') anode = Node(mo, 'bar')
anode.requestConfig() anode.requestConfig()
out, err = capsys.readouterr()
assert re.search(r'Requesting preferences from remote node', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -106,13 +109,16 @@ def test_reboot(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_setURL_empty_url(): def test_setURL_empty_url(capsys):
"""Test reboot""" """Test reboot"""
anode = Node('foo', 'bar', noProto=True) anode = Node('foo', 'bar', noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
anode.setURL('') anode.setURL('')
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: No RadioConfig has been read', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -133,7 +139,7 @@ def test_setURL_valid_URL(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_setURL_valid_URL_but_no_settings(caplog): def test_setURL_valid_URL_but_no_settings(caplog, 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/#"
@@ -143,6 +149,9 @@ def test_setURL_valid_URL_but_no_settings(caplog):
anode.setURL(url) anode.setURL(url)
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'Warning: There were no settings', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit
@@ -623,7 +632,7 @@ def test_writeConfig(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_requestChannel_not_localNode(caplog): def test_requestChannel_not_localNode(caplog, capsys):
"""Test _requestChannel()""" """Test _requestChannel()"""
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:
@@ -633,6 +642,9 @@ def test_requestChannel_not_localNode(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
anode._requestChannel(0) anode._requestChannel(0)
assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE) assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(r'Requesting channel 0 info', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit

View File

@@ -79,7 +79,7 @@ def test_watchGPIOs(caplog):
@pytest.mark.unit @pytest.mark.unit
def test_sendHardware_no_nodeid(): def test_sendHardware_no_nodeid(capsys):
"""Test sending no nodeid to _sendHardware()""" """Test sending no nodeid to _sendHardware()"""
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:
@@ -87,3 +87,6 @@ def test_sendHardware_no_nodeid():
rhw = RemoteHardwareClient(mo) rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None) rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
out, err = capsys.readouterr()
assert re.search(r'Warning: Must use a destination node ID', out)
assert err == ''

View File

@@ -11,7 +11,7 @@ from ..serial_interface import SerialInterface
@pytest.mark.unit @pytest.mark.unit
@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): def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, 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 +19,12 @@ def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
iface.close() iface.close()
mocked_findPorts.assert_called() mocked_findPorts.assert_called()
mocked_serial.assert_called() mocked_serial.assert_called()
out, err = capsys.readouterr()
assert re.search(r'Nodes in mesh', out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE)
assert re.search(r'Primary channel', out, re.MULTILINE)
assert err == ''
@pytest.mark.unit @pytest.mark.unit

View File

@@ -2,6 +2,7 @@
import re import re
import subprocess import subprocess
import time import time
import platform
import os import os
# Do not like using hard coded sleeps, but it probably makes # Do not like using hard coded sleeps, but it probably makes
@@ -140,8 +141,9 @@ def test_smoke1_nodes():
"""Test --nodes""" """Test --nodes"""
return_value, out = subprocess.getstatusoutput('meshtastic --nodes') return_value, out = subprocess.getstatusoutput('meshtastic --nodes')
assert re.match(r'Connected to radio', out) assert re.match(r'Connected to radio', out)
assert re.search(r'^│ N │ User', out, re.MULTILINE) if platform.system() != 'Windows':
assert re.search(r'^│ 1 │', out, re.MULTILINE) assert re.search(r' User ', out, re.MULTILINE)
assert re.search(r' 1 ', out, re.MULTILINE)
assert return_value == 0 assert return_value == 0

View File

@@ -34,9 +34,9 @@ def test_StreamInterface_with_noProto(caplog, reset_globals):
assert data == test_data assert data == test_data
# Note: This takes a bit, so moving from unit to slow ## Note: This takes a bit, so moving from unit to slow
# 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): def test_sendToRadioImpl(caplog, reset_globals):
"""Test _sendToRadioImpl()""" """Test _sendToRadioImpl()"""
@@ -78,4 +78,3 @@ def test_sendToRadioImpl(caplog, reset_globals):
assert re.search(r'Sending: ', caplog.text, re.MULTILINE) assert re.search(r'Sending: ', caplog.text, re.MULTILINE)
assert re.search(r'reading character', caplog.text, re.MULTILINE) assert re.search(r'reading character', caplog.text, re.MULTILINE)
assert re.search(r'In reader loop', caplog.text, re.MULTILINE) assert re.search(r'In reader loop', caplog.text, re.MULTILINE)
print(caplog.text)

View File

@@ -13,6 +13,7 @@ def test_TCPInterface(capsys):
"""Test that we can instantiate a TCPInterface""" """Test that we can instantiate a TCPInterface"""
with patch('socket.socket') as mock_socket: with patch('socket.socket') as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True) iface = TCPInterface(hostname='localhost', noProto=True)
iface.myConnect()
iface.showInfo() iface.showInfo()
iface.localNode.showInfo() iface.localNode.showInfo()
out, err = capsys.readouterr() out, err = capsys.readouterr()
@@ -24,3 +25,11 @@ def test_TCPInterface(capsys):
assert err == '' assert err == ''
assert mock_socket.called assert mock_socket.called
iface.close() iface.close()
@pytest.mark.unit
def test_TCPInterface_without_connecting(capsys):
"""Test that we can instantiate a TCPInterface with connectNow as false"""
with patch('socket.socket'):
iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False)
assert iface.socket is None

View File

@@ -0,0 +1,266 @@
"""Meshtastic unit tests for tunnel.py"""
import re
import sys
import logging
from unittest.mock import patch, MagicMock
import pytest
from ..tcp_interface import TCPInterface
from ..tunnel import Tunnel, onTunnelReceive
from ..globals import Globals
@pytest.mark.unit
@patch('platform.system')
def test_Tunnel_on_non_linux_system(mock_platform_system, reset_globals):
"""Test that we cannot instantiate a Tunnel on a non Linux system"""
a_mock = MagicMock()
a_mock.return_value = 'notLinux'
mock_platform_system.side_effect = a_mock
with patch('socket.socket') as mock_socket:
with pytest.raises(Exception) as pytest_wrapped_e:
iface = TCPInterface(hostname='localhost', noProto=True)
Tunnel(iface)
assert pytest_wrapped_e.type == Exception
assert mock_socket.called
@pytest.mark.unit
@patch('platform.system')
def test_Tunnel_without_interface(mock_platform_system, reset_globals):
"""Test that we can not instantiate a Tunnel without a valid interface"""
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with pytest.raises(Exception) as pytest_wrapped_e:
Tunnel(None)
assert pytest_wrapped_e.type == Exception
@pytest.mark.unitslow
@patch('platform.system')
def test_Tunnel_with_interface(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test that we can not instantiate a Tunnel without a valid interface"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.WARNING):
with patch('socket.socket'):
tun = Tunnel(iface)
assert tun == Globals.getInstance().get_tunnelInstance()
iface.close()
assert re.search(r'Not creating a TapDevice()', caplog.text, re.MULTILINE)
assert re.search(r'Not starting TUN reader', caplog.text, re.MULTILINE)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
@pytest.mark.unitslow
@patch('platform.system')
def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test onTunnelReceive"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
sys.argv = ['']
Globals.getInstance().set_args(sys.argv)
packet = {'decoded': { 'payload': 'foo'}, 'from': 2475227164}
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
Globals.getInstance().set_tunnelInstance(tun)
onTunnelReceive(packet, iface)
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE)
assert re.search(r'Ignoring message we sent', caplog.text, re.MULTILINE)
@pytest.mark.unit
@patch('platform.system')
def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test onTunnelReceive"""
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
sys.argv = ['']
Globals.getInstance().set_args(sys.argv)
packet = {'decoded': { 'payload': 'foo'}, 'from': 123}
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
Globals.getInstance().set_tunnelInstance(tun)
onTunnelReceive(packet, iface)
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE)
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_random(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# random packet
packet = b'1234567890123456789012345678901234567890'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert not ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked IGMP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_icmp(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked ICMP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding ICMP message', caplog.text, re.MULTILINE)
assert not ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_udp(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked UDP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding udp', caplog.text, re.MULTILINE)
assert not ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked UDP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x6c\x07\x6c\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
# Note: custom logging level
LOG_TRACE = 5
with caplog.at_level(LOG_TRACE):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'ignoring blacklisted UDP', caplog.text, re.MULTILINE)
assert ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_tcp(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked TCP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding tcp', caplog.text, re.MULTILINE)
assert not ignore
@pytest.mark.unit
@patch('platform.system')
def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _shouldFilterPacket()"""
iface = iface_with_nodes
iface.noProto = True
# faked TCP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x0c\x17\x0c\x00\x00\x00'
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
# Note: custom logging level
LOG_TRACE = 5
with caplog.at_level(LOG_TRACE):
with patch('socket.socket'):
tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet)
assert re.search(r'ignoring blacklisted TCP', caplog.text, re.MULTILINE)
assert ignore
@pytest.mark.unit
@patch('platform.system')
def test_ipToNodeId_none(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _ipToNodeId()"""
iface = iface_with_nodes
iface.noProto = True
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
nodeid = tun._ipToNodeId('something not useful')
assert nodeid is None
@pytest.mark.unit
@patch('platform.system')
def test_ipToNodeId_all(mock_platform_system, caplog, reset_globals, iface_with_nodes):
"""Test _ipToNodeId()"""
iface = iface_with_nodes
iface.noProto = True
a_mock = MagicMock()
a_mock.return_value = 'Linux'
mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG):
with patch('socket.socket'):
tun = Tunnel(iface)
nodeid = tun._ipToNodeId(b'\x00\x00\xff\xff')
assert nodeid == '^all'

View File

@@ -3,11 +3,14 @@
import re import re
import logging import logging
from unittest.mock import patch
import pytest import pytest
from meshtastic.util import (fixme, stripnl, pskToString, our_exit, from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
support_info, genPSK256, fromStr, fromPSK, support_info, genPSK256, fromStr, fromPSK,
quoteBooleans, catchAndIgnore) quoteBooleans, catchAndIgnore,
remove_keys_from_dict, Timeout, hexstr,
ipstr, readnet_u16, findPorts, convert_mac_addr)
@pytest.mark.unit @pytest.mark.unit
@@ -38,7 +41,7 @@ def test_fromStr():
assert fromStr('abc') == 'abc' assert fromStr('abc') == 'abc'
@pytest.mark.unit @pytest.mark.unitslow
def test_quoteBooleans(): def test_quoteBooleans():
"""Test quoteBooleans""" """Test quoteBooleans"""
assert quoteBooleans('') == '' assert quoteBooleans('') == ''
@@ -91,7 +94,7 @@ def test_pskToString_one_byte_non_zero_value():
assert pskToString(bytes([0x01])) == 'default' assert pskToString(bytes([0x01])) == 'default'
@pytest.mark.unit @pytest.mark.unitslow
def test_pskToString_many_bytes(): def test_pskToString_many_bytes():
"""Test pskToString many bytes""" """Test pskToString many bytes"""
assert pskToString(bytes([0x02, 0x01])) == 'secret' assert pskToString(bytes([0x02, 0x01])) == 'secret'
@@ -103,20 +106,26 @@ def test_pskToString_simple():
assert pskToString(bytes([0x03])) == 'simple2' assert pskToString(bytes([0x03])) == 'simple2'
@pytest.mark.unit @pytest.mark.unitslow
def test_our_exit_zero_return_value(): def test_our_exit_zero_return_value(capsys):
"""Test our_exit with a zero return value""" """Test our_exit with a zero return value"""
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Warning: Some message", 0) our_exit("Warning: Some message", 0)
out, err = capsys.readouterr()
assert re.search(r'Warning: Some message', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0 assert pytest_wrapped_e.value.code == 0
@pytest.mark.unit @pytest.mark.unit
def test_our_exit_non_zero_return_value(): 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:
our_exit("Error: Some message", 1) our_exit("Error: Some message", 1)
out, err = capsys.readouterr()
assert re.search(r'Error: Some message', out, re.MULTILINE)
assert err == ''
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@@ -149,3 +158,77 @@ def test_catchAndIgnore(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
catchAndIgnore("something", some_closure) catchAndIgnore("something", some_closure)
assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE) assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)
@pytest.mark.unitslow
def test_remove_keys_from_dict_empty_keys_empty_dict():
"""Test when keys and dict both are empty"""
assert not remove_keys_from_dict((), {})
@pytest.mark.unit
def test_remove_keys_from_dict_empty_dict():
"""Test when dict is empty"""
assert not remove_keys_from_dict(('a'), {})
@pytest.mark.unit
def test_remove_keys_from_dict_empty_keys():
"""Test when keys is empty"""
assert remove_keys_from_dict((), {'a':1}) == {'a':1}
@pytest.mark.unit
def test_remove_keys_from_dict():
"""Test remove_keys_from_dict()"""
assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1}
@pytest.mark.unitslow
def test_Timeout_not_found():
"""Test Timeout()"""
to = Timeout(0.2)
attrs = ('foo')
to.waitForSet('bar', attrs)
@pytest.mark.unitslow
def test_Timeout_found():
"""Test Timeout()"""
to = Timeout(0.2)
attrs = ()
to.waitForSet('bar', attrs)
@pytest.mark.unitslow
def test_hexstr():
"""Test hexstr()"""
assert hexstr(b'123') == '31:32:33'
assert hexstr(b'') == ''
@pytest.mark.unit
def test_ipstr():
"""Test ipstr()"""
assert ipstr(b'1234') == '49.50.51.52'
assert ipstr(b'') == ''
@pytest.mark.unitslow
def test_readnet_u16():
"""Test readnet_u16()"""
assert readnet_u16(b'123456', 2) == 13108
@pytest.mark.unit
@patch('serial.tools.list_ports.comports', return_value=[])
def test_findPorts_when_none_found(patch_comports):
"""Test findPorts()"""
assert not findPorts()
@pytest.mark.unit
def test_convert_mac_addr():
"""Test convert_mac_addr()"""
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b'
assert convert_mac_addr('') == ''

View File

@@ -17,61 +17,28 @@
import logging import logging
import threading import threading
import platform
from pubsub import pub from pubsub import pub
from pytap2 import TapDevice from pytap2 import TapDevice
from . import portnums_pb2 from . import portnums_pb2
from .util import ipstr, readnet_u16
# A new non standard log level that is lower level than DEBUG from .globals import Globals
LOG_TRACE = 5
# fixme - find a way to move onTunnelReceive inside of the class
tunnelInstance = None
"""A list of chatty UDP services we should never accidentally
forward to our slow network"""
udpBlacklist = {
1900, # SSDP
5353, # multicast DNS
}
"""A list of TCP services to block"""
tcpBlacklist = {}
"""A list of protocols we ignore"""
protocolBlacklist = {
0x02, # IGMP
0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
}
def hexstr(barray):
"""Print a string of hex digits"""
return ":".join('{:02x}'.format(x) for x in barray)
def ipstr(barray):
"""Print a string of ip digits"""
return ".".join('{}'.format(x) for x in barray)
def readnet_u16(p, offset):
"""Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1]
def onTunnelReceive(packet, interface): def onTunnelReceive(packet, interface):
"""Callback for received tunneled messages from mesh """Callback for received tunneled messages from mesh."""
logging.debug(f'in onTunnelReceive()')
FIXME figure out how to do closures with methods in python""" our_globals = Globals.getInstance()
tunnelInstance = our_globals.get_tunnelInstance()
tunnelInstance.onReceive(packet) tunnelInstance.onReceive(packet)
class Tunnel: class Tunnel:
"""A TUN based IP tunnel over meshtastic""" """A TUN based IP tunnel over meshtastic"""
def __init__(self, iface, subnet=None, netmask="255.255.0.0"): def __init__(self, iface, subnet='10.115', netmask="255.255.0.0"):
""" """
Constructor Constructor
@@ -79,35 +46,69 @@ class Tunnel:
subnet is used to construct our network number (normally 10.115.x.x) subnet is used to construct our network number (normally 10.115.x.x)
""" """
if subnet is None: if not iface:
subnet = "10.115" raise Exception("Tunnel() must have a interface")
self.iface = iface self.iface = iface
self.subnetPrefix = subnet self.subnetPrefix = subnet
global tunnelInstance if platform.system() != 'Linux':
tunnelInstance = self raise Exception("Tunnel() can only be run instantiated on a Linux system")
our_globals = Globals.getInstance()
our_globals.set_tunnelInstance(self)
"""A list of chatty UDP services we should never accidentally
forward to our slow network"""
self.udpBlacklist = {
1900, # SSDP
5353, # multicast DNS
}
"""A list of TCP services to block"""
self.tcpBlacklist = {
5900, # VNC (Note: Only adding for testing purposes.)
}
"""A list of protocols we ignore"""
self.protocolBlacklist = {
0x02, # IGMP
0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
}
# A new non standard log level that is lower level than DEBUG
self.LOG_TRACE = 5
# TODO: check if root?
logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\ logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\
"feature to work). Mesh members:") "feature to work). Mesh members:")
pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP") pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num) myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
for node in self.iface.nodes.values(): if self.iface.nodes:
nodeId = node["user"]["id"] for node in self.iface.nodes.values():
ip = self._nodeNumToIp(node["num"]) nodeId = node["user"]["id"]
logging.info(f"Node { nodeId } has IP address { ip }") ip = self._nodeNumToIp(node["num"])
logging.info(f"Node { nodeId } has IP address { ip }")
logging.debug("creating TUN device with MTU=200") logging.debug("creating TUN device with MTU=200")
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
self.tun = TapDevice(name="mesh") self.tun = None
self.tun.up() if self.iface.noProto:
self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200) logging.warning(f"Not creating a TapDevice() because it is disabled by noProto")
logging.debug(f"starting TUN reader, our IP address is {myAddr}") else:
self._rxThread = threading.Thread( self.tun = TapDevice(name="mesh")
target=self.__tunReader, args=(), daemon=True) self.tun.up()
self._rxThread.start() self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
self._rxThread = None
if self.iface.noProto:
logging.warning(f"Not starting TUN reader because it is disabled by noProto")
else:
logging.debug(f"starting TUN reader, our IP address is {myAddr}")
self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True)
self._rxThread.start()
def onReceive(self, packet): def onReceive(self, packet):
"""onReceive""" """onReceive"""
@@ -115,12 +116,12 @@ class Tunnel:
if packet["from"] == self.iface.myInfo.my_node_num: if packet["from"] == self.iface.myInfo.my_node_num:
logging.debug("Ignoring message we sent") logging.debug("Ignoring message we sent")
else: else:
logging.debug( logging.debug(f"Received mesh tunnel message type={type(p)} len={len(p)}")
f"Received mesh tunnel message type={type(p)} len={len(p)}")
# we don't really need to check for filtering here (sender should have checked), # we don't really need to check for filtering here (sender should have checked),
# but this provides useful debug printing on types of packets received # but this provides useful debug printing on types of packets received
if not self._shouldFilterPacket(p): if not self.iface.noProto:
self.tun.write(p) if not self._shouldFilterPacket(p):
self.tun.write(p)
def _shouldFilterPacket(self, p): def _shouldFilterPacket(self, p):
"""Given a packet, decode it and return true if it should be ignored""" """Given a packet, decode it and return true if it should be ignored"""
@@ -129,10 +130,9 @@ class Tunnel:
destAddr = p[16:20] destAddr = p[16:20]
subheader = 20 subheader = 20
ignore = False # Assume we will be forwarding the packet ignore = False # Assume we will be forwarding the packet
if protocol in protocolBlacklist: if protocol in self.protocolBlacklist:
ignore = True ignore = True
logging.log( logging.log(self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
elif protocol == 0x01: # ICMP elif protocol == 0x01: # ICMP
icmpType = p[20] icmpType = p[20]
icmpCode = p[21] icmpCode = p[21]
@@ -145,19 +145,17 @@ class Tunnel:
elif protocol == 0x11: # UDP elif protocol == 0x11: # UDP
srcport = readnet_u16(p, subheader) srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2) destport = readnet_u16(p, subheader + 2)
if destport in udpBlacklist: if destport in self.udpBlacklist:
ignore = True ignore = True
logging.log( logging.log(self.LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
else: else:
logging.debug( logging.debug(f"forwarding udp srcport={srcport}, destport={destport}")
f"forwarding udp srcport={srcport}, destport={destport}")
elif protocol == 0x06: # TCP elif protocol == 0x06: # TCP
srcport = readnet_u16(p, subheader) srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2) destport = readnet_u16(p, subheader + 2)
if destport in tcpBlacklist: if destport in self.tcpBlacklist:
ignore = True ignore = True
logging.log(LOG_TRACE, f"ignoring blacklisted TCP port {destport}") logging.log(self.LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
else: else:
logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}") logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
else: else:

View File

@@ -4,6 +4,7 @@ import traceback
from queue import Queue from queue import Queue
import os import os
import sys import sys
import base64
import time import time
import platform import platform
import logging import logging
@@ -169,8 +170,7 @@ class DeferredExecution():
o = self.queue.get() o = self.queue.get()
o() o()
except: except:
logging.error( logging.error(f"Unexpected error in deferred execution {sys.exc_info()[0]}")
f"Unexpected error in deferred execution {sys.exc_info()[0]}")
print(traceback.format_exc()) print(traceback.format_exc())
@@ -201,3 +201,36 @@ def support_info():
platform.python_implementation(), platform.python_compiler())) 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')
def remove_keys_from_dict(keys, adict):
"""Return a dictionary without some keys in it."""
newdict = adict
for key in keys:
if key in adict:
del newdict[key]
return newdict
def hexstr(barray):
"""Print a string of hex digits"""
return ":".join('{:02x}'.format(x) for x in barray)
def ipstr(barray):
"""Print a string of ip digits"""
return ".".join('{}'.format(x) for x in barray)
def readnet_u16(p, offset):
"""Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1]
def convert_mac_addr(val):
"""Convert the base 64 encoded value to a mac address
val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
"""
val_as_bytes = base64.b64decode(val)
return hexstr(val_as_bytes)

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.46", version="1.2.49",
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",

5
vercel.json Normal file
View File

@@ -0,0 +1,5 @@
{
"github": {
"silent": true
}
}