mirror of
https://github.com/meshtastic/python.git
synced 2025-12-30 19:37:52 -05:00
basic stress tester
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
README
|
||||
build
|
||||
dist
|
||||
*.egg-info
|
||||
*.egg-info
|
||||
log_*
|
||||
@@ -47,13 +47,14 @@ interface = meshtastic.StreamInterface() # By default will try to find a meshtas
|
||||
|
||||
import google.protobuf.json_format
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import threading
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
from . import mesh_pb2
|
||||
from . import util
|
||||
from pubsub import pub
|
||||
from dotmap import DotMap
|
||||
|
||||
START1 = 0x94
|
||||
START2 = 0xc3
|
||||
@@ -68,12 +69,19 @@ MY_CONFIG_ID = 42
|
||||
|
||||
class MeshInterface:
|
||||
"""Interface class for meshtastic devices
|
||||
|
||||
Properties:
|
||||
|
||||
isConnected
|
||||
nodes
|
||||
debugOut
|
||||
"""
|
||||
|
||||
def __init__(self, debugOut=None):
|
||||
"""Constructor"""
|
||||
self.debugOut = debugOut
|
||||
self.nodes = None # FIXME
|
||||
self.isConnected = False
|
||||
self._startConfig()
|
||||
|
||||
def sendText(self, text, destinationId=BROADCAST_ADDR):
|
||||
@@ -106,8 +114,15 @@ class MeshInterface:
|
||||
|
||||
def _disconnected(self):
|
||||
"""Called by subclasses to tell clients this interface has disconnected"""
|
||||
self.isConnected = False
|
||||
pub.sendMessage("meshtastic.connection.lost")
|
||||
|
||||
def _connected(self):
|
||||
"""Called by this class to tell clients we are now fully connected to a node
|
||||
"""
|
||||
self.isConnected = True
|
||||
pub.sendMessage("meshtastic.connection.established")
|
||||
|
||||
def _startConfig(self):
|
||||
"""Start device packets flowing"""
|
||||
self.myInfo = None
|
||||
@@ -142,7 +157,7 @@ class MeshInterface:
|
||||
self.nodes[node.user.id] = node
|
||||
elif fromRadio.config_complete_id == MY_CONFIG_ID:
|
||||
# we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id
|
||||
pub.sendMessage("meshtastic.connection.established")
|
||||
self._connected()
|
||||
elif fromRadio.HasField("packet"):
|
||||
self._handlePacketFromRadio(fromRadio.packet)
|
||||
elif fromRadio.rebooted:
|
||||
@@ -160,15 +175,16 @@ class MeshInterface:
|
||||
- meshtastic.receive.data(packet = MeshPacket dictionary)
|
||||
"""
|
||||
# FIXME, update node DB as needed
|
||||
json = google.protobuf.json_format.MessageToDict(meshPacket)
|
||||
# We provide our objects as DotMaps - which work with . notation or as dictionaries
|
||||
asObj = DotMap(google.protobuf.json_format.MessageToDict(meshPacket))
|
||||
if meshPacket.payload.HasField("position"):
|
||||
pub.sendMessage("meshtastic.receive.position", packet=json)
|
||||
pub.sendMessage("meshtastic.receive.position", packet=asObj)
|
||||
if meshPacket.payload.HasField("user"):
|
||||
pub.sendMessage("meshtastic.receive.user",
|
||||
packet=json)
|
||||
packet=asObj)
|
||||
if meshPacket.payload.HasField("data"):
|
||||
pub.sendMessage("meshtastic.receive.data",
|
||||
packet=json)
|
||||
packet=asObj)
|
||||
|
||||
|
||||
class StreamInterface(MeshInterface):
|
||||
@@ -188,15 +204,14 @@ class StreamInterface(MeshInterface):
|
||||
"""
|
||||
|
||||
if devPath is None:
|
||||
ports = list(filter(lambda port: port.vid != None,
|
||||
serial.tools.list_ports.comports()))
|
||||
ports = util.findPorts()
|
||||
if len(ports) == 0:
|
||||
raise Exception("No Meshtastic devices detected")
|
||||
elif len(ports) > 1:
|
||||
raise Exception(
|
||||
f"Multiple ports detected, you must specify a device, such as {ports[0].device}")
|
||||
else:
|
||||
devPath = ports[0].device
|
||||
devPath = ports[0]
|
||||
|
||||
logging.debug(f"Connecting to {devPath}")
|
||||
self._rxBuf = bytes() # empty
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!python3
|
||||
|
||||
import argparse
|
||||
from . import StreamInterface
|
||||
from . import StreamInterface, test
|
||||
import logging
|
||||
import sys
|
||||
from pubsub import pub
|
||||
@@ -47,20 +47,26 @@ def main():
|
||||
parser.add_argument("--debug", help="Show API library debug log messages",
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument("--test", help="Run stress test against all connected Meshtastic devices",
|
||||
action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
|
||||
|
||||
if args.seriallog == "stdout":
|
||||
logfile = sys.stdout
|
||||
elif args.seriallog == "none":
|
||||
logging.debug("Not logging serial output")
|
||||
logfile = None
|
||||
if args.test:
|
||||
test.testAll()
|
||||
else:
|
||||
logging.info(f"Logging serial output to {args.seriallog}")
|
||||
logfile = open(args.seriallog, 'w+', buffering=1) # line buffering
|
||||
if args.seriallog == "stdout":
|
||||
logfile = sys.stdout
|
||||
elif args.seriallog == "none":
|
||||
logging.debug("Not logging serial output")
|
||||
logfile = None
|
||||
else:
|
||||
logging.info(f"Logging serial output to {args.seriallog}")
|
||||
logfile = open(args.seriallog, 'w+', buffering=1) # line buffering
|
||||
|
||||
subscribe()
|
||||
client = StreamInterface(args.device, debugOut=logfile)
|
||||
subscribe()
|
||||
client = StreamInterface(args.device, debugOut=logfile)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
104
meshtastic/test.py
Normal file
104
meshtastic/test.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import logging
|
||||
from . import util
|
||||
from . import StreamInterface
|
||||
from pubsub import pub
|
||||
import time
|
||||
|
||||
"""The interfaces we are using for our tests"""
|
||||
interfaces = None
|
||||
|
||||
"""A list of all packets we received while the current test was running"""
|
||||
receivedPackets = None
|
||||
|
||||
testsRunning = False
|
||||
|
||||
testNumber = 0
|
||||
|
||||
|
||||
def onReceive(packet):
|
||||
"""Callback invoked when a packet arrives"""
|
||||
print(f"Received: {packet}")
|
||||
if packet.payload.data.typ == "CLEAR_TEXT":
|
||||
# We only care a about clear text packets
|
||||
receivedPackets.extend(packet)
|
||||
|
||||
|
||||
def onNode(node):
|
||||
"""Callback invoked when the node DB changes"""
|
||||
print(f"Node changed: {node}")
|
||||
|
||||
|
||||
def subscribe():
|
||||
"""Subscribe to the topics the user probably wants to see, prints output to stdout"""
|
||||
|
||||
pub.subscribe(onNode, "meshtastic.node")
|
||||
|
||||
|
||||
def testSend(fromInterface, toInterface):
|
||||
"""
|
||||
Sends one test packet between two nodes and then returns success or failure
|
||||
|
||||
Arguments:
|
||||
fromInterface {[type]} -- [description]
|
||||
toInterface {[type]} -- [description]
|
||||
|
||||
Returns:
|
||||
boolean -- True for success
|
||||
"""
|
||||
global receivedPackets
|
||||
receivedPackets = []
|
||||
fromNode = fromInterface.myInfo.my_node_num
|
||||
toNode = toInterface.myInfo.my_node_num
|
||||
logging.info(f"Sending test packet from {fromNode} to {toNode}")
|
||||
fromInterface.sendText(f"Test {testNumber}", toNode)
|
||||
time.sleep(10)
|
||||
if (len(receivedPackets) < 1):
|
||||
logging.error("Test failed, expected packet not received")
|
||||
return True
|
||||
else:
|
||||
logging.info("Test succeeded")
|
||||
return False
|
||||
|
||||
|
||||
def startTests():
|
||||
logging.info("Found devices, starting tests...")
|
||||
while True:
|
||||
global testNumber
|
||||
testNumber = testNumber + 1
|
||||
testSend(interfaces[0], interfaces[1])
|
||||
time.sleep(20)
|
||||
|
||||
|
||||
def onConnection(topic=pub.AUTO_TOPIC):
|
||||
"""Callback invoked when we connect/disconnect from a radio"""
|
||||
print(f"Connection changed: {topic.getName()}")
|
||||
|
||||
global testsRunning
|
||||
if (all(iface.isConnected for iface in interfaces) and not testsRunning):
|
||||
testsRunning = True
|
||||
startTests()
|
||||
|
||||
|
||||
def openDebugLog(portName):
|
||||
debugname = "log" + portName.replace("/", "_")
|
||||
logging.info(f"Writing serial debugging to {debugname}")
|
||||
return open(debugname, 'w+', buffering=1)
|
||||
|
||||
|
||||
def testAll():
|
||||
"""
|
||||
Run a series of tests using devices we can find.
|
||||
|
||||
Raises:
|
||||
Exception: If not enough devices are found
|
||||
"""
|
||||
ports = util.findPorts()
|
||||
if (len(ports) != 2):
|
||||
raise Exception("Must have at least two devices connected to USB")
|
||||
|
||||
pub.subscribe(onConnection, "meshtastic.connection")
|
||||
pub.subscribe(onReceive, "meshtastic.receive")
|
||||
global interfaces
|
||||
interfaces = list(map(lambda port: StreamInterface(
|
||||
port, debugOut=openDebugLog(port)), ports))
|
||||
logging.info("Ports opened, waiting for device to complete connection")
|
||||
16
meshtastic/util.py
Normal file
16
meshtastic/util.py
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
|
||||
|
||||
def findPorts():
|
||||
"""Find all ports that might have meshtastic devices
|
||||
|
||||
Returns:
|
||||
list -- a list of device paths
|
||||
"""
|
||||
l = list(map(lambda port: port.device,
|
||||
filter(lambda port: port.vid != None,
|
||||
serial.tools.list_ports.comports())))
|
||||
l.sort()
|
||||
return l
|
||||
3
setup.py
3
setup.py
@@ -25,7 +25,8 @@ setup(
|
||||
],
|
||||
packages=["meshtastic"],
|
||||
include_package_data=True,
|
||||
install_requires=["pyserial>=3.4", "protobuf>=3.6.1", "pypubsub>=4.0.3"],
|
||||
install_requires=["pyserial>=3.4", "protobuf>=3.6.1",
|
||||
"pypubsub>=4.0.3", "dotmap>=1.3.14"],
|
||||
python_requires='>=3',
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
|
||||
Reference in New Issue
Block a user