Compare commits

..

14 Commits

Author SHA1 Message Date
mkinney
1a3a840269 Merge pull request #206 from mkinney/fully_qualify_imports
need to fully qualify imports so projects consuming the library will …
2022-01-05 11:24:13 -08:00
Mike Kinney
fe69f05e75 add python 3.6, 3.7, 3.8, and 3.9 for ci and validation 2022-01-05 11:20:04 -08:00
Mike Kinney
5c662822b9 need to fully qualify imports so projects consuming the library will work 2022-01-05 11:16:08 -08:00
mkinney
c049d3424a Merge pull request #205 from mkinney/remove_nested_keys
remove nested keys from nodes so we do not display garbage
2022-01-02 11:28:07 -08:00
Mike Kinney
471535853b bump version 2022-01-02 11:20:15 -08:00
Mike Kinney
676148cc14 meant to use decoded not decode 2022-01-02 11:19:17 -08:00
Mike Kinney
a915b05240 remove nested keys from nodes so we do not display garbage 2022-01-02 11:15:19 -08:00
Jm Casler
a1668e8c66 updating proto submodule to latest 2022-01-01 23:25:22 -08:00
mkinney
e7664cb40b Merge pull request #204 from mkinney/add_more_unit_tests
get last two lines covered in node
2022-01-01 15:51:21 -08:00
Mike Kinney
83c18f4008 working on more unit tests 2022-01-01 15:48:33 -08:00
Mike Kinney
8b6321ce7f add a few more tests 2022-01-01 15:21:53 -08:00
Mike Kinney
9fac981ba6 test heartbeatTimer 2022-01-01 14:53:57 -08:00
Mike Kinney
ccc71930f7 get last two lines covered in node 2022-01-01 13:25:36 -08:00
mkinney
9380f048fa Update setup.py
bump version
2022-01-01 11:11:54 -08:00
18 changed files with 189 additions and 47 deletions

View File

@@ -10,12 +10,17 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Uninstall meshtastic - name: Uninstall meshtastic
run: | run: |
pip3 uninstall meshtastic pip3 uninstall meshtastic
@@ -46,12 +51,17 @@ jobs:
fail_ci_if_error: true fail_ci_if_error: true
validate: validate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install meshtastic from local - name: Install meshtastic from local
run: | run: |
pip3 install . pip3 install .

View File

@@ -77,9 +77,11 @@ from pubsub import pub
from dotmap import DotMap from dotmap import DotMap
from tabulate import tabulate from tabulate import tabulate
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout from meshtastic.util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
from .node import Node from meshtastic.node import Node
from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2,
environmental_measurement_pb2, remote_hardware_pb2,
channel_pb2, radioconfig_pb2, util)
# Note: To follow PEP224, comments should be after the module variable. # Note: To follow PEP224, comments should be after the module variable.

View File

@@ -13,10 +13,10 @@ import pyqrcode
import pkg_resources import pkg_resources
import meshtastic.util import meshtastic.util
import meshtastic.test import meshtastic.test
from . import remote_hardware from meshtastic import remote_hardware
from .ble_interface import BLEInterface from meshtastic.ble_interface import BLEInterface
from . import portnums_pb2, channel_pb2, radioconfig_pb2 from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2
from .globals import Globals from meshtastic.globals import Globals
def onReceive(packet, interface): def onReceive(packet, interface):

View File

@@ -4,7 +4,7 @@ import logging
import pygatt import pygatt
from .mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
# Our standard BLE characteristics # Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7" TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"

View File

@@ -17,9 +17,9 @@ from google.protobuf.json_format import MessageToJson
import meshtastic.node import meshtastic.node
from . import portnums_pb2, mesh_pb2 from meshtastic import portnums_pb2, mesh_pb2
from .util import stripnl, Timeout, our_exit, remove_keys_from_dict, convert_mac_addr from meshtastic.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 meshtastic.__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
class MeshInterface: class MeshInterface:
"""Interface class for meshtastic devices """Interface class for meshtastic devices
@@ -68,8 +68,7 @@ class MeshInterface:
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and exc_value is not None: if exc_type is not None and exc_value is not None:
logging.error( logging.error(f'An exception of type {exc_type} with value {exc_value} has occurred')
f'An exception of type {exc_type} with value {exc_value} has occurred')
if traceback is not None: if traceback is not None:
logging.error(f'Traceback: {traceback}') logging.error(f'Traceback: {traceback}')
self.close() self.close()
@@ -84,10 +83,10 @@ class MeshInterface:
nodes = "" nodes = ""
if self.nodes: if self.nodes:
for n in self.nodes.values(): for n in self.nodes.values():
# when the TBeam is first booted, it sometimes shows the 'raw' data # when the TBeam is first booted, it sometimes shows the raw data
# so, we will just remove any raw keys # so, we will just remove any raw keys
n2 = remove_keys_from_dict('raw', n) keys_to_remove = ('raw', 'decoded', 'payload')
n2 = remove_keys_from_dict('decode', n2) n2 = remove_keys_from_dict(keys_to_remove, n)
# if we have 'macaddr', re-format it # if we have 'macaddr', re-format it
if 'macaddr' in n2['user']: if 'macaddr' in n2['user']:
@@ -394,11 +393,11 @@ class MeshInterface:
return user.get('shortName', None) return user.get('shortName', None)
return None return None
def _waitConnected(self): def _waitConnected(self, timeout=15.0):
"""Block until the initial node db download is complete, or timeout """Block until the initial node db download is complete, or timeout
and raise an exception""" and raise an exception"""
if not self.noProto: if not self.noProto:
if not self.isConnected.wait(15.0): # timeout after x seconds if not self.isConnected.wait(timeout): # timeout after x seconds
raise Exception("Timed out waiting for connection completion") raise Exception("Timed out waiting for connection completion")
# If we failed while connecting, raise the connection to the client # If we failed while connecting, raise the connection to the client
@@ -416,8 +415,7 @@ class MeshInterface:
def _disconnected(self): def _disconnected(self):
"""Called by subclasses to tell clients this interface has disconnected""" """Called by subclasses to tell clients this interface has disconnected"""
self.isConnected.clear() self.isConnected.clear()
publishingThread.queueWork(lambda: pub.sendMessage( publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.lost", interface=self))
"meshtastic.connection.lost", interface=self))
def _startHeartbeat(self): def _startHeartbeat(self):
"""We need to send a heartbeat message to the device every X seconds""" """We need to send a heartbeat message to the device every X seconds"""
@@ -443,8 +441,7 @@ class MeshInterface:
if not self.isConnected.is_set(): if not self.isConnected.is_set():
self.isConnected.set() self.isConnected.set()
self._startHeartbeat() self._startHeartbeat()
publishingThread.queueWork(lambda: pub.sendMessage( publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.established", interface=self))
"meshtastic.connection.established", interface=self))
def _startConfig(self): def _startConfig(self):
"""Start device packets flowing""" """Start device packets flowing"""

View File

@@ -4,8 +4,8 @@
import logging import logging
import base64 import base64
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2 from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK

View File

@@ -2,8 +2,8 @@
""" """
import logging import logging
from pubsub import pub from pubsub import pub
from . import portnums_pb2, remote_hardware_pb2 from meshtastic import portnums_pb2, remote_hardware_pb2
from .util import our_exit from meshtastic.util import our_exit
def onGPIOreceive(packet, interface): def onGPIOreceive(packet, interface):

View File

@@ -6,7 +6,7 @@ import platform
import serial import serial
import meshtastic.util import meshtastic.util
from .stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
if platform.system() != 'Windows': if platform.system() != 'Windows':
import termios import termios

View File

@@ -7,8 +7,8 @@ import traceback
import serial import serial
from .mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
from .util import stripnl from meshtastic.util import stripnl
START1 = 0x94 START1 = 0x94

View File

@@ -4,7 +4,7 @@ import logging
import socket import socket
from typing import AnyStr from typing import AnyStr
from .stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
class TCPInterface(StreamInterface): class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link""" """Interface class for meshtastic devices over a TCP link"""

View File

@@ -8,9 +8,9 @@ import traceback
from dotmap import DotMap from dotmap import DotMap
from pubsub import pub from pubsub import pub
import meshtastic.util import meshtastic.util
from .__init__ import BROADCAST_NUM from meshtastic.__init__ import BROADCAST_NUM
from .serial_interface import SerialInterface from meshtastic.serial_interface import SerialInterface
from .tcp_interface import TCPInterface from meshtastic.tcp_interface import TCPInterface
"""The interfaces we are using for our tests""" """The interfaces we are using for our tests"""

View File

@@ -10,6 +10,8 @@ from ..mesh_interface import MeshInterface
from ..node import Node from ..node import Node
from .. import mesh_pb2 from .. import mesh_pb2
from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR
from ..radioconfig_pb2 import RadioConfig
from ..util import Timeout
@pytest.mark.unit @pytest.mark.unit
@@ -164,6 +166,22 @@ def test_sendPosition(reset_globals, caplog):
assert re.search(r'p.time:', caplog.text, re.MULTILINE) assert re.search(r'p.time:', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_close_with_heartbeatTimer(reset_globals, caplog):
"""Test close() with heartbeatTimer"""
iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar')
radioConfig = RadioConfig()
radioConfig.preferences.phone_timeout_secs = 10
anode.radioConfig = radioConfig
iface.localNode = anode
assert iface.heartbeatTimer is None
with caplog.at_level(logging.DEBUG):
iface._startHeartbeat()
assert iface.heartbeatTimer is not None
iface.close()
@pytest.mark.unit @pytest.mark.unit
def test_handleFromRadio_empty_payload(reset_globals, caplog): def test_handleFromRadio_empty_payload(reset_globals, caplog):
"""Test _handleFromRadio""" """Test _handleFromRadio"""
@@ -543,3 +561,70 @@ def test_getOrCreateByNum(capsys, reset_globals, iface_with_nodes):
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(2475227164) tmp = iface._getOrCreateByNum(2475227164)
assert tmp['num'] == 2475227164 assert tmp['num'] == 2475227164
@pytest.mark.unit
def test_enter():
"""Test __enter__()"""
iface = MeshInterface(noProto=True)
assert iface == iface.__enter__()
@pytest.mark.unit
def test_exit_with_exception(caplog):
"""Test __exit__()"""
iface = MeshInterface(noProto=True)
with caplog.at_level(logging.ERROR):
iface.__exit__('foo', 'bar', 'baz')
assert re.search(r'An exception of type foo with value bar has occurred', caplog.text, re.MULTILINE)
assert re.search(r'Traceback: baz', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_showNodes_exclude_self(capsys, caplog, reset_globals, iface_with_nodes):
"""Test that we hit that continue statement"""
with caplog.at_level(logging.DEBUG):
iface = iface_with_nodes
iface.localNode.nodeNum = 2475227164
iface.showNodes()
iface.showNodes(includeSelf=False)
capsys.readouterr()
@pytest.mark.unit
def test_waitForConfig(caplog, capsys):
"""Test waitForConfig()"""
iface = MeshInterface(noProto=True)
# override how long to wait
iface._timeout = Timeout(0.01)
with pytest.raises(Exception) as pytest_wrapped_e:
iface.waitForConfig()
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r'Exception: Timed out waiting for interface config', err, re.MULTILINE)
assert out == ''
@pytest.mark.unit
def test_waitConnected_raises_an_exception(caplog, capsys):
"""Test waitConnected()"""
iface = MeshInterface(noProto=True)
with pytest.raises(Exception) as pytest_wrapped_e:
iface.failure = "warn about something"
iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r'warn about something', err, re.MULTILINE)
assert out == ''
@pytest.mark.unit
def test_waitConnected_isConnected_timeout(caplog, capsys):
"""Test waitConnected()"""
with pytest.raises(Exception) as pytest_wrapped_e:
iface = MeshInterface()
iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr()
assert re.search(r'warn about something', err, re.MULTILINE)
assert out == ''

View File

@@ -11,6 +11,7 @@ from ..serial_interface import SerialInterface
from ..admin_pb2 import AdminMessage from ..admin_pb2 import AdminMessage
from ..channel_pb2 import Channel from ..channel_pb2 import Channel
from ..radioconfig_pb2 import RadioConfig from ..radioconfig_pb2 import RadioConfig
from ..util import Timeout
@pytest.mark.unit @pytest.mark.unit
@@ -90,6 +91,16 @@ def test_setOwner_no_short_name_and_long_name_has_words(caplog):
assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE) assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_setOwner_long_name_no_short(caplog):
"""Test setOwner"""
anode = Node('foo', 'bar', noProto=True)
with caplog.at_level(logging.DEBUG):
anode.setOwner(long_name ='Aabo', is_licensed=True)
assert re.search(r'p.set_owner.long_name:Aabo:', caplog.text, re.MULTILINE)
assert re.search(r'p.set_owner.short_name:Aab:', caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
def test_exitSimulator(caplog): def test_exitSimulator(caplog):
"""Test exitSimulator""" """Test exitSimulator"""
@@ -869,3 +880,14 @@ def test_onResponseRequestSetting_with_error(capsys):
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Error on response', out) assert re.search(r'Error on response', out)
assert err == '' assert err == ''
@pytest.mark.unit
def test_waitForConfig():
"""Test waitForConfig()"""
anode = Node('foo', 'bar')
radioConfig = RadioConfig()
anode.radioConfig = radioConfig
anode._timeout = Timeout(0.01)
result = anode.waitForConfig()
assert not result

View File

@@ -184,6 +184,23 @@ def test_remove_keys_from_dict():
assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1} assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1}
@pytest.mark.unit
def test_remove_keys_from_dict_multiple_keys():
"""Test remove_keys_from_dict()"""
keys = ('a', 'b')
adict = {'a': 1, 'b': 2, 'c': 3}
assert remove_keys_from_dict(keys, adict) == {'c':3}
@pytest.mark.unit
def test_remove_keys_from_dict_nested():
"""Test remove_keys_from_dict()"""
keys = ('b')
adict = {'a': {'b': 1}, 'b': 2, 'c': 3}
exp = {'a': {}, 'c': 3}
assert remove_keys_from_dict(keys, adict) == exp
@pytest.mark.unitslow @pytest.mark.unitslow
def test_Timeout_not_found(): def test_Timeout_not_found():
"""Test Timeout()""" """Test Timeout()"""

View File

@@ -22,9 +22,9 @@ from pubsub import pub
from pytap2 import TapDevice from pytap2 import TapDevice
from . import portnums_pb2 from meshtastic import portnums_pb2
from .util import ipstr, readnet_u16 from meshtastic.util import ipstr, readnet_u16
from .globals import Globals from meshtastic.globals import Globals
def onTunnelReceive(packet, interface): def onTunnelReceive(packet, interface):

View File

@@ -204,12 +204,18 @@ def support_info():
def remove_keys_from_dict(keys, adict): def remove_keys_from_dict(keys, adict):
"""Return a dictionary without some keys in it.""" """Return a dictionary without some keys in it.
newdict = adict Will removed nested keys.
"""
for key in keys: for key in keys:
if key in adict: try:
del newdict[key] del adict[key]
return newdict except:
pass
for val in adict.values():
if isinstance(val, dict):
remove_keys_from_dict(keys, val)
return adict
def hexstr(barray): def hexstr(barray):

2
proto

Submodule proto updated: 1d3b4806ab...7b80bde421

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.49", version="1.2.52",
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",
@@ -23,7 +23,10 @@ setup(
classifiers=[ classifiers=[
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
], ],
packages=["meshtastic"], packages=["meshtastic"],
include_package_data=True, include_package_data=True,