Compare commits

...

26 Commits

Author SHA1 Message Date
Ian McEwen
bbc526d0a8 Merge pull request #584 from ianmcorvidae/improve-fixed-position
Use new fixed position admin messages and add `--remove-position` argument
2024-06-01 00:44:17 -07:00
Ian McEwen
abe98f5079 Merge pull request #585 from ianmcorvidae/position-rounding
Fix rounding of position values when converting from integer to float in _fixupPosition
2024-06-01 00:42:58 -07:00
Ian McEwen
e8dfee8454 Fix rounding of position values when converting from integer to float in _fixupPosition. Fixes #572 2024-05-31 18:57:30 -07:00
Ian McEwen
1746ad15d7 Use new fixed position admin messages and add --remove-position argument. Fixes #525 2024-05-31 18:44:33 -07:00
Ian McEwen
4d67e7fc76 Fix up/add some more types 2024-05-30 17:51:42 -07:00
Ian McEwen
3b112d2f49 Merge pull request #583 from ianmcorvidae/update-unknown-node-setup
Initialize unknown nodes more in line with meshtastic/design#16; show hardware in --nodes
2024-05-30 15:52:16 -07:00
Ian McEwen
93e9c1c66c Initialize unknown nodes more in line with meshtastic/design#16 2024-05-30 13:50:52 -07:00
Ian McEwen
8e641b3186 Merge pull request #581 from 868meshbot/868meshbot-fix-ignore-incoming
Fix the ignore_incoming management BUG 568
2024-05-26 00:03:11 -07:00
Ian McEwen
ed545cd9b4 Merge pull request #580 from todd-herbert/wait-to-disconnect
Add "wait to disconnect" argument
2024-05-26 00:02:08 -07:00
868meshbot
bcd60c9ef7 Update __main__.py
Simple patch to fix the ignore_incoming management aka
https://github.com/meshtastic/python/issues/568
2024-05-25 16:28:16 +01:00
Todd Herbert
c3d044e3f2 Optional pause before disconnecting 2024-05-24 17:10:07 +12:00
Ian McEwen
8d538e8f24 protobufs: v2.3.10 2024-05-18 12:56:41 -07:00
github-actions
fa1a3d7901 bump version 2024-05-18 00:15:11 +00:00
Ian McEwen
3adeb925dd tweak the roadmap in README a little 2024-05-14 16:05:21 -07:00
Ian McEwen
9de04a12e1 Merge pull request #574 from ianmcorvidae/warn-enable-disable
add a warning on --ch-enable and --ch-disable, which should usually be avoided
2024-05-13 21:30:01 -07:00
Ian McEwen
e01a1bb6e0 add a warning on --ch-enable and --ch-disable, which should usually be avoided 2024-05-13 21:27:53 -07:00
Ian McEwen
1ebea53703 make pylint happy with import order 2024-05-11 22:28:50 -07:00
Ian McEwen
ba2d6c9d93 continue progressively typing things (SerialInterface/BLEInterface initializations) 2024-05-11 22:27:31 -07:00
Ian McEwen
fef0e1b77f Remove an unuseful/inaccurate comment 2024-05-10 20:17:48 -07:00
Ian McEwen
dc1dec17b5 protobufs: v2.3.9 2024-05-10 14:56:07 -07:00
Ian McEwen
02ed17837b Improve FromRadio handling, publish messages for client proxy/xmodem stuff tentatively 2024-05-10 14:54:32 -07:00
Ian McEwen
8cf996a242 Merge pull request #564 from jhollowe-forks/fix-help-typo
Fix typo in __main__.py
2024-05-02 19:04:08 -07:00
John Hollowell
02189aae07 Fix typo in __main__.py 2024-05-02 21:51:09 -04:00
Ian McEwen
894581ce00 Merge pull request #563 from ianmcorvidae/show-channel-0
Print 0 for channel rather than N/A, since we should probably always have a channel
2024-05-02 18:29:00 -07:00
Ian McEwen
fb8db01427 Print 0 for channel rather than N/A, since we should probably always have a channel. Fixes #562 2024-05-02 18:22:39 -07:00
github-actions
fa5ede93ed bump version 2024-05-01 16:42:38 +00:00
18 changed files with 307 additions and 135 deletions

View File

@@ -28,8 +28,7 @@ If you're interested in contributing but don't have specific things you'd like t
This should always be considered a list in progress and flux -- inclusion doesn't guarantee implementation, and exclusion doesn't mean something's not wanted. GitHub issues are a great place to discuss ideas.
* Types
* type annotations throughout the codebase
* mypy running in CI to type-check new code
* type annotations throughout the codebase, and upgrading mypy running in CI to `--strict`
* async-friendliness
* CLI completeness & consistency
* the CLI should support all features of the firmware
@@ -40,9 +39,9 @@ This should always be considered a list in progress and flux -- inclusion doesn'
* pubsub events should be documented clearly
* helpers for third-party code
* it should be easy to write a script that supports similar options to the CLI so many tools support the same ways of connecting to nodes
* interactive client
* data storage & processing
* there should be a standardized way of recording packets for later use, debugging, etc.
* a persistence layer could also keep track of nodes beyond nodedb, as the apps do
* a sqlite database schema and tools for writing to it may be a good starting point
* enable maps, charts, visualizations

View File

@@ -226,13 +226,14 @@ def setPref(config, comp_name, valStr) -> bool:
config_values = getattr(config_part, config_type.name)
setattr(config_values, pref.name, valStr)
else:
config_values = getattr(config, config_type.name)
if val == 0:
# clear values
print("Clearing ignore_incoming list")
del config_type.message_type.ignore_incoming[:]
del config_values.ignore_incoming[:]
else:
print(f"Adding '{val}' to the ignore_incoming list")
config_type.message_type.ignore_incoming.extend([val])
config_values.ignore_incoming.extend([int(valStr)])
prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else ""
if mt_config.camel_case:
@@ -256,34 +257,41 @@ def onConnected(interface):
if not args.export_config:
print("Connected to radio")
if args.setlat or args.setlon or args.setalt:
if args.remove_position:
if args.dest != BROADCAST_ADDR:
print("Setting positions of remote nodes is not supported.")
return
closeNow = True
print("Removing fixed position and disabling fixed position setting")
interface.localNode.removeFixedPosition()
elif args.setlat or args.setlon or args.setalt:
if args.dest != BROADCAST_ADDR:
print("Setting latitude, longitude, and altitude of remote nodes is not supported.")
return
closeNow = True
alt = 0
lat = 0.0
lon = 0.0
# TODO: use getNode(args.dest) to be able to set it for a remote node
localConfig = interface.localNode.localConfig
lat = 0
lon = 0
if args.setalt:
alt = int(args.setalt)
localConfig.position.fixed_position = True
print(f"Fixing altitude at {alt} meters")
if args.setlat:
lat = float(args.setlat)
localConfig.position.fixed_position = True
try:
lat = int(args.setlat)
except ValueError:
lat = float(args.setlat)
print(f"Fixing latitude at {lat} degrees")
if args.setlon:
lon = float(args.setlon)
localConfig.position.fixed_position = True
try:
lon = int(args.setlon)
except ValueError:
lon = float(args.setlon)
print(f"Fixing longitude at {lon} degrees")
print("Setting device position")
print("Setting device position and enabling fixed position setting")
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
interface.sendPosition(lat, lon, alt)
interface.localNode.writeConfig("position")
interface.localNode.setFixedPosition(lat, lon, alt)
elif not args.no_time:
# We normally provide a current time to the mesh when we connect
if interface.localNode.nodeNum in interface.nodesByNum and "position" in interface.nodesByNum[interface.localNode.nodeNum]:
@@ -722,6 +730,10 @@ def onConnected(interface):
ch = interface.getNode(args.dest).channels[channelIndex]
if args.ch_enable or args.ch_disable:
print(
"Warning: --ch-enable and --ch-disable can produce noncontiguous channels, "
"which can cause errors in some clients. Whenever possible, use --ch-add and --ch-del instead."
)
if channelIndex == 0:
meshtastic.util.our_exit(
"Warning: Cannot enable/disable PRIMARY channel."
@@ -858,6 +870,10 @@ def onConnected(interface):
)
interface.getNode(args.dest, False).iface.waitForAckNak()
if args.wait_to_disconnect:
print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting" )
time.sleep(int(args.wait_to_disconnect))
# 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:
interface.close() # after running command then exit
@@ -1351,7 +1367,7 @@ def initParser():
group.add_argument(
"--request-position",
help="Request the position from a nade. "
help="Request the position from a node. "
"You need to pass the destination ID as an argument with '--dest'. "
"For repeaters, the nodeNum is required.",
action="store_true",
@@ -1437,12 +1453,25 @@ def initParser():
action="store_true",
)
group.add_argument("--setalt", help="Set device altitude in meters (allows use without GPS)")
group.add_argument("--setlat", help="Set device latitude (allows use without GPS)")
group.add_argument(
"--setalt",
help="Set device altitude in meters (allows use without GPS), and enable fixed position.",
)
group.add_argument(
"--setlon", help="Set device longitude (allows use without GPS)"
"--setlat",
help="Set device latitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7.",
)
group.add_argument(
"--setlon",
help="Set device longitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7.",
)
group.add_argument(
"--remove-position",
help="Clear any existing fixed position and disable fixed position.",
action="store_true",
)
group.add_argument(
@@ -1470,6 +1499,14 @@ def initParser():
action="store_true",
)
group.add_argument(
"--wait-to-disconnect",
help="How many seconds to wait before disconnecting from the device.",
const="5",
nargs="?",
action="store",
)
group.add_argument(
"--noproto",
help="Don't start the API, just function as a dumb serial terminal.",

View File

@@ -5,6 +5,8 @@ import time
import struct
import asyncio
from threading import Thread, Event
from typing import Optional
from bleak import BleakScanner, BleakClient
from meshtastic.mesh_interface import MeshInterface
@@ -30,7 +32,7 @@ class BLEInterface(MeshInterface):
MESH = False
def __init__(self, address, noProto = False, debugOut = None):
def __init__(self, address: Optional[str], noProto: bool = False, debugOut = None):
self.state = BLEInterface.BLEState()
if not address:

View File

@@ -9,6 +9,7 @@ import sys
import threading
import time
from datetime import datetime
from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Union
@@ -166,7 +167,8 @@ class MeshInterface:
if not includeSelf and node["num"] == self.localNode.nodeNum:
continue
row = {"N": 0, "User": f"UNK: {node['num']}", "ID": f"!{node['num']:08x}"}
presumptive_id = f"!{node['num']:08x}"
row = {"N": 0, "User": f"Meshtastic {presumptive_id[-4:]}", "ID": presumptive_id}
user = node.get("user")
if user:
@@ -175,6 +177,7 @@ class MeshInterface:
"User": user.get("longName", "N/A"),
"AKA": user.get("shortName", "N/A"),
"ID": user["id"],
"Hardware": user.get("hwModel", "UNSET")
}
)
@@ -211,8 +214,8 @@ class MeshInterface:
row.update(
{
"SNR": formatFloat(node.get("snr"), 2, " dB"),
"Hops Away": node.get("hopsAway", "unknown"),
"Channel": node.get("channel"),
"Hops Away": node.get("hopsAway", "0/unknown"),
"Channel": node.get("channel", 0),
"LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")),
}
@@ -248,7 +251,7 @@ class MeshInterface:
destinationId: Union[int, str]=BROADCAST_ADDR,
wantAck: bool=False,
wantResponse: bool=False,
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
onResponse: Optional[Callable[[dict], Any]]=None,
channelIndex: int=0,
):
"""Send a utf8 string to some other node, if the node has a display it
@@ -288,7 +291,7 @@ class MeshInterface:
portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
wantAck: bool=False,
wantResponse: bool=False,
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
onResponse: Optional[Callable[[dict], Any]]=None,
channelIndex: int=0,
):
"""Send a data packet to some other node
@@ -444,7 +447,7 @@ class MeshInterface:
waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
self.waitForTraceRoute(waitFactor)
def onResponseTraceRoute(self, p):
def onResponseTraceRoute(self, p: dict):
"""on response for trace route"""
routeDiscovery = mesh_pb2.RouteDiscovery()
routeDiscovery.ParseFromString(p["decoded"]["payload"])
@@ -498,7 +501,7 @@ class MeshInterface:
if wantResponse:
self.waitForTelemetry()
def onResponseTelemetry(self, p):
def onResponseTelemetry(self, p: dict):
"""on response for telemetry"""
if p["decoded"]["portnum"] == 'TELEMETRY_APP':
self._acknowledgment.receivedTelemetry = True
@@ -521,7 +524,7 @@ class MeshInterface:
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
def _addResponseHandler(self, requestId: int, callback: Callable):
def _addResponseHandler(self, requestId: int, callback: Callable[[dict], Any]):
self.responseHandlers[requestId] = ResponseHandler(callback)
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
@@ -844,16 +847,18 @@ class MeshInterface:
logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
elif fromRadio.HasField("node_info"):
node = asDict["nodeInfo"]
logging.debug(f"Received nodeinfo: {asDict['nodeInfo']}")
node = self._getOrCreateByNum(asDict["nodeInfo"]["num"])
node.update(asDict["nodeInfo"])
try:
newpos = self._fixupPosition(node["position"])
node["position"] = newpos
except:
logging.debug("Node without position")
logging.debug(f"Received nodeinfo: {node}")
self.nodesByNum[node["num"]] = node
# no longer necessary since we're mutating directly in nodesByNum via _getOrCreateByNum
#self.nodesByNum[node["num"]] = node
if "user" in node: # Some nodes might not have user/ids assigned yet
if "id" in node["user"]:
self.nodes[node["user"]["id"]] = node
@@ -875,14 +880,28 @@ class MeshInterface:
elif fromRadio.HasField("queueStatus"):
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
elif fromRadio.rebooted:
elif fromRadio.HasField("mqttClientProxyMessage"):
publishingThread.queueWork(
lambda: pub.sendMessage(
"meshtastic.mqttclientproxymessage", proxymessage=fromRadio.mqttClientProxyMessage, interface=self
)
)
elif fromRadio.HasField("xmodemPacket"):
publishingThread.queueWork(
lambda: pub.sendMessage(
"meshtastic.xmodempacket", packet=fromRadio.xmodemPacket, interface=self
)
)
elif fromRadio.HasField("rebooted") and fromRadio.rebooted:
# Tell clients the device went away. Careful not to call the overridden
# subclass version that closes the serial port
MeshInterface._disconnected(self)
self._startConfig() # redownload the node db etc...
elif fromRadio.config or fromRadio.moduleConfig:
elif fromRadio.HasField("config") or fromRadio.HasField("moduleConfig"):
if fromRadio.config.HasField("device"):
self.localNode.localConfig.device.CopyFrom(fromRadio.config.device)
elif fromRadio.config.HasField("position"):
@@ -960,9 +979,9 @@ class MeshInterface:
Returns the position with the updated keys
"""
if "latitudeI" in position:
position["latitude"] = position["latitudeI"] * 1e-7
position["latitude"] = float(position["latitudeI"] * Decimal("1e-7"))
if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7
position["longitude"] = float(position["longitudeI"] * Decimal("1e-7"))
return position
def _nodeNumToId(self, num):
@@ -991,7 +1010,16 @@ class MeshInterface:
if nodeNum in self.nodesByNum:
return self.nodesByNum[nodeNum]
else:
n = {"num": nodeNum} # Create a minimal node db entry
presumptive_id = f"!{nodeNum:08x}"
n = {
"num": nodeNum,
"user": {
"id": presumptive_id,
"longName": f"Meshtastic {presumptive_id[-4:]}",
"shortName": f"{presumptive_id[-4:]}",
"hwModel": "UNSET"
}
} # Create a minimal node db entry
self.nodesByNum[nodeNum] = n
return n

View File

File diff suppressed because one or more lines are too long

View File

@@ -112,6 +112,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
"""
LoRAType device: https://loratype.org/
"""
WIPHONE: _HardwareModel.ValueType # 20
"""
wiphone https://www.wiphone.io/
"""
STATION_G1: _HardwareModel.ValueType # 25
"""
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
@@ -276,6 +280,11 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
TWC_MESH_V4
Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS
"""
NRF52_PROMICRO_DIY: _HardwareModel.ValueType # 63
"""
NRF52_PROMICRO_DIY
Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS
"""
PRIVATE_HW: _HardwareModel.ValueType # 255
"""
------------------------------------------------------------------------------------------------------------------------------------------
@@ -373,6 +382,10 @@ LORA_TYPE: HardwareModel.ValueType # 19
"""
LoRAType device: https://loratype.org/
"""
WIPHONE: HardwareModel.ValueType # 20
"""
wiphone https://www.wiphone.io/
"""
STATION_G1: HardwareModel.ValueType # 25
"""
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
@@ -537,6 +550,11 @@ TWC_MESH_V4: HardwareModel.ValueType # 62
TWC_MESH_V4
Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS
"""
NRF52_PROMICRO_DIY: HardwareModel.ValueType # 63
"""
NRF52_PROMICRO_DIY
Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS
"""
PRIVATE_HW: HardwareModel.ValueType # 255
"""
------------------------------------------------------------------------------------------------------------------------------------------

View File

File diff suppressed because one or more lines are too long

View File

@@ -405,6 +405,8 @@ class ModuleConfig(google.protobuf.message.Message):
ENABLED_FIELD_NUMBER: builtins.int
PAXCOUNTER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
WIFI_THRESHOLD_FIELD_NUMBER: builtins.int
BLE_THRESHOLD_FIELD_NUMBER: builtins.int
enabled: builtins.bool
"""
Enable the Paxcounter Module
@@ -414,13 +416,23 @@ class ModuleConfig(google.protobuf.message.Message):
Interval in seconds of how often we should try to send our
metrics to the mesh
"""
wifi_threshold: builtins.int
"""
WiFi RSSI threshold. Defaults to -80
"""
ble_threshold: builtins.int
"""
BLE RSSI threshold. Defaults to -80
"""
def __init__(
self,
*,
enabled: builtins.bool = ...,
paxcounter_update_interval: builtins.int = ...,
wifi_threshold: builtins.int = ...,
ble_threshold: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval"]) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["ble_threshold", b"ble_threshold", "enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval", "wifi_threshold", b"wifi_threshold"]) -> None: ...
@typing_extensions.final
class SerialConfig(google.protobuf.message.Message):

View File

@@ -7,7 +7,7 @@ import time
from typing import Union
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2
from meshtastic.util import (
Timeout,
camel_to_snake,
@@ -655,6 +655,38 @@ class Node:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int):
"""Tell the node to set fixed position to the provided value and enable the fixed position setting"""
if self != self.iface.localNode:
logging.error("Setting position of remote nodes is not supported.")
return None
p = mesh_pb2.Position()
if isinstance(lat, float) and lat != 0.0:
p.latitude_i = int(lat / 1e-7)
elif isinstance(lat, int) and lat != 0:
p.latitude_i = lat
if isinstance(lon, float) and lon != 0.0:
p.longitude_i = int(lon / 1e-7)
elif isinstance(lon, int) and lon != 0:
p.longitude_i = lon
if alt != 0:
p.altitude = alt
a = admin_pb2.AdminMessage()
a.set_fixed_position.CopyFrom(p)
return self._sendAdmin(a)
def removeFixedPosition(self):
"""Tell the node to remove the fixed position and set the fixed position setting to false"""
p = admin_pb2.AdminMessage()
p.remove_fixed_position = True
logging.info(f"Telling node to remove fixed position")
return self._sendAdmin(p)
def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed"""

View File

@@ -4,6 +4,8 @@ import logging
import platform
import time
from typing import Optional
import serial # type: ignore[import-untyped]
import meshtastic.util
@@ -16,7 +18,7 @@ if platform.system() != "Windows":
class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link"""
def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
def __init__(self, devPath: Optional[str]=None, debugOut=None, noProto=False, connectNow=True):
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing
@@ -26,7 +28,7 @@ class SerialInterface(StreamInterface):
"""
self.noProto = noProto
self.devPath = devPath
self.devPath: Optional[str] = devPath
if self.devPath is None:
ports = meshtastic.util.findPorts(True)

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"\x81\x01\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\x12\x16\n\x0euptime_seconds\x18\x05 \x01(\r\"\xba\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x12\x0b\n\x03iaq\x18\x07 \x01(\r\x12\x10\n\x08\x64istance\x18\x08 \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xee\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x12\x0c\n\x08RCWL9620\x10\x10\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"\x81\x01\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\x12\x16\n\x0euptime_seconds\x18\x05 \x01(\r\"\xda\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x12\x0b\n\x03iaq\x18\x07 \x01(\r\x12\x10\n\x08\x64istance\x18\x08 \x01(\x02\x12\x0b\n\x03lux\x18\t \x01(\x02\x12\x11\n\twhite_lux\x18\n \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xc0\x02\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x12\x0c\n\x08RCWL9620\x10\x10\x12\t\n\x05SHT4X\x10\x11\x12\x0c\n\x08VEML7700\x10\x12\x12\x0c\n\x08MLX90632\x10\x13\x12\x0b\n\x07OPT3001\x10\x14\x12\x0c\n\x08LTR390UV\x10\x15\x12\x0e\n\nTSL25911FN\x10\x16\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
@@ -21,16 +21,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_TELEMETRYSENSORTYPE._serialized_start=1097
_TELEMETRYSENSORTYPE._serialized_end=1335
_TELEMETRYSENSORTYPE._serialized_start=1129
_TELEMETRYSENSORTYPE._serialized_end=1449
_DEVICEMETRICS._serialized_start=43
_DEVICEMETRICS._serialized_end=172
_ENVIRONMENTMETRICS._serialized_start=175
_ENVIRONMENTMETRICS._serialized_end=361
_POWERMETRICS._serialized_start=364
_POWERMETRICS._serialized_end=504
_AIRQUALITYMETRICS._serialized_start=507
_AIRQUALITYMETRICS._serialized_end=826
_TELEMETRY._serialized_start=829
_TELEMETRY._serialized_end=1094
_ENVIRONMENTMETRICS._serialized_end=393
_POWERMETRICS._serialized_start=396
_POWERMETRICS._serialized_end=536
_AIRQUALITYMETRICS._serialized_start=539
_AIRQUALITYMETRICS._serialized_end=858
_TELEMETRY._serialized_start=861
_TELEMETRY._serialized_end=1126
# @@protoc_insertion_point(module_scope)

View File

@@ -90,6 +90,30 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
"""
RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
"""
SHT4X: _TelemetrySensorType.ValueType # 17
"""
Sensirion High accuracy temperature and humidity
"""
VEML7700: _TelemetrySensorType.ValueType # 18
"""
VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
"""
MLX90632: _TelemetrySensorType.ValueType # 19
"""
MLX90632 non-contact IR temperature sensor.
"""
OPT3001: _TelemetrySensorType.ValueType # 20
"""
TI OPT3001 Ambient Light Sensor
"""
LTR390UV: _TelemetrySensorType.ValueType # 21
"""
Lite On LTR-390UV-01 UV Light Sensor
"""
TSL25911FN: _TelemetrySensorType.ValueType # 22
"""
AMS TSL25911FN RGB Light Sensor
"""
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
"""
@@ -164,6 +188,30 @@ RCWL9620: TelemetrySensorType.ValueType # 16
"""
RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
"""
SHT4X: TelemetrySensorType.ValueType # 17
"""
Sensirion High accuracy temperature and humidity
"""
VEML7700: TelemetrySensorType.ValueType # 18
"""
VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
"""
MLX90632: TelemetrySensorType.ValueType # 19
"""
MLX90632 non-contact IR temperature sensor.
"""
OPT3001: TelemetrySensorType.ValueType # 20
"""
TI OPT3001 Ambient Light Sensor
"""
LTR390UV: TelemetrySensorType.ValueType # 21
"""
Lite On LTR-390UV-01 UV Light Sensor
"""
TSL25911FN: TelemetrySensorType.ValueType # 22
"""
AMS TSL25911FN RGB Light Sensor
"""
global___TelemetrySensorType = TelemetrySensorType
@typing_extensions.final
@@ -228,6 +276,8 @@ class EnvironmentMetrics(google.protobuf.message.Message):
CURRENT_FIELD_NUMBER: builtins.int
IAQ_FIELD_NUMBER: builtins.int
DISTANCE_FIELD_NUMBER: builtins.int
LUX_FIELD_NUMBER: builtins.int
WHITE_LUX_FIELD_NUMBER: builtins.int
temperature: builtins.float
"""
Temperature measured
@@ -261,6 +311,14 @@ class EnvironmentMetrics(google.protobuf.message.Message):
"""
RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm.
"""
lux: builtins.float
"""
VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
"""
white_lux: builtins.float
"""
VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor.
"""
def __init__(
self,
*,
@@ -272,8 +330,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
current: builtins.float = ...,
iaq: builtins.int = ...,
distance: builtins.float = ...,
lux: builtins.float = ...,
white_lux: builtins.float = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage"]) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage", "white_lux", b"white_lux"]) -> None: ...
global___EnvironmentMetrics = EnvironmentMetrics

View File

@@ -44,7 +44,7 @@ def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
packet = {
"from": "foo",
"from": 4808675309,
"decoded": {
"user": {
"id": "bar",

View File

@@ -734,19 +734,14 @@ def test_main_setlat(capsys):
mocked_node = MagicMock(autospec=Node)
def mock_writeConfig():
print("inside mocked writeConfig")
mocked_node.writeConfig.side_effect = mock_writeConfig
iface = MagicMock(autospec=SerialInterface)
def mock_sendPosition(lat, lon, alt):
print("inside mocked sendPosition")
def mock_setFixedPosition(lat, lon, alt):
print("inside mocked setFixedPosition")
print(f"{lat} {lon} {alt}")
iface.sendPosition.side_effect = mock_sendPosition
iface.localNode.return_value = mocked_node
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main()
@@ -754,8 +749,7 @@ def test_main_setlat(capsys):
assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"Fixing latitude", out, re.MULTILINE)
assert re.search(r"Setting device position", out, re.MULTILINE)
assert re.search(r"inside mocked sendPosition", out, re.MULTILINE)
# TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
assert re.search(r"inside mocked setFixedPosition", out, re.MULTILINE)
assert err == ""
mo.assert_called()
@@ -769,19 +763,14 @@ def test_main_setlon(capsys):
mocked_node = MagicMock(autospec=Node)
def mock_writeConfig():
print("inside mocked writeConfig")
mocked_node.writeConfig.side_effect = mock_writeConfig
iface = MagicMock(autospec=SerialInterface)
def mock_sendPosition(lat, lon, alt):
print("inside mocked sendPosition")
def mock_setFixedPosition(lat, lon, alt):
print("inside mocked setFixedPosition")
print(f"{lat} {lon} {alt}")
iface.sendPosition.side_effect = mock_sendPosition
iface.localNode.return_value = mocked_node
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main()
@@ -789,8 +778,7 @@ def test_main_setlon(capsys):
assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"Fixing longitude", out, re.MULTILINE)
assert re.search(r"Setting device position", out, re.MULTILINE)
assert re.search(r"inside mocked sendPosition", out, re.MULTILINE)
# TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
assert re.search(r"inside mocked setFixedPosition", out, re.MULTILINE)
assert err == ""
mo.assert_called()
@@ -804,19 +792,14 @@ def test_main_setalt(capsys):
mocked_node = MagicMock(autospec=Node)
def mock_writeConfig():
print("inside mocked writeConfig")
mocked_node.writeConfig.side_effect = mock_writeConfig
iface = MagicMock(autospec=SerialInterface)
def mock_sendPosition(lat, lon, alt):
print("inside mocked sendPosition")
def mock_setFixedPosition(lat, lon, alt):
print("inside mocked setFixedPosition")
print(f"{lat} {lon} {alt}")
iface.sendPosition.side_effect = mock_sendPosition
iface.localNode.return_value = mocked_node
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main()
@@ -824,8 +807,7 @@ def test_main_setalt(capsys):
assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"Fixing altitude", out, re.MULTILINE)
assert re.search(r"Setting device position", out, re.MULTILINE)
assert re.search(r"inside mocked sendPosition", out, re.MULTILINE)
# TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
assert re.search(r"inside mocked setFixedPosition", out, re.MULTILINE)
assert err == ""
mo.assert_called()

View File

@@ -588,7 +588,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes):
iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(123)
assert tmp == {"num": 123}
assert tmp == {"num": 123, "user": {"hwModel": "UNSET", "id": "!0000007b", "shortName": "007b", "longName": "Meshtastic 007b"}}
@pytest.mark.unit

View File

@@ -11,7 +11,7 @@ import threading
import time
import traceback
from queue import Queue
from typing import Union
from typing import List, NoReturn, Union
from google.protobuf.json_format import MessageToJson
@@ -122,7 +122,7 @@ def catchAndIgnore(reason, closure):
logging.error(f"Exception thrown in {reason}: {ex}")
def findPorts(eliminate_duplicates=False):
def findPorts(eliminate_duplicates: bool=False) -> List[str]:
"""Find all ports that might have meshtastic devices
eliminate_duplicates will run the eliminate_duplicate_port() on the collection
@@ -263,7 +263,7 @@ class DeferredExecution:
print(traceback.format_exc())
def our_exit(message, return_value=1):
def our_exit(message, return_value=1) -> NoReturn:
"""Print the message and return a value.
return_value defaults to 1 (non-successful)
"""

View File

@@ -13,7 +13,7 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work
setup(
name="meshtastic",
version="2.3.7",
version="2.3.9",
description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description,
long_description_content_type="text/markdown",