mirror of
https://github.com/meshtastic/python.git
synced 2026-01-01 12:27:59 -05:00
Compare commits
18 Commits
2.6.1
...
key-verifi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54ecb3feb8 | ||
|
|
0deb98b4c6 | ||
|
|
04a0ff6322 | ||
|
|
b4764d3bc3 | ||
|
|
9281c4a335 | ||
|
|
3c2dd6f4ff | ||
|
|
8e48d141c8 | ||
|
|
622a435465 | ||
|
|
56680f8da6 | ||
|
|
321a960c13 | ||
|
|
4668852b0b | ||
|
|
c3973117c8 | ||
|
|
d456e4ce30 | ||
|
|
ec78f62992 | ||
|
|
dfc9547ffc | ||
|
|
ee0f73a20e | ||
|
|
2e73fe310c | ||
|
|
d4bc39153a |
@@ -472,6 +472,21 @@ def onConnected(interface):
|
|||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
interface.getNode(args.dest, False, **getNode_kwargs).removeIgnored(args.remove_ignored_node)
|
interface.getNode(args.dest, False, **getNode_kwargs).removeIgnored(args.remove_ignored_node)
|
||||||
|
|
||||||
|
if args.key_verification:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
interface.getNode(args.dest, False, **getNode_kwargs).keyVerification(args.key_verification)
|
||||||
|
|
||||||
|
if args.key_verification_number:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
interface.getNode(args.dest, False, **getNode_kwargs).keyVerificationNumber(args.key_verification_number, args.key_verification_nonce)
|
||||||
|
|
||||||
|
if args.key_verification_confirm:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
interface.getNode(args.dest, False, **getNode_kwargs).keyVerificationConfirm(args.key_verification_confirm)
|
||||||
|
|
||||||
if args.reset_nodedb:
|
if args.reset_nodedb:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
@@ -647,6 +662,7 @@ def onConnected(interface):
|
|||||||
print(f"Setting device owner to {configuration['owner']}")
|
print(f"Setting device owner to {configuration['owner']}")
|
||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(configuration["owner"])
|
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(configuration["owner"])
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "owner_short" in configuration:
|
if "owner_short" in configuration:
|
||||||
print(
|
print(
|
||||||
@@ -656,6 +672,7 @@ def onConnected(interface):
|
|||||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
||||||
long_name=None, short_name=configuration["owner_short"]
|
long_name=None, short_name=configuration["owner_short"]
|
||||||
)
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "ownerShort" in configuration:
|
if "ownerShort" in configuration:
|
||||||
print(
|
print(
|
||||||
@@ -665,14 +682,17 @@ def onConnected(interface):
|
|||||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
||||||
long_name=None, short_name=configuration["ownerShort"]
|
long_name=None, short_name=configuration["ownerShort"]
|
||||||
)
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "channel_url" in configuration:
|
if "channel_url" in configuration:
|
||||||
print("Setting channel url to", configuration["channel_url"])
|
print("Setting channel url to", configuration["channel_url"])
|
||||||
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channel_url"])
|
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channel_url"])
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "channelUrl" in configuration:
|
if "channelUrl" in configuration:
|
||||||
print("Setting channel url to", configuration["channelUrl"])
|
print("Setting channel url to", configuration["channelUrl"])
|
||||||
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channelUrl"])
|
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channelUrl"])
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "location" in configuration:
|
if "location" in configuration:
|
||||||
alt = 0
|
alt = 0
|
||||||
@@ -691,6 +711,7 @@ def onConnected(interface):
|
|||||||
print(f"Fixing longitude at {lon} degrees")
|
print(f"Fixing longitude at {lon} degrees")
|
||||||
print("Setting device position")
|
print("Setting device position")
|
||||||
interface.localNode.setFixedPosition(lat, lon, alt)
|
interface.localNode.setFixedPosition(lat, lon, alt)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "config" in configuration:
|
if "config" in configuration:
|
||||||
localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
|
localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
|
||||||
@@ -701,6 +722,7 @@ def onConnected(interface):
|
|||||||
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
|
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
|
||||||
meshtastic.util.camel_to_snake(section)
|
meshtastic.util.camel_to_snake(section)
|
||||||
)
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if "module_config" in configuration:
|
if "module_config" in configuration:
|
||||||
moduleConfig = interface.getNode(args.dest, **getNode_kwargs).moduleConfig
|
moduleConfig = interface.getNode(args.dest, **getNode_kwargs).moduleConfig
|
||||||
@@ -713,6 +735,7 @@ def onConnected(interface):
|
|||||||
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
|
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
|
||||||
meshtastic.util.camel_to_snake(section)
|
meshtastic.util.camel_to_snake(section)
|
||||||
)
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
|
interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
|
||||||
print("Writing modified configuration to device")
|
print("Writing modified configuration to device")
|
||||||
@@ -1824,6 +1847,22 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
|
|||||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||||
metavar="!xxxxxxxx"
|
metavar="!xxxxxxxx"
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--key-verification",
|
||||||
|
help="start key verification. "
|
||||||
|
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||||
|
metavar="!xxxxxxxx"
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--key-verification-number",
|
||||||
|
help="start key verification. "
|
||||||
|
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--key-verification-confirm",
|
||||||
|
help="start key verification. "
|
||||||
|
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--reset-nodedb",
|
"--reset-nodedb",
|
||||||
help="Tell the destination node to clear its list of nodes",
|
help="Tell the destination node to clear its list of nodes",
|
||||||
@@ -1953,6 +1992,12 @@ def initParser():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--key-verification-nonce",
|
||||||
|
help="start key verification. "
|
||||||
|
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||||
|
)
|
||||||
|
|
||||||
power_group = parser.add_argument_group(
|
power_group = parser.add_argument_group(
|
||||||
"Power Testing", "Options for power testing/logging."
|
"Power Testing", "Options for power testing/logging."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -83,6 +83,17 @@ class BLEInterface(MeshInterface):
|
|||||||
# Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
|
# Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
|
||||||
self._exit_handler = atexit.register(self.client.disconnect)
|
self._exit_handler = atexit.register(self.client.disconnect)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
rep = f"BLEInterface(address={self.client.address if self.client else None!r}"
|
||||||
|
if self.debugOut is not None:
|
||||||
|
rep += f", debugOut={self.debugOut!r}"
|
||||||
|
if self.noProto:
|
||||||
|
rep += ", noProto=True"
|
||||||
|
if self.noNodes:
|
||||||
|
rep += ", noNodes=True"
|
||||||
|
rep += ")"
|
||||||
|
return rep
|
||||||
|
|
||||||
def from_num_handler(self, _, b: bytes) -> None: # pylint: disable=C0116
|
def from_num_handler(self, _, b: bytes) -> None: # pylint: disable=C0116
|
||||||
"""Handle callbacks for fromnum notify.
|
"""Handle callbacks for fromnum notify.
|
||||||
Note: this method does not need to be async because it is just setting a bool.
|
Note: this method does not need to be async because it is just setting a bool.
|
||||||
@@ -163,7 +174,7 @@ class BLEInterface(MeshInterface):
|
|||||||
|
|
||||||
# Bleak docs recommend always doing a scan before connecting (even if we know addr)
|
# Bleak docs recommend always doing a scan before connecting (even if we know addr)
|
||||||
device = self.find_device(address)
|
device = self.find_device(address)
|
||||||
client = BLEClient(device.address, disconnected_callback=lambda _: self.close)
|
client = BLEClient(device.address, disconnected_callback=lambda _: self.close())
|
||||||
client.connect()
|
client.connect()
|
||||||
client.discover()
|
client.discover()
|
||||||
return client
|
return client
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ class Node:
|
|||||||
|
|
||||||
self.gotResponse = None
|
self.gotResponse = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
r = f"Node({self.iface!r}, 0x{self.nodeNum:08x}"
|
||||||
|
if self.noProto:
|
||||||
|
r += ", noProto=True"
|
||||||
|
if self._timeout.expireTimeout != 300:
|
||||||
|
r += ", timeout={self._timeout.expireTimeout!r}"
|
||||||
|
r += ")"
|
||||||
|
return r
|
||||||
|
|
||||||
def showChannels(self):
|
def showChannels(self):
|
||||||
"""Show human readable description of our channels."""
|
"""Show human readable description of our channels."""
|
||||||
print("Channels:")
|
print("Channels:")
|
||||||
@@ -761,6 +770,49 @@ class Node:
|
|||||||
onResponse = self.onAckNak
|
onResponse = self.onAckNak
|
||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
|
def keyVerification(self, nodeId: Union[int, str]):
|
||||||
|
if isinstance(nodeId, str):
|
||||||
|
if nodeId.startswith("!"):
|
||||||
|
nodeId = int(nodeId[1:], 16)
|
||||||
|
else:
|
||||||
|
nodeId = int(nodeId)
|
||||||
|
p = admin_pb2.KeyVerificationAdmin()
|
||||||
|
p.message_type = p.MessageType.INITIATE_VERIFICATION
|
||||||
|
p.remote_nodenum = nodeId
|
||||||
|
p.nonce = 0
|
||||||
|
a = admin_pb2.AdminMessage()
|
||||||
|
a.key_verification_admin.CopyFrom(p)
|
||||||
|
if self == self.iface.localNode:
|
||||||
|
onResponse = None
|
||||||
|
else:
|
||||||
|
onResponse = self.onAckNak
|
||||||
|
return self._sendAdmin(a, onResponse=onResponse)
|
||||||
|
def keyVerificationNumber(self, number: int, nonce: int,):
|
||||||
|
print(int(number))
|
||||||
|
print(int(nonce))
|
||||||
|
p = admin_pb2.KeyVerificationAdmin()
|
||||||
|
p.message_type = p.MessageType.PROVIDE_SECURITY_NUMBER
|
||||||
|
p.nonce = int(nonce)
|
||||||
|
p.security_number = int(number)
|
||||||
|
a = admin_pb2.AdminMessage()
|
||||||
|
a.key_verification_admin.CopyFrom(p)
|
||||||
|
if self == self.iface.localNode:
|
||||||
|
onResponse = None
|
||||||
|
else:
|
||||||
|
onResponse = self.onAckNak
|
||||||
|
return self._sendAdmin(a, onResponse=onResponse)
|
||||||
|
def keyVerificationConfirm(self, nonce: int,):
|
||||||
|
print(int(nonce))
|
||||||
|
p = admin_pb2.KeyVerificationAdmin()
|
||||||
|
p.message_type = p.MessageType.DO_VERIFY
|
||||||
|
p.nonce = int(nonce)
|
||||||
|
a = admin_pb2.AdminMessage()
|
||||||
|
a.key_verification_admin.CopyFrom(p)
|
||||||
|
if self == self.iface.localNode:
|
||||||
|
onResponse = None
|
||||||
|
else:
|
||||||
|
onResponse = self.onAckNak
|
||||||
|
return self._sendAdmin(a, onResponse=onResponse)
|
||||||
def resetNodeDb(self):
|
def resetNodeDb(self):
|
||||||
"""Tell the node to reset its list of nodes."""
|
"""Tell the node to reset its list of nodes."""
|
||||||
self.ensureSessionKey()
|
self.ensureSessionKey()
|
||||||
@@ -983,7 +1035,7 @@ class Node:
|
|||||||
p,
|
p,
|
||||||
self.nodeNum,
|
self.nodeNum,
|
||||||
portNum=portnums_pb2.PortNum.ADMIN_APP,
|
portNum=portnums_pb2.PortNum.ADMIN_APP,
|
||||||
wantAck=False,
|
wantAck=True,
|
||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=adminIndex,
|
channelIndex=adminIndex,
|
||||||
|
|||||||
44
meshtastic/protobuf/config_pb2.py
generated
44
meshtastic/protobuf/config_pb2.py
generated
File diff suppressed because one or more lines are too long
20
meshtastic/protobuf/config_pb2.pyi
generated
20
meshtastic/protobuf/config_pb2.pyi
generated
@@ -956,20 +956,24 @@ class Config(google.protobuf.message.Message):
|
|||||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||||
OLED_AUTO: Config.DisplayConfig._OledType.ValueType # 0
|
OLED_AUTO: Config.DisplayConfig._OledType.ValueType # 0
|
||||||
"""
|
"""
|
||||||
Default / Auto
|
Default / Autodetect
|
||||||
"""
|
"""
|
||||||
OLED_SSD1306: Config.DisplayConfig._OledType.ValueType # 1
|
OLED_SSD1306: Config.DisplayConfig._OledType.ValueType # 1
|
||||||
"""
|
"""
|
||||||
Default / Auto
|
Default / Autodetect
|
||||||
"""
|
"""
|
||||||
OLED_SH1106: Config.DisplayConfig._OledType.ValueType # 2
|
OLED_SH1106: Config.DisplayConfig._OledType.ValueType # 2
|
||||||
"""
|
"""
|
||||||
Default / Auto
|
Default / Autodetect
|
||||||
"""
|
"""
|
||||||
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
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
class OledType(_OledType, metaclass=_OledTypeEnumTypeWrapper):
|
class OledType(_OledType, metaclass=_OledTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -978,20 +982,24 @@ class Config(google.protobuf.message.Message):
|
|||||||
|
|
||||||
OLED_AUTO: Config.DisplayConfig.OledType.ValueType # 0
|
OLED_AUTO: Config.DisplayConfig.OledType.ValueType # 0
|
||||||
"""
|
"""
|
||||||
Default / Auto
|
Default / Autodetect
|
||||||
"""
|
"""
|
||||||
OLED_SSD1306: Config.DisplayConfig.OledType.ValueType # 1
|
OLED_SSD1306: Config.DisplayConfig.OledType.ValueType # 1
|
||||||
"""
|
"""
|
||||||
Default / Auto
|
Default / Autodetect
|
||||||
"""
|
"""
|
||||||
OLED_SH1106: Config.DisplayConfig.OledType.ValueType # 2
|
OLED_SH1106: Config.DisplayConfig.OledType.ValueType # 2
|
||||||
"""
|
"""
|
||||||
Default / Auto
|
Default / Autodetect
|
||||||
"""
|
"""
|
||||||
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
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
class _DisplayMode:
|
class _DisplayMode:
|
||||||
ValueType = typing.NewType("ValueType", builtins.int)
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
|
|||||||
16
meshtastic/protobuf/mesh_pb2.py
generated
16
meshtastic/protobuf/mesh_pb2.py
generated
File diff suppressed because one or more lines are too long
56
meshtastic/protobuf/mesh_pb2.pyi
generated
56
meshtastic/protobuf/mesh_pb2.pyi
generated
@@ -421,6 +421,26 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
Heltec HRI-3621 industrial probe
|
Heltec HRI-3621 industrial probe
|
||||||
"""
|
"""
|
||||||
|
RESERVED_FRIED_CHICKEN: _HardwareModel.ValueType # 93
|
||||||
|
"""
|
||||||
|
Reserved Fried Chicken ID for future use
|
||||||
|
"""
|
||||||
|
HELTEC_MESH_POCKET: _HardwareModel.ValueType # 94
|
||||||
|
"""
|
||||||
|
Heltec Magnetic Power Bank with Meshtastic compatible
|
||||||
|
"""
|
||||||
|
SEEED_SOLAR_NODE: _HardwareModel.ValueType # 95
|
||||||
|
"""
|
||||||
|
Seeed Solar Node
|
||||||
|
"""
|
||||||
|
NOMADSTAR_METEOR_PRO: _HardwareModel.ValueType # 96
|
||||||
|
"""
|
||||||
|
NomadStar Meteor Pro https://nomadstar.ch/
|
||||||
|
"""
|
||||||
|
CROWPANEL: _HardwareModel.ValueType # 97
|
||||||
|
"""
|
||||||
|
Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin
|
||||||
|
"""
|
||||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -825,6 +845,26 @@ HELTEC_SENSOR_HUB: HardwareModel.ValueType # 92
|
|||||||
"""
|
"""
|
||||||
Heltec HRI-3621 industrial probe
|
Heltec HRI-3621 industrial probe
|
||||||
"""
|
"""
|
||||||
|
RESERVED_FRIED_CHICKEN: HardwareModel.ValueType # 93
|
||||||
|
"""
|
||||||
|
Reserved Fried Chicken ID for future use
|
||||||
|
"""
|
||||||
|
HELTEC_MESH_POCKET: HardwareModel.ValueType # 94
|
||||||
|
"""
|
||||||
|
Heltec Magnetic Power Bank with Meshtastic compatible
|
||||||
|
"""
|
||||||
|
SEEED_SOLAR_NODE: HardwareModel.ValueType # 95
|
||||||
|
"""
|
||||||
|
Seeed Solar Node
|
||||||
|
"""
|
||||||
|
NOMADSTAR_METEOR_PRO: HardwareModel.ValueType # 96
|
||||||
|
"""
|
||||||
|
NomadStar Meteor Pro https://nomadstar.ch/
|
||||||
|
"""
|
||||||
|
CROWPANEL: HardwareModel.ValueType # 97
|
||||||
|
"""
|
||||||
|
Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin
|
||||||
|
"""
|
||||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1068,6 +1108,14 @@ class _ExcludedModulesEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper
|
|||||||
"""
|
"""
|
||||||
Paxcounter module
|
Paxcounter module
|
||||||
"""
|
"""
|
||||||
|
BLUETOOTH_CONFIG: _ExcludedModules.ValueType # 8192
|
||||||
|
"""
|
||||||
|
Bluetooth config (not technically a module, but used to indicate bluetooth capabilities)
|
||||||
|
"""
|
||||||
|
NETWORK_CONFIG: _ExcludedModules.ValueType # 16384
|
||||||
|
"""
|
||||||
|
Network config (not technically a module, but used to indicate network capabilities)
|
||||||
|
"""
|
||||||
|
|
||||||
class ExcludedModules(_ExcludedModules, metaclass=_ExcludedModulesEnumTypeWrapper):
|
class ExcludedModules(_ExcludedModules, metaclass=_ExcludedModulesEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -1132,6 +1180,14 @@ PAXCOUNTER_CONFIG: ExcludedModules.ValueType # 4096
|
|||||||
"""
|
"""
|
||||||
Paxcounter module
|
Paxcounter module
|
||||||
"""
|
"""
|
||||||
|
BLUETOOTH_CONFIG: ExcludedModules.ValueType # 8192
|
||||||
|
"""
|
||||||
|
Bluetooth config (not technically a module, but used to indicate bluetooth capabilities)
|
||||||
|
"""
|
||||||
|
NETWORK_CONFIG: ExcludedModules.ValueType # 16384
|
||||||
|
"""
|
||||||
|
Network config (not technically a module, but used to indicate network capabilities)
|
||||||
|
"""
|
||||||
global___ExcludedModules = ExcludedModules
|
global___ExcludedModules = ExcludedModules
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
|
|||||||
44
meshtastic/protobuf/module_config_pb2.py
generated
44
meshtastic/protobuf/module_config_pb2.py
generated
File diff suppressed because one or more lines are too long
8
meshtastic/protobuf/module_config_pb2.pyi
generated
8
meshtastic/protobuf/module_config_pb2.pyi
generated
@@ -554,6 +554,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
"""NMEA messages specifically tailored for CalTopo"""
|
"""NMEA messages specifically tailored for CalTopo"""
|
||||||
WS85: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 6
|
WS85: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 6
|
||||||
"""Ecowitt WS85 weather station"""
|
"""Ecowitt WS85 weather station"""
|
||||||
|
VE_DIRECT: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 7
|
||||||
|
"""VE.Direct is a serial protocol used by Victron Energy products
|
||||||
|
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||||
|
"""
|
||||||
|
|
||||||
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -569,6 +573,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
"""NMEA messages specifically tailored for CalTopo"""
|
"""NMEA messages specifically tailored for CalTopo"""
|
||||||
WS85: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 6
|
WS85: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 6
|
||||||
"""Ecowitt WS85 weather station"""
|
"""Ecowitt WS85 weather station"""
|
||||||
|
VE_DIRECT: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 7
|
||||||
|
"""VE.Direct is a serial protocol used by Victron Energy products
|
||||||
|
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||||
|
"""
|
||||||
|
|
||||||
ENABLED_FIELD_NUMBER: builtins.int
|
ENABLED_FIELD_NUMBER: builtins.int
|
||||||
ECHO_FIELD_NUMBER: builtins.int
|
ECHO_FIELD_NUMBER: builtins.int
|
||||||
|
|||||||
2
meshtastic/protobuf/telemetry_pb2.pyi
generated
2
meshtastic/protobuf/telemetry_pb2.pyi
generated
@@ -723,7 +723,7 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
co2: builtins.int
|
co2: builtins.int
|
||||||
"""
|
"""
|
||||||
10.0um Particle Count
|
CO2 concentration in ppm
|
||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -46,12 +46,7 @@ class SerialInterface(StreamInterface):
|
|||||||
|
|
||||||
logging.debug(f"Connecting to {self.devPath}")
|
logging.debug(f"Connecting to {self.devPath}")
|
||||||
|
|
||||||
# set port to None to prevent automatically opening
|
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
|
||||||
self.stream = serial.Serial(
|
|
||||||
port=None, baudrate=115200, exclusive=True, timeout=0.5, write_timeout=0
|
|
||||||
)
|
|
||||||
|
|
||||||
# first we need to clear HUPCL (UNIX) or clear RTS/DTR (Windows) so the device will not reboot based on RTS and/or DTR
|
|
||||||
# see https://github.com/pyserial/pyserial/issues/124
|
# see https://github.com/pyserial/pyserial/issues/124
|
||||||
if platform.system() != "Windows":
|
if platform.system() != "Windows":
|
||||||
with open(self.devPath, encoding="utf8") as f:
|
with open(self.devPath, encoding="utf8") as f:
|
||||||
@@ -60,14 +55,10 @@ class SerialInterface(StreamInterface):
|
|||||||
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
|
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
|
||||||
f.close()
|
f.close()
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else:
|
|
||||||
self.stream.rts = 0
|
|
||||||
self.stream.dtr = 0
|
|
||||||
|
|
||||||
# set proper port and open now that we've worked-around RTS/DTR issues
|
|
||||||
self.stream.port = self.devPath
|
|
||||||
self.stream.open()
|
|
||||||
|
|
||||||
|
self.stream = serial.Serial(
|
||||||
|
self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0
|
||||||
|
)
|
||||||
self.stream.flush() # type: ignore[attr-defined]
|
self.stream.flush() # type: ignore[attr-defined]
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
@@ -75,6 +66,17 @@ class SerialInterface(StreamInterface):
|
|||||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
rep = f"SerialInterface(devPath={self.devPath!r}"
|
||||||
|
if hasattr(self, 'debugOut') and self.debugOut is not None:
|
||||||
|
rep += f", debugOut={self.debugOut!r}"
|
||||||
|
if self.noProto:
|
||||||
|
rep += ", noProto=True"
|
||||||
|
if hasattr(self, 'noNodes') and self.noNodes:
|
||||||
|
rep += ", noNodes=True"
|
||||||
|
rep += ")"
|
||||||
|
return rep
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Close a connection to the device"""
|
"""Close a connection to the device"""
|
||||||
if self.stream: # Stream can be null if we were already closed
|
if self.stream: # Stream can be null if we were already closed
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"""TCPInterface class for interfacing with http endpoint
|
"""TCPInterface class for interfacing with http endpoint
|
||||||
"""
|
"""
|
||||||
# pylint: disable=R0917
|
# pylint: disable=R0917
|
||||||
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from typing import Optional, cast
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from meshtastic.stream_interface import StreamInterface
|
from meshtastic.stream_interface import StreamInterface
|
||||||
|
|
||||||
@@ -35,52 +37,78 @@ class TCPInterface(StreamInterface):
|
|||||||
self.socket: Optional[socket.socket] = None
|
self.socket: Optional[socket.socket] = None
|
||||||
|
|
||||||
if connectNow:
|
if connectNow:
|
||||||
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
|
self.myConnect()
|
||||||
server_address: tuple[str, int] = (hostname, portNumber)
|
|
||||||
sock: Optional[socket.socket] = socket.create_connection(server_address)
|
|
||||||
self.socket = sock
|
|
||||||
else:
|
else:
|
||||||
self.socket = None
|
self.socket = None
|
||||||
|
|
||||||
StreamInterface.__init__(
|
super().__init__(debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes)
|
||||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
|
||||||
)
|
def __repr__(self):
|
||||||
|
rep = f"TCPInterface({self.hostname!r}"
|
||||||
|
if self.debugOut is not None:
|
||||||
|
rep += f", debugOut={self.debugOut!r}"
|
||||||
|
if self.noProto:
|
||||||
|
rep += ", noProto=True"
|
||||||
|
if self.socket is None:
|
||||||
|
rep += ", connectNow=False"
|
||||||
|
if self.portNumber != DEFAULT_TCP_PORT:
|
||||||
|
rep += f", portNumber={self.portNumber!r}"
|
||||||
|
if self.noNodes:
|
||||||
|
rep += ", noNodes=True"
|
||||||
|
rep += ")"
|
||||||
|
return rep
|
||||||
|
|
||||||
def _socket_shutdown(self) -> None:
|
def _socket_shutdown(self) -> None:
|
||||||
"""Shutdown the socket.
|
"""Shutdown the socket.
|
||||||
Note: Broke out this line so the exception could be unit tested.
|
Note: Broke out this line so the exception could be unit tested.
|
||||||
"""
|
"""
|
||||||
if self.socket: #mian: please check that this should be "if self.socket:"
|
if self.socket is not None:
|
||||||
cast(socket.socket, self.socket).shutdown(socket.SHUT_RDWR)
|
self.socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
|
||||||
def myConnect(self) -> None:
|
def myConnect(self) -> None:
|
||||||
"""Connect to socket"""
|
"""Connect to socket"""
|
||||||
server_address: tuple[str, int] = (self.hostname, self.portNumber)
|
logging.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe]
|
||||||
sock: Optional[socket.socket] = socket.create_connection(server_address)
|
server_address = (self.hostname, self.portNumber)
|
||||||
self.socket = sock
|
self.socket = socket.create_connection(server_address)
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Close a connection to the device"""
|
"""Close a connection to the device"""
|
||||||
logging.debug("Closing TCP stream")
|
logging.debug("Closing TCP stream")
|
||||||
StreamInterface.close(self)
|
super().close()
|
||||||
# Sometimes the socket read might be blocked in the reader thread.
|
# Sometimes the socket read might be blocked in the reader thread.
|
||||||
# Therefore we force the shutdown by closing the socket here
|
# Therefore we force the shutdown by closing the socket here
|
||||||
self._wantExit: bool = True
|
self._wantExit = True
|
||||||
if not self.socket is None:
|
if self.socket is not None:
|
||||||
try:
|
with contextlib.suppress(Exception): # Ignore errors in shutdown, because we might have a race with the server
|
||||||
self._socket_shutdown()
|
self._socket_shutdown()
|
||||||
except:
|
|
||||||
pass # Ignore errors in shutdown, because we might have a race with the server
|
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
|
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
def _writeBytes(self, b: bytes) -> None:
|
def _writeBytes(self, b: bytes) -> None:
|
||||||
"""Write an array of bytes to our stream and flush"""
|
"""Write an array of bytes to our stream and flush"""
|
||||||
if self.socket:
|
if self.socket is not None:
|
||||||
self.socket.send(b)
|
self.socket.send(b)
|
||||||
|
|
||||||
def _readBytes(self, length) -> Optional[bytes]:
|
def _readBytes(self, length) -> Optional[bytes]:
|
||||||
"""Read an array of bytes from our stream"""
|
"""Read an array of bytes from our stream"""
|
||||||
if self.socket:
|
if self.socket is not None:
|
||||||
return self.socket.recv(length)
|
data = self.socket.recv(length)
|
||||||
else:
|
# empty byte indicates a disconnected socket,
|
||||||
return None
|
# we need to handle it to avoid an infinite loop reading from null socket
|
||||||
|
if data == b'':
|
||||||
|
logging.debug("dead socket, re-connecting")
|
||||||
|
# cleanup and reconnect socket without breaking reader thread
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
self._socket_shutdown()
|
||||||
|
self.socket.close()
|
||||||
|
self.socket = None
|
||||||
|
time.sleep(1)
|
||||||
|
self.myConnect()
|
||||||
|
self._startConfig()
|
||||||
|
return None
|
||||||
|
return data
|
||||||
|
|
||||||
|
# no socket, break reader thread
|
||||||
|
self._wantExit = True
|
||||||
|
return None
|
||||||
|
|||||||
@@ -198,9 +198,9 @@ class Timeout:
|
|||||||
self.sleepInterval: float = 0.1
|
self.sleepInterval: float = 0.1
|
||||||
self.expireTimeout: int = maxSecs
|
self.expireTimeout: int = maxSecs
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self, expireTimeout=None):
|
||||||
"""Restart the waitForSet timer"""
|
"""Restart the waitForSet timer"""
|
||||||
self.expireTime = time.time() + self.expireTimeout
|
self.expireTime = time.time() + (self.expireTimeout if expireTimeout is None else expireTimeout)
|
||||||
|
|
||||||
def waitForSet(self, target, attrs=()) -> bool:
|
def waitForSet(self, target, attrs=()) -> bool:
|
||||||
"""Block until the specified attributes are set. Returns True if config has been received."""
|
"""Block until the specified attributes are set. Returns True if config has been received."""
|
||||||
@@ -225,8 +225,7 @@ class Timeout:
|
|||||||
|
|
||||||
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute") -> bool:
|
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute") -> bool:
|
||||||
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
|
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
|
||||||
self.expireTimeout *= waitFactor
|
self.reset(self.expireTimeout * waitFactor)
|
||||||
self.reset()
|
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
if getattr(acknowledgment, attr, None):
|
if getattr(acknowledgment, attr, None):
|
||||||
acknowledgment.reset()
|
acknowledgment.reset()
|
||||||
|
|||||||
Submodule protobufs updated: f00e96f12d...078ac8dfbe
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "meshtastic"
|
name = "meshtastic"
|
||||||
version = "2.6.1"
|
version = "2.6.3"
|
||||||
description = "Python API & client shell for talking to Meshtastic devices"
|
description = "Python API & client shell for talking to Meshtastic devices"
|
||||||
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|||||||
Reference in New Issue
Block a user