Compare commits

...

12 Commits
2.7.0 ... 2.7.2

Author SHA1 Message Date
github-actions
e5159f1156 bump version to 2.7.2 2025-09-12 16:49:34 +00:00
Ian McEwen
593b05dbcd protobufs: v2.7.8 2025-09-12 09:46:52 -07:00
Ian McEwen
f519d1f2d2 Merge pull request #818 from pdxlocations/check-module-bitmask
Fix Getting and Setting Excluded Modules: Ringtone and Canned Messages
2025-09-12 09:44:29 -07:00
Ian McEwen
8b36561406 Merge pull request #821 from shukari/contributionHelp
Added Github CONTRIBUTING.md file
2025-09-12 09:43:30 -07:00
shukari
e2b4948d45 Github CONTRIBUTING.md file 2025-09-09 18:17:29 +02:00
pdxlocations
7e3d347b63 remove trailing whitespaces 2025-08-29 23:19:22 -07:00
pdxlocations
c6efccdbd2 init 2025-08-29 23:11:26 -07:00
Ian McEwen
2b10459db0 remove dangling submodule 2025-08-20 10:13:27 -07:00
github-actions
d53ced216c bump version to 2.7.1 2025-08-18 19:40:36 +00:00
Ian McEwen
f5ecd28705 protobufs: v2.7.5 2025-08-18 09:52:48 -07:00
Ian McEwen
82ad9b2f51 Merge pull request #813 from pdxlocations/unmess-unmessageable
Fix --set-owner by calling setOwner!
2025-08-18 09:50:28 -07:00
pdxlocations
03aaa4c98e always call setOwner 2025-08-06 21:58:42 -07:00
14 changed files with 308 additions and 143 deletions

22
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,22 @@
# Contributing to Meshtastic Python
## Development resources
- [API Documentation](https://python.meshtastic.org/)
- [Meshtastic Python Development](https://meshtastic.org/docs/development/python/)
- [Building Meshtastic Python](https://meshtastic.org/docs/development/python/building/)
- [Using the Meshtastic Python Library](https://meshtastic.org/docs/development/python/library/)
## How to check your code (pytest/pylint) before a PR
- [Pre-requisites](https://meshtastic.org/docs/development/python/building/#pre-requisites)
- also execute `poetry install --all-extras --with dev,powermon` for all optional dependencies
- check your code with github ci actions locally
- You need to have act installed. You can get it at https://nektosact.com/
- on linux: `act -P ubuntu-latest=-self-hosted --matrix "python-version:3.12"`
- on windows:
- linux checks (linux docker): `act --matrix "python-version:3.12"`
- windows checks (windows host): `act -P ubuntu-latest=-self-hosted --matrix "python-version:3.12"`
- or run all locally:
- run `poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"`
- run `poetry run mypy meshtastic/`
- run `poetry run pytest`
- more commands see [CI workflow](https://github.com/meshtastic/python/blob/master/.github/workflows/ci.yml)

View File

@@ -59,7 +59,7 @@ except ImportError as e:
have_powermon = False have_powermon = False
powermon_exception = e powermon_exception = e
meter = None meter = None
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2 from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2, mesh_pb2
from meshtastic.version import get_active_version from meshtastic.version import get_active_version
def onReceive(packet, interface) -> None: def onReceive(packet, interface) -> None:
@@ -343,51 +343,56 @@ def onConnected(interface):
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
# Validate owner names before connecting to device long_name = args.set_owner.strip() if args.set_owner else None
if args.set_owner is not None: short_name = args.set_owner_short.strip() if args.set_owner_short else None
stripped_long_name = args.set_owner.strip()
if not stripped_long_name:
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
if hasattr(args, 'set_owner_short') and args.set_owner_short is not None: if long_name is not None and not long_name:
stripped_short_name = args.set_owner_short.strip() meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
if not stripped_short_name:
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
if args.set_owner and args.set_owner_short: if short_name is not None and not short_name:
print(f"Setting device owner to {args.set_owner} and short name to {args.set_owner_short}") meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
elif args.set_owner:
print(f"Setting device owner to {args.set_owner}")
elif args.set_owner_short and not args.set_owner:
print(f"Setting device owner short to {args.set_owner_short}")
if args.set_is_unmessageable: if long_name and short_name:
print(f"Setting device owner to {long_name} and short name to {short_name}")
elif long_name:
print(f"Setting device owner to {long_name}")
elif short_name:
print(f"Setting device owner short to {short_name}")
unmessagable = None
if args.set_is_unmessageable is not None:
unmessagable = ( unmessagable = (
meshtastic.util.fromStr(args.set_is_unmessageable) meshtastic.util.fromStr(args.set_is_unmessageable)
if isinstance(args.set_is_unmessageable, str) if isinstance(args.set_is_unmessageable, str)
else args.set_is_unmessageable else args.set_is_unmessageable
) )
print(f"Setting device owner is_unmessageable to {unmessagable}")
if unmessagable is not None: interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
print(f"Setting device owner is_unmessageable to {unmessagable}") long_name=long_name,
interface.getNode( short_name=short_name,
args.dest, False, **getNode_kwargs).setOwner(long_name=args.set_owner, is_unmessagable=unmessagable
short_name=args.set_owner_short, is_unmessagable=unmessagable )
)
if args.set_canned_message: if args.set_canned_message:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
print(f"Setting canned plugin message to {args.set_canned_message}") node = interface.getNode(args.dest, False, **getNode_kwargs)
interface.getNode(args.dest, False, **getNode_kwargs).set_canned_message( if node.module_available(mesh_pb2.CANNEDMSG_CONFIG):
args.set_canned_message print(f"Setting canned plugin message to {args.set_canned_message}")
) node.set_canned_message(args.set_canned_message)
else:
print("Canned Message module is excluded by firmware; skipping set.")
if args.set_ringtone: if args.set_ringtone:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
print(f"Setting ringtone to {args.set_ringtone}") node = interface.getNode(args.dest, False, **getNode_kwargs)
interface.getNode(args.dest, False, **getNode_kwargs).set_ringtone(args.set_ringtone) if node.module_available(mesh_pb2.EXTNOTIF_CONFIG):
print(f"Setting ringtone to {args.set_ringtone}")
node.set_ringtone(args.set_ringtone)
else:
print("External Notification is excluded by firmware; skipping ringtone set.")
if args.pos_fields: if args.pos_fields:
# If --pos-fields invoked with args, set position fields # If --pos-fields invoked with args, set position fields

View File

@@ -51,6 +51,16 @@ class Node:
r += ")" r += ")"
return r return r
def module_available(self, excluded_bit: int) -> bool:
"""Check DeviceMetadata.excluded_modules to see if a module is available."""
meta = getattr(self.iface, "metadata", None)
if meta is None:
return True
try:
return (meta.excluded_modules & excluded_bit) == 0
except Exception:
return True
def showChannels(self): def showChannels(self):
"""Show human readable description of our channels.""" """Show human readable description of our channels."""
print("Channels:") print("Channels:")
@@ -441,6 +451,10 @@ class Node:
def get_ringtone(self): def get_ringtone(self):
"""Get the ringtone. Concatenate all pieces together and return a single string.""" """Get the ringtone. Concatenate all pieces together and return a single string."""
logging.debug(f"in get_ringtone()") logging.debug(f"in get_ringtone()")
if not self.module_available(mesh_pb2.EXTNOTIF_CONFIG):
logging.warning("External Notification module not present (excluded by firmware)")
return None
if not self.ringtone: if not self.ringtone:
p1 = admin_pb2.AdminMessage() p1 = admin_pb2.AdminMessage()
p1.get_ringtone_request = True p1.get_ringtone_request = True
@@ -462,6 +476,9 @@ class Node:
def set_ringtone(self, ringtone): def set_ringtone(self, ringtone):
"""Set the ringtone. The ringtone length must be less than 230 character.""" """Set the ringtone. The ringtone length must be less than 230 character."""
if not self.module_available(mesh_pb2.EXTNOTIF_CONFIG):
logging.warning("External Notification module not present (excluded by firmware)")
return None
if len(ringtone) > 230: if len(ringtone) > 230:
our_exit("Warning: The ringtone must be less than 230 characters.") our_exit("Warning: The ringtone must be less than 230 characters.")
@@ -512,6 +529,9 @@ class Node:
def get_canned_message(self): def get_canned_message(self):
"""Get the canned message string. Concatenate all pieces together and return a single string.""" """Get the canned message string. Concatenate all pieces together and return a single string."""
logging.debug(f"in get_canned_message()") logging.debug(f"in get_canned_message()")
if not self.module_available(mesh_pb2.CANNEDMSG_CONFIG):
logging.warning("Canned Message module not present (excluded by firmware)")
return None
if not self.cannedPluginMessage: if not self.cannedPluginMessage:
p1 = admin_pb2.AdminMessage() p1 = admin_pb2.AdminMessage()
p1.get_canned_message_module_messages_request = True p1.get_canned_message_module_messages_request = True
@@ -537,6 +557,9 @@ class Node:
def set_canned_message(self, message): def set_canned_message(self, message):
"""Set the canned message. The canned messages length must be less than 200 character.""" """Set the canned message. The canned messages length must be less than 200 character."""
if not self.module_available(mesh_pb2.CANNEDMSG_CONFIG):
logging.warning("Canned Message module not present (excluded by firmware)")
return None
if len(message) > 200: if len(message) > 200:
our_exit("Warning: The canned message must be less than 200 characters.") our_exit("Warning: The canned message must be less than 200 characters.")

View File

File diff suppressed because one or more lines are too long

View File

@@ -1048,12 +1048,12 @@ class Config(google.protobuf.message.Message):
""" """
OLED_SH1107: Config.DisplayConfig._OledType.ValueType # 3 OLED_SH1107: Config.DisplayConfig._OledType.ValueType # 3
""" """
Can not be auto detected but set by proto. Used for 128x128 screens
"""
OLED_SH1107_128_64: Config.DisplayConfig._OledType.ValueType # 4
"""
Can not be auto detected but set by proto. Used for 128x64 screens Can not be auto detected but set by proto. Used for 128x64 screens
""" """
OLED_SH1107_128_128: Config.DisplayConfig._OledType.ValueType # 4
"""
Can not be auto detected but set by proto. Used for 128x128 screens
"""
class OledType(_OledType, metaclass=_OledTypeEnumTypeWrapper): class OledType(_OledType, metaclass=_OledTypeEnumTypeWrapper):
""" """
@@ -1074,12 +1074,12 @@ class Config(google.protobuf.message.Message):
""" """
OLED_SH1107: Config.DisplayConfig.OledType.ValueType # 3 OLED_SH1107: Config.DisplayConfig.OledType.ValueType # 3
""" """
Can not be auto detected but set by proto. Used for 128x128 screens
"""
OLED_SH1107_128_64: Config.DisplayConfig.OledType.ValueType # 4
"""
Can not be auto detected but set by proto. Used for 128x64 screens Can not be auto detected but set by proto. Used for 128x64 screens
""" """
OLED_SH1107_128_128: Config.DisplayConfig.OledType.ValueType # 4
"""
Can not be auto detected but set by proto. Used for 128x128 screens
"""
class _DisplayMode: class _DisplayMode:
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)

View File

File diff suppressed because one or more lines are too long

View File

@@ -486,6 +486,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices. MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices.
https://heltec.org/project/meshsolar/ https://heltec.org/project/meshsolar/
""" """
T_ECHO_LITE: _HardwareModel.ValueType # 109
"""
Lilygo T-Echo Lite
"""
PRIVATE_HW: _HardwareModel.ValueType # 255 PRIVATE_HW: _HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -955,6 +959,10 @@ HELTEC_MESH_SOLAR: HardwareModel.ValueType # 108
MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices. MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices.
https://heltec.org/project/meshsolar/ https://heltec.org/project/meshsolar/
""" """
T_ECHO_LITE: HardwareModel.ValueType # 109
"""
Lilygo T-Echo Lite
"""
PRIVATE_HW: HardwareModel.ValueType # 255 PRIVATE_HW: HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -2382,6 +2390,83 @@ class MeshPacket(google.protobuf.message.Message):
The message is delayed and was originally a direct message The message is delayed and was originally a direct message
""" """
class _TransportMechanism:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TransportMechanismEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MeshPacket._TransportMechanism.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
TRANSPORT_INTERNAL: MeshPacket._TransportMechanism.ValueType # 0
"""
The default case is that the node generated a packet itself
"""
TRANSPORT_LORA: MeshPacket._TransportMechanism.ValueType # 1
"""
Arrived via the primary LoRa radio
"""
TRANSPORT_LORA_ALT1: MeshPacket._TransportMechanism.ValueType # 2
"""
Arrived via a secondary LoRa radio
"""
TRANSPORT_LORA_ALT2: MeshPacket._TransportMechanism.ValueType # 3
"""
Arrived via a tertiary LoRa radio
"""
TRANSPORT_LORA_ALT3: MeshPacket._TransportMechanism.ValueType # 4
"""
Arrived via a quaternary LoRa radio
"""
TRANSPORT_MQTT: MeshPacket._TransportMechanism.ValueType # 5
"""
Arrived via an MQTT connection
"""
TRANSPORT_MULTICAST_UDP: MeshPacket._TransportMechanism.ValueType # 6
"""
Arrived via Multicast UDP
"""
TRANSPORT_API: MeshPacket._TransportMechanism.ValueType # 7
"""
Arrived via API connection
"""
class TransportMechanism(_TransportMechanism, metaclass=_TransportMechanismEnumTypeWrapper):
"""
Enum to identify which transport mechanism this packet arrived over
"""
TRANSPORT_INTERNAL: MeshPacket.TransportMechanism.ValueType # 0
"""
The default case is that the node generated a packet itself
"""
TRANSPORT_LORA: MeshPacket.TransportMechanism.ValueType # 1
"""
Arrived via the primary LoRa radio
"""
TRANSPORT_LORA_ALT1: MeshPacket.TransportMechanism.ValueType # 2
"""
Arrived via a secondary LoRa radio
"""
TRANSPORT_LORA_ALT2: MeshPacket.TransportMechanism.ValueType # 3
"""
Arrived via a tertiary LoRa radio
"""
TRANSPORT_LORA_ALT3: MeshPacket.TransportMechanism.ValueType # 4
"""
Arrived via a quaternary LoRa radio
"""
TRANSPORT_MQTT: MeshPacket.TransportMechanism.ValueType # 5
"""
Arrived via an MQTT connection
"""
TRANSPORT_MULTICAST_UDP: MeshPacket.TransportMechanism.ValueType # 6
"""
Arrived via Multicast UDP
"""
TRANSPORT_API: MeshPacket.TransportMechanism.ValueType # 7
"""
Arrived via API connection
"""
FROM_FIELD_NUMBER: builtins.int FROM_FIELD_NUMBER: builtins.int
TO_FIELD_NUMBER: builtins.int TO_FIELD_NUMBER: builtins.int
CHANNEL_FIELD_NUMBER: builtins.int CHANNEL_FIELD_NUMBER: builtins.int
@@ -2402,6 +2487,7 @@ class MeshPacket(google.protobuf.message.Message):
NEXT_HOP_FIELD_NUMBER: builtins.int NEXT_HOP_FIELD_NUMBER: builtins.int
RELAY_NODE_FIELD_NUMBER: builtins.int RELAY_NODE_FIELD_NUMBER: builtins.int
TX_AFTER_FIELD_NUMBER: builtins.int TX_AFTER_FIELD_NUMBER: builtins.int
TRANSPORT_MECHANISM_FIELD_NUMBER: builtins.int
to: builtins.int to: builtins.int
""" """
The (immediate) destination for this packet The (immediate) destination for this packet
@@ -2509,6 +2595,10 @@ class MeshPacket(google.protobuf.message.Message):
Timestamp after which this packet may be sent. Timestamp after which this packet may be sent.
Set by the firmware internally, clients are not supposed to set this. Set by the firmware internally, clients are not supposed to set this.
""" """
transport_mechanism: global___MeshPacket.TransportMechanism.ValueType
"""
Indicates which transport mechanism this packet arrived over
"""
@property @property
def decoded(self) -> global___Data: def decoded(self) -> global___Data:
""" """
@@ -2537,9 +2627,10 @@ class MeshPacket(google.protobuf.message.Message):
next_hop: builtins.int = ..., next_hop: builtins.int = ...,
relay_node: builtins.int = ..., relay_node: builtins.int = ...,
tx_after: builtins.int = ..., tx_after: builtins.int = ...,
transport_mechanism: global___MeshPacket.TransportMechanism.ValueType = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "next_hop", b"next_hop", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "relay_node", b"relay_node", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "tx_after", b"tx_after", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ... def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "next_hop", b"next_hop", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "relay_node", b"relay_node", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "transport_mechanism", b"transport_mechanism", "tx_after", b"tx_after", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["decoded", "encrypted"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["decoded", "encrypted"] | None: ...
global___MeshPacket = MeshPacket global___MeshPacket = MeshPacket
@@ -3493,9 +3584,17 @@ class Heartbeat(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
NONCE_FIELD_NUMBER: builtins.int
nonce: builtins.int
"""
The nonce of the heartbeat message
"""
def __init__( def __init__(
self, self,
*,
nonce: builtins.int = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["nonce", b"nonce"]) -> None: ...
global___Heartbeat = Heartbeat global___Heartbeat = Heartbeat

View File

File diff suppressed because one or more lines are too long

View File

@@ -824,6 +824,7 @@ class ModuleConfig(google.protobuf.message.Message):
ENABLED_FIELD_NUMBER: builtins.int ENABLED_FIELD_NUMBER: builtins.int
SENDER_FIELD_NUMBER: builtins.int SENDER_FIELD_NUMBER: builtins.int
SAVE_FIELD_NUMBER: builtins.int SAVE_FIELD_NUMBER: builtins.int
CLEAR_ON_REBOOT_FIELD_NUMBER: builtins.int
enabled: builtins.bool enabled: builtins.bool
""" """
Enable the Range Test Module Enable the Range Test Module
@@ -837,14 +838,20 @@ class ModuleConfig(google.protobuf.message.Message):
Bool value indicating that this node should save a RangeTest.csv file. Bool value indicating that this node should save a RangeTest.csv file.
ESP32 Only ESP32 Only
""" """
clear_on_reboot: builtins.bool
"""
Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
ESP32 Only
"""
def __init__( def __init__(
self, self,
*, *,
enabled: builtins.bool = ..., enabled: builtins.bool = ...,
sender: builtins.int = ..., sender: builtins.int = ...,
save: builtins.bool = ..., save: builtins.bool = ...,
clear_on_reboot: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "save", b"save", "sender", b"sender"]) -> None: ... def ClearField(self, field_name: typing.Literal["clear_on_reboot", b"clear_on_reboot", "enabled", b"enabled", "save", b"save", "sender", b"sender"]) -> None: ...
@typing.final @typing.final
class TelemetryConfig(google.protobuf.message.Message): class TelemetryConfig(google.protobuf.message.Message):

View File

File diff suppressed because one or more lines are too long

View File

@@ -199,6 +199,10 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
""" """
SEN5X PM SENSORS SEN5X PM SENSORS
""" """
TSL2561: _TelemetrySensorType.ValueType # 44
"""
TSL2561 light sensor
"""
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper): class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
""" """
@@ -381,6 +385,10 @@ SEN5X: TelemetrySensorType.ValueType # 43
""" """
SEN5X PM SENSORS SEN5X PM SENSORS
""" """
TSL2561: TelemetrySensorType.ValueType # 44
"""
TSL2561 light sensor
"""
global___TelemetrySensorType = TelemetrySensorType global___TelemetrySensorType = TelemetrySensorType
@typing.final @typing.final

1
nanopb

Submodule nanopb deleted from 4380dd9e94

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "meshtastic" name = "meshtastic"
version = "2.7.0" version = "2.7.2"
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"