mirror of
https://github.com/meshtastic/python.git
synced 2025-12-26 01:17:51 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a3a840269 | ||
|
|
fe69f05e75 | ||
|
|
5c662822b9 | ||
|
|
c049d3424a | ||
|
|
471535853b | ||
|
|
676148cc14 | ||
|
|
a915b05240 | ||
|
|
a1668e8c66 | ||
|
|
e7664cb40b | ||
|
|
83c18f4008 | ||
|
|
8b6321ce7f | ||
|
|
9fac981ba6 | ||
|
|
ccc71930f7 | ||
|
|
9380f048fa |
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -10,12 +10,17 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Python 3
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Uninstall meshtastic
|
||||
run: |
|
||||
pip3 uninstall meshtastic
|
||||
@@ -46,12 +51,17 @@ jobs:
|
||||
fail_ci_if_error: true
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Python 3
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
pip3 install .
|
||||
|
||||
@@ -77,9 +77,11 @@ from pubsub import pub
|
||||
from dotmap import DotMap
|
||||
from tabulate import tabulate
|
||||
from google.protobuf.json_format import MessageToJson
|
||||
from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
|
||||
from .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.util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
|
||||
from meshtastic.node import Node
|
||||
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.
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ import pyqrcode
|
||||
import pkg_resources
|
||||
import meshtastic.util
|
||||
import meshtastic.test
|
||||
from . import remote_hardware
|
||||
from .ble_interface import BLEInterface
|
||||
from . import portnums_pb2, channel_pb2, radioconfig_pb2
|
||||
from .globals import Globals
|
||||
from meshtastic import remote_hardware
|
||||
from meshtastic.ble_interface import BLEInterface
|
||||
from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2
|
||||
from meshtastic.globals import Globals
|
||||
|
||||
|
||||
def onReceive(packet, interface):
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import pygatt
|
||||
|
||||
|
||||
from .mesh_interface import MeshInterface
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
|
||||
# Our standard BLE characteristics
|
||||
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
|
||||
|
||||
@@ -17,9 +17,9 @@ from google.protobuf.json_format import MessageToJson
|
||||
|
||||
|
||||
import meshtastic.node
|
||||
from . import portnums_pb2, mesh_pb2
|
||||
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 meshtastic import portnums_pb2, mesh_pb2
|
||||
from meshtastic.util import stripnl, Timeout, our_exit, remove_keys_from_dict, convert_mac_addr
|
||||
from meshtastic.__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
|
||||
|
||||
class MeshInterface:
|
||||
"""Interface class for meshtastic devices
|
||||
@@ -68,8 +68,7 @@ class MeshInterface:
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if exc_type is not None and exc_value is not None:
|
||||
logging.error(
|
||||
f'An exception of type {exc_type} with value {exc_value} has occurred')
|
||||
logging.error(f'An exception of type {exc_type} with value {exc_value} has occurred')
|
||||
if traceback is not None:
|
||||
logging.error(f'Traceback: {traceback}')
|
||||
self.close()
|
||||
@@ -84,10 +83,10 @@ class MeshInterface:
|
||||
nodes = ""
|
||||
if self.nodes:
|
||||
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
|
||||
n2 = remove_keys_from_dict('raw', n)
|
||||
n2 = remove_keys_from_dict('decode', n2)
|
||||
keys_to_remove = ('raw', 'decoded', 'payload')
|
||||
n2 = remove_keys_from_dict(keys_to_remove, n)
|
||||
|
||||
# if we have 'macaddr', re-format it
|
||||
if 'macaddr' in n2['user']:
|
||||
@@ -394,11 +393,11 @@ class MeshInterface:
|
||||
return user.get('shortName', None)
|
||||
return None
|
||||
|
||||
def _waitConnected(self):
|
||||
def _waitConnected(self, timeout=15.0):
|
||||
"""Block until the initial node db download is complete, or timeout
|
||||
and raise an exception"""
|
||||
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")
|
||||
|
||||
# If we failed while connecting, raise the connection to the client
|
||||
@@ -416,8 +415,7 @@ class MeshInterface:
|
||||
def _disconnected(self):
|
||||
"""Called by subclasses to tell clients this interface has disconnected"""
|
||||
self.isConnected.clear()
|
||||
publishingThread.queueWork(lambda: pub.sendMessage(
|
||||
"meshtastic.connection.lost", interface=self))
|
||||
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.lost", interface=self))
|
||||
|
||||
def _startHeartbeat(self):
|
||||
"""We need to send a heartbeat message to the device every X seconds"""
|
||||
@@ -443,8 +441,7 @@ class MeshInterface:
|
||||
if not self.isConnected.is_set():
|
||||
self.isConnected.set()
|
||||
self._startHeartbeat()
|
||||
publishingThread.queueWork(lambda: pub.sendMessage(
|
||||
"meshtastic.connection.established", interface=self))
|
||||
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.connection.established", interface=self))
|
||||
|
||||
def _startConfig(self):
|
||||
"""Start device packets flowing"""
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import logging
|
||||
import base64
|
||||
from google.protobuf.json_format import MessageToJson
|
||||
from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
|
||||
from .util import pskToString, stripnl, Timeout, our_exit, fromPSK
|
||||
from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
|
||||
from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"""
|
||||
import logging
|
||||
from pubsub import pub
|
||||
from . import portnums_pb2, remote_hardware_pb2
|
||||
from .util import our_exit
|
||||
from meshtastic import portnums_pb2, remote_hardware_pb2
|
||||
from meshtastic.util import our_exit
|
||||
|
||||
|
||||
def onGPIOreceive(packet, interface):
|
||||
|
||||
@@ -6,7 +6,7 @@ import platform
|
||||
import serial
|
||||
|
||||
import meshtastic.util
|
||||
from .stream_interface import StreamInterface
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
if platform.system() != 'Windows':
|
||||
import termios
|
||||
|
||||
@@ -7,8 +7,8 @@ import traceback
|
||||
import serial
|
||||
|
||||
|
||||
from .mesh_interface import MeshInterface
|
||||
from .util import stripnl
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
from meshtastic.util import stripnl
|
||||
|
||||
|
||||
START1 = 0x94
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import socket
|
||||
from typing import AnyStr
|
||||
|
||||
from .stream_interface import StreamInterface
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
class TCPInterface(StreamInterface):
|
||||
"""Interface class for meshtastic devices over a TCP link"""
|
||||
|
||||
@@ -8,9 +8,9 @@ import traceback
|
||||
from dotmap import DotMap
|
||||
from pubsub import pub
|
||||
import meshtastic.util
|
||||
from .__init__ import BROADCAST_NUM
|
||||
from .serial_interface import SerialInterface
|
||||
from .tcp_interface import TCPInterface
|
||||
from meshtastic.__init__ import BROADCAST_NUM
|
||||
from meshtastic.serial_interface import SerialInterface
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
|
||||
"""The interfaces we are using for our tests"""
|
||||
|
||||
@@ -10,6 +10,8 @@ from ..mesh_interface import MeshInterface
|
||||
from ..node import Node
|
||||
from .. import mesh_pb2
|
||||
from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR
|
||||
from ..radioconfig_pb2 import RadioConfig
|
||||
from ..util import Timeout
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -164,6 +166,22 @@ def test_sendPosition(reset_globals, caplog):
|
||||
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
|
||||
def test_handleFromRadio_empty_payload(reset_globals, caplog):
|
||||
"""Test _handleFromRadio"""
|
||||
@@ -543,3 +561,70 @@ def test_getOrCreateByNum(capsys, reset_globals, iface_with_nodes):
|
||||
iface.myInfo.my_node_num = 2475227164
|
||||
tmp = iface._getOrCreateByNum(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 == ''
|
||||
|
||||
@@ -11,6 +11,7 @@ from ..serial_interface import SerialInterface
|
||||
from ..admin_pb2 import AdminMessage
|
||||
from ..channel_pb2 import Channel
|
||||
from ..radioconfig_pb2 import RadioConfig
|
||||
from ..util import Timeout
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@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
|
||||
def test_exitSimulator(caplog):
|
||||
"""Test exitSimulator"""
|
||||
@@ -869,3 +880,14 @@ def test_onResponseRequestSetting_with_error(capsys):
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'Error on response', out)
|
||||
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
|
||||
|
||||
@@ -184,6 +184,23 @@ def test_remove_keys_from_dict():
|
||||
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
|
||||
def test_Timeout_not_found():
|
||||
"""Test Timeout()"""
|
||||
|
||||
@@ -22,9 +22,9 @@ from pubsub import pub
|
||||
|
||||
from pytap2 import TapDevice
|
||||
|
||||
from . import portnums_pb2
|
||||
from .util import ipstr, readnet_u16
|
||||
from .globals import Globals
|
||||
from meshtastic import portnums_pb2
|
||||
from meshtastic.util import ipstr, readnet_u16
|
||||
from meshtastic.globals import Globals
|
||||
|
||||
|
||||
def onTunnelReceive(packet, interface):
|
||||
|
||||
@@ -204,12 +204,18 @@ def support_info():
|
||||
|
||||
|
||||
def remove_keys_from_dict(keys, adict):
|
||||
"""Return a dictionary without some keys in it."""
|
||||
newdict = adict
|
||||
"""Return a dictionary without some keys in it.
|
||||
Will removed nested keys.
|
||||
"""
|
||||
for key in keys:
|
||||
if key in adict:
|
||||
del newdict[key]
|
||||
return newdict
|
||||
try:
|
||||
del adict[key]
|
||||
except:
|
||||
pass
|
||||
for val in adict.values():
|
||||
if isinstance(val, dict):
|
||||
remove_keys_from_dict(keys, val)
|
||||
return adict
|
||||
|
||||
|
||||
def hexstr(barray):
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: 1d3b4806ab...7b80bde421
5
setup.py
5
setup.py
@@ -12,7 +12,7 @@ with open("README.md", "r") as fh:
|
||||
# This call to setup() does all the work
|
||||
setup(
|
||||
name="meshtastic",
|
||||
version="1.2.49",
|
||||
version="1.2.52",
|
||||
description="Python API & client shell for talking to Meshtastic devices",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
@@ -23,7 +23,10 @@ setup(
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
],
|
||||
packages=["meshtastic"],
|
||||
include_package_data=True,
|
||||
|
||||
Reference in New Issue
Block a user