Compare commits

...

21 Commits
2.5.2 ... 2.5.3

Author SHA1 Message Date
github-actions
f08ec1885b bump version to 2.5.3 2024-10-18 17:02:53 +00:00
Ian McEwen
feca49faed Merge pull request #696 from fmoessbauer/master
fix base64 encoding of key field in config
2024-10-18 09:57:13 -07:00
Felix Moessbauer
dfaf1a275d fix base64 encoding of key field in config
The security.privateKey and security.publicKey fields are of type bytes,
but the protobuf MessageToDict converts them to base64 encoded strings.
When importing the config again, this is read as a string, which breaks
the import. Instead, the value needs to be prefixed with "base64:", so
the type handling logic on import kicks in and decodes the value to a
bytes array again.

Fixes: #678
2024-10-18 16:13:25 +02:00
Ian McEwen
da7fa31805 tweak documentation formatting 2024-10-16 20:52:10 -07:00
Ian McEwen
12fd29b203 Merge pull request #694 from ianmcorvidae/configure-fixed-position
Use dedicated fixed position admin message for --configure
2024-10-16 16:01:06 -07:00
Ian McEwen
a43dd201ba Merge pull request #695 from ianmcorvidae/ble-disconnect
Send the meshtastic.connection.lost message from BLEInterface's close method
2024-10-16 16:00:49 -07:00
Ian McEwen
a64a9d203a Send the meshtastic.connection.lost message from BLEInterface's close method 2024-10-16 12:13:30 -07:00
Ian McEwen
10136962d7 Use dedicated fixed position admin message for --configure 2024-10-15 07:28:55 -07:00
Ian McEwen
3afb294f9b Merge pull request #691 from logikal/telemetry_output
autoprint other types of telemetry when returned from --request-telemetry
2024-10-14 17:11:00 -07:00
Sean Kilgore
ee405fec41 autoprint other types of telemetry when returned from --request-telemetry 2024-10-14 16:58:52 -07:00
Ian McEwen
3eabaf91d0 Merge pull request #687 from ianmcorvidae/telemetry-types
Support requesting different telemetry types
2024-10-13 21:50:04 -07:00
Ian McEwen
78b92cecc9 fix type check 2024-10-13 20:40:22 -07:00
Ian McEwen
7088b90514 Support requesting different telemetry types 2024-10-13 20:35:11 -07:00
Ian McEwen
2ae81f8602 Merge pull request #686 from jose1711/typofix
Fix typo.
2024-10-13 18:00:16 -07:00
Jose Riha
923f5e82d0 Fix typo. 2024-10-14 02:48:41 +02:00
Ian McEwen
05731128fa missed a spot 2024-10-12 12:52:36 -07:00
Ian McEwen
0523d4c94f disable R0917 pylint failures 2024-10-12 12:49:14 -07:00
Ian McEwen
90e901de79 Upgrade bleak and therefore also the supported python versions 2024-10-12 12:32:43 -07:00
Ian McEwen
6606851135 Merge pull request #685 from ianmcorvidae/more-telemetry
Add other telemetry variants to automatic handling/adding to node information
2024-10-12 11:59:45 -07:00
Ian McEwen
33fecbd74d Add other telemetry variants to automatic handling/adding to node information 2024-10-12 11:56:55 -07:00
Ian McEwen
6b9db7abd9 2.5.3 setup 2024-10-12 09:33:28 -07:00
16 changed files with 1762 additions and 1446 deletions

View File

@@ -2,41 +2,44 @@
# A library for the Meshtastic Client API # A library for the Meshtastic Client API
Primary interfaces: SerialInterface, TCPInterface, BLEInterface Primary interfaces: SerialInterface, TCPInterface, BLEInterface
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)" Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
Source code on [github](https://github.com/meshtastic/python) Source code on [github](https://github.com/meshtastic/python)
notable properties of interface classes: notable properties of interface classes:
- nodes - The database of received nodes. Includes always up-to-date location and username information for each - `nodes` - The database of received nodes. Includes always up-to-date location and username information for each
node in the mesh. This is a read-only datastructure. node in the mesh. This is a read-only datastructure.
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId - `nodesByNum` - like "nodes" but keyed by nodeNum instead of nodeId. As such, includes "unknown" nodes which haven't seen a User packet yet
- myInfo & metadata - Contain read-only information about the local radio device (software version, hardware version, etc) - `myInfo` & `metadata` - Contain read-only information about the local radio device (software version, hardware version, etc)
- localNode - Pointer to a node object for the local node - `localNode` - Pointer to a node object for the local node
notable properties of nodes: notable properties of nodes:
- localConfig - Current radio settings, can be written to the radio with the `writeConfig` method.
- moduleConfig - Current module settings, can be written to the radio with the `writeConfig` method. - `localConfig` - Current radio settings, can be written to the radio with the `writeConfig` method.
- channels - The node's channels, keyed by index. - `moduleConfig` - Current module settings, can be written to the radio with the `writeConfig` method.
- `channels` - The node's channels, keyed by index.
# Published PubSub topics # Published PubSub topics
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
topics: topics:
- meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB - `meshtastic.connection.established` - published once we've successfully connected to the radio and downloaded the node DB
- meshtastic.connection.lost - published once we've lost our link to the radio - `meshtastic.connection.lost` - published once we've lost our link to the radio
- meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular - `meshtastic.receive.text(packet)` - delivers a received packet as a dictionary, if you only care about a particular
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive". type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
- meshtastic.receive.position(packet) - `meshtastic.receive.position(packet)`
- meshtastic.receive.user(packet) - `meshtastic.receive.user(packet)`
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum) - `meshtastic.receive.data.portnum(packet)` (where portnum is an integer or well known PortNum enum)
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...) - `meshtastic.node.updated(node = NodeInfo)` - published when a node in the DB changes (appears, location changed, username changed, etc...)
- meshtastic.log.line(line) - a raw unparsed log line from the radio - `meshtastic.log.line(line)` - a raw unparsed log line from the radio
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for We receive position, user, or data packets from the mesh. You probably only care about `meshtastic.receive.data`. The first argument for
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet that publish will be the packet. Text or binary data packets (from `sendData` or `sendText`) will both arrive this way. If you print packet
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with you'll see the fields in the dictionary. `decoded.data.payload` will contain the raw bytes that were sent. If the packet was sent with
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for `sendText`, `decoded.data.text` will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
unicode scripts they can be different. unicode scripts they can be different.
# Example Usage # Example Usage
@@ -194,13 +197,31 @@ def _onNodeInfoReceive(iface, asDict):
def _onTelemetryReceive(iface, asDict): def _onTelemetryReceive(iface, asDict):
"""Automatically update device metrics on received packets""" """Automatically update device metrics on received packets"""
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}") logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
deviceMetrics = asDict.get("decoded", {}).get("telemetry", {}).get("deviceMetrics") if "from" not in asDict:
if "from" in asDict and deviceMetrics is not None: return
node = iface._getOrCreateByNum(asDict["from"])
newMetrics = node.get("deviceMetrics", {}) toUpdate = None
newMetrics.update(deviceMetrics)
logging.debug(f"updating metrics for {asDict['from']} to {newMetrics}") telemetry = asDict.get("decoded", {}).get("telemetry", {})
node["deviceMetrics"] = newMetrics node = iface._getOrCreateByNum(asDict["from"])
if "deviceMetrics" in telemetry:
toUpdate = "deviceMetrics"
elif "environmentMetrics" in telemetry:
toUpdate = "environmentMetrics"
elif "airQualityMetrics" in telemetry:
toUpdate = "airQualityMetrics"
elif "powerMetrics" in telemetry:
toUpdate = "powerMetrics"
elif "localStats" in telemetry:
toUpdate = "localStats"
else:
return
updateObj = telemetry.get(toUpdate)
newMetrics = node.get(toUpdate, {})
newMetrics.update(updateObj)
logging.debug(f"updating {toUpdate} metrics for {asDict['from']} to {newMetrics}")
node[toUpdate] = newMetrics
def _receiveInfoUpdate(iface, asDict): def _receiveInfoUpdate(iface, asDict):
if "from" in asDict: if "from" in asDict:

View File

@@ -475,13 +475,22 @@ def onConnected(interface):
else: else:
channelIndex = mt_config.channel_index or 0 channelIndex = mt_config.channel_index or 0
if checkChannel(interface, channelIndex): if checkChannel(interface, channelIndex):
telemMap = {
"device": "device_metrics",
"environment": "environment_metrics",
"air_quality": "air_quality_metrics",
"airquality": "air_quality_metrics",
"power": "power_metrics",
}
telemType = telemMap.get(args.request_telemetry, "device_metrics")
print( print(
f"Sending telemetry request to {args.dest} on channelIndex:{channelIndex} (this could take a while)" f"Sending {telemType} telemetry request to {args.dest} on channelIndex:{channelIndex} (this could take a while)"
) )
interface.sendTelemetry( interface.sendTelemetry(
destinationId=args.dest, destinationId=args.dest,
wantResponse=True, wantResponse=True,
channelIndex=channelIndex, channelIndex=channelIndex,
telemetryType=telemType,
) )
if args.request_position: if args.request_position:
@@ -622,19 +631,15 @@ def onConnected(interface):
if "alt" in configuration["location"]: if "alt" in configuration["location"]:
alt = int(configuration["location"]["alt"] or 0) alt = int(configuration["location"]["alt"] or 0)
localConfig.position.fixed_position = True
print(f"Fixing altitude at {alt} meters") print(f"Fixing altitude at {alt} meters")
if "lat" in configuration["location"]: if "lat" in configuration["location"]:
lat = float(configuration["location"]["lat"] or 0) lat = float(configuration["location"]["lat"] or 0)
localConfig.position.fixed_position = True
print(f"Fixing latitude at {lat} degrees") print(f"Fixing latitude at {lat} degrees")
if "lon" in configuration["location"]: if "lon" in configuration["location"]:
lon = float(configuration["location"]["lon"] or 0) lon = float(configuration["location"]["lon"] or 0)
localConfig.position.fixed_position = True
print(f"Fixing longitude at {lon} degrees") print(f"Fixing longitude at {lon} degrees")
print("Setting device position") print("Setting device position")
interface.sendPosition(lat, lon, alt) interface.localNode.setFixedPosition(lat, lon, alt)
interface.localNode.writeConfig("position")
if "config" in configuration: if "config" in configuration:
localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
@@ -1029,6 +1034,15 @@ def export_config(interface):
prefs[meshtastic.util.snake_to_camel(pref)] = config[pref] prefs[meshtastic.util.snake_to_camel(pref)] = config[pref]
else: else:
prefs[pref] = config[pref] prefs[pref] = config[pref]
# mark base64 encoded fields as such
if pref == "security":
if 'privateKey' in prefs[pref]:
prefs[pref]['privateKey'] = 'base64:' + prefs[pref]['privateKey']
if 'publicKey' in prefs[pref]:
prefs[pref]['publicKey'] = 'base64:' + prefs[pref]['publicKey']
if 'adminKey' in prefs[pref]:
for i in range(len(prefs[pref]['adminKey'])):
prefs[pref]['adminKey'][i] = 'base64:' + prefs[pref]['adminKey'][i]
if mt_config.camel_case: if mt_config.camel_case:
configObj["config"] = config configObj["config"] = config
else: else:
@@ -1592,10 +1606,14 @@ def addRemoteActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar
group.add_argument( group.add_argument(
"--request-telemetry", "--request-telemetry",
help="Request telemetry from a node. " help="Request telemetry from a node. With an argument, requests that specific type of telemetry. "
"You need to pass the destination ID as argument with '--dest'. " "You need to pass the destination ID as argument with '--dest'. "
"For repeaters, the nodeNum is required.", "For repeaters, the nodeNum is required.",
action="store_true", action="store",
nargs="?",
default=None,
const="device",
metavar="TYPE",
) )
group.add_argument( group.add_argument(
@@ -1842,7 +1860,7 @@ def initParser():
power_group.add_argument( power_group.add_argument(
"--slog", "--slog",
help="Store structured-logs (slogs) for this run, optionally you can specifiy a destination directory", help="Store structured-logs (slogs) for this run, optionally you can specify a destination directory",
nargs="?", nargs="?",
default=None, default=None,
const="default", const="default",

View File

@@ -218,7 +218,7 @@ class BLEInterface(MeshInterface):
logging.error(f"Error closing mesh interface: {e}") logging.error(f"Error closing mesh interface: {e}")
if self._want_receive: if self._want_receive:
self.want_receive = False # Tell the thread we want it to stop self._want_receive = False # Tell the thread we want it to stop
if self._receiveThread: if self._receiveThread:
self._receiveThread.join( self._receiveThread.join(
timeout=2 timeout=2
@@ -230,6 +230,7 @@ class BLEInterface(MeshInterface):
self.client.disconnect() self.client.disconnect()
self.client.close() self.client.close()
self.client = None self.client = None
self._disconnected() # send the disconnected indicator up to clients
class BLEClient: class BLEClient:

View File

@@ -1,5 +1,6 @@
"""Mesh Interface class """Mesh Interface class
""" """
# pylint: disable=R0917
import collections import collections
import json import json
@@ -605,32 +606,38 @@ class MeshInterface: # pylint: disable=R0902
destinationId: Union[int, str] = BROADCAST_ADDR, destinationId: Union[int, str] = BROADCAST_ADDR,
wantResponse: bool = False, wantResponse: bool = False,
channelIndex: int = 0, channelIndex: int = 0,
telemetryType: str = "device_metrics"
): ):
"""Send telemetry and optionally ask for a response""" """Send telemetry and optionally ask for a response"""
r = telemetry_pb2.Telemetry() r = telemetry_pb2.Telemetry()
if self.nodes is not None: if telemetryType == "environment_metrics":
node = next( r.environment_metrics.CopyFrom(telemetry_pb2.EnvironmentMetrics())
n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum elif telemetryType == "air_quality_metrics":
) r.air_quality_metrics.CopyFrom(telemetry_pb2.AirQualityMetrics())
if node is not None: elif telemetryType == "power_metrics":
metrics = node.get("deviceMetrics") r.power_metrics.CopyFrom(telemetry_pb2.PowerMetrics())
if metrics: else: # fall through to device metrics
batteryLevel = metrics.get("batteryLevel") if self.nodesByNum is not None:
if batteryLevel is not None: node = self.nodesByNum.get(self.localNode.nodeNum)
r.device_metrics.battery_level = batteryLevel if node is not None:
voltage = metrics.get("voltage") metrics = node.get("deviceMetrics")
if voltage is not None: if metrics:
r.device_metrics.voltage = voltage batteryLevel = metrics.get("batteryLevel")
channel_utilization = metrics.get("channelUtilization") if batteryLevel is not None:
if channel_utilization is not None: r.device_metrics.battery_level = batteryLevel
r.device_metrics.channel_utilization = channel_utilization voltage = metrics.get("voltage")
air_util_tx = metrics.get("airUtilTx") if voltage is not None:
if air_util_tx is not None: r.device_metrics.voltage = voltage
r.device_metrics.air_util_tx = air_util_tx channel_utilization = metrics.get("channelUtilization")
uptime_seconds = metrics.get("uptimeSeconds") if channel_utilization is not None:
if uptime_seconds is not None: r.device_metrics.channel_utilization = channel_utilization
r.device_metrics.uptime_seconds = uptime_seconds air_util_tx = metrics.get("airUtilTx")
if air_util_tx is not None:
r.device_metrics.air_util_tx = air_util_tx
uptime_seconds = metrics.get("uptimeSeconds")
if uptime_seconds is not None:
r.device_metrics.uptime_seconds = uptime_seconds
if wantResponse: if wantResponse:
onResponse = self.onResponseTelemetry onResponse = self.onResponseTelemetry
@@ -654,22 +661,32 @@ class MeshInterface: # pylint: disable=R0902
self._acknowledgment.receivedTelemetry = True self._acknowledgment.receivedTelemetry = True
telemetry = telemetry_pb2.Telemetry() telemetry = telemetry_pb2.Telemetry()
telemetry.ParseFromString(p["decoded"]["payload"]) telemetry.ParseFromString(p["decoded"]["payload"])
print("Telemetry received:") print("Telemetry received:")
if telemetry.device_metrics.battery_level is not None: # Check if the telemetry message has the device_metrics field
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%") # This is the original code that was the default for --request-telemetry and is kept for compatibility
if telemetry.device_metrics.voltage is not None: if telemetry.HasField("device_metrics"):
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V") if telemetry.device_metrics.battery_level is not None:
if telemetry.device_metrics.channel_utilization is not None: print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
print( if telemetry.device_metrics.voltage is not None:
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%" print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
) if telemetry.device_metrics.channel_utilization is not None:
if telemetry.device_metrics.air_util_tx is not None: print(
print( f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%" )
) if telemetry.device_metrics.air_util_tx is not None:
if telemetry.device_metrics.uptime_seconds is not None: print(
print(f"Uptime: {telemetry.device_metrics.uptime_seconds} s") f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%"
)
if telemetry.device_metrics.uptime_seconds is not None:
print(f"Uptime: {telemetry.device_metrics.uptime_seconds} s")
else:
# this is the new code if --request-telemetry <type> is used.
telemetry_dict = google.protobuf.json_format.MessageToDict(telemetry)
for key, value in telemetry_dict.items():
if key != "time": # protobuf includes a time field that we don't print for device_metrics.
print(f"{key}:")
for sub_key, sub_value in value.items():
print(f" {sub_key}: {sub_value}")
elif p["decoded"]["portnum"] == "ROUTING_APP": elif p["decoded"]["portnum"] == "ROUTING_APP":
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE": if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":

View File

File diff suppressed because one or more lines are too long

View File

@@ -369,6 +369,16 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
"""Pico2 with Waveshare Hat, same as Pico""" """Pico2 with Waveshare Hat, same as Pico"""
M5STACK_CORES3: _HardwareModel.ValueType # 80 M5STACK_CORES3: _HardwareModel.ValueType # 80
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/""" """M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/"""
SEEED_XIAO_S3: _HardwareModel.ValueType # 81
"""Seeed XIAO S3 DK"""
MS24SF1: _HardwareModel.ValueType # 82
"""
Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1
"""
TLORA_C6: _HardwareModel.ValueType # 83
"""
Lilygo TLora-C6 with the new ESP32-C6 MCU
"""
PRIVATE_HW: _HardwareModel.ValueType # 255 PRIVATE_HW: _HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -722,6 +732,16 @@ RPI_PICO2: HardwareModel.ValueType # 79
"""Pico2 with Waveshare Hat, same as Pico""" """Pico2 with Waveshare Hat, same as Pico"""
M5STACK_CORES3: HardwareModel.ValueType # 80 M5STACK_CORES3: HardwareModel.ValueType # 80
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/""" """M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/"""
SEEED_XIAO_S3: HardwareModel.ValueType # 81
"""Seeed XIAO S3 DK"""
MS24SF1: HardwareModel.ValueType # 82
"""
Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1
"""
TLORA_C6: HardwareModel.ValueType # 83
"""
Lilygo TLora-C6 with the new ESP32-C6 MCU
"""
PRIVATE_HW: HardwareModel.ValueType # 255 PRIVATE_HW: HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------

View File

File diff suppressed because one or more lines are too long

View File

@@ -250,13 +250,54 @@ class ModuleConfig(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _TriggerType:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TriggerTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ModuleConfig.DetectionSensorConfig._TriggerType.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
LOGIC_LOW: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 0
"""Event is triggered if pin is low"""
LOGIC_HIGH: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 1
"""Event is triggered if pin is high"""
FALLING_EDGE: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 2
"""Event is triggered when pin goes high to low"""
RISING_EDGE: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 3
"""Event is triggered when pin goes low to high"""
EITHER_EDGE_ACTIVE_LOW: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 4
"""Event is triggered on every pin state change, low is considered to be
"active"
"""
EITHER_EDGE_ACTIVE_HIGH: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 5
"""Event is triggered on every pin state change, high is considered to be
"active"
"""
class TriggerType(_TriggerType, metaclass=_TriggerTypeEnumTypeWrapper): ...
LOGIC_LOW: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 0
"""Event is triggered if pin is low"""
LOGIC_HIGH: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 1
"""Event is triggered if pin is high"""
FALLING_EDGE: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 2
"""Event is triggered when pin goes high to low"""
RISING_EDGE: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 3
"""Event is triggered when pin goes low to high"""
EITHER_EDGE_ACTIVE_LOW: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 4
"""Event is triggered on every pin state change, low is considered to be
"active"
"""
EITHER_EDGE_ACTIVE_HIGH: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 5
"""Event is triggered on every pin state change, high is considered to be
"active"
"""
ENABLED_FIELD_NUMBER: builtins.int ENABLED_FIELD_NUMBER: builtins.int
MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int
STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int
SEND_BELL_FIELD_NUMBER: builtins.int SEND_BELL_FIELD_NUMBER: builtins.int
NAME_FIELD_NUMBER: builtins.int NAME_FIELD_NUMBER: builtins.int
MONITOR_PIN_FIELD_NUMBER: builtins.int MONITOR_PIN_FIELD_NUMBER: builtins.int
DETECTION_TRIGGERED_HIGH_FIELD_NUMBER: builtins.int DETECTION_TRIGGER_TYPE_FIELD_NUMBER: builtins.int
USE_PULLUP_FIELD_NUMBER: builtins.int USE_PULLUP_FIELD_NUMBER: builtins.int
enabled: builtins.bool enabled: builtins.bool
""" """
@@ -264,13 +305,15 @@ class ModuleConfig(google.protobuf.message.Message):
""" """
minimum_broadcast_secs: builtins.int minimum_broadcast_secs: builtins.int
""" """
Interval in seconds of how often we can send a message to the mesh when a state change is detected Interval in seconds of how often we can send a message to the mesh when a
trigger event is detected
""" """
state_broadcast_secs: builtins.int state_broadcast_secs: builtins.int
""" """
Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes Interval in seconds of how often we should send a message to the mesh
When set to 0, only state changes will be broadcasted with the current state regardless of trigger events When set to 0, only
Works as a sort of status heartbeat for peace of mind trigger events will be broadcasted Works as a sort of status heartbeat
for peace of mind
""" """
send_bell: builtins.bool send_bell: builtins.bool
""" """
@@ -287,10 +330,9 @@ class ModuleConfig(google.protobuf.message.Message):
""" """
GPIO pin to monitor for state changes GPIO pin to monitor for state changes
""" """
detection_triggered_high: builtins.bool detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType
""" """
Whether or not the GPIO pin state detection is triggered on HIGH (1) The type of trigger event to be used
Otherwise LOW (0)
""" """
use_pullup: builtins.bool use_pullup: builtins.bool
""" """
@@ -306,10 +348,10 @@ class ModuleConfig(google.protobuf.message.Message):
send_bell: builtins.bool = ..., send_bell: builtins.bool = ...,
name: builtins.str = ..., name: builtins.str = ...,
monitor_pin: builtins.int = ..., monitor_pin: builtins.int = ...,
detection_triggered_high: builtins.bool = ..., detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType = ...,
use_pullup: builtins.bool = ..., use_pullup: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["detection_triggered_high", b"detection_triggered_high", "enabled", b"enabled", "minimum_broadcast_secs", b"minimum_broadcast_secs", "monitor_pin", b"monitor_pin", "name", b"name", "send_bell", b"send_bell", "state_broadcast_secs", b"state_broadcast_secs", "use_pullup", b"use_pullup"]) -> None: ... def ClearField(self, field_name: typing.Literal["detection_trigger_type", b"detection_trigger_type", "enabled", b"enabled", "minimum_broadcast_secs", b"minimum_broadcast_secs", "monitor_pin", b"monitor_pin", "name", b"name", "send_bell", b"send_bell", "state_broadcast_secs", b"state_broadcast_secs", "use_pullup", b"use_pullup"]) -> None: ...
@typing.final @typing.final
class AudioConfig(google.protobuf.message.Message): class AudioConfig(google.protobuf.message.Message):

View File

@@ -1,5 +1,6 @@
""" Serial interface class """ Serial interface class
""" """
# pylint: disable=R0917
import logging import logging
import platform import platform
import time import time

View File

@@ -1,6 +1,7 @@
""" Supported Meshtastic Devices - This is a class and collection of Meshtastic devices. """ Supported Meshtastic Devices - This is a class and collection of Meshtastic devices.
It is used for auto detection as to which device might be connected. It is used for auto detection as to which device might be connected.
""" """
# pylint: disable=R0917
# Goal is to detect which device and port to use from the supported devices # Goal is to detect which device and port to use from the supported devices
# without installing any libraries that are not currently in the python meshtastic library # without installing any libraries that are not currently in the python meshtastic library

View File

@@ -1,5 +1,6 @@
"""TCPInterface class for interfacing with http endpoint """TCPInterface class for interfacing with http endpoint
""" """
# pylint: disable=R0917
import logging import logging
import socket import socket
from typing import Optional from typing import Optional

View File

@@ -1,5 +1,5 @@
"""Meshtastic unit tests for __main__.py""" """Meshtastic unit tests for __main__.py"""
# pylint: disable=C0302,W0613 # pylint: disable=C0302,W0613,R0917
import logging import logging
import os import os

View File

@@ -1,4 +1,5 @@
"""Meshtastic unit tests for serial_interface.py""" """Meshtastic unit tests for serial_interface.py"""
# pylint: disable=R0917
import re import re
from unittest.mock import mock_open, patch from unittest.mock import mock_open, patch

2827
poetry.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
[tool.poetry] [tool.poetry]
name = "meshtastic" name = "meshtastic"
version = "2.5.2" version = "2.5.3"
description = "Python API & client shell for talking to Meshtastic devices" description = "Python API & client shell for talking to Meshtastic devices"
authors = ["Meshtastic Developers <contact@meshtastic.org>"] authors = ["Meshtastic Developers <contact@meshtastic.org>"]
license = "GPL-3.0-only" license = "GPL-3.0-only"
readme = "README.md" readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9,<3.13" # 3.9 is needed for pandas, bleak requires a max of 3.13 for some reason python = "^3.9,<3.14" # 3.9 is needed for pandas, bleak requires <3.14
pyserial = "^3.5" pyserial = "^3.5"
protobuf = ">=5.26.0" protobuf = ">=5.26.0"
dotmap = "^1.3.30" dotmap = "^1.3.30"
@@ -19,13 +19,14 @@ requests = "^2.31.0"
pyparsing = "^3.1.2" pyparsing = "^3.1.2"
pyyaml = "^6.0.1" pyyaml = "^6.0.1"
pypubsub = "^4.0.3" pypubsub = "^4.0.3"
bleak = "^0.21.1" bleak = "^0.22.3"
packaging = "^24.0" packaging = "^24.0"
print-color = "^0.4.6" print-color = "^0.4.6"
dash = { version = "^2.17.1", optional = true } dash = { version = "^2.17.1", optional = true }
pytap2 = { version = "^2.3.0", optional = true } pytap2 = { version = "^2.3.0", optional = true }
dash-bootstrap-components = { version = "^1.6.0", optional = true } dash-bootstrap-components = { version = "^1.6.0", optional = true }
pandas = { version = "^2.2.2", optional = true } pandas = { version = "^2.2.2", optional = true }
pandas-stubs = { version = "^2.2.2.240603", optional = true }
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
hypothesis = "^6.103.2" hypothesis = "^6.103.2"
@@ -43,7 +44,6 @@ types-requests = "^2.31.0.20240406"
types-setuptools = "^69.5.0.20240423" types-setuptools = "^69.5.0.20240423"
types-pyyaml = "^6.0.12.20240311" types-pyyaml = "^6.0.12.20240311"
pyarrow-stubs = "^10.0.1.7" pyarrow-stubs = "^10.0.1.7"
pandas-stubs = "^2.2.2.240603"
[tool.poetry.group.powermon] [tool.poetry.group.powermon]
optional = true optional = true