Compare commits

...

13 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
12 changed files with 240 additions and 114 deletions

View File

@@ -226,13 +226,14 @@ def setPref(config, comp_name, valStr) -> bool:
config_values = getattr(config_part, config_type.name) config_values = getattr(config_part, config_type.name)
setattr(config_values, pref.name, valStr) setattr(config_values, pref.name, valStr)
else: else:
config_values = getattr(config, config_type.name)
if val == 0: if val == 0:
# clear values # clear values
print("Clearing ignore_incoming list") print("Clearing ignore_incoming list")
del config_type.message_type.ignore_incoming[:] del config_values.ignore_incoming[:]
else: else:
print(f"Adding '{val}' to the ignore_incoming list") 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 "" prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else ""
if mt_config.camel_case: if mt_config.camel_case:
@@ -256,33 +257,41 @@ def onConnected(interface):
if not args.export_config: if not args.export_config:
print("Connected to radio") 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: if args.dest != BROADCAST_ADDR:
print("Setting latitude, longitude, and altitude of remote nodes is not supported.") print("Setting latitude, longitude, and altitude of remote nodes is not supported.")
return return
closeNow = True closeNow = True
alt = 0 alt = 0
lat = 0.0 lat = 0
lon = 0.0 lon = 0
localConfig = interface.localNode.localConfig
if args.setalt: if args.setalt:
alt = int(args.setalt) alt = int(args.setalt)
localConfig.position.fixed_position = True
print(f"Fixing altitude at {alt} meters") print(f"Fixing altitude at {alt} meters")
if args.setlat: if args.setlat:
lat = float(args.setlat) try:
localConfig.position.fixed_position = True lat = int(args.setlat)
except ValueError:
lat = float(args.setlat)
print(f"Fixing latitude at {lat} degrees") print(f"Fixing latitude at {lat} degrees")
if args.setlon: if args.setlon:
lon = float(args.setlon) try:
localConfig.position.fixed_position = True lon = int(args.setlon)
except ValueError:
lon = float(args.setlon)
print(f"Fixing longitude at {lon} degrees") 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 # can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
interface.sendPosition(lat, lon, alt) interface.localNode.setFixedPosition(lat, lon, alt)
interface.localNode.writeConfig("position")
elif not args.no_time: elif not args.no_time:
# We normally provide a current time to the mesh when we connect # 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]: if interface.localNode.nodeNum in interface.nodesByNum and "position" in interface.nodesByNum[interface.localNode.nodeNum]:
@@ -861,6 +870,10 @@ def onConnected(interface):
) )
interface.getNode(args.dest, False).iface.waitForAckNak() 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 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:
interface.close() # after running command then exit interface.close() # after running command then exit
@@ -1440,12 +1453,25 @@ def initParser():
action="store_true", action="store_true",
) )
group.add_argument("--setalt", help="Set device altitude in meters (allows use without GPS)") group.add_argument(
"--setalt",
group.add_argument("--setlat", help="Set device latitude (allows use without GPS)") help="Set device altitude in meters (allows use without GPS), and enable fixed position.",
)
group.add_argument( 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( group.add_argument(
@@ -1473,6 +1499,14 @@ def initParser():
action="store_true", 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( group.add_argument(
"--noproto", "--noproto",
help="Don't start the API, just function as a dumb serial terminal.", help="Don't start the API, just function as a dumb serial terminal.",

View File

@@ -9,6 +9,7 @@ import sys
import threading import threading
import time import time
from datetime import datetime from datetime import datetime
from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Union from typing import Any, Callable, Dict, List, Optional, Union
@@ -166,7 +167,8 @@ class MeshInterface:
if not includeSelf and node["num"] == self.localNode.nodeNum: if not includeSelf and node["num"] == self.localNode.nodeNum:
continue 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") user = node.get("user")
if user: if user:
@@ -175,6 +177,7 @@ class MeshInterface:
"User": user.get("longName", "N/A"), "User": user.get("longName", "N/A"),
"AKA": user.get("shortName", "N/A"), "AKA": user.get("shortName", "N/A"),
"ID": user["id"], "ID": user["id"],
"Hardware": user.get("hwModel", "UNSET")
} }
) )
@@ -248,7 +251,7 @@ class MeshInterface:
destinationId: Union[int, str]=BROADCAST_ADDR, destinationId: Union[int, str]=BROADCAST_ADDR,
wantAck: bool=False, wantAck: bool=False,
wantResponse: bool=False, wantResponse: bool=False,
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None, onResponse: Optional[Callable[[dict], Any]]=None,
channelIndex: int=0, channelIndex: int=0,
): ):
"""Send a utf8 string to some other node, if the node has a display it """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, portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
wantAck: bool=False, wantAck: bool=False,
wantResponse: bool=False, wantResponse: bool=False,
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None, onResponse: Optional[Callable[[dict], Any]]=None,
channelIndex: int=0, channelIndex: int=0,
): ):
"""Send a data packet to some other node """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) waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
self.waitForTraceRoute(waitFactor) self.waitForTraceRoute(waitFactor)
def onResponseTraceRoute(self, p): def onResponseTraceRoute(self, p: dict):
"""on response for trace route""" """on response for trace route"""
routeDiscovery = mesh_pb2.RouteDiscovery() routeDiscovery = mesh_pb2.RouteDiscovery()
routeDiscovery.ParseFromString(p["decoded"]["payload"]) routeDiscovery.ParseFromString(p["decoded"]["payload"])
@@ -498,7 +501,7 @@ class MeshInterface:
if wantResponse: if wantResponse:
self.waitForTelemetry() self.waitForTelemetry()
def onResponseTelemetry(self, p): def onResponseTelemetry(self, p: dict):
"""on response for telemetry""" """on response for telemetry"""
if p["decoded"]["portnum"] == 'TELEMETRY_APP': if p["decoded"]["portnum"] == 'TELEMETRY_APP':
self._acknowledgment.receivedTelemetry = True self._acknowledgment.receivedTelemetry = True
@@ -521,7 +524,7 @@ class MeshInterface:
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE': 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.") 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) self.responseHandlers[requestId] = ResponseHandler(callback)
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False): 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)}") logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
elif fromRadio.HasField("node_info"): 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: try:
newpos = self._fixupPosition(node["position"]) newpos = self._fixupPosition(node["position"])
node["position"] = newpos node["position"] = newpos
except: except:
logging.debug("Node without position") logging.debug("Node without position")
logging.debug(f"Received nodeinfo: {node}") # no longer necessary since we're mutating directly in nodesByNum via _getOrCreateByNum
#self.nodesByNum[node["num"]] = node
self.nodesByNum[node["num"]] = node
if "user" in node: # Some nodes might not have user/ids assigned yet if "user" in node: # Some nodes might not have user/ids assigned yet
if "id" in node["user"]: if "id" in node["user"]:
self.nodes[node["user"]["id"]] = node self.nodes[node["user"]["id"]] = node
@@ -974,9 +979,9 @@ class MeshInterface:
Returns the position with the updated keys Returns the position with the updated keys
""" """
if "latitudeI" in position: if "latitudeI" in position:
position["latitude"] = position["latitudeI"] * 1e-7 position["latitude"] = float(position["latitudeI"] * Decimal("1e-7"))
if "longitudeI" in position: if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7 position["longitude"] = float(position["longitudeI"] * Decimal("1e-7"))
return position return position
def _nodeNumToId(self, num): def _nodeNumToId(self, num):
@@ -1005,7 +1010,16 @@ class MeshInterface:
if nodeNum in self.nodesByNum: if nodeNum in self.nodesByNum:
return self.nodesByNum[nodeNum] return self.nodesByNum[nodeNum]
else: 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 self.nodesByNum[nodeNum] = n
return n return n

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 ENABLED_FIELD_NUMBER: builtins.int
PAXCOUNTER_UPDATE_INTERVAL_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 enabled: builtins.bool
""" """
Enable the Paxcounter Module 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 Interval in seconds of how often we should try to send our
metrics to the mesh 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__( def __init__(
self, self,
*, *,
enabled: builtins.bool = ..., enabled: builtins.bool = ...,
paxcounter_update_interval: builtins.int = ..., paxcounter_update_interval: builtins.int = ...,
wifi_threshold: builtins.int = ...,
ble_threshold: builtins.int = ...,
) -> None: ... ) -> 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 @typing_extensions.final
class SerialConfig(google.protobuf.message.Message): class SerialConfig(google.protobuf.message.Message):

View File

@@ -7,7 +7,7 @@ import time
from typing import Union 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 ( from meshtastic.util import (
Timeout, Timeout,
camel_to_snake, camel_to_snake,
@@ -655,6 +655,38 @@ class Node:
onResponse = self.onAckNak onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse) 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): def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed""" """Fixup indexes and add disabled channels as needed"""

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*\xf9\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\x12\t\n\x05SHT4X\x10\x11\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.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
@@ -21,16 +21,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' 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_start=1129
_TELEMETRYSENSORTYPE._serialized_end=1346 _TELEMETRYSENSORTYPE._serialized_end=1449
_DEVICEMETRICS._serialized_start=43 _DEVICEMETRICS._serialized_start=43
_DEVICEMETRICS._serialized_end=172 _DEVICEMETRICS._serialized_end=172
_ENVIRONMENTMETRICS._serialized_start=175 _ENVIRONMENTMETRICS._serialized_start=175
_ENVIRONMENTMETRICS._serialized_end=361 _ENVIRONMENTMETRICS._serialized_end=393
_POWERMETRICS._serialized_start=364 _POWERMETRICS._serialized_start=396
_POWERMETRICS._serialized_end=504 _POWERMETRICS._serialized_end=536
_AIRQUALITYMETRICS._serialized_start=507 _AIRQUALITYMETRICS._serialized_start=539
_AIRQUALITYMETRICS._serialized_end=826 _AIRQUALITYMETRICS._serialized_end=858
_TELEMETRY._serialized_start=829 _TELEMETRY._serialized_start=861
_TELEMETRY._serialized_end=1094 _TELEMETRY._serialized_end=1126
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -94,6 +94,26 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
""" """
Sensirion High accuracy temperature and humidity 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): class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
""" """
@@ -172,6 +192,26 @@ SHT4X: TelemetrySensorType.ValueType # 17
""" """
Sensirion High accuracy temperature and humidity 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 global___TelemetrySensorType = TelemetrySensorType
@typing_extensions.final @typing_extensions.final
@@ -236,6 +276,8 @@ class EnvironmentMetrics(google.protobuf.message.Message):
CURRENT_FIELD_NUMBER: builtins.int CURRENT_FIELD_NUMBER: builtins.int
IAQ_FIELD_NUMBER: builtins.int IAQ_FIELD_NUMBER: builtins.int
DISTANCE_FIELD_NUMBER: builtins.int DISTANCE_FIELD_NUMBER: builtins.int
LUX_FIELD_NUMBER: builtins.int
WHITE_LUX_FIELD_NUMBER: builtins.int
temperature: builtins.float temperature: builtins.float
""" """
Temperature measured Temperature measured
@@ -269,6 +311,14 @@ class EnvironmentMetrics(google.protobuf.message.Message):
""" """
RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. 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__( def __init__(
self, self,
*, *,
@@ -280,8 +330,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
current: builtins.float = ..., current: builtins.float = ...,
iaq: builtins.int = ..., iaq: builtins.int = ...,
distance: builtins.float = ..., distance: builtins.float = ...,
lux: builtins.float = ...,
white_lux: builtins.float = ...,
) -> None: ... ) -> 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 global___EnvironmentMetrics = EnvironmentMetrics

View File

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

View File

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

View File

@@ -588,7 +588,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(123) 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 @pytest.mark.unit

View File

@@ -13,7 +13,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="2.3.8", version="2.3.9",
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",