Compare commits

...

13 Commits

Author SHA1 Message Date
Ian McEwen
5cc0dae394 protobufs: v2.7.15 2025-11-20 14:30:39 -07:00
Ian McEwen
1a50377d34 Merge pull request #865 from ianmcorvidae/device-metadata-details
Add a bunch more detail to --device-metadata output, from fields we weren't formerly using in the output.
2025-11-20 14:29:20 -07:00
Ian McEwen
2a44be9269 Add a bunch more detail to --device-metadata output, from fields we weren't formerly using in the output. 2025-11-20 11:42:57 -07:00
github-actions
776debcc86 bump version to 2.7.5 2025-11-18 19:48:40 +00:00
Ian McEwen
1088cc607e Merge branch 'revert-close-refactor' 2025-11-13 12:10:46 -07:00
Ian McEwen
aeec5447ed Revert "Merge pull request #841 from SpudGunMan/master"
This reverts commit b4662251ed, reversing
changes made to 2065598754.
2025-11-13 12:08:42 -07:00
Ian McEwen
096fec95c8 Revert "Wrap double-close in a try-catch. Slightly ugly but oh well."
This reverts commit dbc0101a7a.
2025-11-13 12:07:48 -07:00
Ian McEwen
dea5f788a2 Revert "Add more exception logging, fix some additional stream read/write issues"
This reverts commit f15a0bdc0b.
2025-11-13 12:07:32 -07:00
Ian McEwen
f15a0bdc0b Add more exception logging, fix some additional stream read/write issues 2025-11-13 11:38:00 -07:00
Ian McEwen
0906fc6bc0 Merge pull request #858 from Travis-L-R/conf_work
Splitting out true_defaults for moduleConfig in export_config()
2025-11-12 10:16:19 -07:00
Travis-L-R
ccb530574b Splitting true_defaults for moduleConfig out in export_config() 2025-11-11 21:03:01 +10:30
Ian McEwen
dbc0101a7a Wrap double-close in a try-catch. Slightly ugly but oh well. 2025-11-10 13:58:12 -07:00
Ian McEwen
debbb8caeb Update some dependencies 2025-11-10 13:09:44 -07:00
11 changed files with 3369 additions and 1732 deletions

View File

@@ -1147,13 +1147,16 @@ def export_config(interface) -> str:
configObj = {} configObj = {}
# A list of configuration keys that should be set to False if they are missing # A list of configuration keys that should be set to False if they are missing
true_defaults = { config_true_defaults = {
("bluetooth", "enabled"), ("bluetooth", "enabled"),
("lora", "sx126xRxBoostedGain"), ("lora", "sx126xRxBoostedGain"),
("lora", "txEnabled"), ("lora", "txEnabled"),
("lora", "usePreset"), ("lora", "usePreset"),
("position", "positionBroadcastSmartEnabled"), ("position", "positionBroadcastSmartEnabled"),
("security", "serialEnabled"), ("security", "serialEnabled"),
}
module_true_defaults = {
("mqtt", "encryptionEnabled"), ("mqtt", "encryptionEnabled"),
} }
@@ -1215,7 +1218,7 @@ def export_config(interface) -> str:
else: else:
configObj["config"] = config configObj["config"] = config
set_missing_flags_false(configObj["config"], true_defaults) set_missing_flags_false(configObj["config"], config_true_defaults)
module_config = MessageToDict(interface.localNode.moduleConfig) module_config = MessageToDict(interface.localNode.moduleConfig)
if module_config: if module_config:
@@ -1229,6 +1232,8 @@ def export_config(interface) -> str:
else: else:
configObj["module_config"] = prefs configObj["module_config"] = prefs
set_missing_flags_false(configObj["module_config"], module_true_defaults)
config_txt = "# start of Meshtastic configure yaml\n" #checkme - "config" (now changed to config_out) config_txt = "# start of Meshtastic configure yaml\n" #checkme - "config" (now changed to config_out)
#was used as a string here and a Dictionary above #was used as a string here and a Dictionary above
config_txt += yaml.dump(configObj) config_txt += yaml.dump(configObj)

View File

@@ -7,7 +7,7 @@ import time
from typing import Optional, Union, List from typing import Optional, Union, List
from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2 from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, config_pb2, localonly_pb2, mesh_pb2, portnums_pb2
from meshtastic.util import ( from meshtastic.util import (
Timeout, Timeout,
camel_to_snake, camel_to_snake,
@@ -18,6 +18,7 @@ from meshtastic.util import (
message_to_json, message_to_json,
generate_channel_hash, generate_channel_hash,
to_node_num, to_node_num,
flags_to_list,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -54,6 +55,16 @@ class Node:
r += ")" r += ")"
return r return r
@staticmethod
def position_flags_list(position_flags: int) -> List[str]:
"Return a list of position flags from the given flags integer"
return flags_to_list(config_pb2.Config.PositionConfig.PositionFlags, position_flags)
@staticmethod
def excluded_modules_list(excluded_modules: int) -> List[str]:
"Return a list of excluded modules from the given flags integer"
return flags_to_list(mesh_pb2.ExcludedModules, excluded_modules)
def module_available(self, excluded_bit: int) -> bool: def module_available(self, excluded_bit: int) -> bool:
"""Check DeviceMetadata.excluded_modules to see if a module is available.""" """Check DeviceMetadata.excluded_modules to see if a module is available."""
meta = getattr(self.iface, "metadata", None) meta = getattr(self.iface, "metadata", None)
@@ -902,6 +913,18 @@ class Node:
logger.debug(f"Received metadata {stripnl(c)}") logger.debug(f"Received metadata {stripnl(c)}")
print(f"\nfirmware_version: {c.firmware_version}") print(f"\nfirmware_version: {c.firmware_version}")
print(f"device_state_version: {c.device_state_version}") print(f"device_state_version: {c.device_state_version}")
if c.role in config_pb2.Config.DeviceConfig.Role.values():
print(f"role: {config_pb2.Config.DeviceConfig.Role.Name(c.role)}")
else:
print(f"role: {c.role}")
print(f"position_flags: {self.position_flags_list(c.position_flags)}")
if c.hw_model in mesh_pb2.HardwareModel.values():
print(f"hw_model: {mesh_pb2.HardwareModel.Name(c.hw_model)}")
else:
print(f"hw_model: {c.hw_model}")
print(f"hasPKC: {c.hasPKC}")
if c.excluded_modules > 0:
print(f"excluded_modules: {self.excluded_modules_list(c.excluded_modules)}")
def onResponseRequestChannel(self, p): def onResponseRequestChannel(self, p):
"""Handle the response packet for requesting a channel _requestChannel()""" """Handle the response packet for requesting a channel _requestChannel()"""

View File

File diff suppressed because one or more lines are too long

View File

@@ -515,9 +515,10 @@ class AdminMessage(google.protobuf.message.Message):
""" """
Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved.
""" """
nodedb_reset: builtins.int nodedb_reset: builtins.bool
""" """
Tell the node to reset the nodedb. Tell the node to reset the nodedb.
When true, favorites are preserved through reset.
""" """
@property @property
def get_channel_response(self) -> meshtastic.protobuf.channel_pb2.Channel: def get_channel_response(self) -> meshtastic.protobuf.channel_pb2.Channel:
@@ -689,7 +690,7 @@ class AdminMessage(google.protobuf.message.Message):
reboot_seconds: builtins.int = ..., reboot_seconds: builtins.int = ...,
shutdown_seconds: builtins.int = ..., shutdown_seconds: builtins.int = ...,
factory_reset_config: builtins.int = ..., factory_reset_config: builtins.int = ...,
nodedb_reset: builtins.int = ..., nodedb_reset: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "session_passkey", b"session_passkey", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> None: ... def ClearField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "session_passkey", b"session_passkey", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> None: ...

View File

File diff suppressed because one or more lines are too long

View File

@@ -523,6 +523,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
RAK3401 RAK3401
""" """
RAK6421: _HardwareModel.ValueType # 118
"""
RAK6421 Hat+
"""
PRIVATE_HW: _HardwareModel.ValueType # 255 PRIVATE_HW: _HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -1029,6 +1033,10 @@ RAK3401: HardwareModel.ValueType # 117
""" """
RAK3401 RAK3401
""" """
RAK6421: HardwareModel.ValueType # 118
"""
RAK6421 Hat+
"""
PRIVATE_HW: HardwareModel.ValueType # 255 PRIVATE_HW: HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -2557,6 +2565,10 @@ class MeshPacket(google.protobuf.message.Message):
to: builtins.int to: builtins.int
""" """
The (immediate) destination for this packet The (immediate) destination for this packet
If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was
not destined for a specific node, but for a channel as indicated by the value of `channel` below.
If the value is another, this indicates that the packet was destined for a specific
node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel.
""" """
channel: builtins.int channel: builtins.int
""" """

View File

@@ -94,16 +94,10 @@ class SerialInterface(StreamInterface):
def close(self) -> None: def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
if hasattr(self, "stream") and self.stream and getattr(self.stream, "is_open", False): if self.stream: # Stream can be null if we were already closed
try: self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary
self.stream.flush() time.sleep(0.1)
time.sleep(0.1) self.stream.flush()
except Exception as e: time.sleep(0.1)
logger.debug(f"Exception during flush: {e}")
try:
self.stream.close()
except Exception as e:
logger.debug(f"Exception during close: {e}")
self.stream = None
logger.debug("Closing Serial stream") logger.debug("Closing Serial stream")
StreamInterface.close(self) StreamInterface.close(self)

View File

@@ -735,3 +735,16 @@ def to_node_num(node_id: Union[int, str]) -> int:
return int(s, 10) return int(s, 10)
except ValueError: except ValueError:
return int(s, 16) return int(s, 16)
def flags_to_list(flag_type, flags: int) -> List[str]:
"""Given a flag_type that's a protobuf EnumTypeWrapper, and a flag int, give a list of flags enabled."""
ret = []
for key in flag_type.keys():
if key == "EXCLUDED_NONE":
continue
if flags & flag_type.Value(key):
ret.append(key)
flags = flags - flag_type.Value(key)
if flags > 0:
ret.append(f"UNKNOWN_ADDITIONAL_FLAGS({flags})")
return ret

4989
poetry.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,20 @@
[tool.poetry] [tool.poetry]
name = "meshtastic" name = "meshtastic"
version = "2.7.4" version = "2.7.5"
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.14" # 3.9 is needed for pandas, bleak requires <3.14 python = "^3.9,<3.15" # 3.9 is needed for pandas
pyserial = "^3.5" pyserial = "^3.5"
protobuf = ">=4.21.12" protobuf = ">=4.21.12"
tabulate = "^0.9.0" tabulate = "^0.9.0"
requests = "^2.31.0" requests = "^2.31.0"
pyyaml = "^6.0.1" pyyaml = "^6.0.1"
pypubsub = "^4.0.3" pypubsub = "^4.0.3"
bleak = "^0.22.3" bleak = ">=0.22.3"
packaging = "^24.0" packaging = "^24.0"
argcomplete = { version = "^3.5.2", optional = true } argcomplete = { version = "^3.5.2", optional = true }
pyqrcode = { version = "^1.2.1", optional = true } pyqrcode = { version = "^1.2.1", optional = true }
@@ -34,7 +34,7 @@ pytest-cov = "^5.0.0"
pdoc3 = "^0.10.0" pdoc3 = "^0.10.0"
autopep8 = "^2.1.0" autopep8 = "^2.1.0"
pylint = "^3.2.3" pylint = "^3.2.3"
pyinstaller = "^6.8.0" pyinstaller = "^6.10.0"
mypy = "^1.10.0" mypy = "^1.10.0"
mypy-protobuf = "^3.3.0" mypy-protobuf = "^3.3.0"
types-protobuf = "^5.26.0.20240422" types-protobuf = "^5.26.0.20240422"