Compare commits

...

27 Commits
2.5.1 ... 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
github-actions
ece6286d82 bump version to 2.5.2 2024-10-12 16:31:48 +00:00
Ian McEwen
0bb4b31b6a Merge pull request #679 from ianmcorvidae/argument-groups
Add a number of argument groups to organize the help output
2024-10-03 19:45:13 -07:00
Ian McEwen
915066e0af add metavars for a bunch of arguments for nicer docs 2024-10-03 19:45:03 -07:00
Ian McEwen
6be3969577 Add a number of argument groups to organize the help output 2024-10-01 18:10:14 -07:00
Ian McEwen
b73cc1f499 Make it so wantconfig isn't a 1 in 4294967296 lottery for getting no nodes 2024-10-01 14:00:06 -07:00
Ian McEwen
65305af184 protobufs/alpha version: 2.5.2 2024-09-29 14:34:53 -07:00
16 changed files with 2197 additions and 1764 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:
@@ -1228,7 +1242,7 @@ def common():
def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add connection specifiation arguments""" """Add connection specification arguments"""
outer = parser.add_argument_group( outer = parser.add_argument_group(
"Connection", "Connection",
@@ -1264,64 +1278,92 @@ def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParse
const="any", const="any",
) )
outer.add_argument(
"--ble-scan",
help="Scan for Meshtastic BLE devices that may be available to connect to",
action="store_true",
)
return parser return parser
def addSelectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
def initParser(): """Add node/channel specification arguments"""
"""Initialize the command line argument parsing.""" group = parser.add_argument_group(
parser = mt_config.parser "Selection",
args = mt_config.args "Arguments that select channels to use, destination nodes, etc."
# The "Help" group includes the help option and other informational stuff about the CLI itself
outerHelpGroup = parser.add_argument_group("Help")
helpGroup = outerHelpGroup.add_mutually_exclusive_group()
helpGroup.add_argument(
"-h", "--help", action="help", help="show this help message and exit"
) )
the_version = get_active_version() group.add_argument(
helpGroup.add_argument("--version", action="version", version=f"{the_version}") "--dest",
help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate",
helpGroup.add_argument( default=None,
"--support", metavar="!xxxxxxxx",
action="store_true",
help="Show support info (useful when troubleshooting an issue)",
) )
# Connection arguments to indicate a device to connect to group.add_argument(
parser = addConnectionArgs(parser) "--ch-index",
help="Set the specified channel index for channel-specific commands. Channels start at 0 (0 is the PRIMARY channel).",
action="store",
metavar="INDEX",
)
# Arguments concerning viewing and setting configuration return parser
# Arguments for sending or requesting things from the local device def addImportExportArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add import/export config arguments"""
group = parser.add_argument_group(
"Import/Export",
"Arguments that concern importing and exporting configuration of Meshtastic devices",
)
# Arguments for sending or requesting things from the mesh
# All the rest of the arguments
group = parser.add_argument_group("optional arguments")
group.add_argument( group.add_argument(
"--configure", "--configure",
help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.", help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.",
action="append", action="append",
) )
group.add_argument( group.add_argument(
"--export-config", "--export-config",
help="Export the configuration in yaml(.yml) format.", help="Export the configuration in yaml(.yml) format.",
action="store_true", action="store_true",
) )
return parser
group.add_argument( def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"--seriallog", """Add arguments to do with configuring a device"""
help="Log device serial output to either 'none' or a filename to append to. Defaults to 'stdout' if no filename specified.",
nargs="?", group = parser.add_argument_group(
const="stdout", "Configuration",
default=None, "Arguments that concern general configuration of Meshtastic devices",
) )
group.add_argument( group.add_argument(
"--info", "--get",
help="Read and display the radio config information", help=(
"Get a preferences field. Use an invalid field such as '0' to get a list of all fields."
" Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')"
),
nargs=1,
action="append",
metavar="FIELD"
)
group.add_argument(
"--set",
help="Set a preferences field. Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')",
nargs=2,
action="append",
metavar=("FIELD", "VALUE"),
)
group.add_argument(
"--begin-edit",
help="Tell the node to open a transaction to edit settings",
action="store_true",
)
group.add_argument(
"--commit-edit",
help="Tell the node to commit open settings transaction",
action="store_true", action="store_true",
) )
@@ -1331,16 +1373,119 @@ def initParser():
action="store_true", action="store_true",
) )
group.add_argument(
"--set-canned-message",
help="Set the canned messages plugin message (up to 200 characters).",
action="store",
)
group.add_argument( group.add_argument(
"--get-ringtone", help="Show the stored ringtone", action="store_true" "--get-ringtone", help="Show the stored ringtone", action="store_true"
) )
group.add_argument( group.add_argument(
"--nodes", "--set-ringtone",
help="Print Node List in a pretty formatted table", help="Set the Notification Ringtone (up to 230 characters).",
action="store",
metavar="RINGTONE",
)
group.add_argument(
"--ch-vlongslow",
help="Change to the very long-range and slow modem preset",
action="store_true", action="store_true",
) )
group.add_argument(
"--ch-longslow",
help="Change to the long-range and slow modem preset",
action="store_true",
)
group.add_argument(
"--ch-longfast",
help="Change to the long-range and fast modem preset",
action="store_true",
)
group.add_argument(
"--ch-medslow",
help="Change to the med-range and slow modem preset",
action="store_true",
)
group.add_argument(
"--ch-medfast",
help="Change to the med-range and fast modem preset",
action="store_true",
)
group.add_argument(
"--ch-shortslow",
help="Change to the short-range and slow modem preset",
action="store_true",
)
group.add_argument(
"--ch-shortfast",
help="Change to the short-range and fast modem preset",
action="store_true",
)
group.add_argument("--set-owner", help="Set device owner name", action="store")
group.add_argument(
"--set-owner-short", help="Set device owner short name", action="store"
)
group.add_argument(
"--set-ham", help="Set licensed Ham ID and turn off encryption", action="store"
)
group.add_argument("--seturl", help="Set a channel URL", action="store")
return parser
def addChannelConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add arguments to do with configuring channels"""
group = parser.add_argument_group(
"Channel Configuration",
"Arguments that concern configuration of channels",
)
group.add_argument(
"--ch-add",
help="Add a secondary channel, you must specify a channel name",
default=None,
)
group.add_argument(
"--ch-del", help="Delete the ch-index channel", action="store_true"
)
group.add_argument(
"--ch-set",
help=(
"Set a channel parameter. To see channel settings available:'--ch-set all all --ch-index 0'. "
"Can set the 'psk' using this command. To disable encryption on primary channel:'--ch-set psk none --ch-index 0'. "
"To set encryption with a new random key on second channel:'--ch-set psk random --ch-index 1'. "
"To set encryption back to the default:'--ch-set psk default --ch-index 0'. To set encryption with your "
"own key: '--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --ch-index 0'."
),
nargs=2,
action="append",
metavar=("FIELD", "VALUE"),
)
group.add_argument(
"--channel-fetch-attempts",
help=("Attempt to retrieve channel settings for --ch-set this many times before giving up. Default %(default)s."),
default=3,
type=int,
metavar="ATTEMPTS",
)
group.add_argument( group.add_argument(
"--qr", "--qr",
help=( help=(
@@ -1356,44 +1501,9 @@ def initParser():
action="store_true", action="store_true",
) )
group.add_argument(
"--get",
help=(
"Get a preferences field. Use an invalid field such as '0' to get a list of all fields."
" Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')"
),
nargs=1,
action="append",
)
group.add_argument(
"--set",
help="Set a preferences field. Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')",
nargs=2,
action="append",
)
group.add_argument("--seturl", help="Set a channel URL", action="store")
group.add_argument(
"--ch-index",
help="Set the specified channel index. Channels start at 0 (0 is the PRIMARY channel).",
action="store",
)
group.add_argument(
"--ch-add",
help="Add a secondary channel, you must specify a channel name",
default=None,
)
group.add_argument(
"--ch-del", help="Delete the ch-index channel", action="store_true"
)
group.add_argument( group.add_argument(
"--ch-enable", "--ch-enable",
help="Enable the specified channel", help="Enable the specified channel. Use --ch-add instead whenever possible.",
action="store_true", action="store_true",
dest="ch_enable", dest="ch_enable",
default=False, default=False,
@@ -1402,231 +1512,21 @@ def initParser():
# Note: We are doing a double negative here (Do we want to disable? If ch_disable==True, then disable.) # Note: We are doing a double negative here (Do we want to disable? If ch_disable==True, then disable.)
group.add_argument( group.add_argument(
"--ch-disable", "--ch-disable",
help="Disable the specified channel", help="Disable the specified channel Use --ch-del instead whenever possible.",
action="store_true", action="store_true",
dest="ch_disable", dest="ch_disable",
default=False, default=False,
) )
group.add_argument( return parser
"--ch-set",
help=(
"Set a channel parameter. To see channel settings available:'--ch-set all all --ch-index 0'. "
"Can set the 'psk' using this command. To disable encryption on primary channel:'--ch-set psk none --ch-index 0'. "
"To set encryption with a new random key on second channel:'--ch-set psk random --ch-index 1'. "
"To set encryption back to the default:'--ch-set psk default --ch-index 0'. To set encryption with your "
"own key: '--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --ch-index 0'."
),
nargs=2,
action="append",
)
group.add_argument( def addPositionConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"--channel-fetch-attempts", """Add arguments to do with fixed positions and position config"""
help=("Attempt to retrieve channel settings for --ch-set this many times before giving up."),
default=3,
type=int,
)
group.add_argument( group = parser.add_argument_group(
"--timeout", "Position Configuration",
help="How long to wait for replies", "Arguments that modify fixed position and other position-related configuration.",
default=300,
type=int,
) )
group.add_argument(
"--ch-vlongslow",
help="Change to the very long-range and slow channel",
action="store_true",
)
group.add_argument(
"--ch-longslow",
help="Change to the long-range and slow channel",
action="store_true",
)
group.add_argument(
"--ch-longfast",
help="Change to the long-range and fast channel",
action="store_true",
)
group.add_argument(
"--ch-medslow",
help="Change to the med-range and slow channel",
action="store_true",
)
group.add_argument(
"--ch-medfast",
help="Change to the med-range and fast channel",
action="store_true",
)
group.add_argument(
"--ch-shortslow",
help="Change to the short-range and slow channel",
action="store_true",
)
group.add_argument(
"--ch-shortfast",
help="Change to the short-range and fast channel",
action="store_true",
)
group.add_argument("--set-owner", help="Set device owner name", action="store")
group.add_argument(
"--set-owner-short", help="Set device owner short name", action="store"
)
group.add_argument(
"--set-canned-message",
help="Set the canned messages plugin message (up to 200 characters).",
action="store",
)
group.add_argument(
"--set-ringtone",
help="Set the Notification Ringtone (up to 230 characters).",
action="store",
)
group.add_argument(
"--set-ham", help="Set licensed Ham ID and turn off encryption", action="store"
)
group.add_argument(
"--dest",
help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate",
default=None,
)
group.add_argument(
"--sendtext",
help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.",
)
group.add_argument(
"--traceroute",
help="Traceroute from connected node to a destination. "
"You need pass the destination ID as argument, like "
"this: '--traceroute !ba4bf9d0' "
"Only nodes that have the encryption key can be traced.",
)
group.add_argument(
"--request-telemetry",
help="Request telemetry from a node. "
"You need to pass the destination ID as argument with '--dest'. "
"For repeaters, the nodeNum is required.",
action="store_true",
)
group.add_argument(
"--request-position",
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",
)
group.add_argument(
"--ack",
help="Use in combination with --sendtext to wait for an acknowledgment.",
action="store_true",
)
group.add_argument(
"--reboot", help="Tell the destination node to reboot", action="store_true"
)
group.add_argument(
"--reboot-ota",
help="Tell the destination node to reboot into factory firmware (ESP32)",
action="store_true",
)
group.add_argument(
"--enter-dfu",
help="Tell the destination node to enter DFU mode (NRF52)",
action="store_true",
)
group.add_argument(
"--shutdown", help="Tell the destination node to shutdown", action="store_true"
)
group.add_argument(
"--device-metadata",
help="Get the device metadata from the node",
action="store_true",
)
group.add_argument(
"--begin-edit",
help="Tell the node to open a transaction to edit settings",
action="store_true",
)
group.add_argument(
"--commit-edit",
help="Tell the node to commit open settings transaction",
action="store_true",
)
group.add_argument(
"--factory-reset", "--factory-reset-config",
help="Tell the destination node to install the default config, preserving BLE bonds & PKI keys",
action="store_true",
)
group.add_argument(
"--factory-reset-device",
help="Tell the destination node to install the default config and clear BLE bonds & PKI keys",
action="store_true",
)
group.add_argument(
"--remove-node",
help="Tell the destination node to remove a specific node from its DB, by node number or ID",
)
group.add_argument(
"--reset-nodedb",
help="Tell the destination node to clear its list of nodes",
action="store_true",
)
group.add_argument(
"--set-time",
help="Set the time to the provided unix epoch timestamp, or the system's current time if omitted or 0.",
action="store",
type=int,
nargs="?",
default=None,
const=0,
)
group.add_argument(
"--reply", help="Reply to received messages", action="store_true"
)
group.add_argument(
"--no-time",
help="Deprecated. Retained for backwards compatibility in scripts, but is a no-op.",
action="store_true",
)
group.add_argument(
"--no-nodes",
help="Request that the node not send node info to the client. "
"Will break things that depend on the nodedb, but will speed up startup. Requires 2.3.11+ firmware.",
action="store_true",
)
group.add_argument( group.add_argument(
"--setalt", "--setalt",
help="Set device altitude in meters (allows use without GPS), and enable fixed position. " help="Set device altitude in meters (allows use without GPS), and enable fixed position. "
@@ -1659,6 +1559,221 @@ def initParser():
nargs="*", nargs="*",
action="store", action="store",
) )
return parser
def addLocalActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add arguments concerning local-only information & actions"""
group = parser.add_argument_group(
"Local Actions",
"Arguments that take actions or request information from the local node only.",
)
group.add_argument(
"--info",
help="Read and display the radio config information",
action="store_true",
)
group.add_argument(
"--nodes",
help="Print Node List in a pretty formatted table",
action="store_true",
)
return parser
def addRemoteActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add arguments concerning information & actions that may interact with the mesh"""
group = parser.add_argument_group(
"Remote Actions",
"Arguments that take actions or request information from either the local node or remote nodes via the mesh.",
)
group.add_argument(
"--sendtext",
help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.",
metavar="TEXT",
)
group.add_argument(
"--traceroute",
help="Traceroute from connected node to a destination. "
"You need pass the destination ID as argument, like "
"this: '--traceroute !ba4bf9d0' "
"Only nodes with a shared channel can be traced.",
metavar="!xxxxxxxx",
)
group.add_argument(
"--request-telemetry",
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'. "
"For repeaters, the nodeNum is required.",
action="store",
nargs="?",
default=None,
const="device",
metavar="TYPE",
)
group.add_argument(
"--request-position",
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",
)
group.add_argument(
"--reply", help="Reply to received messages", action="store_true"
)
return parser
def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add arguments concerning admin actions that may interact with the mesh"""
outer = parser.add_argument_group(
"Remote Admin Actions",
"Arguments that interact with local node or remote nodes via the mesh, requiring admin access.",
)
group = outer.add_mutually_exclusive_group()
group.add_argument(
"--reboot", help="Tell the destination node to reboot", action="store_true"
)
group.add_argument(
"--reboot-ota",
help="Tell the destination node to reboot into factory firmware (ESP32)",
action="store_true",
)
group.add_argument(
"--enter-dfu",
help="Tell the destination node to enter DFU mode (NRF52)",
action="store_true",
)
group.add_argument(
"--shutdown", help="Tell the destination node to shutdown", action="store_true"
)
group.add_argument(
"--device-metadata",
help="Get the device metadata from the node",
action="store_true",
)
group.add_argument(
"--factory-reset", "--factory-reset-config",
help="Tell the destination node to install the default config, preserving BLE bonds & PKI keys",
action="store_true",
)
group.add_argument(
"--factory-reset-device",
help="Tell the destination node to install the default config and clear BLE bonds & PKI keys",
action="store_true",
)
group.add_argument(
"--remove-node",
help="Tell the destination node to remove a specific node from its DB, by node number or ID",
metavar="!xxxxxxxx"
)
group.add_argument(
"--reset-nodedb",
help="Tell the destination node to clear its list of nodes",
action="store_true",
)
group.add_argument(
"--set-time",
help="Set the time to the provided unix epoch timestamp, or the system's current time if omitted or 0.",
action="store",
type=int,
nargs="?",
default=None,
const=0,
metavar="TIMESTAMP",
)
return parser
def initParser():
"""Initialize the command line argument parsing."""
parser = mt_config.parser
args = mt_config.args
# The "Help" group includes the help option and other informational stuff about the CLI itself
outerHelpGroup = parser.add_argument_group("Help")
helpGroup = outerHelpGroup.add_mutually_exclusive_group()
helpGroup.add_argument(
"-h", "--help", action="help", help="show this help message and exit"
)
the_version = get_active_version()
helpGroup.add_argument("--version", action="version", version=f"{the_version}")
helpGroup.add_argument(
"--support",
action="store_true",
help="Show support info (useful when troubleshooting an issue)",
)
# Connection arguments to indicate a device to connect to
parser = addConnectionArgs(parser)
# Selection arguments to denote nodes and channels to use
parser = addSelectionArgs(parser)
# Arguments concerning viewing and setting configuration
parser = addImportExportArgs(parser)
parser = addConfigArgs(parser)
parser = addPositionConfigArgs(parser)
parser = addChannelConfigArgs(parser)
# Arguments for sending or requesting things from the local device
parser = addLocalActionArgs(parser)
# Arguments for sending or requesting things from the mesh
parser = addRemoteActionArgs(parser)
parser = addRemoteAdminArgs(parser)
# All the rest of the arguments
group = parser.add_argument_group("Miscellaneous arguments")
group.add_argument(
"--seriallog",
help="Log device serial output to either 'none' or a filename to append to. Defaults to '%(const)s' if no filename specified.",
nargs="?",
const="stdout",
default=None,
metavar="LOG_DESTINATION",
)
group.add_argument(
"--ack",
help="Use in combination with compatible actions (e.g. --sendtext) to wait for an acknowledgment.",
action="store_true",
)
group.add_argument(
"--timeout",
help="How long to wait for replies. Default %(default)ss.",
default=300,
type=int,
metavar="SECONDS",
)
group.add_argument(
"--no-nodes",
help="Request that the node not send node info to the client. "
"Will break things that depend on the nodedb, but will speed up startup. Requires 2.3.11+ firmware.",
action="store_true",
)
group.add_argument( group.add_argument(
"--debug", help="Show API library debug log messages", action="store_true" "--debug", help="Show API library debug log messages", action="store_true"
@@ -1670,6 +1785,33 @@ 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",
metavar="SECONDS",
)
group.add_argument(
"--noproto",
help="Don't start the API, just function as a dumb serial terminal.",
action="store_true",
)
group.add_argument(
"--listen",
help="Just stay open and listen to the protobuf stream. Enables debug logging.",
action="store_true",
)
group.add_argument(
"--no-time",
help="Deprecated. Retained for backwards compatibility in scripts, but is a no-op.",
action="store_true",
)
power_group = parser.add_argument_group( power_group = parser.add_argument_group(
"Power Testing", "Options for power testing/logging." "Power Testing", "Options for power testing/logging."
) )
@@ -1718,37 +1860,12 @@ 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",
) )
group.add_argument(
"--ble-scan",
help="Scan for Meshtastic BLE devices",
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.",
action="store_true",
)
group.add_argument(
"--listen",
help="Just stay open and listen to the protobuf stream. Enables debug logging.",
action="store_true",
)
remoteHardwareArgs = parser.add_argument_group( remoteHardwareArgs = parser.add_argument_group(
"Remote Hardware", "Arguments related to the Remote Hardware module" "Remote Hardware", "Arguments related to the Remote Hardware module"

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":
@@ -914,6 +931,8 @@ class MeshInterface: # pylint: disable=R0902
startConfig = mesh_pb2.ToRadio() startConfig = mesh_pb2.ToRadio()
if self.configId is None or not self.noNodes: if self.configId is None or not self.noNodes:
self.configId = random.randint(0, 0xFFFFFFFF) self.configId = random.randint(0, 0xFFFFFFFF)
if self.configId == NODELESS_WANT_CONFIG_ID:
self.configId = self.configId + 1
startConfig.want_config_id = self.configId startConfig.want_config_id = self.configId
self._sendToRadio(startConfig) self._sendToRadio(startConfig)

View File

File diff suppressed because one or more lines are too long

View File

@@ -129,6 +129,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
Heltec HRU-3601: https://heltec.org/project/hru-3601/ Heltec HRU-3601: https://heltec.org/project/hru-3601/
""" """
HELTEC_WIRELESS_BRIDGE: _HardwareModel.ValueType # 24
"""
Heltec Wireless Bridge
"""
STATION_G1: _HardwareModel.ValueType # 25 STATION_G1: _HardwareModel.ValueType # 25
""" """
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
@@ -201,7 +205,7 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
M5STACK: _HardwareModel.ValueType # 42 M5STACK: _HardwareModel.ValueType # 42
""" """
M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, 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/
""" """
HELTEC_V3: _HardwareModel.ValueType # 43 HELTEC_V3: _HardwareModel.ValueType # 43
""" """
@@ -359,8 +363,22 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
^^^ short A0 to switch to I2C address 0x3C ^^^ short A0 to switch to I2C address 0x3C
""" """
M5STACK_COREBASIC: _HardwareModel.ValueType # 77 M5STACK_COREBASIC: _HardwareModel.ValueType # 77
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, 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/"""
M5STACK_CORE2: _HardwareModel.ValueType # 78 M5STACK_CORE2: _HardwareModel.ValueType # 78
RPI_PICO2: _HardwareModel.ValueType # 79
"""Pico2 with Waveshare Hat, same as Pico"""
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/"""
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
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -474,6 +492,10 @@ HELTEC_HRU_3601: HardwareModel.ValueType # 23
""" """
Heltec HRU-3601: https://heltec.org/project/hru-3601/ Heltec HRU-3601: https://heltec.org/project/hru-3601/
""" """
HELTEC_WIRELESS_BRIDGE: HardwareModel.ValueType # 24
"""
Heltec Wireless Bridge
"""
STATION_G1: HardwareModel.ValueType # 25 STATION_G1: HardwareModel.ValueType # 25
""" """
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
@@ -546,7 +568,7 @@ Custom Disaster Radio esp32 v3 device https://github.com/sudomesh/disaster-radio
""" """
M5STACK: HardwareModel.ValueType # 42 M5STACK: HardwareModel.ValueType # 42
""" """
M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, 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/
""" """
HELTEC_V3: HardwareModel.ValueType # 43 HELTEC_V3: HardwareModel.ValueType # 43
""" """
@@ -704,8 +726,22 @@ https://www.adafruit.com/product/938
^^^ short A0 to switch to I2C address 0x3C ^^^ short A0 to switch to I2C address 0x3C
""" """
M5STACK_COREBASIC: HardwareModel.ValueType # 77 M5STACK_COREBASIC: HardwareModel.ValueType # 77
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, 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/"""
M5STACK_CORE2: HardwareModel.ValueType # 78 M5STACK_CORE2: HardwareModel.ValueType # 78
RPI_PICO2: HardwareModel.ValueType # 79
"""Pico2 with Waveshare Hat, same as Pico"""
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/"""
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.1" 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