mirror of
https://github.com/meshtastic/python.git
synced 2025-12-26 17:37:51 -05:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f08ec1885b | ||
|
|
feca49faed | ||
|
|
dfaf1a275d | ||
|
|
da7fa31805 | ||
|
|
12fd29b203 | ||
|
|
a43dd201ba | ||
|
|
a64a9d203a | ||
|
|
10136962d7 | ||
|
|
3afb294f9b | ||
|
|
ee405fec41 | ||
|
|
3eabaf91d0 | ||
|
|
78b92cecc9 | ||
|
|
7088b90514 | ||
|
|
2ae81f8602 | ||
|
|
923f5e82d0 | ||
|
|
05731128fa | ||
|
|
0523d4c94f | ||
|
|
90e901de79 | ||
|
|
6606851135 | ||
|
|
33fecbd74d | ||
|
|
6b9db7abd9 | ||
|
|
ece6286d82 | ||
|
|
0bb4b31b6a | ||
|
|
915066e0af | ||
|
|
6be3969577 | ||
|
|
b73cc1f499 | ||
|
|
65305af184 |
@@ -2,41 +2,44 @@
|
||||
# A library for the Meshtastic Client API
|
||||
|
||||
Primary interfaces: SerialInterface, TCPInterface, BLEInterface
|
||||
|
||||
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
|
||||
|
||||
Source code on [github](https://github.com/meshtastic/python)
|
||||
|
||||
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.
|
||||
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
|
||||
- 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
|
||||
- `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)
|
||||
- `localNode` - Pointer to a node object for the local node
|
||||
|
||||
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.
|
||||
- channels - The node's channels, keyed by index.
|
||||
|
||||
- `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.
|
||||
- `channels` - The node's channels, keyed by index.
|
||||
|
||||
# Published PubSub topics
|
||||
|
||||
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
|
||||
topics:
|
||||
|
||||
- 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.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
|
||||
- `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.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".
|
||||
- meshtastic.receive.position(packet)
|
||||
- meshtastic.receive.user(packet)
|
||||
- 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.log.line(line) - a raw unparsed log line from the radio
|
||||
- `meshtastic.receive.position(packet)`
|
||||
- `meshtastic.receive.user(packet)`
|
||||
- `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.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
|
||||
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
|
||||
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but 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
|
||||
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
|
||||
unicode scripts they can be different.
|
||||
|
||||
# Example Usage
|
||||
@@ -194,13 +197,31 @@ def _onNodeInfoReceive(iface, asDict):
|
||||
def _onTelemetryReceive(iface, asDict):
|
||||
"""Automatically update device metrics on received packets"""
|
||||
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
|
||||
deviceMetrics = asDict.get("decoded", {}).get("telemetry", {}).get("deviceMetrics")
|
||||
if "from" in asDict and deviceMetrics is not None:
|
||||
node = iface._getOrCreateByNum(asDict["from"])
|
||||
newMetrics = node.get("deviceMetrics", {})
|
||||
newMetrics.update(deviceMetrics)
|
||||
logging.debug(f"updating metrics for {asDict['from']} to {newMetrics}")
|
||||
node["deviceMetrics"] = newMetrics
|
||||
if "from" not in asDict:
|
||||
return
|
||||
|
||||
toUpdate = None
|
||||
|
||||
telemetry = asDict.get("decoded", {}).get("telemetry", {})
|
||||
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):
|
||||
if "from" in asDict:
|
||||
|
||||
@@ -475,13 +475,22 @@ def onConnected(interface):
|
||||
else:
|
||||
channelIndex = mt_config.channel_index or 0
|
||||
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(
|
||||
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(
|
||||
destinationId=args.dest,
|
||||
wantResponse=True,
|
||||
channelIndex=channelIndex,
|
||||
telemetryType=telemType,
|
||||
)
|
||||
|
||||
if args.request_position:
|
||||
@@ -622,19 +631,15 @@ def onConnected(interface):
|
||||
|
||||
if "alt" in configuration["location"]:
|
||||
alt = int(configuration["location"]["alt"] or 0)
|
||||
localConfig.position.fixed_position = True
|
||||
print(f"Fixing altitude at {alt} meters")
|
||||
if "lat" in configuration["location"]:
|
||||
lat = float(configuration["location"]["lat"] or 0)
|
||||
localConfig.position.fixed_position = True
|
||||
print(f"Fixing latitude at {lat} degrees")
|
||||
if "lon" in configuration["location"]:
|
||||
lon = float(configuration["location"]["lon"] or 0)
|
||||
localConfig.position.fixed_position = True
|
||||
print(f"Fixing longitude at {lon} degrees")
|
||||
print("Setting device position")
|
||||
interface.sendPosition(lat, lon, alt)
|
||||
interface.localNode.writeConfig("position")
|
||||
interface.localNode.setFixedPosition(lat, lon, alt)
|
||||
|
||||
if "config" in configuration:
|
||||
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]
|
||||
else:
|
||||
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:
|
||||
configObj["config"] = config
|
||||
else:
|
||||
@@ -1228,7 +1242,7 @@ def common():
|
||||
|
||||
|
||||
def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"""Add connection specifiation arguments"""
|
||||
"""Add connection specification arguments"""
|
||||
|
||||
outer = parser.add_argument_group(
|
||||
"Connection",
|
||||
@@ -1264,64 +1278,92 @@ def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParse
|
||||
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
|
||||
|
||||
|
||||
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"
|
||||
def addSelectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"""Add node/channel specification arguments"""
|
||||
group = parser.add_argument_group(
|
||||
"Selection",
|
||||
"Arguments that select channels to use, destination nodes, etc."
|
||||
)
|
||||
|
||||
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)",
|
||||
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,
|
||||
metavar="!xxxxxxxx",
|
||||
)
|
||||
|
||||
# Connection arguments to indicate a device to connect to
|
||||
parser = addConnectionArgs(parser)
|
||||
group.add_argument(
|
||||
"--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(
|
||||
"--configure",
|
||||
help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.",
|
||||
action="append",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--export-config",
|
||||
help="Export the configuration in yaml(.yml) format.",
|
||||
action="store_true",
|
||||
)
|
||||
return parser
|
||||
|
||||
group.add_argument(
|
||||
"--seriallog",
|
||||
help="Log device serial output to either 'none' or a filename to append to. Defaults to 'stdout' if no filename specified.",
|
||||
nargs="?",
|
||||
const="stdout",
|
||||
default=None,
|
||||
def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"""Add arguments to do with configuring a device"""
|
||||
|
||||
group = parser.add_argument_group(
|
||||
"Configuration",
|
||||
"Arguments that concern general configuration of Meshtastic devices",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--info",
|
||||
help="Read and display the radio config information",
|
||||
"--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",
|
||||
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",
|
||||
)
|
||||
|
||||
@@ -1331,16 +1373,119 @@ def initParser():
|
||||
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(
|
||||
"--get-ringtone", help="Show the stored ringtone", action="store_true"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--nodes",
|
||||
help="Print Node List in a pretty formatted table",
|
||||
"--set-ringtone",
|
||||
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",
|
||||
)
|
||||
|
||||
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(
|
||||
"--qr",
|
||||
help=(
|
||||
@@ -1356,44 +1501,9 @@ def initParser():
|
||||
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(
|
||||
"--ch-enable",
|
||||
help="Enable the specified channel",
|
||||
help="Enable the specified channel. Use --ch-add instead whenever possible.",
|
||||
action="store_true",
|
||||
dest="ch_enable",
|
||||
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.)
|
||||
group.add_argument(
|
||||
"--ch-disable",
|
||||
help="Disable the specified channel",
|
||||
help="Disable the specified channel Use --ch-del instead whenever possible.",
|
||||
action="store_true",
|
||||
dest="ch_disable",
|
||||
default=False,
|
||||
)
|
||||
|
||||
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",
|
||||
)
|
||||
return parser
|
||||
|
||||
group.add_argument(
|
||||
"--channel-fetch-attempts",
|
||||
help=("Attempt to retrieve channel settings for --ch-set this many times before giving up."),
|
||||
default=3,
|
||||
type=int,
|
||||
)
|
||||
def addPositionConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"""Add arguments to do with fixed positions and position config"""
|
||||
|
||||
group.add_argument(
|
||||
"--timeout",
|
||||
help="How long to wait for replies",
|
||||
default=300,
|
||||
type=int,
|
||||
group = parser.add_argument_group(
|
||||
"Position Configuration",
|
||||
"Arguments that modify fixed position and other position-related configuration.",
|
||||
)
|
||||
|
||||
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(
|
||||
"--setalt",
|
||||
help="Set device altitude in meters (allows use without GPS), and enable fixed position. "
|
||||
@@ -1659,6 +1559,221 @@ def initParser():
|
||||
nargs="*",
|
||||
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(
|
||||
"--debug", help="Show API library debug log messages", action="store_true"
|
||||
@@ -1670,6 +1785,33 @@ def initParser():
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--wait-to-disconnect",
|
||||
help="How many seconds to wait before disconnecting from the device.",
|
||||
const="5",
|
||||
nargs="?",
|
||||
action="store",
|
||||
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 Testing", "Options for power testing/logging."
|
||||
)
|
||||
@@ -1718,37 +1860,12 @@ def initParser():
|
||||
|
||||
power_group.add_argument(
|
||||
"--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="?",
|
||||
default=None,
|
||||
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(
|
||||
"Remote Hardware", "Arguments related to the Remote Hardware module"
|
||||
|
||||
@@ -218,7 +218,7 @@ class BLEInterface(MeshInterface):
|
||||
logging.error(f"Error closing mesh interface: {e}")
|
||||
|
||||
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:
|
||||
self._receiveThread.join(
|
||||
timeout=2
|
||||
@@ -230,6 +230,7 @@ class BLEInterface(MeshInterface):
|
||||
self.client.disconnect()
|
||||
self.client.close()
|
||||
self.client = None
|
||||
self._disconnected() # send the disconnected indicator up to clients
|
||||
|
||||
|
||||
class BLEClient:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Mesh Interface class
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
|
||||
import collections
|
||||
import json
|
||||
@@ -605,32 +606,38 @@ class MeshInterface: # pylint: disable=R0902
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
wantResponse: bool = False,
|
||||
channelIndex: int = 0,
|
||||
telemetryType: str = "device_metrics"
|
||||
):
|
||||
"""Send telemetry and optionally ask for a response"""
|
||||
r = telemetry_pb2.Telemetry()
|
||||
|
||||
if self.nodes is not None:
|
||||
node = next(
|
||||
n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum
|
||||
)
|
||||
if node is not None:
|
||||
metrics = node.get("deviceMetrics")
|
||||
if metrics:
|
||||
batteryLevel = metrics.get("batteryLevel")
|
||||
if batteryLevel is not None:
|
||||
r.device_metrics.battery_level = batteryLevel
|
||||
voltage = metrics.get("voltage")
|
||||
if voltage is not None:
|
||||
r.device_metrics.voltage = voltage
|
||||
channel_utilization = metrics.get("channelUtilization")
|
||||
if channel_utilization is not None:
|
||||
r.device_metrics.channel_utilization = channel_utilization
|
||||
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 telemetryType == "environment_metrics":
|
||||
r.environment_metrics.CopyFrom(telemetry_pb2.EnvironmentMetrics())
|
||||
elif telemetryType == "air_quality_metrics":
|
||||
r.air_quality_metrics.CopyFrom(telemetry_pb2.AirQualityMetrics())
|
||||
elif telemetryType == "power_metrics":
|
||||
r.power_metrics.CopyFrom(telemetry_pb2.PowerMetrics())
|
||||
else: # fall through to device metrics
|
||||
if self.nodesByNum is not None:
|
||||
node = self.nodesByNum.get(self.localNode.nodeNum)
|
||||
if node is not None:
|
||||
metrics = node.get("deviceMetrics")
|
||||
if metrics:
|
||||
batteryLevel = metrics.get("batteryLevel")
|
||||
if batteryLevel is not None:
|
||||
r.device_metrics.battery_level = batteryLevel
|
||||
voltage = metrics.get("voltage")
|
||||
if voltage is not None:
|
||||
r.device_metrics.voltage = voltage
|
||||
channel_utilization = metrics.get("channelUtilization")
|
||||
if channel_utilization is not None:
|
||||
r.device_metrics.channel_utilization = channel_utilization
|
||||
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:
|
||||
onResponse = self.onResponseTelemetry
|
||||
@@ -654,22 +661,32 @@ class MeshInterface: # pylint: disable=R0902
|
||||
self._acknowledgment.receivedTelemetry = True
|
||||
telemetry = telemetry_pb2.Telemetry()
|
||||
telemetry.ParseFromString(p["decoded"]["payload"])
|
||||
|
||||
print("Telemetry received:")
|
||||
if telemetry.device_metrics.battery_level is not None:
|
||||
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
|
||||
if telemetry.device_metrics.voltage is not None:
|
||||
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
|
||||
if telemetry.device_metrics.channel_utilization is not None:
|
||||
print(
|
||||
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
|
||||
)
|
||||
if telemetry.device_metrics.air_util_tx is not None:
|
||||
print(
|
||||
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")
|
||||
# Check if the telemetry message has the device_metrics field
|
||||
# This is the original code that was the default for --request-telemetry and is kept for compatibility
|
||||
if telemetry.HasField("device_metrics"):
|
||||
if telemetry.device_metrics.battery_level is not None:
|
||||
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
|
||||
if telemetry.device_metrics.voltage is not None:
|
||||
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
|
||||
if telemetry.device_metrics.channel_utilization is not None:
|
||||
print(
|
||||
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
|
||||
)
|
||||
if telemetry.device_metrics.air_util_tx is not None:
|
||||
print(
|
||||
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":
|
||||
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":
|
||||
@@ -914,6 +931,8 @@ class MeshInterface: # pylint: disable=R0902
|
||||
startConfig = mesh_pb2.ToRadio()
|
||||
if self.configId is None or not self.noNodes:
|
||||
self.configId = random.randint(0, 0xFFFFFFFF)
|
||||
if self.configId == NODELESS_WANT_CONFIG_ID:
|
||||
self.configId = self.configId + 1
|
||||
startConfig.want_config_id = self.configId
|
||||
self._sendToRadio(startConfig)
|
||||
|
||||
|
||||
12
meshtastic/protobuf/mesh_pb2.py
generated
12
meshtastic/protobuf/mesh_pb2.py
generated
File diff suppressed because one or more lines are too long
44
meshtastic/protobuf/mesh_pb2.pyi
generated
44
meshtastic/protobuf/mesh_pb2.pyi
generated
@@ -129,6 +129,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
"""
|
||||
Heltec HRU-3601: https://heltec.org/project/hru-3601/
|
||||
"""
|
||||
HELTEC_WIRELESS_BRIDGE: _HardwareModel.ValueType # 24
|
||||
"""
|
||||
Heltec Wireless Bridge
|
||||
"""
|
||||
STATION_G1: _HardwareModel.ValueType # 25
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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
|
||||
"""
|
||||
@@ -359,8 +363,22 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
^^^ short A0 to switch to I2C address 0x3C
|
||||
"""
|
||||
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
|
||||
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
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -474,6 +492,10 @@ HELTEC_HRU_3601: HardwareModel.ValueType # 23
|
||||
"""
|
||||
Heltec HRU-3601: https://heltec.org/project/hru-3601/
|
||||
"""
|
||||
HELTEC_WIRELESS_BRIDGE: HardwareModel.ValueType # 24
|
||||
"""
|
||||
Heltec Wireless Bridge
|
||||
"""
|
||||
STATION_G1: HardwareModel.ValueType # 25
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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
|
||||
"""
|
||||
@@ -704,8 +726,22 @@ https://www.adafruit.com/product/938
|
||||
^^^ short A0 to switch to I2C address 0x3C
|
||||
"""
|
||||
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
|
||||
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
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
68
meshtastic/protobuf/module_config_pb2.py
generated
68
meshtastic/protobuf/module_config_pb2.py
generated
File diff suppressed because one or more lines are too long
62
meshtastic/protobuf/module_config_pb2.pyi
generated
62
meshtastic/protobuf/module_config_pb2.pyi
generated
@@ -250,13 +250,54 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
|
||||
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
|
||||
MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int
|
||||
STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int
|
||||
SEND_BELL_FIELD_NUMBER: builtins.int
|
||||
NAME_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
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
@@ -264,13 +305,15 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes
|
||||
When set to 0, only state changes will be broadcasted
|
||||
Works as a sort of status heartbeat for peace of mind
|
||||
Interval in seconds of how often we should send a message to the mesh
|
||||
with the current state regardless of trigger events When set to 0, only
|
||||
trigger events will be broadcasted Works as a sort of status heartbeat
|
||||
for peace of mind
|
||||
"""
|
||||
send_bell: builtins.bool
|
||||
"""
|
||||
@@ -287,10 +330,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
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)
|
||||
Otherwise LOW (0)
|
||||
The type of trigger event to be used
|
||||
"""
|
||||
use_pullup: builtins.bool
|
||||
"""
|
||||
@@ -306,10 +348,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
send_bell: builtins.bool = ...,
|
||||
name: builtins.str = ...,
|
||||
monitor_pin: builtins.int = ...,
|
||||
detection_triggered_high: builtins.bool = ...,
|
||||
detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType = ...,
|
||||
use_pullup: builtins.bool = ...,
|
||||
) -> 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
|
||||
class AudioConfig(google.protobuf.message.Message):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
""" Serial interface class
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
import logging
|
||||
import platform
|
||||
import time
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" 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.
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""TCPInterface class for interfacing with http endpoint
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
import logging
|
||||
import socket
|
||||
from typing import Optional
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Meshtastic unit tests for __main__.py"""
|
||||
# pylint: disable=C0302,W0613
|
||||
# pylint: disable=C0302,W0613,R0917
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Meshtastic unit tests for serial_interface.py"""
|
||||
# pylint: disable=R0917
|
||||
|
||||
import re
|
||||
from unittest.mock import mock_open, patch
|
||||
|
||||
2827
poetry.lock
generated
2827
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
Submodule protobufs updated: 5709c0a05e...83c78e26e3
@@ -1,13 +1,13 @@
|
||||
[tool.poetry]
|
||||
name = "meshtastic"
|
||||
version = "2.5.1"
|
||||
version = "2.5.3"
|
||||
description = "Python API & client shell for talking to Meshtastic devices"
|
||||
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
||||
license = "GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
|
||||
[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"
|
||||
protobuf = ">=5.26.0"
|
||||
dotmap = "^1.3.30"
|
||||
@@ -19,13 +19,14 @@ requests = "^2.31.0"
|
||||
pyparsing = "^3.1.2"
|
||||
pyyaml = "^6.0.1"
|
||||
pypubsub = "^4.0.3"
|
||||
bleak = "^0.21.1"
|
||||
bleak = "^0.22.3"
|
||||
packaging = "^24.0"
|
||||
print-color = "^0.4.6"
|
||||
dash = { version = "^2.17.1", optional = true }
|
||||
pytap2 = { version = "^2.3.0", optional = true }
|
||||
dash-bootstrap-components = { version = "^1.6.0", optional = true }
|
||||
pandas = { version = "^2.2.2", optional = true }
|
||||
pandas-stubs = { version = "^2.2.2.240603", optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
hypothesis = "^6.103.2"
|
||||
@@ -43,7 +44,6 @@ types-requests = "^2.31.0.20240406"
|
||||
types-setuptools = "^69.5.0.20240423"
|
||||
types-pyyaml = "^6.0.12.20240311"
|
||||
pyarrow-stubs = "^10.0.1.7"
|
||||
pandas-stubs = "^2.2.2.240603"
|
||||
|
||||
[tool.poetry.group.powermon]
|
||||
optional = true
|
||||
|
||||
Reference in New Issue
Block a user