From ac5d729cdf9872e155f46712bd96395f44fae7fc Mon Sep 17 00:00:00 2001 From: Jm Casler Date: Wed, 29 Dec 2021 14:18:34 -0800 Subject: [PATCH] Bumped to 1.2.47. --- docs/meshtastic/admin_pb2.html | 1118 ++ docs/meshtastic/apponly_pb2.html | 707 ++ docs/meshtastic/ble.html | 53 + docs/meshtastic/ble_interface.html | 212 + docs/meshtastic/channel_pb2.html | 1741 +++ docs/meshtastic/deviceonly_pb2.html | 2241 ++++ .../environmental_measurement_pb2.html | 746 ++ docs/meshtastic/globals.html | 380 + docs/meshtastic/index.html | 538 + docs/meshtastic/mesh_interface.html | 1851 +++ docs/meshtastic/mesh_pb2.html | 9935 +++++++++++++++++ docs/meshtastic/mqtt_pb2.html | 759 ++ docs/meshtastic/node.html | 1280 +++ docs/meshtastic/portnums_pb2.html | 190 + docs/meshtastic/radioconfig_pb2.html | 1604 +++ docs/meshtastic/remote_hardware.html | 324 + docs/meshtastic/remote_hardware_pb2.html | 822 ++ docs/meshtastic/serial_interface.html | 254 + docs/meshtastic/storeforward_pb2.html | 1165 ++ docs/meshtastic/stream_interface.html | 519 + docs/meshtastic/tcp_interface.html | 251 + docs/meshtastic/test.html | 544 + docs/meshtastic/tests/conftest.html | 199 + docs/meshtastic/tests/index.html | 142 + docs/meshtastic/tests/test_ble_interface.html | 95 + docs/meshtastic/tests/test_examples.html | 132 + docs/meshtastic/tests/test_globals.html | 130 + docs/meshtastic/tests/test_int.html | 177 + docs/meshtastic/tests/test_main.html | 4552 ++++++++ .../meshtastic/tests/test_mesh_interface.html | 1290 +++ docs/meshtastic/tests/test_node.html | 4143 +++++++ .../tests/test_remote_hardware.html | 304 + .../tests/test_serial_interface.html | 186 + docs/meshtastic/tests/test_smoke1.html | 1822 +++ docs/meshtastic/tests/test_smoke2.html | 127 + docs/meshtastic/tests/test_smoke_wifi.html | 115 + .../tests/test_stream_interface.html | 246 + docs/meshtastic/tests/test_tcp_interface.html | 120 + docs/meshtastic/tests/test_util.html | 519 + docs/meshtastic/tunnel.html | 615 + docs/meshtastic/util.html | 688 ++ meshtastic/admin_pb2.py | 37 +- meshtastic/mesh_pb2.py | 116 +- meshtastic/radioconfig_pb2.py | 55 +- meshtastic/storeforward_pb2.py | 119 +- 45 files changed, 43087 insertions(+), 76 deletions(-) create mode 100644 docs/meshtastic/admin_pb2.html create mode 100644 docs/meshtastic/apponly_pb2.html create mode 100644 docs/meshtastic/ble.html create mode 100644 docs/meshtastic/ble_interface.html create mode 100644 docs/meshtastic/channel_pb2.html create mode 100644 docs/meshtastic/deviceonly_pb2.html create mode 100644 docs/meshtastic/environmental_measurement_pb2.html create mode 100644 docs/meshtastic/globals.html create mode 100644 docs/meshtastic/index.html create mode 100644 docs/meshtastic/mesh_interface.html create mode 100644 docs/meshtastic/mesh_pb2.html create mode 100644 docs/meshtastic/mqtt_pb2.html create mode 100644 docs/meshtastic/node.html create mode 100644 docs/meshtastic/portnums_pb2.html create mode 100644 docs/meshtastic/radioconfig_pb2.html create mode 100644 docs/meshtastic/remote_hardware.html create mode 100644 docs/meshtastic/remote_hardware_pb2.html create mode 100644 docs/meshtastic/serial_interface.html create mode 100644 docs/meshtastic/storeforward_pb2.html create mode 100644 docs/meshtastic/stream_interface.html create mode 100644 docs/meshtastic/tcp_interface.html create mode 100644 docs/meshtastic/test.html create mode 100644 docs/meshtastic/tests/conftest.html create mode 100644 docs/meshtastic/tests/index.html create mode 100644 docs/meshtastic/tests/test_ble_interface.html create mode 100644 docs/meshtastic/tests/test_examples.html create mode 100644 docs/meshtastic/tests/test_globals.html create mode 100644 docs/meshtastic/tests/test_int.html create mode 100644 docs/meshtastic/tests/test_main.html create mode 100644 docs/meshtastic/tests/test_mesh_interface.html create mode 100644 docs/meshtastic/tests/test_node.html create mode 100644 docs/meshtastic/tests/test_remote_hardware.html create mode 100644 docs/meshtastic/tests/test_serial_interface.html create mode 100644 docs/meshtastic/tests/test_smoke1.html create mode 100644 docs/meshtastic/tests/test_smoke2.html create mode 100644 docs/meshtastic/tests/test_smoke_wifi.html create mode 100644 docs/meshtastic/tests/test_stream_interface.html create mode 100644 docs/meshtastic/tests/test_tcp_interface.html create mode 100644 docs/meshtastic/tests/test_util.html create mode 100644 docs/meshtastic/tunnel.html create mode 100644 docs/meshtastic/util.html diff --git a/docs/meshtastic/admin_pb2.html b/docs/meshtastic/admin_pb2.html new file mode 100644 index 0000000..1c05faa --- /dev/null +++ b/docs/meshtastic/admin_pb2.html @@ -0,0 +1,1118 @@ + + + + + + +meshtastic.admin_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.admin_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: admin.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import channel_pb2 as channel__pb2
+from . import radioconfig_pb2 as radioconfig__pb2
+from . import mesh_pb2 as mesh__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='admin.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\x11radioconfig.proto\x1a\nmesh.proto\"\xbd\x03\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18  \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+  ,
+  dependencies=[channel__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,])
+
+
+
+
+_ADMINMESSAGE = _descriptor.Descriptor(
+  name='AdminMessage',
+  full_name='AdminMessage',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='set_radio', full_name='AdminMessage.set_radio', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='set_owner', full_name='AdminMessage.set_owner', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='set_channel', full_name='AdminMessage.set_channel', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get_radio_request', full_name='AdminMessage.get_radio_request', index=3,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get_radio_response', full_name='AdminMessage.get_radio_response', index=4,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get_channel_request', full_name='AdminMessage.get_channel_request', index=5,
+      number=6, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get_channel_response', full_name='AdminMessage.get_channel_response', index=6,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get_owner_request', full_name='AdminMessage.get_owner_request', index=7,
+      number=8, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='get_owner_response', full_name='AdminMessage.get_owner_response', index=8,
+      number=9, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=9,
+      number=32, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=10,
+      number=33, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='exit_simulator', full_name='AdminMessage.exit_simulator', index=11,
+      number=34, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=12,
+      number=35, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='variant', full_name='AdminMessage.variant',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=62,
+  serialized_end=507,
+)
+
+_ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG
+_ADMINMESSAGE.fields_by_name['set_owner'].message_type = mesh__pb2._USER
+_ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL
+_ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG
+_ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL
+_ADMINMESSAGE.fields_by_name['get_owner_response'].message_type = mesh__pb2._USER
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['set_radio'])
+_ADMINMESSAGE.fields_by_name['set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['set_owner'])
+_ADMINMESSAGE.fields_by_name['set_owner'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['set_channel'])
+_ADMINMESSAGE.fields_by_name['set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['get_radio_request'])
+_ADMINMESSAGE.fields_by_name['get_radio_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['get_radio_response'])
+_ADMINMESSAGE.fields_by_name['get_radio_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['get_channel_request'])
+_ADMINMESSAGE.fields_by_name['get_channel_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['get_channel_response'])
+_ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['get_owner_request'])
+_ADMINMESSAGE.fields_by_name['get_owner_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['get_owner_response'])
+_ADMINMESSAGE.fields_by_name['get_owner_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['confirm_set_channel'])
+_ADMINMESSAGE.fields_by_name['confirm_set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['confirm_set_radio'])
+_ADMINMESSAGE.fields_by_name['confirm_set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['exit_simulator'])
+_ADMINMESSAGE.fields_by_name['exit_simulator'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
+  _ADMINMESSAGE.fields_by_name['reboot_seconds'])
+_ADMINMESSAGE.fields_by_name['reboot_seconds'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
+DESCRIPTOR.message_types_by_name['AdminMessage'] = _ADMINMESSAGE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_message.Message,), {
+  'DESCRIPTOR' : _ADMINMESSAGE,
+  '__module__' : 'admin_pb2'
+  # @@protoc_insertion_point(class_scope:AdminMessage)
+  })
+_sym_db.RegisterMessage(AdminMessage)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class AdminMessage +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CONFIRM_SET_CHANNEL_FIELD_NUMBER
+
+
+
+
var CONFIRM_SET_RADIO_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var EXIT_SIMULATOR_FIELD_NUMBER
+
+
+
+
var GET_CHANNEL_REQUEST_FIELD_NUMBER
+
+
+
+
var GET_CHANNEL_RESPONSE_FIELD_NUMBER
+
+
+
+
var GET_OWNER_REQUEST_FIELD_NUMBER
+
+
+
+
var GET_OWNER_RESPONSE_FIELD_NUMBER
+
+
+
+
var GET_RADIO_REQUEST_FIELD_NUMBER
+
+
+
+
var GET_RADIO_RESPONSE_FIELD_NUMBER
+
+
+
+
var REBOOT_SECONDS_FIELD_NUMBER
+
+
+
+
var SET_CHANNEL_FIELD_NUMBER
+
+
+
+
var SET_OWNER_FIELD_NUMBER
+
+
+
+
var SET_RADIO_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var confirm_set_channel
+
+

Getter for confirm_set_channel.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var confirm_set_radio
+
+

Getter for confirm_set_radio.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var exit_simulator
+
+

Getter for exit_simulator.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_channel_request
+
+

Getter for get_channel_request.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_channel_response
+
+

Getter for get_channel_response.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var get_owner_request
+
+

Getter for get_owner_request.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_owner_response
+
+

Getter for get_owner_response.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var get_radio_request
+
+

Getter for get_radio_request.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_radio_response
+
+

Getter for get_radio_response.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var reboot_seconds
+
+

Getter for reboot_seconds.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var set_channel
+
+

Getter for set_channel.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var set_owner
+
+

Getter for set_owner.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var set_radio
+
+

Getter for set_radio.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/apponly_pb2.html b/docs/meshtastic/apponly_pb2.html new file mode 100644 index 0000000..1180130 --- /dev/null +++ b/docs/meshtastic/apponly_pb2.html @@ -0,0 +1,707 @@ + + + + + + +meshtastic.apponly_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.apponly_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: apponly.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import channel_pb2 as channel__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='apponly.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\rapponly.proto\x1a\rchannel.proto\"0\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettingsBI\n\x13\x63om.geeksville.meshB\rAppOnlyProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+  ,
+  dependencies=[channel__pb2.DESCRIPTOR,])
+
+
+
+
+_CHANNELSET = _descriptor.Descriptor(
+  name='ChannelSet',
+  full_name='ChannelSet',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='settings', full_name='ChannelSet.settings', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=32,
+  serialized_end=80,
+)
+
+_CHANNELSET.fields_by_name['settings'].message_type = channel__pb2._CHANNELSETTINGS
+DESCRIPTOR.message_types_by_name['ChannelSet'] = _CHANNELSET
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Message,), {
+  'DESCRIPTOR' : _CHANNELSET,
+  '__module__' : 'apponly_pb2'
+  # @@protoc_insertion_point(class_scope:ChannelSet)
+  })
+_sym_db.RegisterMessage(ChannelSet)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ChannelSet +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var SETTINGS_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var settings
+
+

Getter for settings.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/ble.html b/docs/meshtastic/ble.html new file mode 100644 index 0000000..154731f --- /dev/null +++ b/docs/meshtastic/ble.html @@ -0,0 +1,53 @@ + + + + + + +meshtastic.ble API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.ble

+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/ble_interface.html b/docs/meshtastic/ble_interface.html new file mode 100644 index 0000000..7bf14a3 --- /dev/null +++ b/docs/meshtastic/ble_interface.html @@ -0,0 +1,212 @@ + + + + + + +meshtastic.ble_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.ble_interface

+
+
+

Bluetooth interface

+
+ +Expand source code + +
"""Bluetooth interface
+"""
+import logging
+import pygatt
+
+
+from .mesh_interface import MeshInterface
+
+# Our standard BLE characteristics
+TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
+FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
+FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
+
+
+class BLEInterface(MeshInterface):
+    """A not quite ready - FIXME - BLE interface to devices"""
+
+    def __init__(self, address, noProto=False, debugOut=None):
+        self.address = address
+        if not noProto:
+            self.adapter = pygatt.GATTToolBackend()  # BGAPIBackend()
+            self.adapter.start()
+            logging.debug(f"Connecting to {self.address}")
+            self.device = self.adapter.connect(address)
+        else:
+            self.adapter = None
+            self.device = None
+        logging.debug("Connected to device")
+        # fromradio = self.device.char_read(FROMRADIO_UUID)
+        MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
+
+        self._readFromRadio()  # read the initial responses
+
+        def handle_data(handle, data):
+            self._handleFromRadio(data)
+
+        if self.device:
+            self.device.subscribe(FROMNUM_UUID, callback=handle_data)
+
+    def _sendToRadioImpl(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        #logging.debug(f"Sending: {stripnl(toRadio)}")
+        b = toRadio.SerializeToString()
+        self.device.char_write(TORADIO_UUID, b)
+
+    def close(self):
+        MeshInterface.close(self)
+        if self.adapter:
+            self.adapter.stop()
+
+    def _readFromRadio(self):
+        if not self.noProto:
+            wasEmpty = False
+            while not wasEmpty:
+                if self.device:
+                    b = self.device.char_read(FROMRADIO_UUID)
+                    wasEmpty = len(b) == 0
+                    if not wasEmpty:
+                        self._handleFromRadio(b)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class BLEInterface +(address, noProto=False, debugOut=None) +
+
+

A not quite ready - FIXME - BLE interface to devices

+

Constructor

+

Keyword Arguments: +noProto – If True, don't try to run our protocol on the +link - just be a dumb serial client.

+
+ +Expand source code + +
class BLEInterface(MeshInterface):
+    """A not quite ready - FIXME - BLE interface to devices"""
+
+    def __init__(self, address, noProto=False, debugOut=None):
+        self.address = address
+        if not noProto:
+            self.adapter = pygatt.GATTToolBackend()  # BGAPIBackend()
+            self.adapter.start()
+            logging.debug(f"Connecting to {self.address}")
+            self.device = self.adapter.connect(address)
+        else:
+            self.adapter = None
+            self.device = None
+        logging.debug("Connected to device")
+        # fromradio = self.device.char_read(FROMRADIO_UUID)
+        MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
+
+        self._readFromRadio()  # read the initial responses
+
+        def handle_data(handle, data):
+            self._handleFromRadio(data)
+
+        if self.device:
+            self.device.subscribe(FROMNUM_UUID, callback=handle_data)
+
+    def _sendToRadioImpl(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        #logging.debug(f"Sending: {stripnl(toRadio)}")
+        b = toRadio.SerializeToString()
+        self.device.char_write(TORADIO_UUID, b)
+
+    def close(self):
+        MeshInterface.close(self)
+        if self.adapter:
+            self.adapter.stop()
+
+    def _readFromRadio(self):
+        if not self.noProto:
+            wasEmpty = False
+            while not wasEmpty:
+                if self.device:
+                    b = self.device.char_read(FROMRADIO_UUID)
+                    wasEmpty = len(b) == 0
+                    if not wasEmpty:
+                        self._handleFromRadio(b)
+
+

Ancestors

+ +

Inherited members

+ +
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/channel_pb2.html b/docs/meshtastic/channel_pb2.html new file mode 100644 index 0000000..f717c6d --- /dev/null +++ b/docs/meshtastic/channel_pb2.html @@ -0,0 +1,1741 @@ + + + + + + +meshtastic.channel_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.channel_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: channel.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='channel.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\rChannelProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\rchannel.proto\"\x91\x03\n\x0f\x43hannelSettings\x12\x10\n\x08tx_power\x18\x01 \x01(\x05\x12\x32\n\x0cmodem_config\x18\x03 \x01(\x0e\x32\x1c.ChannelSettings.ModemConfig\x12\x11\n\tbandwidth\x18\x06 \x01(\r\x12\x15\n\rspread_factor\x18\x07 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x08 \x01(\r\x12\x13\n\x0b\x63hannel_num\x18\t \x01(\r\x12\x0b\n\x03psk\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\n\n\x02id\x18\n \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x10 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x11 \x01(\x08\"\x8a\x01\n\x0bModemConfig\x12\x12\n\x0e\x42w125Cr45Sf128\x10\x00\x12\x12\n\x0e\x42w500Cr45Sf128\x10\x01\x12\x14\n\x10\x42w31_25Cr48Sf512\x10\x02\x12\x13\n\x0f\x42w125Cr48Sf4096\x10\x03\x12\x13\n\x0f\x42w250Cr46Sf2048\x10\x04\x12\x13\n\x0f\x42w250Cr47Sf1024\x10\x05\"\x8b\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\"\n\x08settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x12\x1b\n\x04role\x18\x03 \x01(\x0e\x32\r.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42I\n\x13\x63om.geeksville.meshB\rChannelProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+)
+
+
+
+_CHANNELSETTINGS_MODEMCONFIG = _descriptor.EnumDescriptor(
+  name='ModemConfig',
+  full_name='ChannelSettings.ModemConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='Bw125Cr45Sf128', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Bw500Cr45Sf128', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Bw31_25Cr48Sf512', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Bw125Cr48Sf4096', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Bw250Cr46Sf2048', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Bw250Cr47Sf1024', index=5, number=5,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=281,
+  serialized_end=419,
+)
+_sym_db.RegisterEnumDescriptor(_CHANNELSETTINGS_MODEMCONFIG)
+
+_CHANNEL_ROLE = _descriptor.EnumDescriptor(
+  name='Role',
+  full_name='Channel.Role',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='DISABLED', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PRIMARY', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SECONDARY', index=2, number=2,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=513,
+  serialized_end=561,
+)
+_sym_db.RegisterEnumDescriptor(_CHANNEL_ROLE)
+
+
+_CHANNELSETTINGS = _descriptor.Descriptor(
+  name='ChannelSettings',
+  full_name='ChannelSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='tx_power', full_name='ChannelSettings.tx_power', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='modem_config', full_name='ChannelSettings.modem_config', index=1,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='bandwidth', full_name='ChannelSettings.bandwidth', index=2,
+      number=6, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='spread_factor', full_name='ChannelSettings.spread_factor', index=3,
+      number=7, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='coding_rate', full_name='ChannelSettings.coding_rate', index=4,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='channel_num', full_name='ChannelSettings.channel_num', index=5,
+      number=9, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='psk', full_name='ChannelSettings.psk', index=6,
+      number=4, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='name', full_name='ChannelSettings.name', index=7,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='id', full_name='ChannelSettings.id', index=8,
+      number=10, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='uplink_enabled', full_name='ChannelSettings.uplink_enabled', index=9,
+      number=16, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='downlink_enabled', full_name='ChannelSettings.downlink_enabled', index=10,
+      number=17, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _CHANNELSETTINGS_MODEMCONFIG,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=18,
+  serialized_end=419,
+)
+
+
+_CHANNEL = _descriptor.Descriptor(
+  name='Channel',
+  full_name='Channel',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='index', full_name='Channel.index', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='settings', full_name='Channel.settings', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='role', full_name='Channel.role', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _CHANNEL_ROLE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=422,
+  serialized_end=561,
+)
+
+_CHANNELSETTINGS.fields_by_name['modem_config'].enum_type = _CHANNELSETTINGS_MODEMCONFIG
+_CHANNELSETTINGS_MODEMCONFIG.containing_type = _CHANNELSETTINGS
+_CHANNEL.fields_by_name['settings'].message_type = _CHANNELSETTINGS
+_CHANNEL.fields_by_name['role'].enum_type = _CHANNEL_ROLE
+_CHANNEL_ROLE.containing_type = _CHANNEL
+DESCRIPTOR.message_types_by_name['ChannelSettings'] = _CHANNELSETTINGS
+DESCRIPTOR.message_types_by_name['Channel'] = _CHANNEL
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+ChannelSettings = _reflection.GeneratedProtocolMessageType('ChannelSettings', (_message.Message,), {
+  'DESCRIPTOR' : _CHANNELSETTINGS,
+  '__module__' : 'channel_pb2'
+  # @@protoc_insertion_point(class_scope:ChannelSettings)
+  })
+_sym_db.RegisterMessage(ChannelSettings)
+
+Channel = _reflection.GeneratedProtocolMessageType('Channel', (_message.Message,), {
+  'DESCRIPTOR' : _CHANNEL,
+  '__module__' : 'channel_pb2'
+  # @@protoc_insertion_point(class_scope:Channel)
+  })
+_sym_db.RegisterMessage(Channel)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Channel +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var DISABLED
+
+
+
+
var INDEX_FIELD_NUMBER
+
+
+
+
var PRIMARY
+
+
+
+
var ROLE_FIELD_NUMBER
+
+
+
+
var Role
+
+
+
+
var SECONDARY
+
+
+
+
var SETTINGS_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var index
+
+

Getter for index.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var role
+
+

Getter for role.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var settings
+
+

Getter for settings.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class ChannelSettings +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var BANDWIDTH_FIELD_NUMBER
+
+
+
+
var Bw125Cr45Sf128
+
+
+
+
var Bw125Cr48Sf4096
+
+
+
+
var Bw250Cr46Sf2048
+
+
+
+
var Bw250Cr47Sf1024
+
+
+
+
var Bw31_25Cr48Sf512
+
+
+
+
var Bw500Cr45Sf128
+
+
+
+
var CHANNEL_NUM_FIELD_NUMBER
+
+
+
+
var CODING_RATE_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+ +
+
+
+
var ID_FIELD_NUMBER
+
+
+
+
var MODEM_CONFIG_FIELD_NUMBER
+
+
+
+
var ModemConfig
+
+
+
+
var NAME_FIELD_NUMBER
+
+
+
+
var PSK_FIELD_NUMBER
+
+
+
+
var SPREAD_FACTOR_FIELD_NUMBER
+
+
+
+
var TX_POWER_FIELD_NUMBER
+
+
+
+ +
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var bandwidth
+
+

Getter for bandwidth.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var channel_num
+
+

Getter for channel_num.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var coding_rate
+
+

Getter for coding_rate.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+ +
+

Getter for downlink_enabled.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var id
+
+

Getter for id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var modem_config
+
+

Getter for modem_config.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var name
+
+

Getter for name.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var psk
+
+

Getter for psk.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var spread_factor
+
+

Getter for spread_factor.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var tx_power
+
+

Getter for tx_power.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+ +
+

Getter for uplink_enabled.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/deviceonly_pb2.html b/docs/meshtastic/deviceonly_pb2.html new file mode 100644 index 0000000..6b4df19 --- /dev/null +++ b/docs/meshtastic/deviceonly_pb2.html @@ -0,0 +1,2241 @@ + + + + + + +meshtastic.deviceonly_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.deviceonly_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: deviceonly.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import channel_pb2 as channel__pb2
+from . import mesh_pb2 as mesh__pb2
+from . import radioconfig_pb2 as radioconfig__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='deviceonly.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\nDeviceOnlyH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x10\x64\x65viceonly.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x80\x01\n\x11LegacyRadioConfig\x12\x39\n\x0bpreferences\x18\x01 \x01(\x0b\x32$.LegacyRadioConfig.LegacyPreferences\x1a\x30\n\x11LegacyPreferences\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\"\x8f\x02\n\x0b\x44\x65viceState\x12\'\n\x0blegacyRadio\x18\x01 \x01(\x0b\x32\x12.LegacyRadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08J\x04\x08\x0c\x10\r\")\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.ChannelBF\n\x13\x63om.geeksville.meshB\nDeviceOnlyH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+  ,
+  dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,])
+
+
+
+
+_LEGACYRADIOCONFIG_LEGACYPREFERENCES = _descriptor.Descriptor(
+  name='LegacyPreferences',
+  full_name='LegacyRadioConfig.LegacyPreferences',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='region', full_name='LegacyRadioConfig.LegacyPreferences.region', index=0,
+      number=15, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=147,
+  serialized_end=195,
+)
+
+_LEGACYRADIOCONFIG = _descriptor.Descriptor(
+  name='LegacyRadioConfig',
+  full_name='LegacyRadioConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='preferences', full_name='LegacyRadioConfig.preferences', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_LEGACYRADIOCONFIG_LEGACYPREFERENCES, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=67,
+  serialized_end=195,
+)
+
+
+_DEVICESTATE = _descriptor.Descriptor(
+  name='DeviceState',
+  full_name='DeviceState',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='legacyRadio', full_name='DeviceState.legacyRadio', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='my_node', full_name='DeviceState.my_node', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='owner', full_name='DeviceState.owner', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='node_db', full_name='DeviceState.node_db', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='receive_queue', full_name='DeviceState.receive_queue', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='version', full_name='DeviceState.version', index=5,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rx_text_message', full_name='DeviceState.rx_text_message', index=6,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='no_save', full_name='DeviceState.no_save', index=7,
+      number=9, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='did_gps_reset', full_name='DeviceState.did_gps_reset', index=8,
+      number=11, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=198,
+  serialized_end=469,
+)
+
+
+_CHANNELFILE = _descriptor.Descriptor(
+  name='ChannelFile',
+  full_name='ChannelFile',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='channels', full_name='ChannelFile.channels', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=471,
+  serialized_end=512,
+)
+
+_LEGACYRADIOCONFIG_LEGACYPREFERENCES.fields_by_name['region'].enum_type = radioconfig__pb2._REGIONCODE
+_LEGACYRADIOCONFIG_LEGACYPREFERENCES.containing_type = _LEGACYRADIOCONFIG
+_LEGACYRADIOCONFIG.fields_by_name['preferences'].message_type = _LEGACYRADIOCONFIG_LEGACYPREFERENCES
+_DEVICESTATE.fields_by_name['legacyRadio'].message_type = _LEGACYRADIOCONFIG
+_DEVICESTATE.fields_by_name['my_node'].message_type = mesh__pb2._MYNODEINFO
+_DEVICESTATE.fields_by_name['owner'].message_type = mesh__pb2._USER
+_DEVICESTATE.fields_by_name['node_db'].message_type = mesh__pb2._NODEINFO
+_DEVICESTATE.fields_by_name['receive_queue'].message_type = mesh__pb2._MESHPACKET
+_DEVICESTATE.fields_by_name['rx_text_message'].message_type = mesh__pb2._MESHPACKET
+_CHANNELFILE.fields_by_name['channels'].message_type = channel__pb2._CHANNEL
+DESCRIPTOR.message_types_by_name['LegacyRadioConfig'] = _LEGACYRADIOCONFIG
+DESCRIPTOR.message_types_by_name['DeviceState'] = _DEVICESTATE
+DESCRIPTOR.message_types_by_name['ChannelFile'] = _CHANNELFILE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+LegacyRadioConfig = _reflection.GeneratedProtocolMessageType('LegacyRadioConfig', (_message.Message,), {
+
+  'LegacyPreferences' : _reflection.GeneratedProtocolMessageType('LegacyPreferences', (_message.Message,), {
+    'DESCRIPTOR' : _LEGACYRADIOCONFIG_LEGACYPREFERENCES,
+    '__module__' : 'deviceonly_pb2'
+    # @@protoc_insertion_point(class_scope:LegacyRadioConfig.LegacyPreferences)
+    })
+  ,
+  'DESCRIPTOR' : _LEGACYRADIOCONFIG,
+  '__module__' : 'deviceonly_pb2'
+  # @@protoc_insertion_point(class_scope:LegacyRadioConfig)
+  })
+_sym_db.RegisterMessage(LegacyRadioConfig)
+_sym_db.RegisterMessage(LegacyRadioConfig.LegacyPreferences)
+
+DeviceState = _reflection.GeneratedProtocolMessageType('DeviceState', (_message.Message,), {
+  'DESCRIPTOR' : _DEVICESTATE,
+  '__module__' : 'deviceonly_pb2'
+  # @@protoc_insertion_point(class_scope:DeviceState)
+  })
+_sym_db.RegisterMessage(DeviceState)
+
+ChannelFile = _reflection.GeneratedProtocolMessageType('ChannelFile', (_message.Message,), {
+  'DESCRIPTOR' : _CHANNELFILE,
+  '__module__' : 'deviceonly_pb2'
+  # @@protoc_insertion_point(class_scope:ChannelFile)
+  })
+_sym_db.RegisterMessage(ChannelFile)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ChannelFile +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CHANNELS_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var channels
+
+

Getter for channels.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class DeviceState +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var DID_GPS_RESET_FIELD_NUMBER
+
+
+
+
var LEGACYRADIO_FIELD_NUMBER
+
+
+
+
var MY_NODE_FIELD_NUMBER
+
+
+
+
var NODE_DB_FIELD_NUMBER
+
+
+
+
var NO_SAVE_FIELD_NUMBER
+
+
+
+
var OWNER_FIELD_NUMBER
+
+
+
+
var RECEIVE_QUEUE_FIELD_NUMBER
+
+
+
+
var RX_TEXT_MESSAGE_FIELD_NUMBER
+
+
+
+
var VERSION_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var did_gps_reset
+
+

Getter for did_gps_reset.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var legacyRadio
+
+

Getter for legacyRadio.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var my_node
+
+

Getter for my_node.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var no_save
+
+

Getter for no_save.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var node_db
+
+

Getter for node_db.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var owner
+
+

Getter for owner.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var receive_queue
+
+

Getter for receive_queue.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var rx_text_message
+
+

Getter for rx_text_message.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var version
+
+

Getter for version.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class LegacyRadioConfig +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var LegacyPreferences
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
var PREFERENCES_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var preferences
+
+

Getter for preferences.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/environmental_measurement_pb2.html b/docs/meshtastic/environmental_measurement_pb2.html new file mode 100644 index 0000000..e12dcf3 --- /dev/null +++ b/docs/meshtastic/environmental_measurement_pb2.html @@ -0,0 +1,746 @@ + + + + + + +meshtastic.environmental_measurement_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.environmental_measurement_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: environmental_measurement.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='environmental_measurement.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x1f\x65nvironmental_measurement.proto\"g\n\x18\x45nvironmentalMeasurement\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x42#Z!github.com/meshtastic/gomeshprotob\x06proto3'
+)
+
+
+
+
+_ENVIRONMENTALMEASUREMENT = _descriptor.Descriptor(
+  name='EnvironmentalMeasurement',
+  full_name='EnvironmentalMeasurement',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='temperature', full_name='EnvironmentalMeasurement.temperature', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='relative_humidity', full_name='EnvironmentalMeasurement.relative_humidity', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='barometric_pressure', full_name='EnvironmentalMeasurement.barometric_pressure', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=35,
+  serialized_end=138,
+)
+
+DESCRIPTOR.message_types_by_name['EnvironmentalMeasurement'] = _ENVIRONMENTALMEASUREMENT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+EnvironmentalMeasurement = _reflection.GeneratedProtocolMessageType('EnvironmentalMeasurement', (_message.Message,), {
+  'DESCRIPTOR' : _ENVIRONMENTALMEASUREMENT,
+  '__module__' : 'environmental_measurement_pb2'
+  # @@protoc_insertion_point(class_scope:EnvironmentalMeasurement)
+  })
+_sym_db.RegisterMessage(EnvironmentalMeasurement)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class EnvironmentalMeasurement +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var BAROMETRIC_PRESSURE_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var RELATIVE_HUMIDITY_FIELD_NUMBER
+
+
+
+
var TEMPERATURE_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var barometric_pressure
+
+

Getter for barometric_pressure.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var relative_humidity
+
+

Getter for relative_humidity.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var temperature
+
+

Getter for temperature.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/globals.html b/docs/meshtastic/globals.html new file mode 100644 index 0000000..9ce2f4b --- /dev/null +++ b/docs/meshtastic/globals.html @@ -0,0 +1,380 @@ + + + + + + +meshtastic.globals API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.globals

+
+
+

Globals singleton class.

+

Instead of using a global, stuff your variables in this "trash can". +This is not much better than using python's globals, but it allows +us to better test meshtastic. Plus, there are some weird python +global issues/gotcha that we can hopefully avoid by using this +class instead.

+
+ +Expand source code + +
"""Globals singleton class.
+
+   Instead of using a global, stuff your variables in this "trash can".
+   This is not much better than using python's globals, but it allows
+   us to better test meshtastic. Plus, there are some weird python
+   global issues/gotcha that we can hopefully avoid by using this
+   class instead.
+
+"""
+
+class Globals:
+    """Globals class is a Singleton."""
+    __instance = None
+
+    @staticmethod
+    def getInstance():
+        """Get an instance of the Globals class."""
+        if Globals.__instance is None:
+            Globals()
+        return Globals.__instance
+
+    def __init__(self):
+        """Constructor for the Globals CLass"""
+        if Globals.__instance is not None:
+            raise Exception("This class is a singleton")
+        else:
+            Globals.__instance = self
+        self.args = None
+        self.parser = None
+        self.target_node = None
+        self.channel_index = None
+
+    def reset(self):
+        """Reset all of our globals. If you add a member, add it to this method, too."""
+        self.args = None
+        self.parser = None
+        self.target_node = None
+        self.channel_index = None
+
+    def set_args(self, args):
+        """Set the args"""
+        self.args = args
+
+    def set_parser(self, parser):
+        """Set the parser"""
+        self.parser = parser
+
+    def set_target_node(self, target_node):
+        """Set the target_node"""
+        self.target_node = target_node
+
+    def set_channel_index(self, channel_index):
+        """Set the channel_index"""
+        self.channel_index = channel_index
+
+    def get_args(self):
+        """Get args"""
+        return self.args
+
+    def get_parser(self):
+        """Get parser"""
+        return self.parser
+
+    def get_target_node(self):
+        """Get target_node"""
+        return self.target_node
+
+    def get_channel_index(self):
+        """Get channel_index"""
+        return self.channel_index
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Globals +
+
+

Globals class is a Singleton.

+

Constructor for the Globals CLass

+
+ +Expand source code + +
class Globals:
+    """Globals class is a Singleton."""
+    __instance = None
+
+    @staticmethod
+    def getInstance():
+        """Get an instance of the Globals class."""
+        if Globals.__instance is None:
+            Globals()
+        return Globals.__instance
+
+    def __init__(self):
+        """Constructor for the Globals CLass"""
+        if Globals.__instance is not None:
+            raise Exception("This class is a singleton")
+        else:
+            Globals.__instance = self
+        self.args = None
+        self.parser = None
+        self.target_node = None
+        self.channel_index = None
+
+    def reset(self):
+        """Reset all of our globals. If you add a member, add it to this method, too."""
+        self.args = None
+        self.parser = None
+        self.target_node = None
+        self.channel_index = None
+
+    def set_args(self, args):
+        """Set the args"""
+        self.args = args
+
+    def set_parser(self, parser):
+        """Set the parser"""
+        self.parser = parser
+
+    def set_target_node(self, target_node):
+        """Set the target_node"""
+        self.target_node = target_node
+
+    def set_channel_index(self, channel_index):
+        """Set the channel_index"""
+        self.channel_index = channel_index
+
+    def get_args(self):
+        """Get args"""
+        return self.args
+
+    def get_parser(self):
+        """Get parser"""
+        return self.parser
+
+    def get_target_node(self):
+        """Get target_node"""
+        return self.target_node
+
+    def get_channel_index(self):
+        """Get channel_index"""
+        return self.channel_index
+
+

Static methods

+
+
+def getInstance() +
+
+

Get an instance of the Globals class.

+
+ +Expand source code + +
@staticmethod
+def getInstance():
+    """Get an instance of the Globals class."""
+    if Globals.__instance is None:
+        Globals()
+    return Globals.__instance
+
+
+
+

Methods

+
+
+def get_args(self) +
+
+

Get args

+
+ +Expand source code + +
def get_args(self):
+    """Get args"""
+    return self.args
+
+
+
+def get_channel_index(self) +
+
+

Get channel_index

+
+ +Expand source code + +
def get_channel_index(self):
+    """Get channel_index"""
+    return self.channel_index
+
+
+
+def get_parser(self) +
+
+

Get parser

+
+ +Expand source code + +
def get_parser(self):
+    """Get parser"""
+    return self.parser
+
+
+
+def get_target_node(self) +
+
+

Get target_node

+
+ +Expand source code + +
def get_target_node(self):
+    """Get target_node"""
+    return self.target_node
+
+
+
+def reset(self) +
+
+

Reset all of our globals. If you add a member, add it to this method, too.

+
+ +Expand source code + +
def reset(self):
+    """Reset all of our globals. If you add a member, add it to this method, too."""
+    self.args = None
+    self.parser = None
+    self.target_node = None
+    self.channel_index = None
+
+
+
+def set_args(self, args) +
+
+

Set the args

+
+ +Expand source code + +
def set_args(self, args):
+    """Set the args"""
+    self.args = args
+
+
+
+def set_channel_index(self, channel_index) +
+
+

Set the channel_index

+
+ +Expand source code + +
def set_channel_index(self, channel_index):
+    """Set the channel_index"""
+    self.channel_index = channel_index
+
+
+
+def set_parser(self, parser) +
+
+

Set the parser

+
+ +Expand source code + +
def set_parser(self, parser):
+    """Set the parser"""
+    self.parser = parser
+
+
+
+def set_target_node(self, target_node) +
+
+

Set the target_node

+
+ +Expand source code + +
def set_target_node(self, target_node):
+    """Set the target_node"""
+    self.target_node = target_node
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/index.html b/docs/meshtastic/index.html new file mode 100644 index 0000000..cefec61 --- /dev/null +++ b/docs/meshtastic/index.html @@ -0,0 +1,538 @@ + + + + + + +meshtastic API documentation + + + + + + + + + + + +
+
+
+

Package meshtastic

+
+
+

an API for Meshtastic devices

+

Primary class: SerialInterface +Install with pip: "pip3 install meshtastic" +Source code on github

+

properties of SerialInterface:

+
    +
  • radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to +the device.
  • +
  • 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 - Contains read-only information about the local radio device (software version, hardware version, etc)
  • +
+

Published PubSub topics

+

We use a publish-subscribe 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 +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…)
  • +
+

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

+
import meshtastic
+import meshtastic.serial_interface
+from pubsub import pub
+
+def onReceive(packet, interface): # called when a packet arrives
+    print(f"Received: {packet}")
+
+def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
+    # defaults to broadcast, specify a destination ID if you wish
+    interface.sendText("hello mesh")
+
+pub.subscribe(onReceive, "meshtastic.receive")
+pub.subscribe(onConnection, "meshtastic.connection.established")
+# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
+interface = meshtastic.serial_interface.SerialInterface()
+
+
+
+ +Expand source code + +
"""
+# an API for Meshtastic devices
+
+Primary class: SerialInterface
+Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
+Source code on [github](https://github.com/meshtastic/Meshtastic-python)
+
+properties of SerialInterface:
+
+- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
+the device.
+- 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 - Contains read-only information about the local radio device (software version, hardware version, etc)
+
+# 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
+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...)
+
+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
+```
+import meshtastic
+import meshtastic.serial_interface
+from pubsub import pub
+
+def onReceive(packet, interface): # called when a packet arrives
+    print(f"Received: {packet}")
+
+def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
+    # defaults to broadcast, specify a destination ID if you wish
+    interface.sendText("hello mesh")
+
+pub.subscribe(onReceive, "meshtastic.receive")
+pub.subscribe(onConnection, "meshtastic.connection.established")
+# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
+interface = meshtastic.serial_interface.SerialInterface()
+
+```
+
+"""
+
+import base64
+import logging
+import os
+import platform
+import random
+import socket
+import sys
+import stat
+import threading
+import traceback
+import time
+from datetime import datetime
+from typing import *
+import serial
+import timeago
+import google.protobuf.json_format
+import pygatt
+from pubsub import pub
+from dotmap import DotMap
+from tabulate import tabulate
+from google.protobuf.json_format import MessageToJson
+from .util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout
+from .node import Node
+from . import mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util
+
+# Note: To follow PEP224, comments should be after the module variable.
+
+LOCAL_ADDR = "^local"
+"""A special ID that means the local node"""
+
+BROADCAST_NUM = 0xffffffff
+"""if using 8 bit nodenums this will be shortend on the target"""
+
+BROADCAST_ADDR = "^all"
+"""A special ID that means broadcast"""
+
+OUR_APP_VERSION = 20200
+"""The numeric buildnumber (shared with android apps) specifying the
+   level of device code we are guaranteed to understand
+
+   format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
+"""
+
+publishingThread = DeferredExecution("publishing")
+
+
+class ResponseHandler(NamedTuple):
+    """A pending response callback, waiting for a response to one of our messages"""
+    # requestId: int - used only as a key
+    callback: Callable
+    # FIXME, add timestamp and age out old requests
+
+
+class KnownProtocol(NamedTuple):
+    """Used to automatically decode known protocol payloads"""
+    name: str
+    # portnum: int, now a key
+    # If set, will be called to prase as a protocol buffer
+    protobufFactory: Callable = None
+    # If set, invoked as onReceive(interface, packet)
+    onReceive: Callable = None
+
+
+def _onTextReceive(iface, asDict):
+    """Special text auto parsing for received messages"""
+    # We don't throw if the utf8 is invalid in the text message.  Instead we just don't populate
+    # the decoded.data.text and we log an error message.  This at least allows some delivery to
+    # the app and the app can deal with the missing decoded representation.
+    #
+    # Usually btw this problem is caused by apps sending binary data but setting the payload type to
+    # text.
+    try:
+        asBytes = asDict["decoded"]["payload"]
+        asDict["decoded"]["text"] = asBytes.decode("utf-8")
+    except Exception as ex:
+        logging.error(f"Malformatted utf8 in text message: {ex}")
+    _receiveInfoUpdate(iface, asDict)
+
+
+def _onPositionReceive(iface, asDict):
+    """Special auto parsing for received messages"""
+    p = asDict["decoded"]["position"]
+    iface._fixupPosition(p)
+    # update node DB as needed
+    iface._getOrCreateByNum(asDict["from"])["position"] = p
+
+
+def _onNodeInfoReceive(iface, asDict):
+    """Special auto parsing for received messages"""
+    p = asDict["decoded"]["user"]
+    # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg
+    # update node DB as needed
+    n = iface._getOrCreateByNum(asDict["from"])
+    n["user"] = p
+    # We now have a node ID, make sure it is uptodate in that table
+    iface.nodes[p["id"]] = n
+    _receiveInfoUpdate(iface, asDict)
+
+
+def _receiveInfoUpdate(iface, asDict):
+    if "from" in asDict:
+        iface._getOrCreateByNum(asDict["from"])["lastReceived"] = asDict
+        iface._getOrCreateByNum(asDict["from"])["lastHeard"] = asDict.get("rxTime")
+        iface._getOrCreateByNum(asDict["from"])["snr"] = asDict.get("rxSnr")
+        iface._getOrCreateByNum(asDict["from"])["hopLimit"] = asDict.get("hopLimit")
+
+
+"""Well known message payloads can register decoders for automatic protobuf parsing"""
+protocols = {
+    portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol("text", onReceive=_onTextReceive),
+    portnums_pb2.PortNum.POSITION_APP: KnownProtocol("position", mesh_pb2.Position, _onPositionReceive),
+    portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol("user", mesh_pb2.User, _onNodeInfoReceive),
+    portnums_pb2.PortNum.ADMIN_APP: KnownProtocol("admin", admin_pb2.AdminMessage),
+    portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
+    portnums_pb2.PortNum.ENVIRONMENTAL_MEASUREMENT_APP: KnownProtocol("environmental", environmental_measurement_pb2.EnvironmentalMeasurement),
+    portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol(
+        "remotehw", remote_hardware_pb2.HardwareMessage)
+}
+
+
+
+

Sub-modules

+
+
meshtastic.admin_pb2
+
+
+
+
meshtastic.apponly_pb2
+
+
+
+
meshtastic.ble
+
+
+
+
meshtastic.ble_interface
+
+

Bluetooth interface

+
+
meshtastic.channel_pb2
+
+
+
+
meshtastic.deviceonly_pb2
+
+
+
+
meshtastic.environmental_measurement_pb2
+
+
+
+
meshtastic.globals
+
+

Globals singleton class …

+
+
meshtastic.mesh_interface
+
+

Mesh Interface class

+
+
meshtastic.mesh_pb2
+
+
+
+
meshtastic.mqtt_pb2
+
+
+
+
meshtastic.node
+
+

Node class

+
+
meshtastic.portnums_pb2
+
+
+
+
meshtastic.radioconfig_pb2
+
+
+
+
meshtastic.remote_hardware
+
+

Remote hardware

+
+
meshtastic.remote_hardware_pb2
+
+
+
+
meshtastic.serial_interface
+
+

Serial interface class

+
+
meshtastic.storeforward_pb2
+
+
+
+
meshtastic.stream_interface
+
+

Stream Interface base class

+
+
meshtastic.tcp_interface
+
+

TCPInterface class for interfacing with http endpoint

+
+
meshtastic.test
+
+

With two radios connected serially, send and receive test +messages and report back if successful.

+
+
meshtastic.tests
+
+
+
+
meshtastic.tunnel
+
+

Code for IP tunnel over a mesh …

+
+
meshtastic.util
+
+

Utility functions.

+
+
+
+
+

Global variables

+
+
var BROADCAST_ADDR
+
+

A special ID that means broadcast

+
+
var BROADCAST_NUM
+
+

if using 8 bit nodenums this will be shortend on the target

+
+
var LOCAL_ADDR
+
+

A special ID that means the local node

+
+
var OUR_APP_VERSION
+
+

The numeric buildnumber (shared with android apps) specifying the +level of device code we are guaranteed to understand

+

format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20

+
+
+
+
+
+
+

Classes

+
+
+class KnownProtocol +(name: str, protobufFactory: Callable = None, onReceive: Callable = None) +
+
+

Used to automatically decode known protocol payloads

+
+ +Expand source code + +
class KnownProtocol(NamedTuple):
+    """Used to automatically decode known protocol payloads"""
+    name: str
+    # portnum: int, now a key
+    # If set, will be called to prase as a protocol buffer
+    protobufFactory: Callable = None
+    # If set, invoked as onReceive(interface, packet)
+    onReceive: Callable = None
+
+

Ancestors

+
    +
  • builtins.tuple
  • +
+

Instance variables

+
+
var name : str
+
+

Alias for field number 0

+
+
var onReceive : Callable
+
+

Alias for field number 2

+
+
var protobufFactory : Callable
+
+

Alias for field number 1

+
+
+
+
+class ResponseHandler +(callback: Callable) +
+
+

A pending response callback, waiting for a response to one of our messages

+
+ +Expand source code + +
class ResponseHandler(NamedTuple):
+    """A pending response callback, waiting for a response to one of our messages"""
+    # requestId: int - used only as a key
+    callback: Callable
+    # FIXME, add timestamp and age out old requests
+
+

Ancestors

+
    +
  • builtins.tuple
  • +
+

Instance variables

+
+
var callback : Callable
+
+

Alias for field number 0

+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/mesh_interface.html b/docs/meshtastic/mesh_interface.html new file mode 100644 index 0000000..0dcc735 --- /dev/null +++ b/docs/meshtastic/mesh_interface.html @@ -0,0 +1,1851 @@ + + + + + + +meshtastic.mesh_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.mesh_interface

+
+
+

Mesh Interface class

+
+ +Expand source code + +
"""Mesh Interface class
+"""
+import sys
+import random
+import time
+import logging
+from typing import AnyStr
+import threading
+from datetime import datetime
+import timeago
+from tabulate import tabulate
+
+import google.protobuf.json_format
+
+from pubsub import pub
+from google.protobuf.json_format import MessageToJson
+
+
+import meshtastic.node
+from . import portnums_pb2, mesh_pb2
+from .util import stripnl, Timeout, our_exit
+from .__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols
+
+class MeshInterface:
+    """Interface class for meshtastic devices
+
+    Properties:
+
+    isConnected
+    nodes
+    debugOut
+    """
+
+    def __init__(self, debugOut=None, noProto=False):
+        """Constructor
+
+        Keyword Arguments:
+            noProto -- If True, don't try to run our protocol on the
+                       link - just be a dumb serial client.
+        """
+        self.debugOut = debugOut
+        self.nodes = None  # FIXME
+        self.isConnected = threading.Event()
+        self.noProto = noProto
+        self.localNode = meshtastic.node.Node(self, -1)  # We fixup nodenum later
+        self.myInfo = None  # We don't have device info yet
+        self.responseHandlers = {}  # A map from request ID to the handler
+        self.failure = None  # If we've encountered a fatal exception it will be kept here
+        self._timeout = Timeout()
+        self.heartbeatTimer = None
+        random.seed()  # FIXME, we should not clobber the random seedval here, instead tell user they must call it
+        self.currentPacketId = random.randint(0, 0xffffffff)
+        self.nodesByNum = None
+        self.configId = None
+        self.defaultHopLimit = 3
+        self.gotResponse = False # used in gpio read
+        self.mask = None # used in gpio read and gpio watch
+
+    def close(self):
+        """Shutdown this interface"""
+        if self.heartbeatTimer:
+            self.heartbeatTimer.cancel()
+
+        self._sendDisconnect()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        if exc_type is not None and exc_value is not None:
+            logging.error(
+                f'An exception of type {exc_type} with value {exc_value} has occurred')
+        if traceback is not None:
+            logging.error(f'Traceback: {traceback}')
+        self.close()
+
+    def showInfo(self, file=sys.stdout):
+        """Show human readable summary about this object"""
+        owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
+        myinfo = ''
+        if self.myInfo:
+            myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}"
+        mesh = "\nNodes in mesh:"
+        nodes = ""
+        if self.nodes:
+            for n in self.nodes.values():
+                nodes = nodes + f"  {stripnl(n)}"
+        infos = owner + myinfo + mesh + nodes
+        print(infos)
+        return infos
+
+    def showNodes(self, includeSelf=True, file=sys.stdout):
+        """Show table summary of nodes in mesh"""
+        def formatFloat(value, precision=2, unit=''):
+            """Format a float value with precsion."""
+            return f'{value:.{precision}f}{unit}' if value else None
+
+        def getLH(ts):
+            """Format last heard"""
+            return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None
+
+        def getTimeAgo(ts):
+            """Format how long ago have we heard from this node (aka timeago)."""
+            return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None
+
+        rows = []
+        if self.nodes:
+            logging.debug(f'self.nodes:{self.nodes}')
+            for node in self.nodes.values():
+                if not includeSelf and node['num'] == self.localNode.nodeNum:
+                    continue
+
+                row = {"N": 0}
+
+                user = node.get('user')
+                if user:
+                    row.update({
+                        "User": user['longName'],
+                        "AKA":  user['shortName'],
+                        "ID":   user['id'],
+                    })
+
+                pos = node.get('position')
+                if pos:
+                    row.update({
+                        "Latitude":  formatFloat(pos.get("latitude"),     4, "°"),
+                        "Longitude": formatFloat(pos.get("longitude"),    4, "°"),
+                        "Altitude":  formatFloat(pos.get("altitude"),     0, " m"),
+                        "Battery":   formatFloat(pos.get("batteryLevel"), 2, "%"),
+                    })
+
+                row.update({
+                    "SNR":       formatFloat(node.get("snr"), 2, " dB"),
+                    "LastHeard": getLH(node.get("lastHeard")),
+                    "Since":     getTimeAgo(node.get("lastHeard")),
+                })
+
+                rows.append(row)
+
+        rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True)
+        for i, row in enumerate(rows):
+            row['N'] = i+1
+
+        table = tabulate(rows, headers='keys', missingval='N/A', tablefmt='fancy_grid')
+        print(table)
+        return table
+
+
+    def getNode(self, nodeId):
+        """Return a node object which contains device settings and channel info"""
+        if nodeId == LOCAL_ADDR:
+            return self.localNode
+        else:
+            n = meshtastic.node.Node(self, nodeId)
+            logging.debug("About to requestConfig")
+            n.requestConfig()
+            if not n.waitForConfig():
+                our_exit("Error: Timed out waiting for node config")
+            return n
+
+    def sendText(self, text: AnyStr,
+                 destinationId=BROADCAST_ADDR,
+                 wantAck=False,
+                 wantResponse=False,
+                 hopLimit=None,
+                 onResponse=None,
+                 channelIndex=0):
+        """Send a utf8 string to some other node, if the node has a display it
+           will also be shown on the device.
+
+        Arguments:
+            text {string} -- The text to send
+
+        Keyword Arguments:
+            destinationId {nodeId or nodeNum} -- where to send this
+                                                 message (default: {BROADCAST_ADDR})
+            portNum -- the application portnum (similar to IP port numbers)
+                       of the destination, see portnums.proto for a list
+            wantAck -- True if you want the message sent in a reliable manner
+                       (with retries and ack/nak provided for delivery)
+            wantResponse -- True if you want the service on the other side to
+                            send an application layer response
+
+        Returns the sent packet. The id field will be populated in this packet
+        and can be used to track future message acks/naks.
+        """
+        if hopLimit is None:
+            hopLimit = self.defaultHopLimit
+
+        return self.sendData(text.encode("utf-8"), destinationId,
+                             portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
+                             wantAck=wantAck,
+                             wantResponse=wantResponse,
+                             hopLimit=hopLimit,
+                             onResponse=onResponse,
+                             channelIndex=channelIndex)
+
+    def sendData(self, data, destinationId=BROADCAST_ADDR,
+                 portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
+                 wantResponse=False,
+                 hopLimit=None,
+                 onResponse=None,
+                 channelIndex=0):
+        """Send a data packet to some other node
+
+        Keyword Arguments:
+            data -- the data to send, either as an array of bytes or
+                    as a protobuf (which will be automatically
+                    serialized to bytes)
+            destinationId {nodeId or nodeNum} -- where to send this
+                    message (default: {BROADCAST_ADDR})
+            portNum -- the application portnum (similar to IP port numbers)
+                    of the destination, see portnums.proto for a list
+            wantAck -- True if you want the message sent in a reliable
+                    manner (with retries and ack/nak provided for delivery)
+            wantResponse -- True if you want the service on the other
+                    side to send an application layer response
+            onResponse -- A closure of the form funct(packet), that will be
+                    called when a response packet arrives (or the transaction
+                    is NAKed due to non receipt)
+            channelIndex - channel number to use
+
+        Returns the sent packet. The id field will be populated in this packet
+        and can be used to track future message acks/naks.
+        """
+        if hopLimit is None:
+            hopLimit = self.defaultHopLimit
+
+        if getattr(data, "SerializeToString", None):
+            logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
+            data = data.SerializeToString()
+
+        logging.debug(f"len(data): {len(data)}")
+        logging.debug(f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}")
+        if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
+            raise Exception("Data payload too big")
+
+        if portNum == portnums_pb2.PortNum.UNKNOWN_APP:  # we are now more strict wrt port numbers
+            our_exit("Warning: A non-zero port number must be specified")
+
+        meshPacket = mesh_pb2.MeshPacket()
+        meshPacket.channel = channelIndex
+        meshPacket.decoded.payload = data
+        meshPacket.decoded.portnum = portNum
+        meshPacket.decoded.want_response = wantResponse
+
+        p = self._sendPacket(meshPacket, destinationId,
+                             wantAck=wantAck, hopLimit=hopLimit)
+        if onResponse is not None:
+            self._addResponseHandler(p.id, onResponse)
+        return p
+
+    def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
+                     destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False):
+        """
+        Send a position packet to some other node (normally a broadcast)
+
+        Also, the device software will notice this packet and use it to automatically
+        set its notion of the local position.
+
+        If timeSec is not specified (recommended), we will use the local machine time.
+
+        Returns the sent packet. The id field will be populated in this packet and
+        can be used to track future message acks/naks.
+        """
+        p = mesh_pb2.Position()
+        if latitude != 0.0:
+            p.latitude_i = int(latitude / 1e-7)
+            logging.debug(f'p.latitude_i:{p.latitude_i}')
+
+        if longitude != 0.0:
+            p.longitude_i = int(longitude / 1e-7)
+            logging.debug(f'p.longitude_i:{p.longitude_i}')
+
+        if altitude != 0:
+            p.altitude = int(altitude)
+            logging.debug(f'p.altitude:{p.altitude}')
+
+        if timeSec == 0:
+            timeSec = time.time()  # returns unix timestamp in seconds
+        p.time = int(timeSec)
+        logging.debug(f'p.time:{p.time}')
+
+        return self.sendData(p, destinationId,
+                             portNum=portnums_pb2.PortNum.POSITION_APP,
+                             wantAck=wantAck,
+                             wantResponse=wantResponse)
+
+    def _addResponseHandler(self, requestId, callback):
+        self.responseHandlers[requestId] = ResponseHandler(callback)
+
+    def _sendPacket(self, meshPacket,
+                    destinationId=BROADCAST_ADDR,
+                    wantAck=False, hopLimit=None):
+        """Send a MeshPacket to the specified node (or if unspecified, broadcast).
+        You probably don't want this - use sendData instead.
+
+        Returns the sent packet. The id field will be populated in this packet and
+        can be used to track future message acks/naks.
+        """
+        if hopLimit is None:
+            hopLimit = self.defaultHopLimit
+
+        # We allow users to talk to the local node before we've completed the full connection flow...
+        if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
+            self._waitConnected()
+
+        toRadio = mesh_pb2.ToRadio()
+
+        nodeNum = 0
+        if destinationId is None:
+            our_exit("Warning: destinationId must not be None")
+        elif isinstance(destinationId, int):
+            nodeNum = destinationId
+        elif destinationId == BROADCAST_ADDR:
+            nodeNum = BROADCAST_NUM
+        elif destinationId == LOCAL_ADDR:
+            if self.myInfo:
+                nodeNum = self.myInfo.my_node_num
+            else:
+                our_exit("Warning: No myInfo found.")
+        # A simple hex style nodeid - we can parse this without needing the DB
+        elif destinationId.startswith("!"):
+            nodeNum = int(destinationId[1:], 16)
+        else:
+            if self.nodes:
+                node = self.nodes.get(destinationId)
+                if not node:
+                    our_exit(f"Warning: NodeId {destinationId} not found in DB")
+                nodeNum = node['num']
+            else:
+                logging.warning("Warning: There were no self.nodes.")
+
+        meshPacket.to = nodeNum
+        meshPacket.want_ack = wantAck
+        meshPacket.hop_limit = hopLimit
+
+        # if the user hasn't set an ID for this packet (likely and recommended),
+        # we should pick a new unique ID so the message can be tracked.
+        if meshPacket.id == 0:
+            meshPacket.id = self._generatePacketId()
+
+        toRadio.packet.CopyFrom(meshPacket)
+        if self.noProto:
+            logging.warning(f"Not sending packet because protocol use is disabled by noProto")
+        else:
+            logging.debug(f"Sending packet: {stripnl(meshPacket)}")
+            self._sendToRadio(toRadio)
+        return meshPacket
+
+    def waitForConfig(self):
+        """Block until radio config is received. Returns True if config has been received."""
+        success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
+        if not success:
+            raise Exception("Timed out waiting for interface config")
+
+    def getMyNodeInfo(self):
+        """Get info about my node."""
+        if self.myInfo is None:
+            return None
+        logging.debug(f'self.nodesByNum:{self.nodesByNum}')
+        return self.nodesByNum.get(self.myInfo.my_node_num)
+
+    def getMyUser(self):
+        """Get user"""
+        nodeInfo = self.getMyNodeInfo()
+        if nodeInfo is not None:
+            return nodeInfo.get('user')
+        return None
+
+    def getLongName(self):
+        """Get long name"""
+        user = self.getMyUser()
+        if user is not None:
+            return user.get('longName', None)
+        return None
+
+    def getShortName(self):
+        """Get short name"""
+        user = self.getMyUser()
+        if user is not None:
+            return user.get('shortName', None)
+        return None
+
+    def _waitConnected(self):
+        """Block until the initial node db download is complete, or timeout
+        and raise an exception"""
+        if not self.noProto:
+            if not self.isConnected.wait(15.0):  # timeout after x seconds
+                raise Exception("Timed out waiting for connection completion")
+
+        # If we failed while connecting, raise the connection to the client
+        if self.failure:
+            raise self.failure
+
+    def _generatePacketId(self):
+        """Get a new unique packet ID"""
+        if self.currentPacketId is None:
+            raise Exception("Not connected yet, can not generate packet")
+        else:
+            self.currentPacketId = (self.currentPacketId + 1) & 0xffffffff
+            return self.currentPacketId
+
+    def _disconnected(self):
+        """Called by subclasses to tell clients this interface has disconnected"""
+        self.isConnected.clear()
+        publishingThread.queueWork(lambda: pub.sendMessage(
+            "meshtastic.connection.lost", interface=self))
+
+    def _startHeartbeat(self):
+        """We need to send a heartbeat message to the device every X seconds"""
+        def callback():
+            self.heartbeatTimer = None
+            prefs = self.localNode.radioConfig.preferences
+            i = prefs.phone_timeout_secs / 2
+            logging.debug(f"Sending heartbeat, interval {i}")
+            if i != 0:
+                self.heartbeatTimer = threading.Timer(i, callback)
+                self.heartbeatTimer.start()
+                p = mesh_pb2.ToRadio()
+                self._sendToRadio(p)
+
+        callback()  # run our periodic callback now, it will make another timer if necessary
+
+    def _connected(self):
+        """Called by this class to tell clients we are now fully connected to a node
+        """
+        # (because I'm lazy) _connected might be called when remote Node
+        # objects complete their config reads, don't generate redundant isConnected
+        # for the local interface
+        if not self.isConnected.is_set():
+            self.isConnected.set()
+            self._startHeartbeat()
+            publishingThread.queueWork(lambda: pub.sendMessage(
+                "meshtastic.connection.established", interface=self))
+
+    def _startConfig(self):
+        """Start device packets flowing"""
+        self.myInfo = None
+        self.nodes = {}  # nodes keyed by ID
+        self.nodesByNum = {}  # nodes keyed by nodenum
+
+        startConfig = mesh_pb2.ToRadio()
+        self.configId = random.randint(0, 0xffffffff)
+        startConfig.want_config_id = self.configId
+        self._sendToRadio(startConfig)
+
+    def _sendDisconnect(self):
+        """Tell device we are done using it"""
+        m = mesh_pb2.ToRadio()
+        m.disconnect = True
+        self._sendToRadio(m)
+
+    def _sendToRadio(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        if self.noProto:
+            logging.warning(f"Not sending packet because protocol use is disabled by noProto")
+        else:
+            #logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
+            self._sendToRadioImpl(toRadio)
+
+    def _sendToRadioImpl(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        logging.error(f"Subclass must provide toradio: {toRadio}")
+
+    def _handleConfigComplete(self):
+        """
+        Done with initial config messages, now send regular MeshPackets
+        to ask for settings and channels
+        """
+        self.localNode.requestConfig()
+
+    def _handleFromRadio(self, fromRadioBytes):
+        """
+        Handle a packet that arrived from the radio(update model and publish events)
+
+        Called by subclasses."""
+        fromRadio = mesh_pb2.FromRadio()
+        fromRadio.ParseFromString(fromRadioBytes)
+        logging.debug(f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}")
+        asDict = google.protobuf.json_format.MessageToDict(fromRadio)
+        logging.debug(f"Received from radio: {fromRadio}")
+        if fromRadio.HasField("my_info"):
+            self.myInfo = fromRadio.my_info
+            self.localNode.nodeNum = self.myInfo.my_node_num
+            logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
+
+            failmsg = None
+            # Check for app too old
+            if self.myInfo.min_app_version > OUR_APP_VERSION:
+                failmsg = "This device needs a newer python client, run 'pip install --upgrade meshtastic'."\
+                          "For more information see https://tinyurl.com/5bjsxu32"
+
+            # check for firmware too old
+            if self.myInfo.max_channels == 0:
+                failmsg = "This version of meshtastic-python requires device firmware version 1.2 or later. "\
+                          "For more information see https://tinyurl.com/5bjsxu32"
+
+            if failmsg:
+                self.failure = Exception(failmsg)
+                self.isConnected.set()  # let waitConnected return this exception
+                self.close()
+
+        elif fromRadio.HasField("node_info"):
+            node = asDict["nodeInfo"]
+            try:
+                self._fixupPosition(node["position"])
+            except:
+                logging.debug("Node without position")
+
+            logging.debug(f"Received nodeinfo: {node}")
+
+            self.nodesByNum[node["num"]] = node
+            if "user" in node:  # Some nodes might not have user/ids assigned yet
+                if "id" in node["user"]:
+                    self.nodes[node["user"]["id"]] = node
+            publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
+                                                               node=node, interface=self))
+        elif fromRadio.config_complete_id == self.configId:
+            # we ignore the config_complete_id, it is unneeded for our
+            # stream API fromRadio.config_complete_id
+            logging.debug(f"Config complete ID {self.configId}")
+            self._handleConfigComplete()
+        elif fromRadio.HasField("packet"):
+            self._handlePacketFromRadio(fromRadio.packet)
+        elif fromRadio.rebooted:
+            # Tell clients the device went away.  Careful not to call the overridden
+            # subclass version that closes the serial port
+            MeshInterface._disconnected(self)
+
+            self._startConfig()  # redownload the node db etc...
+        else:
+            logging.debug("Unexpected FromRadio payload")
+
+    def _fixupPosition(self, position):
+        """Convert integer lat/lon into floats
+
+        Arguments:
+            position {Position dictionary} -- object ot fix up
+        """
+        if "latitudeI" in position:
+            position["latitude"] = position["latitudeI"] * 1e-7
+        if "longitudeI" in position:
+            position["longitude"] = position["longitudeI"] * 1e-7
+
+    def _nodeNumToId(self, num):
+        """Map a node node number to a node ID
+
+        Arguments:
+            num {int} -- Node number
+
+        Returns:
+            string -- Node ID
+        """
+        if num == BROADCAST_NUM:
+            return BROADCAST_ADDR
+
+        try:
+            return self.nodesByNum[num]["user"]["id"]
+        except:
+            logging.debug(f"Node {num} not found for fromId")
+            return None
+
+    def _getOrCreateByNum(self, nodeNum):
+        """Given a nodenum find the NodeInfo in the DB (or create if necessary)"""
+        if nodeNum == BROADCAST_NUM:
+            raise Exception("Can not create/find nodenum by the broadcast num")
+
+        if nodeNum in self.nodesByNum:
+            return self.nodesByNum[nodeNum]
+        else:
+            n = {"num": nodeNum}  # Create a minimial node db entry
+            self.nodesByNum[nodeNum] = n
+            return n
+
+    def _handlePacketFromRadio(self, meshPacket, hack=False):
+        """Handle a MeshPacket that just arrived from the radio
+
+        hack - well, since we used 'from', which is a python keyword,
+               as an attribute to MeshPacket in protobufs,
+               there really is no way to do something like this:
+                    meshPacket = mesh_pb2.MeshPacket()
+                    meshPacket.from = 123
+               If hack is True, we can unit test this code.
+
+        Will publish one of the following events:
+        - meshtastic.receive.text(packet = MeshPacket dictionary)
+        - meshtastic.receive.position(packet = MeshPacket dictionary)
+        - meshtastic.receive.user(packet = MeshPacket dictionary)
+        - meshtastic.receive.data(packet = MeshPacket dictionary)
+        """
+        asDict = google.protobuf.json_format.MessageToDict(meshPacket)
+
+        # We normally decompose the payload into a dictionary so that the client
+        # doesn't need to understand protobufs.  But advanced clients might
+        # want the raw protobuf, so we provide it in "raw"
+        asDict["raw"] = meshPacket
+
+        # from might be missing if the nodenum was zero.
+        if not hack and "from" not in asDict:
+            asDict["from"] = 0
+            logging.error(f"Device returned a packet we sent, ignoring: {stripnl(asDict)}")
+            print(f"Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}")
+            return
+        if "to" not in asDict:
+            asDict["to"] = 0
+
+        # /add fromId and toId fields based on the node ID
+        try:
+            asDict["fromId"] = self._nodeNumToId(asDict["from"])
+        except Exception as ex:
+            logging.warning(f"Not populating fromId {ex}")
+        try:
+            asDict["toId"] = self._nodeNumToId(asDict["to"])
+        except Exception as ex:
+            logging.warning(f"Not populating toId {ex}")
+
+        # We could provide our objects as DotMaps - which work with . notation or as dictionaries
+        # asObj = DotMap(asDict)
+        topic = "meshtastic.receive"  # Generic unknown packet type
+
+        decoded = asDict["decoded"]
+        # The default MessageToDict converts byte arrays into base64 strings.
+        # We don't want that - it messes up data payload.  So slam in the correct
+        # byte array.
+        decoded["payload"] = meshPacket.decoded.payload
+
+        # UNKNOWN_APP is the default protobuf portnum value, and therefore if not
+        # set it will not be populated at all to make API usage easier, set
+        # it to prevent confusion
+        if "portnum" not in decoded:
+            new_portnum = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP)
+            decoded["portnum"] = new_portnum
+            logging.warning(f"portnum was not in decoded. Setting to:{new_portnum}")
+
+        portnum = decoded["portnum"]
+
+        topic = f"meshtastic.receive.data.{portnum}"
+
+        # decode position protobufs and update nodedb, provide decoded version
+        # as "position" in the published msg move the following into a 'decoders'
+        # API that clients could register?
+        portNumInt = meshPacket.decoded.portnum  # we want portnum as an int
+        handler = protocols.get(portNumInt)
+        # The decoded protobuf as a dictionary (if we understand this message)
+        p = None
+        if handler is not None:
+            topic = f"meshtastic.receive.{handler.name}"
+
+            # Convert to protobuf if possible
+            if handler.protobufFactory is not None:
+                pb = handler.protobufFactory()
+                pb.ParseFromString(meshPacket.decoded.payload)
+                p = google.protobuf.json_format.MessageToDict(pb)
+                asDict["decoded"][handler.name] = p
+                # Also provide the protobuf raw
+                asDict["decoded"][handler.name]["raw"] = pb
+
+            # Call specialized onReceive if necessary
+            if handler.onReceive is not None:
+                handler.onReceive(self, asDict)
+
+        # Is this message in response to a request, if so, look for a handler
+        requestId = decoded.get("requestId")
+        if requestId is not None:
+            # We ignore ACK packets, but send NAKs and data responses to the handlers
+            routing = decoded.get("routing")
+            isAck = routing is not None and ("errorReason" not in routing)
+            if not isAck:
+                # we keep the responseHandler in dict until we get a non ack
+                handler = self.responseHandlers.pop(requestId, None)
+                if handler is not None:
+                    handler.callback(asDict)
+
+        logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
+        publishingThread.queueWork(lambda: pub.sendMessage(
+            topic, packet=asDict, interface=self))
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class MeshInterface +(debugOut=None, noProto=False) +
+
+

Interface class for meshtastic devices

+

Properties:

+

isConnected +nodes +debugOut

+

Constructor

+

Keyword Arguments: +noProto – If True, don't try to run our protocol on the +link - just be a dumb serial client.

+
+ +Expand source code + +
class MeshInterface:
+    """Interface class for meshtastic devices
+
+    Properties:
+
+    isConnected
+    nodes
+    debugOut
+    """
+
+    def __init__(self, debugOut=None, noProto=False):
+        """Constructor
+
+        Keyword Arguments:
+            noProto -- If True, don't try to run our protocol on the
+                       link - just be a dumb serial client.
+        """
+        self.debugOut = debugOut
+        self.nodes = None  # FIXME
+        self.isConnected = threading.Event()
+        self.noProto = noProto
+        self.localNode = meshtastic.node.Node(self, -1)  # We fixup nodenum later
+        self.myInfo = None  # We don't have device info yet
+        self.responseHandlers = {}  # A map from request ID to the handler
+        self.failure = None  # If we've encountered a fatal exception it will be kept here
+        self._timeout = Timeout()
+        self.heartbeatTimer = None
+        random.seed()  # FIXME, we should not clobber the random seedval here, instead tell user they must call it
+        self.currentPacketId = random.randint(0, 0xffffffff)
+        self.nodesByNum = None
+        self.configId = None
+        self.defaultHopLimit = 3
+        self.gotResponse = False # used in gpio read
+        self.mask = None # used in gpio read and gpio watch
+
+    def close(self):
+        """Shutdown this interface"""
+        if self.heartbeatTimer:
+            self.heartbeatTimer.cancel()
+
+        self._sendDisconnect()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        if exc_type is not None and exc_value is not None:
+            logging.error(
+                f'An exception of type {exc_type} with value {exc_value} has occurred')
+        if traceback is not None:
+            logging.error(f'Traceback: {traceback}')
+        self.close()
+
+    def showInfo(self, file=sys.stdout):
+        """Show human readable summary about this object"""
+        owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
+        myinfo = ''
+        if self.myInfo:
+            myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}"
+        mesh = "\nNodes in mesh:"
+        nodes = ""
+        if self.nodes:
+            for n in self.nodes.values():
+                nodes = nodes + f"  {stripnl(n)}"
+        infos = owner + myinfo + mesh + nodes
+        print(infos)
+        return infos
+
+    def showNodes(self, includeSelf=True, file=sys.stdout):
+        """Show table summary of nodes in mesh"""
+        def formatFloat(value, precision=2, unit=''):
+            """Format a float value with precsion."""
+            return f'{value:.{precision}f}{unit}' if value else None
+
+        def getLH(ts):
+            """Format last heard"""
+            return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None
+
+        def getTimeAgo(ts):
+            """Format how long ago have we heard from this node (aka timeago)."""
+            return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None
+
+        rows = []
+        if self.nodes:
+            logging.debug(f'self.nodes:{self.nodes}')
+            for node in self.nodes.values():
+                if not includeSelf and node['num'] == self.localNode.nodeNum:
+                    continue
+
+                row = {"N": 0}
+
+                user = node.get('user')
+                if user:
+                    row.update({
+                        "User": user['longName'],
+                        "AKA":  user['shortName'],
+                        "ID":   user['id'],
+                    })
+
+                pos = node.get('position')
+                if pos:
+                    row.update({
+                        "Latitude":  formatFloat(pos.get("latitude"),     4, "°"),
+                        "Longitude": formatFloat(pos.get("longitude"),    4, "°"),
+                        "Altitude":  formatFloat(pos.get("altitude"),     0, " m"),
+                        "Battery":   formatFloat(pos.get("batteryLevel"), 2, "%"),
+                    })
+
+                row.update({
+                    "SNR":       formatFloat(node.get("snr"), 2, " dB"),
+                    "LastHeard": getLH(node.get("lastHeard")),
+                    "Since":     getTimeAgo(node.get("lastHeard")),
+                })
+
+                rows.append(row)
+
+        rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True)
+        for i, row in enumerate(rows):
+            row['N'] = i+1
+
+        table = tabulate(rows, headers='keys', missingval='N/A', tablefmt='fancy_grid')
+        print(table)
+        return table
+
+
+    def getNode(self, nodeId):
+        """Return a node object which contains device settings and channel info"""
+        if nodeId == LOCAL_ADDR:
+            return self.localNode
+        else:
+            n = meshtastic.node.Node(self, nodeId)
+            logging.debug("About to requestConfig")
+            n.requestConfig()
+            if not n.waitForConfig():
+                our_exit("Error: Timed out waiting for node config")
+            return n
+
+    def sendText(self, text: AnyStr,
+                 destinationId=BROADCAST_ADDR,
+                 wantAck=False,
+                 wantResponse=False,
+                 hopLimit=None,
+                 onResponse=None,
+                 channelIndex=0):
+        """Send a utf8 string to some other node, if the node has a display it
+           will also be shown on the device.
+
+        Arguments:
+            text {string} -- The text to send
+
+        Keyword Arguments:
+            destinationId {nodeId or nodeNum} -- where to send this
+                                                 message (default: {BROADCAST_ADDR})
+            portNum -- the application portnum (similar to IP port numbers)
+                       of the destination, see portnums.proto for a list
+            wantAck -- True if you want the message sent in a reliable manner
+                       (with retries and ack/nak provided for delivery)
+            wantResponse -- True if you want the service on the other side to
+                            send an application layer response
+
+        Returns the sent packet. The id field will be populated in this packet
+        and can be used to track future message acks/naks.
+        """
+        if hopLimit is None:
+            hopLimit = self.defaultHopLimit
+
+        return self.sendData(text.encode("utf-8"), destinationId,
+                             portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
+                             wantAck=wantAck,
+                             wantResponse=wantResponse,
+                             hopLimit=hopLimit,
+                             onResponse=onResponse,
+                             channelIndex=channelIndex)
+
+    def sendData(self, data, destinationId=BROADCAST_ADDR,
+                 portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
+                 wantResponse=False,
+                 hopLimit=None,
+                 onResponse=None,
+                 channelIndex=0):
+        """Send a data packet to some other node
+
+        Keyword Arguments:
+            data -- the data to send, either as an array of bytes or
+                    as a protobuf (which will be automatically
+                    serialized to bytes)
+            destinationId {nodeId or nodeNum} -- where to send this
+                    message (default: {BROADCAST_ADDR})
+            portNum -- the application portnum (similar to IP port numbers)
+                    of the destination, see portnums.proto for a list
+            wantAck -- True if you want the message sent in a reliable
+                    manner (with retries and ack/nak provided for delivery)
+            wantResponse -- True if you want the service on the other
+                    side to send an application layer response
+            onResponse -- A closure of the form funct(packet), that will be
+                    called when a response packet arrives (or the transaction
+                    is NAKed due to non receipt)
+            channelIndex - channel number to use
+
+        Returns the sent packet. The id field will be populated in this packet
+        and can be used to track future message acks/naks.
+        """
+        if hopLimit is None:
+            hopLimit = self.defaultHopLimit
+
+        if getattr(data, "SerializeToString", None):
+            logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
+            data = data.SerializeToString()
+
+        logging.debug(f"len(data): {len(data)}")
+        logging.debug(f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}")
+        if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
+            raise Exception("Data payload too big")
+
+        if portNum == portnums_pb2.PortNum.UNKNOWN_APP:  # we are now more strict wrt port numbers
+            our_exit("Warning: A non-zero port number must be specified")
+
+        meshPacket = mesh_pb2.MeshPacket()
+        meshPacket.channel = channelIndex
+        meshPacket.decoded.payload = data
+        meshPacket.decoded.portnum = portNum
+        meshPacket.decoded.want_response = wantResponse
+
+        p = self._sendPacket(meshPacket, destinationId,
+                             wantAck=wantAck, hopLimit=hopLimit)
+        if onResponse is not None:
+            self._addResponseHandler(p.id, onResponse)
+        return p
+
+    def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
+                     destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False):
+        """
+        Send a position packet to some other node (normally a broadcast)
+
+        Also, the device software will notice this packet and use it to automatically
+        set its notion of the local position.
+
+        If timeSec is not specified (recommended), we will use the local machine time.
+
+        Returns the sent packet. The id field will be populated in this packet and
+        can be used to track future message acks/naks.
+        """
+        p = mesh_pb2.Position()
+        if latitude != 0.0:
+            p.latitude_i = int(latitude / 1e-7)
+            logging.debug(f'p.latitude_i:{p.latitude_i}')
+
+        if longitude != 0.0:
+            p.longitude_i = int(longitude / 1e-7)
+            logging.debug(f'p.longitude_i:{p.longitude_i}')
+
+        if altitude != 0:
+            p.altitude = int(altitude)
+            logging.debug(f'p.altitude:{p.altitude}')
+
+        if timeSec == 0:
+            timeSec = time.time()  # returns unix timestamp in seconds
+        p.time = int(timeSec)
+        logging.debug(f'p.time:{p.time}')
+
+        return self.sendData(p, destinationId,
+                             portNum=portnums_pb2.PortNum.POSITION_APP,
+                             wantAck=wantAck,
+                             wantResponse=wantResponse)
+
+    def _addResponseHandler(self, requestId, callback):
+        self.responseHandlers[requestId] = ResponseHandler(callback)
+
+    def _sendPacket(self, meshPacket,
+                    destinationId=BROADCAST_ADDR,
+                    wantAck=False, hopLimit=None):
+        """Send a MeshPacket to the specified node (or if unspecified, broadcast).
+        You probably don't want this - use sendData instead.
+
+        Returns the sent packet. The id field will be populated in this packet and
+        can be used to track future message acks/naks.
+        """
+        if hopLimit is None:
+            hopLimit = self.defaultHopLimit
+
+        # We allow users to talk to the local node before we've completed the full connection flow...
+        if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
+            self._waitConnected()
+
+        toRadio = mesh_pb2.ToRadio()
+
+        nodeNum = 0
+        if destinationId is None:
+            our_exit("Warning: destinationId must not be None")
+        elif isinstance(destinationId, int):
+            nodeNum = destinationId
+        elif destinationId == BROADCAST_ADDR:
+            nodeNum = BROADCAST_NUM
+        elif destinationId == LOCAL_ADDR:
+            if self.myInfo:
+                nodeNum = self.myInfo.my_node_num
+            else:
+                our_exit("Warning: No myInfo found.")
+        # A simple hex style nodeid - we can parse this without needing the DB
+        elif destinationId.startswith("!"):
+            nodeNum = int(destinationId[1:], 16)
+        else:
+            if self.nodes:
+                node = self.nodes.get(destinationId)
+                if not node:
+                    our_exit(f"Warning: NodeId {destinationId} not found in DB")
+                nodeNum = node['num']
+            else:
+                logging.warning("Warning: There were no self.nodes.")
+
+        meshPacket.to = nodeNum
+        meshPacket.want_ack = wantAck
+        meshPacket.hop_limit = hopLimit
+
+        # if the user hasn't set an ID for this packet (likely and recommended),
+        # we should pick a new unique ID so the message can be tracked.
+        if meshPacket.id == 0:
+            meshPacket.id = self._generatePacketId()
+
+        toRadio.packet.CopyFrom(meshPacket)
+        if self.noProto:
+            logging.warning(f"Not sending packet because protocol use is disabled by noProto")
+        else:
+            logging.debug(f"Sending packet: {stripnl(meshPacket)}")
+            self._sendToRadio(toRadio)
+        return meshPacket
+
+    def waitForConfig(self):
+        """Block until radio config is received. Returns True if config has been received."""
+        success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
+        if not success:
+            raise Exception("Timed out waiting for interface config")
+
+    def getMyNodeInfo(self):
+        """Get info about my node."""
+        if self.myInfo is None:
+            return None
+        logging.debug(f'self.nodesByNum:{self.nodesByNum}')
+        return self.nodesByNum.get(self.myInfo.my_node_num)
+
+    def getMyUser(self):
+        """Get user"""
+        nodeInfo = self.getMyNodeInfo()
+        if nodeInfo is not None:
+            return nodeInfo.get('user')
+        return None
+
+    def getLongName(self):
+        """Get long name"""
+        user = self.getMyUser()
+        if user is not None:
+            return user.get('longName', None)
+        return None
+
+    def getShortName(self):
+        """Get short name"""
+        user = self.getMyUser()
+        if user is not None:
+            return user.get('shortName', None)
+        return None
+
+    def _waitConnected(self):
+        """Block until the initial node db download is complete, or timeout
+        and raise an exception"""
+        if not self.noProto:
+            if not self.isConnected.wait(15.0):  # timeout after x seconds
+                raise Exception("Timed out waiting for connection completion")
+
+        # If we failed while connecting, raise the connection to the client
+        if self.failure:
+            raise self.failure
+
+    def _generatePacketId(self):
+        """Get a new unique packet ID"""
+        if self.currentPacketId is None:
+            raise Exception("Not connected yet, can not generate packet")
+        else:
+            self.currentPacketId = (self.currentPacketId + 1) & 0xffffffff
+            return self.currentPacketId
+
+    def _disconnected(self):
+        """Called by subclasses to tell clients this interface has disconnected"""
+        self.isConnected.clear()
+        publishingThread.queueWork(lambda: pub.sendMessage(
+            "meshtastic.connection.lost", interface=self))
+
+    def _startHeartbeat(self):
+        """We need to send a heartbeat message to the device every X seconds"""
+        def callback():
+            self.heartbeatTimer = None
+            prefs = self.localNode.radioConfig.preferences
+            i = prefs.phone_timeout_secs / 2
+            logging.debug(f"Sending heartbeat, interval {i}")
+            if i != 0:
+                self.heartbeatTimer = threading.Timer(i, callback)
+                self.heartbeatTimer.start()
+                p = mesh_pb2.ToRadio()
+                self._sendToRadio(p)
+
+        callback()  # run our periodic callback now, it will make another timer if necessary
+
+    def _connected(self):
+        """Called by this class to tell clients we are now fully connected to a node
+        """
+        # (because I'm lazy) _connected might be called when remote Node
+        # objects complete their config reads, don't generate redundant isConnected
+        # for the local interface
+        if not self.isConnected.is_set():
+            self.isConnected.set()
+            self._startHeartbeat()
+            publishingThread.queueWork(lambda: pub.sendMessage(
+                "meshtastic.connection.established", interface=self))
+
+    def _startConfig(self):
+        """Start device packets flowing"""
+        self.myInfo = None
+        self.nodes = {}  # nodes keyed by ID
+        self.nodesByNum = {}  # nodes keyed by nodenum
+
+        startConfig = mesh_pb2.ToRadio()
+        self.configId = random.randint(0, 0xffffffff)
+        startConfig.want_config_id = self.configId
+        self._sendToRadio(startConfig)
+
+    def _sendDisconnect(self):
+        """Tell device we are done using it"""
+        m = mesh_pb2.ToRadio()
+        m.disconnect = True
+        self._sendToRadio(m)
+
+    def _sendToRadio(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        if self.noProto:
+            logging.warning(f"Not sending packet because protocol use is disabled by noProto")
+        else:
+            #logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
+            self._sendToRadioImpl(toRadio)
+
+    def _sendToRadioImpl(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        logging.error(f"Subclass must provide toradio: {toRadio}")
+
+    def _handleConfigComplete(self):
+        """
+        Done with initial config messages, now send regular MeshPackets
+        to ask for settings and channels
+        """
+        self.localNode.requestConfig()
+
+    def _handleFromRadio(self, fromRadioBytes):
+        """
+        Handle a packet that arrived from the radio(update model and publish events)
+
+        Called by subclasses."""
+        fromRadio = mesh_pb2.FromRadio()
+        fromRadio.ParseFromString(fromRadioBytes)
+        logging.debug(f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}")
+        asDict = google.protobuf.json_format.MessageToDict(fromRadio)
+        logging.debug(f"Received from radio: {fromRadio}")
+        if fromRadio.HasField("my_info"):
+            self.myInfo = fromRadio.my_info
+            self.localNode.nodeNum = self.myInfo.my_node_num
+            logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
+
+            failmsg = None
+            # Check for app too old
+            if self.myInfo.min_app_version > OUR_APP_VERSION:
+                failmsg = "This device needs a newer python client, run 'pip install --upgrade meshtastic'."\
+                          "For more information see https://tinyurl.com/5bjsxu32"
+
+            # check for firmware too old
+            if self.myInfo.max_channels == 0:
+                failmsg = "This version of meshtastic-python requires device firmware version 1.2 or later. "\
+                          "For more information see https://tinyurl.com/5bjsxu32"
+
+            if failmsg:
+                self.failure = Exception(failmsg)
+                self.isConnected.set()  # let waitConnected return this exception
+                self.close()
+
+        elif fromRadio.HasField("node_info"):
+            node = asDict["nodeInfo"]
+            try:
+                self._fixupPosition(node["position"])
+            except:
+                logging.debug("Node without position")
+
+            logging.debug(f"Received nodeinfo: {node}")
+
+            self.nodesByNum[node["num"]] = node
+            if "user" in node:  # Some nodes might not have user/ids assigned yet
+                if "id" in node["user"]:
+                    self.nodes[node["user"]["id"]] = node
+            publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
+                                                               node=node, interface=self))
+        elif fromRadio.config_complete_id == self.configId:
+            # we ignore the config_complete_id, it is unneeded for our
+            # stream API fromRadio.config_complete_id
+            logging.debug(f"Config complete ID {self.configId}")
+            self._handleConfigComplete()
+        elif fromRadio.HasField("packet"):
+            self._handlePacketFromRadio(fromRadio.packet)
+        elif fromRadio.rebooted:
+            # Tell clients the device went away.  Careful not to call the overridden
+            # subclass version that closes the serial port
+            MeshInterface._disconnected(self)
+
+            self._startConfig()  # redownload the node db etc...
+        else:
+            logging.debug("Unexpected FromRadio payload")
+
+    def _fixupPosition(self, position):
+        """Convert integer lat/lon into floats
+
+        Arguments:
+            position {Position dictionary} -- object ot fix up
+        """
+        if "latitudeI" in position:
+            position["latitude"] = position["latitudeI"] * 1e-7
+        if "longitudeI" in position:
+            position["longitude"] = position["longitudeI"] * 1e-7
+
+    def _nodeNumToId(self, num):
+        """Map a node node number to a node ID
+
+        Arguments:
+            num {int} -- Node number
+
+        Returns:
+            string -- Node ID
+        """
+        if num == BROADCAST_NUM:
+            return BROADCAST_ADDR
+
+        try:
+            return self.nodesByNum[num]["user"]["id"]
+        except:
+            logging.debug(f"Node {num} not found for fromId")
+            return None
+
+    def _getOrCreateByNum(self, nodeNum):
+        """Given a nodenum find the NodeInfo in the DB (or create if necessary)"""
+        if nodeNum == BROADCAST_NUM:
+            raise Exception("Can not create/find nodenum by the broadcast num")
+
+        if nodeNum in self.nodesByNum:
+            return self.nodesByNum[nodeNum]
+        else:
+            n = {"num": nodeNum}  # Create a minimial node db entry
+            self.nodesByNum[nodeNum] = n
+            return n
+
+    def _handlePacketFromRadio(self, meshPacket, hack=False):
+        """Handle a MeshPacket that just arrived from the radio
+
+        hack - well, since we used 'from', which is a python keyword,
+               as an attribute to MeshPacket in protobufs,
+               there really is no way to do something like this:
+                    meshPacket = mesh_pb2.MeshPacket()
+                    meshPacket.from = 123
+               If hack is True, we can unit test this code.
+
+        Will publish one of the following events:
+        - meshtastic.receive.text(packet = MeshPacket dictionary)
+        - meshtastic.receive.position(packet = MeshPacket dictionary)
+        - meshtastic.receive.user(packet = MeshPacket dictionary)
+        - meshtastic.receive.data(packet = MeshPacket dictionary)
+        """
+        asDict = google.protobuf.json_format.MessageToDict(meshPacket)
+
+        # We normally decompose the payload into a dictionary so that the client
+        # doesn't need to understand protobufs.  But advanced clients might
+        # want the raw protobuf, so we provide it in "raw"
+        asDict["raw"] = meshPacket
+
+        # from might be missing if the nodenum was zero.
+        if not hack and "from" not in asDict:
+            asDict["from"] = 0
+            logging.error(f"Device returned a packet we sent, ignoring: {stripnl(asDict)}")
+            print(f"Error: Device returned a packet we sent, ignoring: {stripnl(asDict)}")
+            return
+        if "to" not in asDict:
+            asDict["to"] = 0
+
+        # /add fromId and toId fields based on the node ID
+        try:
+            asDict["fromId"] = self._nodeNumToId(asDict["from"])
+        except Exception as ex:
+            logging.warning(f"Not populating fromId {ex}")
+        try:
+            asDict["toId"] = self._nodeNumToId(asDict["to"])
+        except Exception as ex:
+            logging.warning(f"Not populating toId {ex}")
+
+        # We could provide our objects as DotMaps - which work with . notation or as dictionaries
+        # asObj = DotMap(asDict)
+        topic = "meshtastic.receive"  # Generic unknown packet type
+
+        decoded = asDict["decoded"]
+        # The default MessageToDict converts byte arrays into base64 strings.
+        # We don't want that - it messes up data payload.  So slam in the correct
+        # byte array.
+        decoded["payload"] = meshPacket.decoded.payload
+
+        # UNKNOWN_APP is the default protobuf portnum value, and therefore if not
+        # set it will not be populated at all to make API usage easier, set
+        # it to prevent confusion
+        if "portnum" not in decoded:
+            new_portnum = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP)
+            decoded["portnum"] = new_portnum
+            logging.warning(f"portnum was not in decoded. Setting to:{new_portnum}")
+
+        portnum = decoded["portnum"]
+
+        topic = f"meshtastic.receive.data.{portnum}"
+
+        # decode position protobufs and update nodedb, provide decoded version
+        # as "position" in the published msg move the following into a 'decoders'
+        # API that clients could register?
+        portNumInt = meshPacket.decoded.portnum  # we want portnum as an int
+        handler = protocols.get(portNumInt)
+        # The decoded protobuf as a dictionary (if we understand this message)
+        p = None
+        if handler is not None:
+            topic = f"meshtastic.receive.{handler.name}"
+
+            # Convert to protobuf if possible
+            if handler.protobufFactory is not None:
+                pb = handler.protobufFactory()
+                pb.ParseFromString(meshPacket.decoded.payload)
+                p = google.protobuf.json_format.MessageToDict(pb)
+                asDict["decoded"][handler.name] = p
+                # Also provide the protobuf raw
+                asDict["decoded"][handler.name]["raw"] = pb
+
+            # Call specialized onReceive if necessary
+            if handler.onReceive is not None:
+                handler.onReceive(self, asDict)
+
+        # Is this message in response to a request, if so, look for a handler
+        requestId = decoded.get("requestId")
+        if requestId is not None:
+            # We ignore ACK packets, but send NAKs and data responses to the handlers
+            routing = decoded.get("routing")
+            isAck = routing is not None and ("errorReason" not in routing)
+            if not isAck:
+                # we keep the responseHandler in dict until we get a non ack
+                handler = self.responseHandlers.pop(requestId, None)
+                if handler is not None:
+                    handler.callback(asDict)
+
+        logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
+        publishingThread.queueWork(lambda: pub.sendMessage(
+            topic, packet=asDict, interface=self))
+
+

Subclasses

+ +

Methods

+
+
+def close(self) +
+
+

Shutdown this interface

+
+ +Expand source code + +
def close(self):
+    """Shutdown this interface"""
+    if self.heartbeatTimer:
+        self.heartbeatTimer.cancel()
+
+    self._sendDisconnect()
+
+
+
+def getLongName(self) +
+
+

Get long name

+
+ +Expand source code + +
def getLongName(self):
+    """Get long name"""
+    user = self.getMyUser()
+    if user is not None:
+        return user.get('longName', None)
+    return None
+
+
+
+def getMyNodeInfo(self) +
+
+

Get info about my node.

+
+ +Expand source code + +
def getMyNodeInfo(self):
+    """Get info about my node."""
+    if self.myInfo is None:
+        return None
+    logging.debug(f'self.nodesByNum:{self.nodesByNum}')
+    return self.nodesByNum.get(self.myInfo.my_node_num)
+
+
+
+def getMyUser(self) +
+
+

Get user

+
+ +Expand source code + +
def getMyUser(self):
+    """Get user"""
+    nodeInfo = self.getMyNodeInfo()
+    if nodeInfo is not None:
+        return nodeInfo.get('user')
+    return None
+
+
+
+def getNode(self, nodeId) +
+
+

Return a node object which contains device settings and channel info

+
+ +Expand source code + +
def getNode(self, nodeId):
+    """Return a node object which contains device settings and channel info"""
+    if nodeId == LOCAL_ADDR:
+        return self.localNode
+    else:
+        n = meshtastic.node.Node(self, nodeId)
+        logging.debug("About to requestConfig")
+        n.requestConfig()
+        if not n.waitForConfig():
+            our_exit("Error: Timed out waiting for node config")
+        return n
+
+
+
+def getShortName(self) +
+
+

Get short name

+
+ +Expand source code + +
def getShortName(self):
+    """Get short name"""
+    user = self.getMyUser()
+    if user is not None:
+        return user.get('shortName', None)
+    return None
+
+
+
+def sendData(self, data, destinationId='^all', portNum=256, wantAck=False, wantResponse=False, hopLimit=None, onResponse=None, channelIndex=0) +
+
+

Send a data packet to some other node

+

Keyword Arguments: +data – the data to send, either as an array of bytes or +as a protobuf (which will be automatically +serialized to bytes) +destinationId {nodeId or nodeNum} – where to send this +message (default: {BROADCAST_ADDR}) +portNum – the application portnum (similar to IP port numbers) +of the destination, see portnums.proto for a list +wantAck – True if you want the message sent in a reliable +manner (with retries and ack/nak provided for delivery) +wantResponse – True if you want the service on the other +side to send an application layer response +onResponse – A closure of the form funct(packet), that will be +called when a response packet arrives (or the transaction +is NAKed due to non receipt) +channelIndex - channel number to use

+

Returns the sent packet. The id field will be populated in this packet +and can be used to track future message acks/naks.

+
+ +Expand source code + +
def sendData(self, data, destinationId=BROADCAST_ADDR,
+             portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
+             wantResponse=False,
+             hopLimit=None,
+             onResponse=None,
+             channelIndex=0):
+    """Send a data packet to some other node
+
+    Keyword Arguments:
+        data -- the data to send, either as an array of bytes or
+                as a protobuf (which will be automatically
+                serialized to bytes)
+        destinationId {nodeId or nodeNum} -- where to send this
+                message (default: {BROADCAST_ADDR})
+        portNum -- the application portnum (similar to IP port numbers)
+                of the destination, see portnums.proto for a list
+        wantAck -- True if you want the message sent in a reliable
+                manner (with retries and ack/nak provided for delivery)
+        wantResponse -- True if you want the service on the other
+                side to send an application layer response
+        onResponse -- A closure of the form funct(packet), that will be
+                called when a response packet arrives (or the transaction
+                is NAKed due to non receipt)
+        channelIndex - channel number to use
+
+    Returns the sent packet. The id field will be populated in this packet
+    and can be used to track future message acks/naks.
+    """
+    if hopLimit is None:
+        hopLimit = self.defaultHopLimit
+
+    if getattr(data, "SerializeToString", None):
+        logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
+        data = data.SerializeToString()
+
+    logging.debug(f"len(data): {len(data)}")
+    logging.debug(f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}")
+    if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
+        raise Exception("Data payload too big")
+
+    if portNum == portnums_pb2.PortNum.UNKNOWN_APP:  # we are now more strict wrt port numbers
+        our_exit("Warning: A non-zero port number must be specified")
+
+    meshPacket = mesh_pb2.MeshPacket()
+    meshPacket.channel = channelIndex
+    meshPacket.decoded.payload = data
+    meshPacket.decoded.portnum = portNum
+    meshPacket.decoded.want_response = wantResponse
+
+    p = self._sendPacket(meshPacket, destinationId,
+                         wantAck=wantAck, hopLimit=hopLimit)
+    if onResponse is not None:
+        self._addResponseHandler(p.id, onResponse)
+    return p
+
+
+
+def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0, destinationId='^all', wantAck=False, wantResponse=False) +
+
+

Send a position packet to some other node (normally a broadcast)

+

Also, the device software will notice this packet and use it to automatically +set its notion of the local position.

+

If timeSec is not specified (recommended), we will use the local machine time.

+

Returns the sent packet. The id field will be populated in this packet and +can be used to track future message acks/naks.

+
+ +Expand source code + +
def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
+                 destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False):
+    """
+    Send a position packet to some other node (normally a broadcast)
+
+    Also, the device software will notice this packet and use it to automatically
+    set its notion of the local position.
+
+    If timeSec is not specified (recommended), we will use the local machine time.
+
+    Returns the sent packet. The id field will be populated in this packet and
+    can be used to track future message acks/naks.
+    """
+    p = mesh_pb2.Position()
+    if latitude != 0.0:
+        p.latitude_i = int(latitude / 1e-7)
+        logging.debug(f'p.latitude_i:{p.latitude_i}')
+
+    if longitude != 0.0:
+        p.longitude_i = int(longitude / 1e-7)
+        logging.debug(f'p.longitude_i:{p.longitude_i}')
+
+    if altitude != 0:
+        p.altitude = int(altitude)
+        logging.debug(f'p.altitude:{p.altitude}')
+
+    if timeSec == 0:
+        timeSec = time.time()  # returns unix timestamp in seconds
+    p.time = int(timeSec)
+    logging.debug(f'p.time:{p.time}')
+
+    return self.sendData(p, destinationId,
+                         portNum=portnums_pb2.PortNum.POSITION_APP,
+                         wantAck=wantAck,
+                         wantResponse=wantResponse)
+
+
+
+def sendText(self, text: ~AnyStr, destinationId='^all', wantAck=False, wantResponse=False, hopLimit=None, onResponse=None, channelIndex=0) +
+
+

Send a utf8 string to some other node, if the node has a display it +will also be shown on the device.

+

Arguments

+

text {string} – The text to send

+

Keyword Arguments: +destinationId {nodeId or nodeNum} – where to send this +message (default: {BROADCAST_ADDR}) +portNum – the application portnum (similar to IP port numbers) +of the destination, see portnums.proto for a list +wantAck – True if you want the message sent in a reliable manner +(with retries and ack/nak provided for delivery) +wantResponse – True if you want the service on the other side to +send an application layer response

+

Returns the sent packet. The id field will be populated in this packet +and can be used to track future message acks/naks.

+
+ +Expand source code + +
def sendText(self, text: AnyStr,
+             destinationId=BROADCAST_ADDR,
+             wantAck=False,
+             wantResponse=False,
+             hopLimit=None,
+             onResponse=None,
+             channelIndex=0):
+    """Send a utf8 string to some other node, if the node has a display it
+       will also be shown on the device.
+
+    Arguments:
+        text {string} -- The text to send
+
+    Keyword Arguments:
+        destinationId {nodeId or nodeNum} -- where to send this
+                                             message (default: {BROADCAST_ADDR})
+        portNum -- the application portnum (similar to IP port numbers)
+                   of the destination, see portnums.proto for a list
+        wantAck -- True if you want the message sent in a reliable manner
+                   (with retries and ack/nak provided for delivery)
+        wantResponse -- True if you want the service on the other side to
+                        send an application layer response
+
+    Returns the sent packet. The id field will be populated in this packet
+    and can be used to track future message acks/naks.
+    """
+    if hopLimit is None:
+        hopLimit = self.defaultHopLimit
+
+    return self.sendData(text.encode("utf-8"), destinationId,
+                         portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
+                         wantAck=wantAck,
+                         wantResponse=wantResponse,
+                         hopLimit=hopLimit,
+                         onResponse=onResponse,
+                         channelIndex=channelIndex)
+
+
+
+def showInfo(self, file=sys.stdout) +
+
+

Show human readable summary about this object

+
+ +Expand source code + +
def showInfo(self, file=sys.stdout):
+    """Show human readable summary about this object"""
+    owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
+    myinfo = ''
+    if self.myInfo:
+        myinfo = f"\nMy info: {stripnl(MessageToJson(self.myInfo))}"
+    mesh = "\nNodes in mesh:"
+    nodes = ""
+    if self.nodes:
+        for n in self.nodes.values():
+            nodes = nodes + f"  {stripnl(n)}"
+    infos = owner + myinfo + mesh + nodes
+    print(infos)
+    return infos
+
+
+
+def showNodes(self, includeSelf=True, file=sys.stdout) +
+
+

Show table summary of nodes in mesh

+
+ +Expand source code + +
def showNodes(self, includeSelf=True, file=sys.stdout):
+    """Show table summary of nodes in mesh"""
+    def formatFloat(value, precision=2, unit=''):
+        """Format a float value with precsion."""
+        return f'{value:.{precision}f}{unit}' if value else None
+
+    def getLH(ts):
+        """Format last heard"""
+        return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else None
+
+    def getTimeAgo(ts):
+        """Format how long ago have we heard from this node (aka timeago)."""
+        return timeago.format(datetime.fromtimestamp(ts), datetime.now()) if ts else None
+
+    rows = []
+    if self.nodes:
+        logging.debug(f'self.nodes:{self.nodes}')
+        for node in self.nodes.values():
+            if not includeSelf and node['num'] == self.localNode.nodeNum:
+                continue
+
+            row = {"N": 0}
+
+            user = node.get('user')
+            if user:
+                row.update({
+                    "User": user['longName'],
+                    "AKA":  user['shortName'],
+                    "ID":   user['id'],
+                })
+
+            pos = node.get('position')
+            if pos:
+                row.update({
+                    "Latitude":  formatFloat(pos.get("latitude"),     4, "°"),
+                    "Longitude": formatFloat(pos.get("longitude"),    4, "°"),
+                    "Altitude":  formatFloat(pos.get("altitude"),     0, " m"),
+                    "Battery":   formatFloat(pos.get("batteryLevel"), 2, "%"),
+                })
+
+            row.update({
+                "SNR":       formatFloat(node.get("snr"), 2, " dB"),
+                "LastHeard": getLH(node.get("lastHeard")),
+                "Since":     getTimeAgo(node.get("lastHeard")),
+            })
+
+            rows.append(row)
+
+    rows.sort(key=lambda r: r.get('LastHeard') or '0000', reverse=True)
+    for i, row in enumerate(rows):
+        row['N'] = i+1
+
+    table = tabulate(rows, headers='keys', missingval='N/A', tablefmt='fancy_grid')
+    print(table)
+    return table
+
+
+
+def waitForConfig(self) +
+
+

Block until radio config is received. Returns True if config has been received.

+
+ +Expand source code + +
def waitForConfig(self):
+    """Block until radio config is received. Returns True if config has been received."""
+    success = self._timeout.waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
+    if not success:
+        raise Exception("Timed out waiting for interface config")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/mesh_pb2.html b/docs/meshtastic/mesh_pb2.html new file mode 100644 index 0000000..de74a85 --- /dev/null +++ b/docs/meshtastic/mesh_pb2.html @@ -0,0 +1,9935 @@ + + + + + + +meshtastic.mesh_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.mesh_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: mesh.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import portnums_pb2 as portnums__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='mesh.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\nMeshProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\nmesh.proto\x1a\x0eportnums.proto\"\x94\x06\n\x08Position\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x15\n\rbattery_level\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\t \x01(\x07\x12,\n\x0flocation_source\x18\n \x01(\x0e\x32\x13.Position.LocSource\x12,\n\x0f\x61ltitude_source\x18\x0b \x01(\x0e\x32\x13.Position.AltSource\x12\x15\n\rpos_timestamp\x18\x0c \x01(\x07\x12\x17\n\x0fpos_time_millis\x18\r \x01(\x05\x12\x14\n\x0c\x61ltitude_hae\x18\x0e \x01(\x11\x12\x15\n\ralt_geoid_sep\x18\x0f \x01(\x11\x12\x0c\n\x04PDOP\x18\x10 \x01(\r\x12\x0c\n\x04HDOP\x18\x11 \x01(\r\x12\x0c\n\x04VDOP\x18\x12 \x01(\r\x12\x14\n\x0cgps_accuracy\x18\x13 \x01(\r\x12\x14\n\x0cground_speed\x18\x14 \x01(\r\x12\x14\n\x0cground_track\x18\x15 \x01(\r\x12\x13\n\x0b\x66ix_quality\x18\x16 \x01(\r\x12\x10\n\x08\x66ix_type\x18\x17 \x01(\r\x12\x14\n\x0csats_in_view\x18\x18 \x01(\r\x12\x11\n\tsensor_id\x18\x19 \x01(\r\x12\x17\n\x0fpos_next_update\x18( \x01(\r\x12\x16\n\x0epos_seq_number\x18) \x01(\r\"n\n\tLocSource\x12\x16\n\x12LOCSRC_UNSPECIFIED\x10\x00\x12\x17\n\x13LOCSRC_MANUAL_ENTRY\x10\x01\x12\x17\n\x13LOCSRC_GPS_INTERNAL\x10\x02\x12\x17\n\x13LOCSRC_GPS_EXTERNAL\x10\x03\"\x85\x01\n\tAltSource\x12\x16\n\x12\x41LTSRC_UNSPECIFIED\x10\x00\x12\x17\n\x13\x41LTSRC_MANUAL_ENTRY\x10\x01\x12\x17\n\x13\x41LTSRC_GPS_INTERNAL\x10\x02\x12\x17\n\x13\x41LTSRC_GPS_EXTERNAL\x10\x03\x12\x15\n\x11\x41LTSRC_BAROMETRIC\x10\x04J\x04\x08\x07\x10\x08J\x04\x08\x08\x10\t\"\xd7\x01\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x0f\n\x07macaddr\x18\x04 \x01(\x0c\x12 \n\x08hw_model\x18\x06 \x01(\x0e\x32\x0e.HardwareModel\x12\x13\n\x0bis_licensed\x18\x07 \x01(\x08\x12\x13\n\x04team\x18\x08 \x01(\x0e\x32\x05.Team\x12\x14\n\x0ctx_power_dbm\x18\n \x01(\r\x12\x14\n\x0c\x61nt_gain_dbi\x18\x0b \x01(\r\x12\x13\n\x0b\x61nt_azimuth\x18\x0c \x01(\r\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x02 \x03(\x07\"\xc5\x02\n\x07Routing\x12(\n\rroute_request\x18\x01 \x01(\x0b\x32\x0f.RouteDiscoveryH\x00\x12&\n\x0broute_reply\x18\x02 \x01(\x0b\x32\x0f.RouteDiscoveryH\x00\x12&\n\x0c\x65rror_reason\x18\x03 \x01(\x0e\x32\x0e.Routing.ErrorH\x00\"\xb4\x01\n\x05\x45rror\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08NO_ROUTE\x10\x01\x12\x0b\n\x07GOT_NAK\x10\x02\x12\x0b\n\x07TIMEOUT\x10\x03\x12\x10\n\x0cNO_INTERFACE\x10\x04\x12\x12\n\x0eMAX_RETRANSMIT\x10\x05\x12\x0e\n\nNO_CHANNEL\x10\x06\x12\r\n\tTOO_LARGE\x10\x07\x12\x0f\n\x0bNO_RESPONSE\x10\x08\x12\x0f\n\x0b\x42\x41\x44_REQUEST\x10 \x12\x12\n\x0eNOT_AUTHORIZED\x10!B\t\n\x07variant\"{\n\x04\x44\x61ta\x12\x19\n\x07portnum\x18\x01 \x01(\x0e\x32\x08.PortNum\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12\x15\n\rwant_response\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x65st\x18\x04 \x01(\x07\x12\x0e\n\x06source\x18\x05 \x01(\x07\x12\x12\n\nrequest_id\x18\x06 \x01(\x07\"\xf0\x03\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x07\x12\n\n\x02to\x18\x02 \x01(\x07\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\x12\x18\n\x07\x64\x65\x63oded\x18\x04 \x01(\x0b\x32\x05.DataH\x00\x12\x13\n\tencrypted\x18\x05 \x01(\x0cH\x00\x12\n\n\x02id\x18\x06 \x01(\x07\x12\x0f\n\x07rx_time\x18\x07 \x01(\x07\x12\x0e\n\x06rx_snr\x18\x08 \x01(\x02\x12\x11\n\thop_limit\x18\n \x01(\r\x12\x10\n\x08want_ack\x18\x0b \x01(\x08\x12&\n\x08priority\x18\x0c \x01(\x0e\x32\x14.MeshPacket.Priority\x12\x0f\n\x07rx_rssi\x18\r \x01(\x05\x12$\n\x07\x64\x65layed\x18\x0f \x01(\x0e\x32\x13.MeshPacket.Delayed\x12\x10\n\x08reply_id\x18\x10 \x01(\x07\x12\x12\n\nis_tapback\x18\x11 \x01(\x08\"[\n\x08Priority\x12\t\n\x05UNSET\x10\x00\x12\x07\n\x03MIN\x10\x01\x12\x0e\n\nBACKGROUND\x10\n\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10@\x12\x0c\n\x08RELIABLE\x10\x46\x12\x07\n\x03\x41\x43K\x10x\x12\x07\n\x03MAX\x10\x7f\"B\n\x07\x44\x65layed\x12\x0c\n\x08NO_DELAY\x10\x00\x12\x15\n\x11\x44\x45LAYED_BROADCAST\x10\x01\x12\x12\n\x0e\x44\x45LAYED_DIRECT\x10\x02\x42\x10\n\x0epayloadVariant\"j\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x13\n\x04user\x18\x02 \x01(\x0b\x32\x05.User\x12\x1b\n\x08position\x18\x03 \x01(\x0b\x32\t.Position\x12\x0b\n\x03snr\x18\x07 \x01(\x02\x12\x12\n\nlast_heard\x18\x04 \x01(\x07\"\xb9\x03\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\r\x12\x0f\n\x07has_gps\x18\x02 \x01(\x08\x12\x11\n\tnum_bands\x18\x03 \x01(\r\x12\x14\n\x0cmax_channels\x18\x0f \x01(\r\x12\x12\n\x06region\x18\x04 \x01(\tB\x02\x18\x01\x12\x1f\n\x13hw_model_deprecated\x18\x05 \x01(\tB\x02\x18\x01\x12\x18\n\x10\x66irmware_version\x18\x06 \x01(\t\x12&\n\nerror_code\x18\x07 \x01(\x0e\x32\x12.CriticalErrorCode\x12\x15\n\rerror_address\x18\x08 \x01(\r\x12\x13\n\x0b\x65rror_count\x18\t \x01(\r\x12\x14\n\x0creboot_count\x18\n \x01(\r\x12\x0f\n\x07\x62itrate\x18\x0b \x01(\x02\x12\x1c\n\x14message_timeout_msec\x18\r \x01(\r\x12\x17\n\x0fmin_app_version\x18\x0e \x01(\r\x12\x15\n\rair_period_tx\x18\x10 \x03(\r\x12\x15\n\rair_period_rx\x18\x11 \x03(\r\x12\x10\n\x08has_wifi\x18\x12 \x01(\x08\x12\x1b\n\x13\x63hannel_utilization\x18\x13 \x01(\x02\"\xb5\x01\n\tLogRecord\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x07\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x1f\n\x05level\x18\x04 \x01(\x0e\x32\x10.LogRecord.Level\"X\n\x05Level\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x43RITICAL\x10\x32\x12\t\n\x05\x45RROR\x10(\x12\x0b\n\x07WARNING\x10\x1e\x12\x08\n\x04INFO\x10\x14\x12\t\n\x05\x44\x45\x42UG\x10\n\x12\t\n\x05TRACE\x10\x05\"\xe9\x01\n\tFromRadio\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x1d\n\x06packet\x18\x0b \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x1e\n\x07my_info\x18\x03 \x01(\x0b\x32\x0b.MyNodeInfoH\x00\x12\x1e\n\tnode_info\x18\x04 \x01(\x0b\x32\t.NodeInfoH\x00\x12 \n\nlog_record\x18\x07 \x01(\x0b\x32\n.LogRecordH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x08 \x01(\rH\x00\x12\x12\n\x08rebooted\x18\t \x01(\x08H\x00\x42\x10\n\x0epayloadVariantJ\x04\x08\x02\x10\x03J\x04\x08\x06\x10\x07\"\xe1\x01\n\x07ToRadio\x12\x1d\n\x06packet\x18\x02 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12&\n\tpeer_info\x18\x03 \x01(\x0b\x32\x11.ToRadio.PeerInfoH\x00\x12\x18\n\x0ewant_config_id\x18\x64 \x01(\rH\x00\x12\x14\n\ndisconnect\x18h \x01(\x08H\x00\x1a\x35\n\x08PeerInfo\x12\x13\n\x0b\x61pp_version\x18\x01 \x01(\r\x12\x14\n\x0cmqtt_gateway\x18\x02 \x01(\x08\x42\x10\n\x0epayloadVariantJ\x04\x08\x01\x10\x02J\x04\x08\x65\x10\x66J\x04\x08\x66\x10gJ\x04\x08g\x10h*\xac\x02\n\rHardwareModel\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08TLORA_V2\x10\x01\x12\x0c\n\x08TLORA_V1\x10\x02\x12\x12\n\x0eTLORA_V2_1_1p6\x10\x03\x12\t\n\x05TBEAM\x10\x04\x12\x0f\n\x0bHELTEC_V2_0\x10\x05\x12\x0c\n\x08TBEAM0p7\x10\x06\x12\n\n\x06T_ECHO\x10\x07\x12\x10\n\x0cTLORA_V1_1p3\x10\x08\x12\x0b\n\x07RAK4631\x10\t\x12\x0f\n\x0bHELTEC_V2_1\x10\n\x12\x11\n\rLORA_RELAY_V1\x10 \x12\x0e\n\nNRF52840DK\x10!\x12\x07\n\x03PPR\x10\"\x12\x0f\n\x0bGENIEBLOCKS\x10#\x12\x11\n\rNRF52_UNKNOWN\x10$\x12\r\n\tPORTDUINO\x10%\x12\x0f\n\x0b\x41NDROID_SIM\x10&\x12\n\n\x06\x44IY_V1\x10\'*\xb5\x01\n\x04Team\x12\t\n\x05\x43LEAR\x10\x00\x12\x08\n\x04\x43YAN\x10\x01\x12\t\n\x05WHITE\x10\x02\x12\n\n\x06YELLOW\x10\x03\x12\n\n\x06ORANGE\x10\x04\x12\x0b\n\x07MAGENTA\x10\x05\x12\x07\n\x03RED\x10\x06\x12\n\n\x06MAROON\x10\x07\x12\n\n\x06PURPLE\x10\x08\x12\r\n\tDARK_BLUE\x10\t\x12\x08\n\x04\x42LUE\x10\n\x12\x08\n\x04TEAL\x10\x0b\x12\t\n\x05GREEN\x10\x0c\x12\x0e\n\nDARK_GREEN\x10\r\x12\t\n\x05\x42ROWN\x10\x0e*.\n\tConstants\x12\n\n\x06Unused\x10\x00\x12\x15\n\x10\x44\x41TA_PAYLOAD_LEN\x10\xed\x01*\xe1\x01\n\x11\x43riticalErrorCode\x12\x08\n\x04None\x10\x00\x12\x0e\n\nTxWatchdog\x10\x01\x12\x12\n\x0eSleepEnterWait\x10\x02\x12\x0b\n\x07NoRadio\x10\x03\x12\x0f\n\x0bUnspecified\x10\x04\x12\x13\n\x0fUBloxInitFailed\x10\x05\x12\x0c\n\x08NoAXP192\x10\x06\x12\x17\n\x13InvalidRadioSetting\x10\x07\x12\x12\n\x0eTransmitFailed\x10\x08\x12\x0c\n\x08\x42rownout\x10\t\x12\x11\n\rSX1262Failure\x10\n\x12\x0f\n\x0bRadioSpiBug\x10\x0b\x42\x46\n\x13\x63om.geeksville.meshB\nMeshProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+  ,
+  dependencies=[portnums__pb2.DESCRIPTOR,])
+
+_HARDWAREMODEL = _descriptor.EnumDescriptor(
+  name='HardwareModel',
+  full_name='HardwareModel',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNSET', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TLORA_V2', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TLORA_V1', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TLORA_V2_1_1p6', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TBEAM', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='HELTEC_V2_0', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TBEAM0p7', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='T_ECHO', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TLORA_V1_1p3', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RAK4631', index=9, number=9,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='HELTEC_V2_1', index=10, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LORA_RELAY_V1', index=11, number=32,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NRF52840DK', index=12, number=33,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PPR', index=13, number=34,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GENIEBLOCKS', index=14, number=35,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NRF52_UNKNOWN', index=15, number=36,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PORTDUINO', index=16, number=37,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ANDROID_SIM', index=17, number=38,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DIY_V1', index=18, number=39,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3225,
+  serialized_end=3525,
+)
+_sym_db.RegisterEnumDescriptor(_HARDWAREMODEL)
+
+HardwareModel = enum_type_wrapper.EnumTypeWrapper(_HARDWAREMODEL)
+_TEAM = _descriptor.EnumDescriptor(
+  name='Team',
+  full_name='Team',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='CLEAR', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CYAN', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='WHITE', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='YELLOW', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ORANGE', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MAGENTA', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RED', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MAROON', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PURPLE', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DARK_BLUE', index=9, number=9,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='BLUE', index=10, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TEAL', index=11, number=11,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GREEN', index=12, number=12,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DARK_GREEN', index=13, number=13,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='BROWN', index=14, number=14,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3528,
+  serialized_end=3709,
+)
+_sym_db.RegisterEnumDescriptor(_TEAM)
+
+Team = enum_type_wrapper.EnumTypeWrapper(_TEAM)
+_CONSTANTS = _descriptor.EnumDescriptor(
+  name='Constants',
+  full_name='Constants',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='Unused', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DATA_PAYLOAD_LEN', index=1, number=237,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3711,
+  serialized_end=3757,
+)
+_sym_db.RegisterEnumDescriptor(_CONSTANTS)
+
+Constants = enum_type_wrapper.EnumTypeWrapper(_CONSTANTS)
+_CRITICALERRORCODE = _descriptor.EnumDescriptor(
+  name='CriticalErrorCode',
+  full_name='CriticalErrorCode',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='None', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TxWatchdog', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SleepEnterWait', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NoRadio', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Unspecified', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='UBloxInitFailed', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NoAXP192', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='InvalidRadioSetting', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TransmitFailed', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='Brownout', index=9, number=9,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SX1262Failure', index=10, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RadioSpiBug', index=11, number=11,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3760,
+  serialized_end=3985,
+)
+_sym_db.RegisterEnumDescriptor(_CRITICALERRORCODE)
+
+CriticalErrorCode = enum_type_wrapper.EnumTypeWrapper(_CRITICALERRORCODE)
+UNSET = 0
+TLORA_V2 = 1
+TLORA_V1 = 2
+TLORA_V2_1_1p6 = 3
+TBEAM = 4
+HELTEC_V2_0 = 5
+TBEAM0p7 = 6
+T_ECHO = 7
+TLORA_V1_1p3 = 8
+RAK4631 = 9
+HELTEC_V2_1 = 10
+LORA_RELAY_V1 = 32
+NRF52840DK = 33
+PPR = 34
+GENIEBLOCKS = 35
+NRF52_UNKNOWN = 36
+PORTDUINO = 37
+ANDROID_SIM = 38
+DIY_V1 = 39
+CLEAR = 0
+CYAN = 1
+WHITE = 2
+YELLOW = 3
+ORANGE = 4
+MAGENTA = 5
+RED = 6
+MAROON = 7
+PURPLE = 8
+DARK_BLUE = 9
+BLUE = 10
+TEAL = 11
+GREEN = 12
+DARK_GREEN = 13
+BROWN = 14
+Unused = 0
+DATA_PAYLOAD_LEN = 237
+globals()['None'] = 0
+TxWatchdog = 1
+SleepEnterWait = 2
+NoRadio = 3
+Unspecified = 4
+UBloxInitFailed = 5
+NoAXP192 = 6
+InvalidRadioSetting = 7
+TransmitFailed = 8
+Brownout = 9
+SX1262Failure = 10
+RadioSpiBug = 11
+
+
+_POSITION_LOCSOURCE = _descriptor.EnumDescriptor(
+  name='LocSource',
+  full_name='Position.LocSource',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='LOCSRC_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LOCSRC_MANUAL_ENTRY', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LOCSRC_GPS_INTERNAL', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LOCSRC_GPS_EXTERNAL', index=3, number=3,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=561,
+  serialized_end=671,
+)
+_sym_db.RegisterEnumDescriptor(_POSITION_LOCSOURCE)
+
+_POSITION_ALTSOURCE = _descriptor.EnumDescriptor(
+  name='AltSource',
+  full_name='Position.AltSource',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='ALTSRC_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ALTSRC_MANUAL_ENTRY', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ALTSRC_GPS_INTERNAL', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ALTSRC_GPS_EXTERNAL', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ALTSRC_BAROMETRIC', index=4, number=4,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=674,
+  serialized_end=807,
+)
+_sym_db.RegisterEnumDescriptor(_POSITION_ALTSOURCE)
+
+_ROUTING_ERROR = _descriptor.EnumDescriptor(
+  name='Error',
+  full_name='Routing.Error',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NONE', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NO_ROUTE', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GOT_NAK', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TIMEOUT', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NO_INTERFACE', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MAX_RETRANSMIT', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NO_CHANNEL', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TOO_LARGE', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NO_RESPONSE', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='BAD_REQUEST', index=9, number=32,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NOT_AUTHORIZED', index=10, number=33,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1207,
+  serialized_end=1387,
+)
+_sym_db.RegisterEnumDescriptor(_ROUTING_ERROR)
+
+_MESHPACKET_PRIORITY = _descriptor.EnumDescriptor(
+  name='Priority',
+  full_name='MeshPacket.Priority',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNSET', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MIN', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='BACKGROUND', index=2, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DEFAULT', index=3, number=64,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RELIABLE', index=4, number=70,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ACK', index=5, number=120,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MAX', index=6, number=127,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1845,
+  serialized_end=1936,
+)
+_sym_db.RegisterEnumDescriptor(_MESHPACKET_PRIORITY)
+
+_MESHPACKET_DELAYED = _descriptor.EnumDescriptor(
+  name='Delayed',
+  full_name='MeshPacket.Delayed',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NO_DELAY', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DELAYED_BROADCAST', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DELAYED_DIRECT', index=2, number=2,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1938,
+  serialized_end=2004,
+)
+_sym_db.RegisterEnumDescriptor(_MESHPACKET_DELAYED)
+
+_LOGRECORD_LEVEL = _descriptor.EnumDescriptor(
+  name='Level',
+  full_name='LogRecord.Level',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNSET', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CRITICAL', index=1, number=50,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR', index=2, number=40,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='WARNING', index=3, number=30,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='INFO', index=4, number=20,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DEBUG', index=5, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TRACE', index=6, number=5,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2670,
+  serialized_end=2758,
+)
+_sym_db.RegisterEnumDescriptor(_LOGRECORD_LEVEL)
+
+
+_POSITION = _descriptor.Descriptor(
+  name='Position',
+  full_name='Position',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='latitude_i', full_name='Position.latitude_i', index=0,
+      number=1, type=15, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='longitude_i', full_name='Position.longitude_i', index=1,
+      number=2, type=15, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='altitude', full_name='Position.altitude', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='battery_level', full_name='Position.battery_level', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='time', full_name='Position.time', index=4,
+      number=9, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='location_source', full_name='Position.location_source', index=5,
+      number=10, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='altitude_source', full_name='Position.altitude_source', index=6,
+      number=11, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='pos_timestamp', full_name='Position.pos_timestamp', index=7,
+      number=12, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='pos_time_millis', full_name='Position.pos_time_millis', index=8,
+      number=13, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='altitude_hae', full_name='Position.altitude_hae', index=9,
+      number=14, type=17, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='alt_geoid_sep', full_name='Position.alt_geoid_sep', index=10,
+      number=15, type=17, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='PDOP', full_name='Position.PDOP', index=11,
+      number=16, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='HDOP', full_name='Position.HDOP', index=12,
+      number=17, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='VDOP', full_name='Position.VDOP', index=13,
+      number=18, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_accuracy', full_name='Position.gps_accuracy', index=14,
+      number=19, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ground_speed', full_name='Position.ground_speed', index=15,
+      number=20, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ground_track', full_name='Position.ground_track', index=16,
+      number=21, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='fix_quality', full_name='Position.fix_quality', index=17,
+      number=22, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='fix_type', full_name='Position.fix_type', index=18,
+      number=23, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='sats_in_view', full_name='Position.sats_in_view', index=19,
+      number=24, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='sensor_id', full_name='Position.sensor_id', index=20,
+      number=25, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='pos_next_update', full_name='Position.pos_next_update', index=21,
+      number=40, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='pos_seq_number', full_name='Position.pos_seq_number', index=22,
+      number=41, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _POSITION_LOCSOURCE,
+    _POSITION_ALTSOURCE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=31,
+  serialized_end=819,
+)
+
+
+_USER = _descriptor.Descriptor(
+  name='User',
+  full_name='User',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='User.id', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='long_name', full_name='User.long_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='short_name', full_name='User.short_name', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='macaddr', full_name='User.macaddr', index=3,
+      number=4, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='hw_model', full_name='User.hw_model', index=4,
+      number=6, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='is_licensed', full_name='User.is_licensed', index=5,
+      number=7, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='team', full_name='User.team', index=6,
+      number=8, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='tx_power_dbm', full_name='User.tx_power_dbm', index=7,
+      number=10, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ant_gain_dbi', full_name='User.ant_gain_dbi', index=8,
+      number=11, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ant_azimuth', full_name='User.ant_azimuth', index=9,
+      number=12, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=822,
+  serialized_end=1037,
+)
+
+
+_ROUTEDISCOVERY = _descriptor.Descriptor(
+  name='RouteDiscovery',
+  full_name='RouteDiscovery',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='route', full_name='RouteDiscovery.route', index=0,
+      number=2, type=7, cpp_type=3, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1039,
+  serialized_end=1070,
+)
+
+
+_ROUTING = _descriptor.Descriptor(
+  name='Routing',
+  full_name='Routing',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='route_request', full_name='Routing.route_request', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='route_reply', full_name='Routing.route_reply', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='error_reason', full_name='Routing.error_reason', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _ROUTING_ERROR,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='variant', full_name='Routing.variant',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=1073,
+  serialized_end=1398,
+)
+
+
+_DATA = _descriptor.Descriptor(
+  name='Data',
+  full_name='Data',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='portnum', full_name='Data.portnum', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='payload', full_name='Data.payload', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='want_response', full_name='Data.want_response', index=2,
+      number=3, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dest', full_name='Data.dest', index=3,
+      number=4, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='source', full_name='Data.source', index=4,
+      number=5, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='request_id', full_name='Data.request_id', index=5,
+      number=6, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1400,
+  serialized_end=1523,
+)
+
+
+_MESHPACKET = _descriptor.Descriptor(
+  name='MeshPacket',
+  full_name='MeshPacket',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='from', full_name='MeshPacket.from', index=0,
+      number=1, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='to', full_name='MeshPacket.to', index=1,
+      number=2, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='channel', full_name='MeshPacket.channel', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='decoded', full_name='MeshPacket.decoded', index=3,
+      number=4, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='encrypted', full_name='MeshPacket.encrypted', index=4,
+      number=5, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='id', full_name='MeshPacket.id', index=5,
+      number=6, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rx_time', full_name='MeshPacket.rx_time', index=6,
+      number=7, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rx_snr', full_name='MeshPacket.rx_snr', index=7,
+      number=8, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='hop_limit', full_name='MeshPacket.hop_limit', index=8,
+      number=10, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='want_ack', full_name='MeshPacket.want_ack', index=9,
+      number=11, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='priority', full_name='MeshPacket.priority', index=10,
+      number=12, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rx_rssi', full_name='MeshPacket.rx_rssi', index=11,
+      number=13, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='delayed', full_name='MeshPacket.delayed', index=12,
+      number=15, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='reply_id', full_name='MeshPacket.reply_id', index=13,
+      number=16, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='is_tapback', full_name='MeshPacket.is_tapback', index=14,
+      number=17, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _MESHPACKET_PRIORITY,
+    _MESHPACKET_DELAYED,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='payloadVariant', full_name='MeshPacket.payloadVariant',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=1526,
+  serialized_end=2022,
+)
+
+
+_NODEINFO = _descriptor.Descriptor(
+  name='NodeInfo',
+  full_name='NodeInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='num', full_name='NodeInfo.num', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='user', full_name='NodeInfo.user', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='position', full_name='NodeInfo.position', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='snr', full_name='NodeInfo.snr', index=3,
+      number=7, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='last_heard', full_name='NodeInfo.last_heard', index=4,
+      number=4, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2024,
+  serialized_end=2130,
+)
+
+
+_MYNODEINFO = _descriptor.Descriptor(
+  name='MyNodeInfo',
+  full_name='MyNodeInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='my_node_num', full_name='MyNodeInfo.my_node_num', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='has_gps', full_name='MyNodeInfo.has_gps', index=1,
+      number=2, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='num_bands', full_name='MyNodeInfo.num_bands', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='max_channels', full_name='MyNodeInfo.max_channels', index=3,
+      number=15, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='region', full_name='MyNodeInfo.region', index=4,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\030\001', file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='hw_model_deprecated', full_name='MyNodeInfo.hw_model_deprecated', index=5,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\030\001', file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='firmware_version', full_name='MyNodeInfo.firmware_version', index=6,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='error_code', full_name='MyNodeInfo.error_code', index=7,
+      number=7, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='error_address', full_name='MyNodeInfo.error_address', index=8,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='error_count', full_name='MyNodeInfo.error_count', index=9,
+      number=9, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='reboot_count', full_name='MyNodeInfo.reboot_count', index=10,
+      number=10, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='bitrate', full_name='MyNodeInfo.bitrate', index=11,
+      number=11, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='message_timeout_msec', full_name='MyNodeInfo.message_timeout_msec', index=12,
+      number=13, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='min_app_version', full_name='MyNodeInfo.min_app_version', index=13,
+      number=14, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='air_period_tx', full_name='MyNodeInfo.air_period_tx', index=14,
+      number=16, type=13, cpp_type=3, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='air_period_rx', full_name='MyNodeInfo.air_period_rx', index=15,
+      number=17, type=13, cpp_type=3, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='has_wifi', full_name='MyNodeInfo.has_wifi', index=16,
+      number=18, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='channel_utilization', full_name='MyNodeInfo.channel_utilization', index=17,
+      number=19, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2133,
+  serialized_end=2574,
+)
+
+
+_LOGRECORD = _descriptor.Descriptor(
+  name='LogRecord',
+  full_name='LogRecord',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='message', full_name='LogRecord.message', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='time', full_name='LogRecord.time', index=1,
+      number=2, type=7, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='source', full_name='LogRecord.source', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='level', full_name='LogRecord.level', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _LOGRECORD_LEVEL,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2577,
+  serialized_end=2758,
+)
+
+
+_FROMRADIO = _descriptor.Descriptor(
+  name='FromRadio',
+  full_name='FromRadio',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='num', full_name='FromRadio.num', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='packet', full_name='FromRadio.packet', index=1,
+      number=11, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='my_info', full_name='FromRadio.my_info', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='node_info', full_name='FromRadio.node_info', index=3,
+      number=4, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='log_record', full_name='FromRadio.log_record', index=4,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='config_complete_id', full_name='FromRadio.config_complete_id', index=5,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rebooted', full_name='FromRadio.rebooted', index=6,
+      number=9, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='payloadVariant', full_name='FromRadio.payloadVariant',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=2761,
+  serialized_end=2994,
+)
+
+
+_TORADIO_PEERINFO = _descriptor.Descriptor(
+  name='PeerInfo',
+  full_name='ToRadio.PeerInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='app_version', full_name='ToRadio.PeerInfo.app_version', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mqtt_gateway', full_name='ToRadio.PeerInfo.mqtt_gateway', index=1,
+      number=2, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3127,
+  serialized_end=3180,
+)
+
+_TORADIO = _descriptor.Descriptor(
+  name='ToRadio',
+  full_name='ToRadio',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='packet', full_name='ToRadio.packet', index=0,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='peer_info', full_name='ToRadio.peer_info', index=1,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='want_config_id', full_name='ToRadio.want_config_id', index=2,
+      number=100, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='disconnect', full_name='ToRadio.disconnect', index=3,
+      number=104, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_TORADIO_PEERINFO, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='payloadVariant', full_name='ToRadio.payloadVariant',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=2997,
+  serialized_end=3222,
+)
+
+_POSITION.fields_by_name['location_source'].enum_type = _POSITION_LOCSOURCE
+_POSITION.fields_by_name['altitude_source'].enum_type = _POSITION_ALTSOURCE
+_POSITION_LOCSOURCE.containing_type = _POSITION
+_POSITION_ALTSOURCE.containing_type = _POSITION
+_USER.fields_by_name['hw_model'].enum_type = _HARDWAREMODEL
+_USER.fields_by_name['team'].enum_type = _TEAM
+_ROUTING.fields_by_name['route_request'].message_type = _ROUTEDISCOVERY
+_ROUTING.fields_by_name['route_reply'].message_type = _ROUTEDISCOVERY
+_ROUTING.fields_by_name['error_reason'].enum_type = _ROUTING_ERROR
+_ROUTING_ERROR.containing_type = _ROUTING
+_ROUTING.oneofs_by_name['variant'].fields.append(
+  _ROUTING.fields_by_name['route_request'])
+_ROUTING.fields_by_name['route_request'].containing_oneof = _ROUTING.oneofs_by_name['variant']
+_ROUTING.oneofs_by_name['variant'].fields.append(
+  _ROUTING.fields_by_name['route_reply'])
+_ROUTING.fields_by_name['route_reply'].containing_oneof = _ROUTING.oneofs_by_name['variant']
+_ROUTING.oneofs_by_name['variant'].fields.append(
+  _ROUTING.fields_by_name['error_reason'])
+_ROUTING.fields_by_name['error_reason'].containing_oneof = _ROUTING.oneofs_by_name['variant']
+_DATA.fields_by_name['portnum'].enum_type = portnums__pb2._PORTNUM
+_MESHPACKET.fields_by_name['decoded'].message_type = _DATA
+_MESHPACKET.fields_by_name['priority'].enum_type = _MESHPACKET_PRIORITY
+_MESHPACKET.fields_by_name['delayed'].enum_type = _MESHPACKET_DELAYED
+_MESHPACKET_PRIORITY.containing_type = _MESHPACKET
+_MESHPACKET_DELAYED.containing_type = _MESHPACKET
+_MESHPACKET.oneofs_by_name['payloadVariant'].fields.append(
+  _MESHPACKET.fields_by_name['decoded'])
+_MESHPACKET.fields_by_name['decoded'].containing_oneof = _MESHPACKET.oneofs_by_name['payloadVariant']
+_MESHPACKET.oneofs_by_name['payloadVariant'].fields.append(
+  _MESHPACKET.fields_by_name['encrypted'])
+_MESHPACKET.fields_by_name['encrypted'].containing_oneof = _MESHPACKET.oneofs_by_name['payloadVariant']
+_NODEINFO.fields_by_name['user'].message_type = _USER
+_NODEINFO.fields_by_name['position'].message_type = _POSITION
+_MYNODEINFO.fields_by_name['error_code'].enum_type = _CRITICALERRORCODE
+_LOGRECORD.fields_by_name['level'].enum_type = _LOGRECORD_LEVEL
+_LOGRECORD_LEVEL.containing_type = _LOGRECORD
+_FROMRADIO.fields_by_name['packet'].message_type = _MESHPACKET
+_FROMRADIO.fields_by_name['my_info'].message_type = _MYNODEINFO
+_FROMRADIO.fields_by_name['node_info'].message_type = _NODEINFO
+_FROMRADIO.fields_by_name['log_record'].message_type = _LOGRECORD
+_FROMRADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _FROMRADIO.fields_by_name['packet'])
+_FROMRADIO.fields_by_name['packet'].containing_oneof = _FROMRADIO.oneofs_by_name['payloadVariant']
+_FROMRADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _FROMRADIO.fields_by_name['my_info'])
+_FROMRADIO.fields_by_name['my_info'].containing_oneof = _FROMRADIO.oneofs_by_name['payloadVariant']
+_FROMRADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _FROMRADIO.fields_by_name['node_info'])
+_FROMRADIO.fields_by_name['node_info'].containing_oneof = _FROMRADIO.oneofs_by_name['payloadVariant']
+_FROMRADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _FROMRADIO.fields_by_name['log_record'])
+_FROMRADIO.fields_by_name['log_record'].containing_oneof = _FROMRADIO.oneofs_by_name['payloadVariant']
+_FROMRADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _FROMRADIO.fields_by_name['config_complete_id'])
+_FROMRADIO.fields_by_name['config_complete_id'].containing_oneof = _FROMRADIO.oneofs_by_name['payloadVariant']
+_FROMRADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _FROMRADIO.fields_by_name['rebooted'])
+_FROMRADIO.fields_by_name['rebooted'].containing_oneof = _FROMRADIO.oneofs_by_name['payloadVariant']
+_TORADIO_PEERINFO.containing_type = _TORADIO
+_TORADIO.fields_by_name['packet'].message_type = _MESHPACKET
+_TORADIO.fields_by_name['peer_info'].message_type = _TORADIO_PEERINFO
+_TORADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _TORADIO.fields_by_name['packet'])
+_TORADIO.fields_by_name['packet'].containing_oneof = _TORADIO.oneofs_by_name['payloadVariant']
+_TORADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _TORADIO.fields_by_name['peer_info'])
+_TORADIO.fields_by_name['peer_info'].containing_oneof = _TORADIO.oneofs_by_name['payloadVariant']
+_TORADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _TORADIO.fields_by_name['want_config_id'])
+_TORADIO.fields_by_name['want_config_id'].containing_oneof = _TORADIO.oneofs_by_name['payloadVariant']
+_TORADIO.oneofs_by_name['payloadVariant'].fields.append(
+  _TORADIO.fields_by_name['disconnect'])
+_TORADIO.fields_by_name['disconnect'].containing_oneof = _TORADIO.oneofs_by_name['payloadVariant']
+DESCRIPTOR.message_types_by_name['Position'] = _POSITION
+DESCRIPTOR.message_types_by_name['User'] = _USER
+DESCRIPTOR.message_types_by_name['RouteDiscovery'] = _ROUTEDISCOVERY
+DESCRIPTOR.message_types_by_name['Routing'] = _ROUTING
+DESCRIPTOR.message_types_by_name['Data'] = _DATA
+DESCRIPTOR.message_types_by_name['MeshPacket'] = _MESHPACKET
+DESCRIPTOR.message_types_by_name['NodeInfo'] = _NODEINFO
+DESCRIPTOR.message_types_by_name['MyNodeInfo'] = _MYNODEINFO
+DESCRIPTOR.message_types_by_name['LogRecord'] = _LOGRECORD
+DESCRIPTOR.message_types_by_name['FromRadio'] = _FROMRADIO
+DESCRIPTOR.message_types_by_name['ToRadio'] = _TORADIO
+DESCRIPTOR.enum_types_by_name['HardwareModel'] = _HARDWAREMODEL
+DESCRIPTOR.enum_types_by_name['Team'] = _TEAM
+DESCRIPTOR.enum_types_by_name['Constants'] = _CONSTANTS
+DESCRIPTOR.enum_types_by_name['CriticalErrorCode'] = _CRITICALERRORCODE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Position = _reflection.GeneratedProtocolMessageType('Position', (_message.Message,), {
+  'DESCRIPTOR' : _POSITION,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:Position)
+  })
+_sym_db.RegisterMessage(Position)
+
+User = _reflection.GeneratedProtocolMessageType('User', (_message.Message,), {
+  'DESCRIPTOR' : _USER,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:User)
+  })
+_sym_db.RegisterMessage(User)
+
+RouteDiscovery = _reflection.GeneratedProtocolMessageType('RouteDiscovery', (_message.Message,), {
+  'DESCRIPTOR' : _ROUTEDISCOVERY,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:RouteDiscovery)
+  })
+_sym_db.RegisterMessage(RouteDiscovery)
+
+Routing = _reflection.GeneratedProtocolMessageType('Routing', (_message.Message,), {
+  'DESCRIPTOR' : _ROUTING,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:Routing)
+  })
+_sym_db.RegisterMessage(Routing)
+
+Data = _reflection.GeneratedProtocolMessageType('Data', (_message.Message,), {
+  'DESCRIPTOR' : _DATA,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:Data)
+  })
+_sym_db.RegisterMessage(Data)
+
+MeshPacket = _reflection.GeneratedProtocolMessageType('MeshPacket', (_message.Message,), {
+  'DESCRIPTOR' : _MESHPACKET,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:MeshPacket)
+  })
+_sym_db.RegisterMessage(MeshPacket)
+
+NodeInfo = _reflection.GeneratedProtocolMessageType('NodeInfo', (_message.Message,), {
+  'DESCRIPTOR' : _NODEINFO,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:NodeInfo)
+  })
+_sym_db.RegisterMessage(NodeInfo)
+
+MyNodeInfo = _reflection.GeneratedProtocolMessageType('MyNodeInfo', (_message.Message,), {
+  'DESCRIPTOR' : _MYNODEINFO,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:MyNodeInfo)
+  })
+_sym_db.RegisterMessage(MyNodeInfo)
+
+LogRecord = _reflection.GeneratedProtocolMessageType('LogRecord', (_message.Message,), {
+  'DESCRIPTOR' : _LOGRECORD,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:LogRecord)
+  })
+_sym_db.RegisterMessage(LogRecord)
+
+FromRadio = _reflection.GeneratedProtocolMessageType('FromRadio', (_message.Message,), {
+  'DESCRIPTOR' : _FROMRADIO,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:FromRadio)
+  })
+_sym_db.RegisterMessage(FromRadio)
+
+ToRadio = _reflection.GeneratedProtocolMessageType('ToRadio', (_message.Message,), {
+
+  'PeerInfo' : _reflection.GeneratedProtocolMessageType('PeerInfo', (_message.Message,), {
+    'DESCRIPTOR' : _TORADIO_PEERINFO,
+    '__module__' : 'mesh_pb2'
+    # @@protoc_insertion_point(class_scope:ToRadio.PeerInfo)
+    })
+  ,
+  'DESCRIPTOR' : _TORADIO,
+  '__module__' : 'mesh_pb2'
+  # @@protoc_insertion_point(class_scope:ToRadio)
+  })
+_sym_db.RegisterMessage(ToRadio)
+_sym_db.RegisterMessage(ToRadio.PeerInfo)
+
+
+DESCRIPTOR._options = None
+_MYNODEINFO.fields_by_name['region']._options = None
+_MYNODEINFO.fields_by_name['hw_model_deprecated']._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Data +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var DEST_FIELD_NUMBER
+
+
+
+
var PAYLOAD_FIELD_NUMBER
+
+
+
+
var PORTNUM_FIELD_NUMBER
+
+
+
+
var REQUEST_ID_FIELD_NUMBER
+
+
+
+
var SOURCE_FIELD_NUMBER
+
+
+
+
var WANT_RESPONSE_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var dest
+
+

Getter for dest.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var payload
+
+

Getter for payload.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var portnum
+
+

Getter for portnum.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var request_id
+
+

Getter for request_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var source
+
+

Getter for source.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var want_response
+
+

Getter for want_response.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class FromRadio +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CONFIG_COMPLETE_ID_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var LOG_RECORD_FIELD_NUMBER
+
+
+
+
var MY_INFO_FIELD_NUMBER
+
+
+
+
var NODE_INFO_FIELD_NUMBER
+
+
+
+
var NUM_FIELD_NUMBER
+
+
+
+
var PACKET_FIELD_NUMBER
+
+
+
+
var REBOOTED_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var config_complete_id
+
+

Getter for config_complete_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var log_record
+
+

Getter for log_record.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var my_info
+
+

Getter for my_info.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var node_info
+
+

Getter for node_info.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var num
+
+

Getter for num.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var packet
+
+

Getter for packet.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var rebooted
+
+

Getter for rebooted.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class LogRecord +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CRITICAL
+
+
+
+
var DEBUG
+
+
+
+
var DESCRIPTOR
+
+
+
+
var ERROR
+
+
+
+
var INFO
+
+
+
+
var LEVEL_FIELD_NUMBER
+
+
+
+
var Level
+
+
+
+
var MESSAGE_FIELD_NUMBER
+
+
+
+
var SOURCE_FIELD_NUMBER
+
+
+
+
var TIME_FIELD_NUMBER
+
+
+
+
var TRACE
+
+
+
+
var UNSET
+
+
+
+
var WARNING
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var level
+
+

Getter for level.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var message
+
+

Getter for message.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var source
+
+

Getter for source.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var time
+
+

Getter for time.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class MeshPacket +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var ACK
+
+
+
+
var BACKGROUND
+
+
+
+
var CHANNEL_FIELD_NUMBER
+
+
+
+
var DECODED_FIELD_NUMBER
+
+
+
+
var DEFAULT
+
+
+
+
var DELAYED_BROADCAST
+
+
+
+
var DELAYED_DIRECT
+
+
+
+
var DELAYED_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var Delayed
+
+
+
+
var ENCRYPTED_FIELD_NUMBER
+
+
+
+
var FROM_FIELD_NUMBER
+
+
+
+
var HOP_LIMIT_FIELD_NUMBER
+
+
+
+
var ID_FIELD_NUMBER
+
+
+
+
var IS_TAPBACK_FIELD_NUMBER
+
+
+
+
var MAX
+
+
+
+
var MIN
+
+
+
+
var NO_DELAY
+
+
+
+
var PRIORITY_FIELD_NUMBER
+
+
+
+
var Priority
+
+
+
+
var RELIABLE
+
+
+
+
var REPLY_ID_FIELD_NUMBER
+
+
+
+
var RX_RSSI_FIELD_NUMBER
+
+
+
+
var RX_SNR_FIELD_NUMBER
+
+
+
+
var RX_TIME_FIELD_NUMBER
+
+
+
+
var TO_FIELD_NUMBER
+
+
+
+
var UNSET
+
+
+
+
var WANT_ACK_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var channel
+
+

Getter for channel.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var decoded
+
+

Getter for decoded.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var delayed
+
+

Getter for delayed.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var encrypted
+
+

Getter for encrypted.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var from
+
+

Getter for from.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var hop_limit
+
+

Getter for hop_limit.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var id
+
+

Getter for id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var is_tapback
+
+

Getter for is_tapback.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var priority
+
+

Getter for priority.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var reply_id
+
+

Getter for reply_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var rx_rssi
+
+

Getter for rx_rssi.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var rx_snr
+
+

Getter for rx_snr.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var rx_time
+
+

Getter for rx_time.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var to
+
+

Getter for to.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var want_ack
+
+

Getter for want_ack.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class MyNodeInfo +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var AIR_PERIOD_RX_FIELD_NUMBER
+
+
+
+
var AIR_PERIOD_TX_FIELD_NUMBER
+
+
+
+
var BITRATE_FIELD_NUMBER
+
+
+
+
var CHANNEL_UTILIZATION_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var ERROR_ADDRESS_FIELD_NUMBER
+
+
+
+
var ERROR_CODE_FIELD_NUMBER
+
+
+
+
var ERROR_COUNT_FIELD_NUMBER
+
+
+
+
var FIRMWARE_VERSION_FIELD_NUMBER
+
+
+
+
var HAS_GPS_FIELD_NUMBER
+
+
+
+
var HAS_WIFI_FIELD_NUMBER
+
+
+
+
var HW_MODEL_DEPRECATED_FIELD_NUMBER
+
+
+
+
var MAX_CHANNELS_FIELD_NUMBER
+
+
+
+
var MESSAGE_TIMEOUT_MSEC_FIELD_NUMBER
+
+
+
+
var MIN_APP_VERSION_FIELD_NUMBER
+
+
+
+
var MY_NODE_NUM_FIELD_NUMBER
+
+
+
+
var NUM_BANDS_FIELD_NUMBER
+
+
+
+
var REBOOT_COUNT_FIELD_NUMBER
+
+
+
+
var REGION_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var air_period_rx
+
+

Getter for air_period_rx.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var air_period_tx
+
+

Getter for air_period_tx.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var bitrate
+
+

Getter for bitrate.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var channel_utilization
+
+

Getter for channel_utilization.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var error_address
+
+

Getter for error_address.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var error_code
+
+

Getter for error_code.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var error_count
+
+

Getter for error_count.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var firmware_version
+
+

Getter for firmware_version.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var has_gps
+
+

Getter for has_gps.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var has_wifi
+
+

Getter for has_wifi.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var hw_model_deprecated
+
+

Getter for hw_model_deprecated.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var max_channels
+
+

Getter for max_channels.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var message_timeout_msec
+
+

Getter for message_timeout_msec.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var min_app_version
+
+

Getter for min_app_version.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var my_node_num
+
+

Getter for my_node_num.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var num_bands
+
+

Getter for num_bands.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var reboot_count
+
+

Getter for reboot_count.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var region
+
+

Getter for region.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class NodeInfo +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var LAST_HEARD_FIELD_NUMBER
+
+
+
+
var NUM_FIELD_NUMBER
+
+
+
+
var POSITION_FIELD_NUMBER
+
+
+
+
var SNR_FIELD_NUMBER
+
+
+
+
var USER_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var last_heard
+
+

Getter for last_heard.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var num
+
+

Getter for num.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var position
+
+

Getter for position.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var snr
+
+

Getter for snr.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var user
+
+

Getter for user.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class Position +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var ALTITUDE_FIELD_NUMBER
+
+
+
+
var ALTITUDE_HAE_FIELD_NUMBER
+
+
+
+
var ALTITUDE_SOURCE_FIELD_NUMBER
+
+
+
+
var ALTSRC_BAROMETRIC
+
+
+
+
var ALTSRC_GPS_EXTERNAL
+
+
+
+
var ALTSRC_GPS_INTERNAL
+
+
+
+
var ALTSRC_MANUAL_ENTRY
+
+
+
+
var ALTSRC_UNSPECIFIED
+
+
+
+
var ALT_GEOID_SEP_FIELD_NUMBER
+
+
+
+
var AltSource
+
+
+
+
var BATTERY_LEVEL_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var FIX_QUALITY_FIELD_NUMBER
+
+
+
+
var FIX_TYPE_FIELD_NUMBER
+
+
+
+
var GPS_ACCURACY_FIELD_NUMBER
+
+
+
+
var GROUND_SPEED_FIELD_NUMBER
+
+
+
+
var GROUND_TRACK_FIELD_NUMBER
+
+
+
+
var HDOP_FIELD_NUMBER
+
+
+
+
var LATITUDE_I_FIELD_NUMBER
+
+
+
+
var LOCATION_SOURCE_FIELD_NUMBER
+
+
+
+
var LOCSRC_GPS_EXTERNAL
+
+
+
+
var LOCSRC_GPS_INTERNAL
+
+
+
+
var LOCSRC_MANUAL_ENTRY
+
+
+
+
var LOCSRC_UNSPECIFIED
+
+
+
+
var LONGITUDE_I_FIELD_NUMBER
+
+
+
+
var LocSource
+
+
+
+
var PDOP_FIELD_NUMBER
+
+
+
+
var POS_NEXT_UPDATE_FIELD_NUMBER
+
+
+
+
var POS_SEQ_NUMBER_FIELD_NUMBER
+
+
+
+
var POS_TIMESTAMP_FIELD_NUMBER
+
+
+
+
var POS_TIME_MILLIS_FIELD_NUMBER
+
+
+
+
var SATS_IN_VIEW_FIELD_NUMBER
+
+
+
+
var SENSOR_ID_FIELD_NUMBER
+
+
+
+
var TIME_FIELD_NUMBER
+
+
+
+
var VDOP_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var HDOP
+
+

Getter for HDOP.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var PDOP
+
+

Getter for PDOP.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var VDOP
+
+

Getter for VDOP.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var alt_geoid_sep
+
+

Getter for alt_geoid_sep.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var altitude
+
+

Getter for altitude.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var altitude_hae
+
+

Getter for altitude_hae.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var altitude_source
+
+

Getter for altitude_source.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var battery_level
+
+

Getter for battery_level.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var fix_quality
+
+

Getter for fix_quality.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var fix_type
+
+

Getter for fix_type.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var gps_accuracy
+
+

Getter for gps_accuracy.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var ground_speed
+
+

Getter for ground_speed.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var ground_track
+
+

Getter for ground_track.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var latitude_i
+
+

Getter for latitude_i.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var location_source
+
+

Getter for location_source.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var longitude_i
+
+

Getter for longitude_i.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var pos_next_update
+
+

Getter for pos_next_update.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var pos_seq_number
+
+

Getter for pos_seq_number.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var pos_time_millis
+
+

Getter for pos_time_millis.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var pos_timestamp
+
+

Getter for pos_timestamp.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var sats_in_view
+
+

Getter for sats_in_view.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var sensor_id
+
+

Getter for sensor_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var time
+
+

Getter for time.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class RouteDiscovery +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var ROUTE_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var route
+
+

Getter for route.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class Routing +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var BAD_REQUEST
+
+
+
+
var DESCRIPTOR
+
+
+
+
var ERROR_REASON_FIELD_NUMBER
+
+
+
+
var Error
+
+
+
+
var GOT_NAK
+
+
+
+
var MAX_RETRANSMIT
+
+
+
+
var NONE
+
+
+
+
var NOT_AUTHORIZED
+
+
+
+
var NO_CHANNEL
+
+
+
+
var NO_INTERFACE
+
+
+
+
var NO_RESPONSE
+
+
+
+
var NO_ROUTE
+
+
+
+
var ROUTE_REPLY_FIELD_NUMBER
+
+
+
+
var ROUTE_REQUEST_FIELD_NUMBER
+
+
+
+
var TIMEOUT
+
+
+
+
var TOO_LARGE
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var error_reason
+
+

Getter for error_reason.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var route_reply
+
+

Getter for route_reply.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var route_request
+
+

Getter for route_request.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class ToRadio +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var DISCONNECT_FIELD_NUMBER
+
+
+
+
var PACKET_FIELD_NUMBER
+
+
+
+
var PEER_INFO_FIELD_NUMBER
+
+
+
+
var PeerInfo
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
var WANT_CONFIG_ID_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var disconnect
+
+

Getter for disconnect.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var packet
+
+

Getter for packet.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var peer_info
+
+

Getter for peer_info.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var want_config_id
+
+

Getter for want_config_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class User +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var ANT_AZIMUTH_FIELD_NUMBER
+
+
+
+
var ANT_GAIN_DBI_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var HW_MODEL_FIELD_NUMBER
+
+
+
+
var ID_FIELD_NUMBER
+
+
+
+
var IS_LICENSED_FIELD_NUMBER
+
+
+
+
var LONG_NAME_FIELD_NUMBER
+
+
+
+
var MACADDR_FIELD_NUMBER
+
+
+
+
var SHORT_NAME_FIELD_NUMBER
+
+
+
+
var TEAM_FIELD_NUMBER
+
+
+
+
var TX_POWER_DBM_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var ant_azimuth
+
+

Getter for ant_azimuth.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var ant_gain_dbi
+
+

Getter for ant_gain_dbi.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var hw_model
+
+

Getter for hw_model.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var id
+
+

Getter for id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var is_licensed
+
+

Getter for is_licensed.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var long_name
+
+

Getter for long_name.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var macaddr
+
+

Getter for macaddr.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var short_name
+
+

Getter for short_name.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var team
+
+

Getter for team.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var tx_power_dbm
+
+

Getter for tx_power_dbm.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/mqtt_pb2.html b/docs/meshtastic/mqtt_pb2.html new file mode 100644 index 0000000..c18a637 --- /dev/null +++ b/docs/meshtastic/mqtt_pb2.html @@ -0,0 +1,759 @@ + + + + + + +meshtastic.mqtt_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.mqtt_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: mqtt.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import mesh_pb2 as mesh__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='mqtt.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\nMQTTProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\nmqtt.proto\x1a\nmesh.proto\"V\n\x0fServiceEnvelope\x12\x1b\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\tBF\n\x13\x63om.geeksville.meshB\nMQTTProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+  ,
+  dependencies=[mesh__pb2.DESCRIPTOR,])
+
+
+
+
+_SERVICEENVELOPE = _descriptor.Descriptor(
+  name='ServiceEnvelope',
+  full_name='ServiceEnvelope',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='packet', full_name='ServiceEnvelope.packet', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='channel_id', full_name='ServiceEnvelope.channel_id', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gateway_id', full_name='ServiceEnvelope.gateway_id', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=26,
+  serialized_end=112,
+)
+
+_SERVICEENVELOPE.fields_by_name['packet'].message_type = mesh__pb2._MESHPACKET
+DESCRIPTOR.message_types_by_name['ServiceEnvelope'] = _SERVICEENVELOPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+ServiceEnvelope = _reflection.GeneratedProtocolMessageType('ServiceEnvelope', (_message.Message,), {
+  'DESCRIPTOR' : _SERVICEENVELOPE,
+  '__module__' : 'mqtt_pb2'
+  # @@protoc_insertion_point(class_scope:ServiceEnvelope)
+  })
+_sym_db.RegisterMessage(ServiceEnvelope)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class ServiceEnvelope +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CHANNEL_ID_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var GATEWAY_ID_FIELD_NUMBER
+
+
+
+
var PACKET_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var channel_id
+
+

Getter for channel_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var gateway_id
+
+

Getter for gateway_id.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var packet
+
+

Getter for packet.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/node.html b/docs/meshtastic/node.html new file mode 100644 index 0000000..b0a7365 --- /dev/null +++ b/docs/meshtastic/node.html @@ -0,0 +1,1280 @@ + + + + + + +meshtastic.node API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.node

+
+
+

Node class

+
+ +Expand source code + +
"""Node class
+"""
+
+import logging
+import base64
+from google.protobuf.json_format import MessageToJson
+from . import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
+from .util import pskToString, stripnl, Timeout, our_exit, fromPSK
+
+
+
+
+class Node:
+    """A model of a (local or remote) node in the mesh
+
+    Includes methods for radioConfig and channels
+    """
+
+    def __init__(self, iface, nodeNum, noProto=False):
+        """Constructor"""
+        self.iface = iface
+        self.nodeNum = nodeNum
+        self.radioConfig = None
+        self.channels = None
+        self._timeout = Timeout(maxSecs=300)
+        self.partialChannels = None
+        self.noProto = noProto
+
+    def showChannels(self):
+        """Show human readable description of our channels."""
+        print("Channels:")
+        if self.channels:
+            logging.debug(f'self.channels:{self.channels}')
+            for c in self.channels:
+                #print('c.settings.psk:', c.settings.psk)
+                cStr = stripnl(MessageToJson(c.settings))
+                # only show if there is no psk (meaning disabled channel)
+                if c.settings.psk:
+                    print(f"  {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
+        publicURL = self.getURL(includeAll=False)
+        adminURL = self.getURL(includeAll=True)
+        print(f"\nPrimary channel URL: {publicURL}")
+        if adminURL != publicURL:
+            print(f"Complete URL (includes all channels): {adminURL}")
+
+    def showInfo(self):
+        """Show human readable description of our node"""
+        prefs = ""
+        if self.radioConfig and self.radioConfig.preferences:
+            prefs = stripnl(MessageToJson(self.radioConfig.preferences))
+        print(f"Preferences: {prefs}\n")
+        self.showChannels()
+
+    def requestConfig(self):
+        """Send regular MeshPackets to ask for settings and channels."""
+        logging.debug(f"requestConfig for nodeNum:{self.nodeNum}")
+        self.radioConfig = None
+        self.channels = None
+        self.partialChannels = []  # We keep our channels in a temp array until finished
+
+        self._requestSettings()
+
+    def turnOffEncryptionOnPrimaryChannel(self):
+        """Turn off encryption on primary channel."""
+        self.channels[0].settings.psk = fromPSK("none")
+        print("Writing modified channels to device")
+        self.writeChannel(0)
+
+    def waitForConfig(self):
+        """Block until radio config is received. Returns True if config has been received."""
+        return self._timeout.waitForSet(self, attrs=('radioConfig', 'channels'))
+
+    def writeConfig(self):
+        """Write the current (edited) radioConfig to the device"""
+        if self.radioConfig is None:
+            our_exit("Error: No RadioConfig has been read")
+
+        p = admin_pb2.AdminMessage()
+        p.set_radio.CopyFrom(self.radioConfig)
+
+        self._sendAdmin(p)
+        logging.debug("Wrote config")
+
+    def writeChannel(self, channelIndex, adminIndex=0):
+        """Write the current (edited) channel to the device"""
+
+        p = admin_pb2.AdminMessage()
+        p.set_channel.CopyFrom(self.channels[channelIndex])
+
+        self._sendAdmin(p, adminIndex=adminIndex)
+        logging.debug(f"Wrote channel {channelIndex}")
+
+    def getChannelByChannelIndex(self, channelIndex):
+        """Get channel by channelIndex
+            channelIndex: number, typically 0-7; based on max number channels
+            returns: None if there is no channel found
+        """
+        ch = None
+        if self.channels and 0 <= channelIndex < len(self.channels):
+            ch = self.channels[channelIndex]
+        return ch
+
+    def deleteChannel(self, channelIndex):
+        """Delete the specifed channelIndex and shift other channels up"""
+        ch = self.channels[channelIndex]
+        if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
+            our_exit("Warning: Only SECONDARY channels can be deleted")
+
+        # we are careful here because if we move the "admin" channel the channelIndex we need to use
+        # for sending admin channels will also change
+        adminIndex = self.iface.localNode._getAdminChannelIndex()
+
+        self.channels.pop(channelIndex)
+        self._fixupChannels()  # expand back to 8 channels
+
+        index = channelIndex
+        while index < self.iface.myInfo.max_channels:
+            self.writeChannel(index, adminIndex=adminIndex)
+            index += 1
+
+            # if we are updating the local node, we might end up
+            # *moving* the admin channel index as we are writing
+            if (self.iface.localNode == self) and index >= adminIndex:
+                # We've now passed the old location for admin index
+                # (and written it), so we can start finding it by name again
+                adminIndex = 0
+
+    def getChannelByName(self, name):
+        """Try to find the named channel or return None"""
+        for c in (self.channels or []):
+            if c.settings and c.settings.name == name:
+                return c
+        return None
+
+    def getDisabledChannel(self):
+        """Return the first channel that is disabled (i.e. available for some new use)"""
+        for c in self.channels:
+            if c.role == channel_pb2.Channel.Role.DISABLED:
+                return c
+        return None
+
+    def _getAdminChannelIndex(self):
+        """Return the channel number of the admin channel, or 0 if no reserved channel"""
+        c = self.getChannelByName("admin")
+        if c:
+            return c.index
+        else:
+            return 0
+
+    def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
+        """Set device owner name"""
+        logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
+        nChars = 3
+        minChars = 2
+        if long_name is not None:
+            long_name = long_name.strip()
+            if short_name is None:
+                words = long_name.split()
+                if len(long_name) <= nChars:
+                    short_name = long_name
+                elif len(words) >= minChars:
+                    short_name = ''.join(map(lambda word: word[0], words))
+                else:
+                    trans = str.maketrans(dict.fromkeys('aeiouAEIOU'))
+                    short_name = long_name[0] + long_name[1:].translate(trans)
+                    if len(short_name) < nChars:
+                        short_name = long_name[:nChars]
+
+        p = admin_pb2.AdminMessage()
+
+        if long_name is not None:
+            p.set_owner.long_name = long_name
+        if short_name is not None:
+            short_name = short_name.strip()
+            if len(short_name) > nChars:
+                short_name = short_name[:nChars]
+            p.set_owner.short_name = short_name
+            p.set_owner.is_licensed = is_licensed
+        if team is not None:
+            p.set_owner.team = team
+
+        # Note: These debug lines are used in unit tests
+        logging.debug(f'p.set_owner.long_name:{p.set_owner.long_name}:')
+        logging.debug(f'p.set_owner.short_name:{p.set_owner.short_name}:')
+        logging.debug(f'p.set_owner.is_licensed:{p.set_owner.is_licensed}')
+        logging.debug(f'p.set_owner.team:{p.set_owner.team}')
+        return self._sendAdmin(p)
+
+    def getURL(self, includeAll: bool = True):
+        """The sharable URL that describes the current channel"""
+        # Only keep the primary/secondary channels, assume primary is first
+        channelSet = apponly_pb2.ChannelSet()
+        if self.channels:
+            for c in self.channels:
+                if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
+                    channelSet.settings.append(c.settings)
+        some_bytes = channelSet.SerializeToString()
+        s = base64.urlsafe_b64encode(some_bytes).decode('ascii')
+        return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
+
+    def setURL(self, url):
+        """Set mesh network URL"""
+        if self.radioConfig is None:
+            our_exit("Warning: No RadioConfig has been read")
+
+        # URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
+        # Split on '/#' to find the base64 encoded channel settings
+        splitURL = url.split("/#")
+        b64 = splitURL[-1]
+
+        # We normally strip padding to make for a shorter URL, but the python parser doesn't like
+        # that.  So add back any missing padding
+        # per https://stackoverflow.com/a/9807138
+        missing_padding = len(b64) % 4
+        if missing_padding:
+            b64 += '=' * (4 - missing_padding)
+
+        decodedURL = base64.urlsafe_b64decode(b64)
+        channelSet = apponly_pb2.ChannelSet()
+        channelSet.ParseFromString(decodedURL)
+
+
+        if len(channelSet.settings) == 0:
+            our_exit("Warning: There were no settings.")
+
+        i = 0
+        for chs in channelSet.settings:
+            ch = channel_pb2.Channel()
+            ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY
+            ch.index = i
+            ch.settings.CopyFrom(chs)
+            self.channels[ch.index] = ch
+            logging.debug(f'Channel i:{i} ch:{ch}')
+            self.writeChannel(ch.index)
+            i = i + 1
+
+
+    def onResponseRequestSettings(self, p):
+        """Handle the response packet for requesting settings _requestSettings()"""
+        logging.debug(f'onResponseRequestSetting() p:{p}')
+        errorFound = False
+        if 'routing' in p["decoded"]:
+            if p["decoded"]["routing"]["errorReason"] != "NONE":
+                errorFound = True
+                print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
+        if errorFound is False:
+            self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
+            logging.debug(f'self.radioConfig:{self.radioConfig}')
+            logging.debug("Received radio config, now fetching channels...")
+            self._timeout.reset()  # We made foreward progress
+            self._requestChannel(0)  # now start fetching channels
+
+
+    def _requestSettings(self):
+        """Done with initial config messages, now send regular
+           MeshPackets to ask for settings."""
+        p = admin_pb2.AdminMessage()
+        p.get_radio_request = True
+
+        # Show progress message for super slow operations
+        if self != self.iface.localNode:
+            print("Requesting preferences from remote node.")
+            print("Be sure:")
+            print(" 1. There is a SECONDARY channel named 'admin'.")
+            print(" 2. The '--seturl' was used to configure.")
+            print(" 3. All devices have the same modem config. (i.e., '--ch-longfast')")
+            print(" 4. All devices have been rebooted after all of the above. (optional, but recommended)")
+            print("Note: This could take a while (it requests remote channel configs, then writes config)")
+
+        return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestSettings)
+
+    def exitSimulator(self):
+        """Tell a simulator node to exit (this message
+           is ignored for other nodes)"""
+        p = admin_pb2.AdminMessage()
+        p.exit_simulator = True
+        logging.debug('in exitSimulator()')
+
+        return self._sendAdmin(p)
+
+    def reboot(self, secs: int = 10):
+        """Tell the node to reboot."""
+        p = admin_pb2.AdminMessage()
+        p.reboot_seconds = secs
+        logging.info(f"Telling node to reboot in {secs} seconds")
+
+        return self._sendAdmin(p)
+
+    def _fixupChannels(self):
+        """Fixup indexes and add disabled channels as needed"""
+
+        # Add extra disabled channels as needed
+        # TODO: These 2 lines seem to not do anything.
+        for index, ch in enumerate(self.channels):
+            ch.index = index  # fixup indexes
+
+        self._fillChannels()
+
+    def _fillChannels(self):
+        """Mark unused channels as disabled"""
+
+        # Add extra disabled channels as needed
+        index = len(self.channels)
+        while index < self.iface.myInfo.max_channels:
+            ch = channel_pb2.Channel()
+            ch.role = channel_pb2.Channel.Role.DISABLED
+            ch.index = index
+            self.channels.append(ch)
+            index += 1
+
+
+    def onResponseRequestChannel(self, p):
+        """Handle the response packet for requesting a channel _requestChannel()"""
+        logging.debug(f'onResponseRequestChannel() p:{p}')
+        c = p["decoded"]["admin"]["raw"].get_channel_response
+        self.partialChannels.append(c)
+        self._timeout.reset()  # We made foreward progress
+        logging.debug(f"Received channel {stripnl(c)}")
+        index = c.index
+
+        # for stress testing, we can always download all channels
+        fastChannelDownload = True
+
+        # Once we see a response that has NO settings, assume
+        # we are at the end of channels and stop fetching
+        quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
+
+        if quitEarly or index >= self.iface.myInfo.max_channels - 1:
+            logging.debug("Finished downloading channels")
+
+            self.channels = self.partialChannels
+            self._fixupChannels()
+
+            # FIXME, the following should only be called after we have settings and channels
+            self.iface._connected()  # Tell everyone else we are ready to go
+        else:
+            self._requestChannel(index + 1)
+
+    def _requestChannel(self, channelNum: int):
+        """Done with initial config messages, now send regular
+           MeshPackets to ask for settings"""
+        p = admin_pb2.AdminMessage()
+        p.get_channel_request = channelNum + 1
+
+        # Show progress message for super slow operations
+        if self != self.iface.localNode:
+            print(f"Requesting channel {channelNum} info from remote node (this could take a while)")
+            logging.debug(f"Requesting channel {channelNum} info from remote node (this could take a while)")
+        else:
+            logging.debug(f"Requesting channel {channelNum}")
+
+        return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel)
+
+
+    # pylint: disable=R1710
+    def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
+                   onResponse=None, adminIndex=0):
+        """Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
+
+        if self.noProto:
+            logging.warning(f"Not sending packet because protocol use is disabled by noProto")
+        else:
+            if adminIndex == 0:  # unless a special channel index was used, we want to use the admin index
+                adminIndex = self.iface.localNode._getAdminChannelIndex()
+            logging.debug(f'adminIndex:{adminIndex}')
+
+            return self.iface.sendData(p, self.nodeNum,
+                                       portNum=portnums_pb2.PortNum.ADMIN_APP,
+                                       wantAck=True,
+                                       wantResponse=wantResponse,
+                                       onResponse=onResponse,
+                                       channelIndex=adminIndex)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Node +(iface, nodeNum, noProto=False) +
+
+

A model of a (local or remote) node in the mesh

+

Includes methods for radioConfig and channels

+

Constructor

+
+ +Expand source code + +
class Node:
+    """A model of a (local or remote) node in the mesh
+
+    Includes methods for radioConfig and channels
+    """
+
+    def __init__(self, iface, nodeNum, noProto=False):
+        """Constructor"""
+        self.iface = iface
+        self.nodeNum = nodeNum
+        self.radioConfig = None
+        self.channels = None
+        self._timeout = Timeout(maxSecs=300)
+        self.partialChannels = None
+        self.noProto = noProto
+
+    def showChannels(self):
+        """Show human readable description of our channels."""
+        print("Channels:")
+        if self.channels:
+            logging.debug(f'self.channels:{self.channels}')
+            for c in self.channels:
+                #print('c.settings.psk:', c.settings.psk)
+                cStr = stripnl(MessageToJson(c.settings))
+                # only show if there is no psk (meaning disabled channel)
+                if c.settings.psk:
+                    print(f"  {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
+        publicURL = self.getURL(includeAll=False)
+        adminURL = self.getURL(includeAll=True)
+        print(f"\nPrimary channel URL: {publicURL}")
+        if adminURL != publicURL:
+            print(f"Complete URL (includes all channels): {adminURL}")
+
+    def showInfo(self):
+        """Show human readable description of our node"""
+        prefs = ""
+        if self.radioConfig and self.radioConfig.preferences:
+            prefs = stripnl(MessageToJson(self.radioConfig.preferences))
+        print(f"Preferences: {prefs}\n")
+        self.showChannels()
+
+    def requestConfig(self):
+        """Send regular MeshPackets to ask for settings and channels."""
+        logging.debug(f"requestConfig for nodeNum:{self.nodeNum}")
+        self.radioConfig = None
+        self.channels = None
+        self.partialChannels = []  # We keep our channels in a temp array until finished
+
+        self._requestSettings()
+
+    def turnOffEncryptionOnPrimaryChannel(self):
+        """Turn off encryption on primary channel."""
+        self.channels[0].settings.psk = fromPSK("none")
+        print("Writing modified channels to device")
+        self.writeChannel(0)
+
+    def waitForConfig(self):
+        """Block until radio config is received. Returns True if config has been received."""
+        return self._timeout.waitForSet(self, attrs=('radioConfig', 'channels'))
+
+    def writeConfig(self):
+        """Write the current (edited) radioConfig to the device"""
+        if self.radioConfig is None:
+            our_exit("Error: No RadioConfig has been read")
+
+        p = admin_pb2.AdminMessage()
+        p.set_radio.CopyFrom(self.radioConfig)
+
+        self._sendAdmin(p)
+        logging.debug("Wrote config")
+
+    def writeChannel(self, channelIndex, adminIndex=0):
+        """Write the current (edited) channel to the device"""
+
+        p = admin_pb2.AdminMessage()
+        p.set_channel.CopyFrom(self.channels[channelIndex])
+
+        self._sendAdmin(p, adminIndex=adminIndex)
+        logging.debug(f"Wrote channel {channelIndex}")
+
+    def getChannelByChannelIndex(self, channelIndex):
+        """Get channel by channelIndex
+            channelIndex: number, typically 0-7; based on max number channels
+            returns: None if there is no channel found
+        """
+        ch = None
+        if self.channels and 0 <= channelIndex < len(self.channels):
+            ch = self.channels[channelIndex]
+        return ch
+
+    def deleteChannel(self, channelIndex):
+        """Delete the specifed channelIndex and shift other channels up"""
+        ch = self.channels[channelIndex]
+        if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
+            our_exit("Warning: Only SECONDARY channels can be deleted")
+
+        # we are careful here because if we move the "admin" channel the channelIndex we need to use
+        # for sending admin channels will also change
+        adminIndex = self.iface.localNode._getAdminChannelIndex()
+
+        self.channels.pop(channelIndex)
+        self._fixupChannels()  # expand back to 8 channels
+
+        index = channelIndex
+        while index < self.iface.myInfo.max_channels:
+            self.writeChannel(index, adminIndex=adminIndex)
+            index += 1
+
+            # if we are updating the local node, we might end up
+            # *moving* the admin channel index as we are writing
+            if (self.iface.localNode == self) and index >= adminIndex:
+                # We've now passed the old location for admin index
+                # (and written it), so we can start finding it by name again
+                adminIndex = 0
+
+    def getChannelByName(self, name):
+        """Try to find the named channel or return None"""
+        for c in (self.channels or []):
+            if c.settings and c.settings.name == name:
+                return c
+        return None
+
+    def getDisabledChannel(self):
+        """Return the first channel that is disabled (i.e. available for some new use)"""
+        for c in self.channels:
+            if c.role == channel_pb2.Channel.Role.DISABLED:
+                return c
+        return None
+
+    def _getAdminChannelIndex(self):
+        """Return the channel number of the admin channel, or 0 if no reserved channel"""
+        c = self.getChannelByName("admin")
+        if c:
+            return c.index
+        else:
+            return 0
+
+    def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
+        """Set device owner name"""
+        logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
+        nChars = 3
+        minChars = 2
+        if long_name is not None:
+            long_name = long_name.strip()
+            if short_name is None:
+                words = long_name.split()
+                if len(long_name) <= nChars:
+                    short_name = long_name
+                elif len(words) >= minChars:
+                    short_name = ''.join(map(lambda word: word[0], words))
+                else:
+                    trans = str.maketrans(dict.fromkeys('aeiouAEIOU'))
+                    short_name = long_name[0] + long_name[1:].translate(trans)
+                    if len(short_name) < nChars:
+                        short_name = long_name[:nChars]
+
+        p = admin_pb2.AdminMessage()
+
+        if long_name is not None:
+            p.set_owner.long_name = long_name
+        if short_name is not None:
+            short_name = short_name.strip()
+            if len(short_name) > nChars:
+                short_name = short_name[:nChars]
+            p.set_owner.short_name = short_name
+            p.set_owner.is_licensed = is_licensed
+        if team is not None:
+            p.set_owner.team = team
+
+        # Note: These debug lines are used in unit tests
+        logging.debug(f'p.set_owner.long_name:{p.set_owner.long_name}:')
+        logging.debug(f'p.set_owner.short_name:{p.set_owner.short_name}:')
+        logging.debug(f'p.set_owner.is_licensed:{p.set_owner.is_licensed}')
+        logging.debug(f'p.set_owner.team:{p.set_owner.team}')
+        return self._sendAdmin(p)
+
+    def getURL(self, includeAll: bool = True):
+        """The sharable URL that describes the current channel"""
+        # Only keep the primary/secondary channels, assume primary is first
+        channelSet = apponly_pb2.ChannelSet()
+        if self.channels:
+            for c in self.channels:
+                if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
+                    channelSet.settings.append(c.settings)
+        some_bytes = channelSet.SerializeToString()
+        s = base64.urlsafe_b64encode(some_bytes).decode('ascii')
+        return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
+
+    def setURL(self, url):
+        """Set mesh network URL"""
+        if self.radioConfig is None:
+            our_exit("Warning: No RadioConfig has been read")
+
+        # URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
+        # Split on '/#' to find the base64 encoded channel settings
+        splitURL = url.split("/#")
+        b64 = splitURL[-1]
+
+        # We normally strip padding to make for a shorter URL, but the python parser doesn't like
+        # that.  So add back any missing padding
+        # per https://stackoverflow.com/a/9807138
+        missing_padding = len(b64) % 4
+        if missing_padding:
+            b64 += '=' * (4 - missing_padding)
+
+        decodedURL = base64.urlsafe_b64decode(b64)
+        channelSet = apponly_pb2.ChannelSet()
+        channelSet.ParseFromString(decodedURL)
+
+
+        if len(channelSet.settings) == 0:
+            our_exit("Warning: There were no settings.")
+
+        i = 0
+        for chs in channelSet.settings:
+            ch = channel_pb2.Channel()
+            ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY
+            ch.index = i
+            ch.settings.CopyFrom(chs)
+            self.channels[ch.index] = ch
+            logging.debug(f'Channel i:{i} ch:{ch}')
+            self.writeChannel(ch.index)
+            i = i + 1
+
+
+    def onResponseRequestSettings(self, p):
+        """Handle the response packet for requesting settings _requestSettings()"""
+        logging.debug(f'onResponseRequestSetting() p:{p}')
+        errorFound = False
+        if 'routing' in p["decoded"]:
+            if p["decoded"]["routing"]["errorReason"] != "NONE":
+                errorFound = True
+                print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
+        if errorFound is False:
+            self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
+            logging.debug(f'self.radioConfig:{self.radioConfig}')
+            logging.debug("Received radio config, now fetching channels...")
+            self._timeout.reset()  # We made foreward progress
+            self._requestChannel(0)  # now start fetching channels
+
+
+    def _requestSettings(self):
+        """Done with initial config messages, now send regular
+           MeshPackets to ask for settings."""
+        p = admin_pb2.AdminMessage()
+        p.get_radio_request = True
+
+        # Show progress message for super slow operations
+        if self != self.iface.localNode:
+            print("Requesting preferences from remote node.")
+            print("Be sure:")
+            print(" 1. There is a SECONDARY channel named 'admin'.")
+            print(" 2. The '--seturl' was used to configure.")
+            print(" 3. All devices have the same modem config. (i.e., '--ch-longfast')")
+            print(" 4. All devices have been rebooted after all of the above. (optional, but recommended)")
+            print("Note: This could take a while (it requests remote channel configs, then writes config)")
+
+        return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestSettings)
+
+    def exitSimulator(self):
+        """Tell a simulator node to exit (this message
+           is ignored for other nodes)"""
+        p = admin_pb2.AdminMessage()
+        p.exit_simulator = True
+        logging.debug('in exitSimulator()')
+
+        return self._sendAdmin(p)
+
+    def reboot(self, secs: int = 10):
+        """Tell the node to reboot."""
+        p = admin_pb2.AdminMessage()
+        p.reboot_seconds = secs
+        logging.info(f"Telling node to reboot in {secs} seconds")
+
+        return self._sendAdmin(p)
+
+    def _fixupChannels(self):
+        """Fixup indexes and add disabled channels as needed"""
+
+        # Add extra disabled channels as needed
+        # TODO: These 2 lines seem to not do anything.
+        for index, ch in enumerate(self.channels):
+            ch.index = index  # fixup indexes
+
+        self._fillChannels()
+
+    def _fillChannels(self):
+        """Mark unused channels as disabled"""
+
+        # Add extra disabled channels as needed
+        index = len(self.channels)
+        while index < self.iface.myInfo.max_channels:
+            ch = channel_pb2.Channel()
+            ch.role = channel_pb2.Channel.Role.DISABLED
+            ch.index = index
+            self.channels.append(ch)
+            index += 1
+
+
+    def onResponseRequestChannel(self, p):
+        """Handle the response packet for requesting a channel _requestChannel()"""
+        logging.debug(f'onResponseRequestChannel() p:{p}')
+        c = p["decoded"]["admin"]["raw"].get_channel_response
+        self.partialChannels.append(c)
+        self._timeout.reset()  # We made foreward progress
+        logging.debug(f"Received channel {stripnl(c)}")
+        index = c.index
+
+        # for stress testing, we can always download all channels
+        fastChannelDownload = True
+
+        # Once we see a response that has NO settings, assume
+        # we are at the end of channels and stop fetching
+        quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
+
+        if quitEarly or index >= self.iface.myInfo.max_channels - 1:
+            logging.debug("Finished downloading channels")
+
+            self.channels = self.partialChannels
+            self._fixupChannels()
+
+            # FIXME, the following should only be called after we have settings and channels
+            self.iface._connected()  # Tell everyone else we are ready to go
+        else:
+            self._requestChannel(index + 1)
+
+    def _requestChannel(self, channelNum: int):
+        """Done with initial config messages, now send regular
+           MeshPackets to ask for settings"""
+        p = admin_pb2.AdminMessage()
+        p.get_channel_request = channelNum + 1
+
+        # Show progress message for super slow operations
+        if self != self.iface.localNode:
+            print(f"Requesting channel {channelNum} info from remote node (this could take a while)")
+            logging.debug(f"Requesting channel {channelNum} info from remote node (this could take a while)")
+        else:
+            logging.debug(f"Requesting channel {channelNum}")
+
+        return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel)
+
+
+    # pylint: disable=R1710
+    def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
+                   onResponse=None, adminIndex=0):
+        """Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
+
+        if self.noProto:
+            logging.warning(f"Not sending packet because protocol use is disabled by noProto")
+        else:
+            if adminIndex == 0:  # unless a special channel index was used, we want to use the admin index
+                adminIndex = self.iface.localNode._getAdminChannelIndex()
+            logging.debug(f'adminIndex:{adminIndex}')
+
+            return self.iface.sendData(p, self.nodeNum,
+                                       portNum=portnums_pb2.PortNum.ADMIN_APP,
+                                       wantAck=True,
+                                       wantResponse=wantResponse,
+                                       onResponse=onResponse,
+                                       channelIndex=adminIndex)
+
+

Methods

+
+
+def deleteChannel(self, channelIndex) +
+
+

Delete the specifed channelIndex and shift other channels up

+
+ +Expand source code + +
def deleteChannel(self, channelIndex):
+    """Delete the specifed channelIndex and shift other channels up"""
+    ch = self.channels[channelIndex]
+    if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED):
+        our_exit("Warning: Only SECONDARY channels can be deleted")
+
+    # we are careful here because if we move the "admin" channel the channelIndex we need to use
+    # for sending admin channels will also change
+    adminIndex = self.iface.localNode._getAdminChannelIndex()
+
+    self.channels.pop(channelIndex)
+    self._fixupChannels()  # expand back to 8 channels
+
+    index = channelIndex
+    while index < self.iface.myInfo.max_channels:
+        self.writeChannel(index, adminIndex=adminIndex)
+        index += 1
+
+        # if we are updating the local node, we might end up
+        # *moving* the admin channel index as we are writing
+        if (self.iface.localNode == self) and index >= adminIndex:
+            # We've now passed the old location for admin index
+            # (and written it), so we can start finding it by name again
+            adminIndex = 0
+
+
+
+def exitSimulator(self) +
+
+

Tell a simulator node to exit (this message +is ignored for other nodes)

+
+ +Expand source code + +
def exitSimulator(self):
+    """Tell a simulator node to exit (this message
+       is ignored for other nodes)"""
+    p = admin_pb2.AdminMessage()
+    p.exit_simulator = True
+    logging.debug('in exitSimulator()')
+
+    return self._sendAdmin(p)
+
+
+
+def getChannelByChannelIndex(self, channelIndex) +
+
+

Get channel by channelIndex +channelIndex: number, typically 0-7; based on max number channels +returns: None if there is no channel found

+
+ +Expand source code + +
def getChannelByChannelIndex(self, channelIndex):
+    """Get channel by channelIndex
+        channelIndex: number, typically 0-7; based on max number channels
+        returns: None if there is no channel found
+    """
+    ch = None
+    if self.channels and 0 <= channelIndex < len(self.channels):
+        ch = self.channels[channelIndex]
+    return ch
+
+
+
+def getChannelByName(self, name) +
+
+

Try to find the named channel or return None

+
+ +Expand source code + +
def getChannelByName(self, name):
+    """Try to find the named channel or return None"""
+    for c in (self.channels or []):
+        if c.settings and c.settings.name == name:
+            return c
+    return None
+
+
+
+def getDisabledChannel(self) +
+
+

Return the first channel that is disabled (i.e. available for some new use)

+
+ +Expand source code + +
def getDisabledChannel(self):
+    """Return the first channel that is disabled (i.e. available for some new use)"""
+    for c in self.channels:
+        if c.role == channel_pb2.Channel.Role.DISABLED:
+            return c
+    return None
+
+
+
+def getURL(self, includeAll: bool = True) +
+
+

The sharable URL that describes the current channel

+
+ +Expand source code + +
def getURL(self, includeAll: bool = True):
+    """The sharable URL that describes the current channel"""
+    # Only keep the primary/secondary channels, assume primary is first
+    channelSet = apponly_pb2.ChannelSet()
+    if self.channels:
+        for c in self.channels:
+            if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY):
+                channelSet.settings.append(c.settings)
+    some_bytes = channelSet.SerializeToString()
+    s = base64.urlsafe_b64encode(some_bytes).decode('ascii')
+    return f"https://www.meshtastic.org/d/#{s}".replace("=", "")
+
+
+
+def onResponseRequestChannel(self, p) +
+
+

Handle the response packet for requesting a channel _requestChannel()

+
+ +Expand source code + +
def onResponseRequestChannel(self, p):
+    """Handle the response packet for requesting a channel _requestChannel()"""
+    logging.debug(f'onResponseRequestChannel() p:{p}')
+    c = p["decoded"]["admin"]["raw"].get_channel_response
+    self.partialChannels.append(c)
+    self._timeout.reset()  # We made foreward progress
+    logging.debug(f"Received channel {stripnl(c)}")
+    index = c.index
+
+    # for stress testing, we can always download all channels
+    fastChannelDownload = True
+
+    # Once we see a response that has NO settings, assume
+    # we are at the end of channels and stop fetching
+    quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload
+
+    if quitEarly or index >= self.iface.myInfo.max_channels - 1:
+        logging.debug("Finished downloading channels")
+
+        self.channels = self.partialChannels
+        self._fixupChannels()
+
+        # FIXME, the following should only be called after we have settings and channels
+        self.iface._connected()  # Tell everyone else we are ready to go
+    else:
+        self._requestChannel(index + 1)
+
+
+
+def onResponseRequestSettings(self, p) +
+
+

Handle the response packet for requesting settings _requestSettings()

+
+ +Expand source code + +
def onResponseRequestSettings(self, p):
+    """Handle the response packet for requesting settings _requestSettings()"""
+    logging.debug(f'onResponseRequestSetting() p:{p}')
+    errorFound = False
+    if 'routing' in p["decoded"]:
+        if p["decoded"]["routing"]["errorReason"] != "NONE":
+            errorFound = True
+            print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
+    if errorFound is False:
+        self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
+        logging.debug(f'self.radioConfig:{self.radioConfig}')
+        logging.debug("Received radio config, now fetching channels...")
+        self._timeout.reset()  # We made foreward progress
+        self._requestChannel(0)  # now start fetching channels
+
+
+
+def reboot(self, secs: int = 10) +
+
+

Tell the node to reboot.

+
+ +Expand source code + +
def reboot(self, secs: int = 10):
+    """Tell the node to reboot."""
+    p = admin_pb2.AdminMessage()
+    p.reboot_seconds = secs
+    logging.info(f"Telling node to reboot in {secs} seconds")
+
+    return self._sendAdmin(p)
+
+
+
+def requestConfig(self) +
+
+

Send regular MeshPackets to ask for settings and channels.

+
+ +Expand source code + +
def requestConfig(self):
+    """Send regular MeshPackets to ask for settings and channels."""
+    logging.debug(f"requestConfig for nodeNum:{self.nodeNum}")
+    self.radioConfig = None
+    self.channels = None
+    self.partialChannels = []  # We keep our channels in a temp array until finished
+
+    self._requestSettings()
+
+
+
+def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None) +
+
+

Set device owner name

+
+ +Expand source code + +
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None):
+    """Set device owner name"""
+    logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
+    nChars = 3
+    minChars = 2
+    if long_name is not None:
+        long_name = long_name.strip()
+        if short_name is None:
+            words = long_name.split()
+            if len(long_name) <= nChars:
+                short_name = long_name
+            elif len(words) >= minChars:
+                short_name = ''.join(map(lambda word: word[0], words))
+            else:
+                trans = str.maketrans(dict.fromkeys('aeiouAEIOU'))
+                short_name = long_name[0] + long_name[1:].translate(trans)
+                if len(short_name) < nChars:
+                    short_name = long_name[:nChars]
+
+    p = admin_pb2.AdminMessage()
+
+    if long_name is not None:
+        p.set_owner.long_name = long_name
+    if short_name is not None:
+        short_name = short_name.strip()
+        if len(short_name) > nChars:
+            short_name = short_name[:nChars]
+        p.set_owner.short_name = short_name
+        p.set_owner.is_licensed = is_licensed
+    if team is not None:
+        p.set_owner.team = team
+
+    # Note: These debug lines are used in unit tests
+    logging.debug(f'p.set_owner.long_name:{p.set_owner.long_name}:')
+    logging.debug(f'p.set_owner.short_name:{p.set_owner.short_name}:')
+    logging.debug(f'p.set_owner.is_licensed:{p.set_owner.is_licensed}')
+    logging.debug(f'p.set_owner.team:{p.set_owner.team}')
+    return self._sendAdmin(p)
+
+
+
+def setURL(self, url) +
+
+

Set mesh network URL

+
+ +Expand source code + +
def setURL(self, url):
+    """Set mesh network URL"""
+    if self.radioConfig is None:
+        our_exit("Warning: No RadioConfig has been read")
+
+    # URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
+    # Split on '/#' to find the base64 encoded channel settings
+    splitURL = url.split("/#")
+    b64 = splitURL[-1]
+
+    # We normally strip padding to make for a shorter URL, but the python parser doesn't like
+    # that.  So add back any missing padding
+    # per https://stackoverflow.com/a/9807138
+    missing_padding = len(b64) % 4
+    if missing_padding:
+        b64 += '=' * (4 - missing_padding)
+
+    decodedURL = base64.urlsafe_b64decode(b64)
+    channelSet = apponly_pb2.ChannelSet()
+    channelSet.ParseFromString(decodedURL)
+
+
+    if len(channelSet.settings) == 0:
+        our_exit("Warning: There were no settings.")
+
+    i = 0
+    for chs in channelSet.settings:
+        ch = channel_pb2.Channel()
+        ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY
+        ch.index = i
+        ch.settings.CopyFrom(chs)
+        self.channels[ch.index] = ch
+        logging.debug(f'Channel i:{i} ch:{ch}')
+        self.writeChannel(ch.index)
+        i = i + 1
+
+
+
+def showChannels(self) +
+
+

Show human readable description of our channels.

+
+ +Expand source code + +
def showChannels(self):
+    """Show human readable description of our channels."""
+    print("Channels:")
+    if self.channels:
+        logging.debug(f'self.channels:{self.channels}')
+        for c in self.channels:
+            #print('c.settings.psk:', c.settings.psk)
+            cStr = stripnl(MessageToJson(c.settings))
+            # only show if there is no psk (meaning disabled channel)
+            if c.settings.psk:
+                print(f"  {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
+    publicURL = self.getURL(includeAll=False)
+    adminURL = self.getURL(includeAll=True)
+    print(f"\nPrimary channel URL: {publicURL}")
+    if adminURL != publicURL:
+        print(f"Complete URL (includes all channels): {adminURL}")
+
+
+
+def showInfo(self) +
+
+

Show human readable description of our node

+
+ +Expand source code + +
def showInfo(self):
+    """Show human readable description of our node"""
+    prefs = ""
+    if self.radioConfig and self.radioConfig.preferences:
+        prefs = stripnl(MessageToJson(self.radioConfig.preferences))
+    print(f"Preferences: {prefs}\n")
+    self.showChannels()
+
+
+
+def turnOffEncryptionOnPrimaryChannel(self) +
+
+

Turn off encryption on primary channel.

+
+ +Expand source code + +
def turnOffEncryptionOnPrimaryChannel(self):
+    """Turn off encryption on primary channel."""
+    self.channels[0].settings.psk = fromPSK("none")
+    print("Writing modified channels to device")
+    self.writeChannel(0)
+
+
+
+def waitForConfig(self) +
+
+

Block until radio config is received. Returns True if config has been received.

+
+ +Expand source code + +
def waitForConfig(self):
+    """Block until radio config is received. Returns True if config has been received."""
+    return self._timeout.waitForSet(self, attrs=('radioConfig', 'channels'))
+
+
+
+def writeChannel(self, channelIndex, adminIndex=0) +
+
+

Write the current (edited) channel to the device

+
+ +Expand source code + +
def writeChannel(self, channelIndex, adminIndex=0):
+    """Write the current (edited) channel to the device"""
+
+    p = admin_pb2.AdminMessage()
+    p.set_channel.CopyFrom(self.channels[channelIndex])
+
+    self._sendAdmin(p, adminIndex=adminIndex)
+    logging.debug(f"Wrote channel {channelIndex}")
+
+
+
+def writeConfig(self) +
+
+

Write the current (edited) radioConfig to the device

+
+ +Expand source code + +
def writeConfig(self):
+    """Write the current (edited) radioConfig to the device"""
+    if self.radioConfig is None:
+        our_exit("Error: No RadioConfig has been read")
+
+    p = admin_pb2.AdminMessage()
+    p.set_radio.CopyFrom(self.radioConfig)
+
+    self._sendAdmin(p)
+    logging.debug("Wrote config")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/portnums_pb2.html b/docs/meshtastic/portnums_pb2.html new file mode 100644 index 0000000..ee0c742 --- /dev/null +++ b/docs/meshtastic/portnums_pb2.html @@ -0,0 +1,190 @@ + + + + + + +meshtastic.portnums_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.portnums_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: portnums.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='portnums.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x0eportnums.proto*\xcb\x02\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12!\n\x1d\x45NVIRONMENTAL_MEASUREMENT_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42\x44\n\x13\x63om.geeksville.meshB\x08PortnumsH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+)
+
+_PORTNUM = _descriptor.EnumDescriptor(
+  name='PortNum',
+  full_name='PortNum',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNKNOWN_APP', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TEXT_MESSAGE_APP', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='REMOTE_HARDWARE_APP', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POSITION_APP', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NODEINFO_APP', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTING_APP', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ADMIN_APP', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='REPLY_APP', index=7, number=32,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='IP_TUNNEL_APP', index=8, number=33,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SERIAL_APP', index=9, number=64,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='STORE_FORWARD_APP', index=10, number=65,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RANGE_TEST_APP', index=11, number=66,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ENVIRONMENTAL_MEASUREMENT_APP', index=12, number=67,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ZPS_APP', index=13, number=68,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PRIVATE_APP', index=14, number=256,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ATAK_FORWARDER', index=15, number=257,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MAX', index=16, number=511,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=19,
+  serialized_end=350,
+)
+_sym_db.RegisterEnumDescriptor(_PORTNUM)
+
+PortNum = enum_type_wrapper.EnumTypeWrapper(_PORTNUM)
+UNKNOWN_APP = 0
+TEXT_MESSAGE_APP = 1
+REMOTE_HARDWARE_APP = 2
+POSITION_APP = 3
+NODEINFO_APP = 4
+ROUTING_APP = 5
+ADMIN_APP = 6
+REPLY_APP = 32
+IP_TUNNEL_APP = 33
+SERIAL_APP = 64
+STORE_FORWARD_APP = 65
+RANGE_TEST_APP = 66
+ENVIRONMENTAL_MEASUREMENT_APP = 67
+ZPS_APP = 68
+PRIVATE_APP = 256
+ATAK_FORWARDER = 257
+MAX = 511
+
+
+DESCRIPTOR.enum_types_by_name['PortNum'] = _PORTNUM
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/radioconfig_pb2.html b/docs/meshtastic/radioconfig_pb2.html new file mode 100644 index 0000000..a0de023 --- /dev/null +++ b/docs/meshtastic/radioconfig_pb2.html @@ -0,0 +1,1604 @@ + + + + + + +meshtastic.radioconfig_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.radioconfig_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: radioconfig.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='radioconfig.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\021RadioConfigProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x11radioconfig.proto\"\xe7\x13\n\x0bRadioConfig\x12\x31\n\x0bpreferences\x18\x01 \x01(\x0b\x32\x1c.RadioConfig.UserPreferences\x1a\xa4\x13\n\x0fUserPreferences\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12 \n\x18position_broadcast_smart\x18\x11 \x01(\x08\x12\x1b\n\x13send_owner_interval\x18\x02 \x01(\r\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x16\n\x0escreen_on_secs\x18\x05 \x01(\r\x12\x1a\n\x12phone_timeout_secs\x18\x06 \x01(\r\x12\x1d\n\x15phone_sds_timeout_sec\x18\x07 \x01(\r\x12\x1d\n\x15mesh_sds_timeout_secs\x18\x08 \x01(\r\x12\x10\n\x08sds_secs\x18\t \x01(\r\x12\x0f\n\x07ls_secs\x18\n \x01(\r\x12\x15\n\rmin_wake_secs\x18\x0b \x01(\r\x12\x11\n\twifi_ssid\x18\x0c \x01(\t\x12\x15\n\rwifi_password\x18\r \x01(\t\x12\x14\n\x0cwifi_ap_mode\x18\x0e \x01(\x08\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\x12&\n\x0e\x63harge_current\x18\x10 \x01(\x0e\x32\x0e.ChargeCurrent\x12\x11\n\tis_router\x18% \x01(\x08\x12\x14\n\x0cis_low_power\x18& \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\' \x01(\x08\x12\x17\n\x0fserial_disabled\x18( \x01(\x08\x12(\n\x0elocation_share\x18  \x01(\x0e\x32\x10.LocationSharing\x12$\n\rgps_operation\x18! \x01(\x0e\x32\r.GpsOperation\x12\x1b\n\x13gps_update_interval\x18\" \x01(\r\x12\x18\n\x10gps_attempt_time\x18$ \x01(\r\x12\x15\n\rgps_accept_2d\x18- \x01(\x08\x12\x13\n\x0bgps_max_dop\x18. \x01(\r\x12\x18\n\x10\x66requency_offset\x18) \x01(\x02\x12\x13\n\x0bmqtt_server\x18* \x01(\t\x12\x15\n\rmqtt_disabled\x18+ \x01(\x08\x12(\n\ngps_format\x18, \x01(\x0e\x32\x14.GpsCoordinateFormat\x12\x15\n\rfactory_reset\x18\x64 \x01(\x08\x12\x19\n\x11\x64\x65\x62ug_log_enabled\x18\x65 \x01(\x08\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x1c\n\x14serialplugin_enabled\x18x \x01(\x08\x12\x19\n\x11serialplugin_echo\x18y \x01(\x08\x12\x18\n\x10serialplugin_rxd\x18z \x01(\r\x12\x18\n\x10serialplugin_txd\x18{ \x01(\r\x12\x1c\n\x14serialplugin_timeout\x18| \x01(\r\x12\x19\n\x11serialplugin_mode\x18} \x01(\r\x12\'\n\x1f\x65xt_notification_plugin_enabled\x18~ \x01(\x08\x12)\n!ext_notification_plugin_output_ms\x18\x7f \x01(\r\x12\'\n\x1e\x65xt_notification_plugin_output\x18\x80\x01 \x01(\r\x12\'\n\x1e\x65xt_notification_plugin_active\x18\x81\x01 \x01(\x08\x12.\n%ext_notification_plugin_alert_message\x18\x82\x01 \x01(\x08\x12+\n\"ext_notification_plugin_alert_bell\x18\x83\x01 \x01(\x08\x12\"\n\x19range_test_plugin_enabled\x18\x84\x01 \x01(\x08\x12!\n\x18range_test_plugin_sender\x18\x85\x01 \x01(\r\x12\x1f\n\x16range_test_plugin_save\x18\x86\x01 \x01(\x08\x12%\n\x1cstore_forward_plugin_enabled\x18\x94\x01 \x01(\x08\x12\'\n\x1estore_forward_plugin_heartbeat\x18\x95\x01 \x01(\x08\x12%\n\x1cstore_forward_plugin_records\x18\x89\x01 \x01(\r\x12\x30\n\'store_forward_plugin_history_return_max\x18\x8a\x01 \x01(\r\x12\x33\n*store_forward_plugin_history_return_window\x18\x8b\x01 \x01(\r\x12=\n4environmental_measurement_plugin_measurement_enabled\x18\x8c\x01 \x01(\x08\x12\x38\n/environmental_measurement_plugin_screen_enabled\x18\x8d\x01 \x01(\x08\x12\x44\n;environmental_measurement_plugin_read_error_count_threshold\x18\x8e\x01 \x01(\r\x12\x39\n0environmental_measurement_plugin_update_interval\x18\x8f\x01 \x01(\r\x12;\n2environmental_measurement_plugin_recovery_interval\x18\x90\x01 \x01(\r\x12;\n2environmental_measurement_plugin_display_farenheit\x18\x91\x01 \x01(\x08\x12v\n,environmental_measurement_plugin_sensor_type\x18\x92\x01 \x01(\x0e\x32?.RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType\x12\x34\n+environmental_measurement_plugin_sensor_pin\x18\x93\x01 \x01(\r\x12\x17\n\x0eposition_flags\x18\x96\x01 \x01(\r\x12\x1a\n\x11is_always_powered\x18\x97\x01 \x01(\x08\x12\"\n\x19\x61uto_screen_carousel_secs\x18\x98\x01 \x01(\r\x12\'\n\x1eon_battery_shutdown_after_secs\x18\x99\x01 \x01(\r\x12\x12\n\thop_limit\x18\x9a\x01 \x01(\r\x12\x16\n\rmqtt_username\x18\x9b\x01 \x01(\t\x12\x16\n\rmqtt_password\x18\x9c\x01 \x01(\t\"<\n\"EnvironmentalMeasurementSensorType\x12\t\n\x05\x44HT11\x10\x00\x12\x0b\n\x07\x44S18B20\x10\x01J\x06\x08\x88\x01\x10\x89\x01*f\n\nRegionCode\x12\t\n\x05Unset\x10\x00\x12\x06\n\x02US\x10\x01\x12\t\n\x05\x45U433\x10\x02\x12\t\n\x05\x45U865\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t*\xd1\x01\n\rChargeCurrent\x12\x0b\n\x07MAUnset\x10\x00\x12\t\n\x05MA100\x10\x01\x12\t\n\x05MA190\x10\x02\x12\t\n\x05MA280\x10\x03\x12\t\n\x05MA360\x10\x04\x12\t\n\x05MA450\x10\x05\x12\t\n\x05MA550\x10\x06\x12\t\n\x05MA630\x10\x07\x12\t\n\x05MA700\x10\x08\x12\t\n\x05MA780\x10\t\x12\t\n\x05MA880\x10\n\x12\t\n\x05MA960\x10\x0b\x12\n\n\x06MA1000\x10\x0c\x12\n\n\x06MA1080\x10\r\x12\n\n\x06MA1160\x10\x0e\x12\n\n\x06MA1240\x10\x0f\x12\n\n\x06MA1320\x10\x10*j\n\x0cGpsOperation\x12\x0e\n\nGpsOpUnset\x10\x00\x12\x13\n\x0fGpsOpStationary\x10\x01\x12\x0f\n\x0bGpsOpMobile\x10\x02\x12\x11\n\rGpsOpTimeOnly\x10\x03\x12\x11\n\rGpsOpDisabled\x10\x04*\x83\x01\n\x13GpsCoordinateFormat\x12\x10\n\x0cGpsFormatDec\x10\x00\x12\x10\n\x0cGpsFormatDMS\x10\x01\x12\x10\n\x0cGpsFormatUTM\x10\x02\x12\x11\n\rGpsFormatMGRS\x10\x03\x12\x10\n\x0cGpsFormatOLC\x10\x04\x12\x11\n\rGpsFormatOSGR\x10\x05*@\n\x0fLocationSharing\x12\x0c\n\x08LocUnset\x10\x00\x12\x0e\n\nLocEnabled\x10\x01\x12\x0f\n\x0bLocDisabled\x10\x02*\xbc\x01\n\rPositionFlags\x12\x11\n\rPOS_UNDEFINED\x10\x00\x12\x10\n\x0cPOS_ALTITUDE\x10\x01\x12\x0f\n\x0bPOS_ALT_MSL\x10\x02\x12\x0f\n\x0bPOS_GEO_SEP\x10\x04\x12\x0b\n\x07POS_DOP\x10\x08\x12\r\n\tPOS_HVDOP\x10\x10\x12\x0f\n\x0bPOS_BATTERY\x10 \x12\x11\n\rPOS_SATINVIEW\x10@\x12\x10\n\x0bPOS_SEQ_NOS\x10\x80\x01\x12\x12\n\rPOS_TIMESTAMP\x10\x80\x02\x42M\n\x13\x63om.geeksville.meshB\x11RadioConfigProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+)
+
+_REGIONCODE = _descriptor.EnumDescriptor(
+  name='RegionCode',
+  full_name='RegionCode',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='Unset', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='US', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='EU433', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='EU865', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CN', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='JP', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ANZ', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='KR', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TW', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RU', index=9, number=9,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2559,
+  serialized_end=2661,
+)
+_sym_db.RegisterEnumDescriptor(_REGIONCODE)
+
+RegionCode = enum_type_wrapper.EnumTypeWrapper(_REGIONCODE)
+_CHARGECURRENT = _descriptor.EnumDescriptor(
+  name='ChargeCurrent',
+  full_name='ChargeCurrent',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='MAUnset', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA100', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA190', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA280', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA360', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA450', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA550', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA630', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA700', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA780', index=9, number=9,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA880', index=10, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA960', index=11, number=11,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA1000', index=12, number=12,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA1080', index=13, number=13,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA1160', index=14, number=14,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA1240', index=15, number=15,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MA1320', index=16, number=16,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2664,
+  serialized_end=2873,
+)
+_sym_db.RegisterEnumDescriptor(_CHARGECURRENT)
+
+ChargeCurrent = enum_type_wrapper.EnumTypeWrapper(_CHARGECURRENT)
+_GPSOPERATION = _descriptor.EnumDescriptor(
+  name='GpsOperation',
+  full_name='GpsOperation',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='GpsOpUnset', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsOpStationary', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsOpMobile', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsOpTimeOnly', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsOpDisabled', index=4, number=4,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2875,
+  serialized_end=2981,
+)
+_sym_db.RegisterEnumDescriptor(_GPSOPERATION)
+
+GpsOperation = enum_type_wrapper.EnumTypeWrapper(_GPSOPERATION)
+_GPSCOORDINATEFORMAT = _descriptor.EnumDescriptor(
+  name='GpsCoordinateFormat',
+  full_name='GpsCoordinateFormat',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='GpsFormatDec', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsFormatDMS', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsFormatUTM', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsFormatMGRS', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsFormatOLC', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GpsFormatOSGR', index=5, number=5,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2984,
+  serialized_end=3115,
+)
+_sym_db.RegisterEnumDescriptor(_GPSCOORDINATEFORMAT)
+
+GpsCoordinateFormat = enum_type_wrapper.EnumTypeWrapper(_GPSCOORDINATEFORMAT)
+_LOCATIONSHARING = _descriptor.EnumDescriptor(
+  name='LocationSharing',
+  full_name='LocationSharing',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='LocUnset', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LocEnabled', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LocDisabled', index=2, number=2,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3117,
+  serialized_end=3181,
+)
+_sym_db.RegisterEnumDescriptor(_LOCATIONSHARING)
+
+LocationSharing = enum_type_wrapper.EnumTypeWrapper(_LOCATIONSHARING)
+_POSITIONFLAGS = _descriptor.EnumDescriptor(
+  name='PositionFlags',
+  full_name='PositionFlags',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='POS_UNDEFINED', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_ALTITUDE', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_ALT_MSL', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_GEO_SEP', index=3, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_DOP', index=4, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_HVDOP', index=5, number=16,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_BATTERY', index=6, number=32,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_SATINVIEW', index=7, number=64,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_SEQ_NOS', index=8, number=128,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='POS_TIMESTAMP', index=9, number=256,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3184,
+  serialized_end=3372,
+)
+_sym_db.RegisterEnumDescriptor(_POSITIONFLAGS)
+
+PositionFlags = enum_type_wrapper.EnumTypeWrapper(_POSITIONFLAGS)
+Unset = 0
+US = 1
+EU433 = 2
+EU865 = 3
+CN = 4
+JP = 5
+ANZ = 6
+KR = 7
+TW = 8
+RU = 9
+MAUnset = 0
+MA100 = 1
+MA190 = 2
+MA280 = 3
+MA360 = 4
+MA450 = 5
+MA550 = 6
+MA630 = 7
+MA700 = 8
+MA780 = 9
+MA880 = 10
+MA960 = 11
+MA1000 = 12
+MA1080 = 13
+MA1160 = 14
+MA1240 = 15
+MA1320 = 16
+GpsOpUnset = 0
+GpsOpStationary = 1
+GpsOpMobile = 2
+GpsOpTimeOnly = 3
+GpsOpDisabled = 4
+GpsFormatDec = 0
+GpsFormatDMS = 1
+GpsFormatUTM = 2
+GpsFormatMGRS = 3
+GpsFormatOLC = 4
+GpsFormatOSGR = 5
+LocUnset = 0
+LocEnabled = 1
+LocDisabled = 2
+POS_UNDEFINED = 0
+POS_ALTITUDE = 1
+POS_ALT_MSL = 2
+POS_GEO_SEP = 4
+POS_DOP = 8
+POS_HVDOP = 16
+POS_BATTERY = 32
+POS_SATINVIEW = 64
+POS_SEQ_NOS = 128
+POS_TIMESTAMP = 256
+
+
+_RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE = _descriptor.EnumDescriptor(
+  name='EnvironmentalMeasurementSensorType',
+  full_name='RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='DHT11', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DS18B20', index=1, number=1,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2489,
+  serialized_end=2549,
+)
+_sym_db.RegisterEnumDescriptor(_RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE)
+
+
+_RADIOCONFIG_USERPREFERENCES = _descriptor.Descriptor(
+  name='UserPreferences',
+  full_name='RadioConfig.UserPreferences',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='position_broadcast_secs', full_name='RadioConfig.UserPreferences.position_broadcast_secs', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='position_broadcast_smart', full_name='RadioConfig.UserPreferences.position_broadcast_smart', index=1,
+      number=17, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='send_owner_interval', full_name='RadioConfig.UserPreferences.send_owner_interval', index=2,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='wait_bluetooth_secs', full_name='RadioConfig.UserPreferences.wait_bluetooth_secs', index=3,
+      number=4, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='screen_on_secs', full_name='RadioConfig.UserPreferences.screen_on_secs', index=4,
+      number=5, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='phone_timeout_secs', full_name='RadioConfig.UserPreferences.phone_timeout_secs', index=5,
+      number=6, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='phone_sds_timeout_sec', full_name='RadioConfig.UserPreferences.phone_sds_timeout_sec', index=6,
+      number=7, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mesh_sds_timeout_secs', full_name='RadioConfig.UserPreferences.mesh_sds_timeout_secs', index=7,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='sds_secs', full_name='RadioConfig.UserPreferences.sds_secs', index=8,
+      number=9, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ls_secs', full_name='RadioConfig.UserPreferences.ls_secs', index=9,
+      number=10, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='min_wake_secs', full_name='RadioConfig.UserPreferences.min_wake_secs', index=10,
+      number=11, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='wifi_ssid', full_name='RadioConfig.UserPreferences.wifi_ssid', index=11,
+      number=12, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='wifi_password', full_name='RadioConfig.UserPreferences.wifi_password', index=12,
+      number=13, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='wifi_ap_mode', full_name='RadioConfig.UserPreferences.wifi_ap_mode', index=13,
+      number=14, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='region', full_name='RadioConfig.UserPreferences.region', index=14,
+      number=15, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='charge_current', full_name='RadioConfig.UserPreferences.charge_current', index=15,
+      number=16, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='is_router', full_name='RadioConfig.UserPreferences.is_router', index=16,
+      number=37, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='is_low_power', full_name='RadioConfig.UserPreferences.is_low_power', index=17,
+      number=38, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='fixed_position', full_name='RadioConfig.UserPreferences.fixed_position', index=18,
+      number=39, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serial_disabled', full_name='RadioConfig.UserPreferences.serial_disabled', index=19,
+      number=40, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='location_share', full_name='RadioConfig.UserPreferences.location_share', index=20,
+      number=32, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_operation', full_name='RadioConfig.UserPreferences.gps_operation', index=21,
+      number=33, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_update_interval', full_name='RadioConfig.UserPreferences.gps_update_interval', index=22,
+      number=34, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_attempt_time', full_name='RadioConfig.UserPreferences.gps_attempt_time', index=23,
+      number=36, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_accept_2d', full_name='RadioConfig.UserPreferences.gps_accept_2d', index=24,
+      number=45, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_max_dop', full_name='RadioConfig.UserPreferences.gps_max_dop', index=25,
+      number=46, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='frequency_offset', full_name='RadioConfig.UserPreferences.frequency_offset', index=26,
+      number=41, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mqtt_server', full_name='RadioConfig.UserPreferences.mqtt_server', index=27,
+      number=42, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mqtt_disabled', full_name='RadioConfig.UserPreferences.mqtt_disabled', index=28,
+      number=43, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gps_format', full_name='RadioConfig.UserPreferences.gps_format', index=29,
+      number=44, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='factory_reset', full_name='RadioConfig.UserPreferences.factory_reset', index=30,
+      number=100, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='debug_log_enabled', full_name='RadioConfig.UserPreferences.debug_log_enabled', index=31,
+      number=101, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ignore_incoming', full_name='RadioConfig.UserPreferences.ignore_incoming', index=32,
+      number=103, type=13, cpp_type=3, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serialplugin_enabled', full_name='RadioConfig.UserPreferences.serialplugin_enabled', index=33,
+      number=120, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serialplugin_echo', full_name='RadioConfig.UserPreferences.serialplugin_echo', index=34,
+      number=121, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serialplugin_rxd', full_name='RadioConfig.UserPreferences.serialplugin_rxd', index=35,
+      number=122, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serialplugin_txd', full_name='RadioConfig.UserPreferences.serialplugin_txd', index=36,
+      number=123, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serialplugin_timeout', full_name='RadioConfig.UserPreferences.serialplugin_timeout', index=37,
+      number=124, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serialplugin_mode', full_name='RadioConfig.UserPreferences.serialplugin_mode', index=38,
+      number=125, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ext_notification_plugin_enabled', full_name='RadioConfig.UserPreferences.ext_notification_plugin_enabled', index=39,
+      number=126, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ext_notification_plugin_output_ms', full_name='RadioConfig.UserPreferences.ext_notification_plugin_output_ms', index=40,
+      number=127, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ext_notification_plugin_output', full_name='RadioConfig.UserPreferences.ext_notification_plugin_output', index=41,
+      number=128, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ext_notification_plugin_active', full_name='RadioConfig.UserPreferences.ext_notification_plugin_active', index=42,
+      number=129, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ext_notification_plugin_alert_message', full_name='RadioConfig.UserPreferences.ext_notification_plugin_alert_message', index=43,
+      number=130, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ext_notification_plugin_alert_bell', full_name='RadioConfig.UserPreferences.ext_notification_plugin_alert_bell', index=44,
+      number=131, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='range_test_plugin_enabled', full_name='RadioConfig.UserPreferences.range_test_plugin_enabled', index=45,
+      number=132, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='range_test_plugin_sender', full_name='RadioConfig.UserPreferences.range_test_plugin_sender', index=46,
+      number=133, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='range_test_plugin_save', full_name='RadioConfig.UserPreferences.range_test_plugin_save', index=47,
+      number=134, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='store_forward_plugin_enabled', full_name='RadioConfig.UserPreferences.store_forward_plugin_enabled', index=48,
+      number=148, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='store_forward_plugin_heartbeat', full_name='RadioConfig.UserPreferences.store_forward_plugin_heartbeat', index=49,
+      number=149, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='store_forward_plugin_records', full_name='RadioConfig.UserPreferences.store_forward_plugin_records', index=50,
+      number=137, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='store_forward_plugin_history_return_max', full_name='RadioConfig.UserPreferences.store_forward_plugin_history_return_max', index=51,
+      number=138, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='store_forward_plugin_history_return_window', full_name='RadioConfig.UserPreferences.store_forward_plugin_history_return_window', index=52,
+      number=139, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_measurement_enabled', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_measurement_enabled', index=53,
+      number=140, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_screen_enabled', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_screen_enabled', index=54,
+      number=141, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_read_error_count_threshold', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_read_error_count_threshold', index=55,
+      number=142, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_update_interval', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_update_interval', index=56,
+      number=143, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_recovery_interval', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_recovery_interval', index=57,
+      number=144, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_display_farenheit', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_display_farenheit', index=58,
+      number=145, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_sensor_type', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_sensor_type', index=59,
+      number=146, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='environmental_measurement_plugin_sensor_pin', full_name='RadioConfig.UserPreferences.environmental_measurement_plugin_sensor_pin', index=60,
+      number=147, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='position_flags', full_name='RadioConfig.UserPreferences.position_flags', index=61,
+      number=150, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='is_always_powered', full_name='RadioConfig.UserPreferences.is_always_powered', index=62,
+      number=151, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='auto_screen_carousel_secs', full_name='RadioConfig.UserPreferences.auto_screen_carousel_secs', index=63,
+      number=152, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='on_battery_shutdown_after_secs', full_name='RadioConfig.UserPreferences.on_battery_shutdown_after_secs', index=64,
+      number=153, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='hop_limit', full_name='RadioConfig.UserPreferences.hop_limit', index=65,
+      number=154, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mqtt_username', full_name='RadioConfig.UserPreferences.mqtt_username', index=66,
+      number=155, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mqtt_password', full_name='RadioConfig.UserPreferences.mqtt_password', index=67,
+      number=156, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=89,
+  serialized_end=2557,
+)
+
+_RADIOCONFIG = _descriptor.Descriptor(
+  name='RadioConfig',
+  full_name='RadioConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='preferences', full_name='RadioConfig.preferences', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_RADIOCONFIG_USERPREFERENCES, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=22,
+  serialized_end=2557,
+)
+
+_RADIOCONFIG_USERPREFERENCES.fields_by_name['region'].enum_type = _REGIONCODE
+_RADIOCONFIG_USERPREFERENCES.fields_by_name['charge_current'].enum_type = _CHARGECURRENT
+_RADIOCONFIG_USERPREFERENCES.fields_by_name['location_share'].enum_type = _LOCATIONSHARING
+_RADIOCONFIG_USERPREFERENCES.fields_by_name['gps_operation'].enum_type = _GPSOPERATION
+_RADIOCONFIG_USERPREFERENCES.fields_by_name['gps_format'].enum_type = _GPSCOORDINATEFORMAT
+_RADIOCONFIG_USERPREFERENCES.fields_by_name['environmental_measurement_plugin_sensor_type'].enum_type = _RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE
+_RADIOCONFIG_USERPREFERENCES.containing_type = _RADIOCONFIG
+_RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE.containing_type = _RADIOCONFIG_USERPREFERENCES
+_RADIOCONFIG.fields_by_name['preferences'].message_type = _RADIOCONFIG_USERPREFERENCES
+DESCRIPTOR.message_types_by_name['RadioConfig'] = _RADIOCONFIG
+DESCRIPTOR.enum_types_by_name['RegionCode'] = _REGIONCODE
+DESCRIPTOR.enum_types_by_name['ChargeCurrent'] = _CHARGECURRENT
+DESCRIPTOR.enum_types_by_name['GpsOperation'] = _GPSOPERATION
+DESCRIPTOR.enum_types_by_name['GpsCoordinateFormat'] = _GPSCOORDINATEFORMAT
+DESCRIPTOR.enum_types_by_name['LocationSharing'] = _LOCATIONSHARING
+DESCRIPTOR.enum_types_by_name['PositionFlags'] = _POSITIONFLAGS
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+RadioConfig = _reflection.GeneratedProtocolMessageType('RadioConfig', (_message.Message,), {
+
+  'UserPreferences' : _reflection.GeneratedProtocolMessageType('UserPreferences', (_message.Message,), {
+    'DESCRIPTOR' : _RADIOCONFIG_USERPREFERENCES,
+    '__module__' : 'radioconfig_pb2'
+    # @@protoc_insertion_point(class_scope:RadioConfig.UserPreferences)
+    })
+  ,
+  'DESCRIPTOR' : _RADIOCONFIG,
+  '__module__' : 'radioconfig_pb2'
+  # @@protoc_insertion_point(class_scope:RadioConfig)
+  })
+_sym_db.RegisterMessage(RadioConfig)
+_sym_db.RegisterMessage(RadioConfig.UserPreferences)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class RadioConfig +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var PREFERENCES_FIELD_NUMBER
+
+
+
+
var UserPreferences
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var preferences
+
+

Getter for preferences.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/remote_hardware.html b/docs/meshtastic/remote_hardware.html new file mode 100644 index 0000000..145872c --- /dev/null +++ b/docs/meshtastic/remote_hardware.html @@ -0,0 +1,324 @@ + + + + + + +meshtastic.remote_hardware API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.remote_hardware

+
+
+

Remote hardware

+
+ +Expand source code + +
"""Remote hardware
+"""
+import logging
+from pubsub import pub
+from . import portnums_pb2, remote_hardware_pb2
+from .util import our_exit
+
+
+def onGPIOreceive(packet, interface):
+    """Callback for received GPIO responses
+    """
+    logging.debug(f"packet:{packet} interface:{interface}")
+    hw = packet["decoded"]["remotehw"]
+    gpioValue = hw["gpioValue"]
+    #print(f'mask:{interface.mask}')
+    value = int(gpioValue) & int(interface.mask)
+    print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}')
+    interface.gotResponse = True
+
+
+class RemoteHardwareClient:
+    """
+    This is the client code to control/monitor simple hardware built into the
+    meshtastic devices.  It is intended to be both a useful API/service and example
+    code for how you can connect to your own custom meshtastic services
+    """
+
+    def __init__(self, iface):
+        """
+        Constructor
+
+        iface is the already open MeshInterface instance
+        """
+        self.iface = iface
+        ch = iface.localNode.getChannelByName("gpio")
+        if not ch:
+            our_exit(
+                "Warning: No channel named 'gpio' was found.\n"\
+                "On the sending and receive nodes create a channel named 'gpio'.\n"\
+                "For example, run '--ch-add gpio' on one device, then '--seturl' on\n"\
+                "the other devices using the url from the device where the channel was added.")
+        self.channelIndex = ch.index
+
+        pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw")
+
+    def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
+        if not nodeid:
+            our_exit(r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)")
+        return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP,
+                                   wantAck=True, channelIndex=self.channelIndex,
+                                   wantResponse=wantResponse, onResponse=onResponse)
+
+    def writeGPIOs(self, nodeid, mask, vals):
+        """
+        Write the specified vals bits to the device GPIOs.  Only bits in mask that
+        are 1 will be changed
+        """
+        logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}')
+        r = remote_hardware_pb2.HardwareMessage()
+        r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
+        r.gpio_mask = mask
+        r.gpio_value = vals
+        return self._sendHardware(nodeid, r)
+
+    def readGPIOs(self, nodeid, mask, onResponse = None):
+        """Read the specified bits from GPIO inputs on the device"""
+        logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}')
+        r = remote_hardware_pb2.HardwareMessage()
+        r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
+        r.gpio_mask = mask
+        return self._sendHardware(nodeid, r, wantResponse=True, onResponse=onResponse)
+
+    def watchGPIOs(self, nodeid, mask):
+        """Watch the specified bits from GPIO inputs on the device for changes"""
+        logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}')
+        r = remote_hardware_pb2.HardwareMessage()
+        r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
+        r.gpio_mask = mask
+        self.iface.mask = mask
+        return self._sendHardware(nodeid, r)
+
+
+
+
+
+
+
+

Functions

+
+
+def onGPIOreceive(packet, interface) +
+
+

Callback for received GPIO responses

+
+ +Expand source code + +
def onGPIOreceive(packet, interface):
+    """Callback for received GPIO responses
+    """
+    logging.debug(f"packet:{packet} interface:{interface}")
+    hw = packet["decoded"]["remotehw"]
+    gpioValue = hw["gpioValue"]
+    #print(f'mask:{interface.mask}')
+    value = int(gpioValue) & int(interface.mask)
+    print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}')
+    interface.gotResponse = True
+
+
+
+
+
+

Classes

+
+
+class RemoteHardwareClient +(iface) +
+
+

This is the client code to control/monitor simple hardware built into the +meshtastic devices. +It is intended to be both a useful API/service and example +code for how you can connect to your own custom meshtastic services

+

Constructor

+

iface is the already open MeshInterface instance

+
+ +Expand source code + +
class RemoteHardwareClient:
+    """
+    This is the client code to control/monitor simple hardware built into the
+    meshtastic devices.  It is intended to be both a useful API/service and example
+    code for how you can connect to your own custom meshtastic services
+    """
+
+    def __init__(self, iface):
+        """
+        Constructor
+
+        iface is the already open MeshInterface instance
+        """
+        self.iface = iface
+        ch = iface.localNode.getChannelByName("gpio")
+        if not ch:
+            our_exit(
+                "Warning: No channel named 'gpio' was found.\n"\
+                "On the sending and receive nodes create a channel named 'gpio'.\n"\
+                "For example, run '--ch-add gpio' on one device, then '--seturl' on\n"\
+                "the other devices using the url from the device where the channel was added.")
+        self.channelIndex = ch.index
+
+        pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw")
+
+    def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
+        if not nodeid:
+            our_exit(r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)")
+        return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP,
+                                   wantAck=True, channelIndex=self.channelIndex,
+                                   wantResponse=wantResponse, onResponse=onResponse)
+
+    def writeGPIOs(self, nodeid, mask, vals):
+        """
+        Write the specified vals bits to the device GPIOs.  Only bits in mask that
+        are 1 will be changed
+        """
+        logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}')
+        r = remote_hardware_pb2.HardwareMessage()
+        r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
+        r.gpio_mask = mask
+        r.gpio_value = vals
+        return self._sendHardware(nodeid, r)
+
+    def readGPIOs(self, nodeid, mask, onResponse = None):
+        """Read the specified bits from GPIO inputs on the device"""
+        logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}')
+        r = remote_hardware_pb2.HardwareMessage()
+        r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
+        r.gpio_mask = mask
+        return self._sendHardware(nodeid, r, wantResponse=True, onResponse=onResponse)
+
+    def watchGPIOs(self, nodeid, mask):
+        """Watch the specified bits from GPIO inputs on the device for changes"""
+        logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}')
+        r = remote_hardware_pb2.HardwareMessage()
+        r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
+        r.gpio_mask = mask
+        self.iface.mask = mask
+        return self._sendHardware(nodeid, r)
+
+

Methods

+
+
+def readGPIOs(self, nodeid, mask, onResponse=None) +
+
+

Read the specified bits from GPIO inputs on the device

+
+ +Expand source code + +
def readGPIOs(self, nodeid, mask, onResponse = None):
+    """Read the specified bits from GPIO inputs on the device"""
+    logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}')
+    r = remote_hardware_pb2.HardwareMessage()
+    r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
+    r.gpio_mask = mask
+    return self._sendHardware(nodeid, r, wantResponse=True, onResponse=onResponse)
+
+
+
+def watchGPIOs(self, nodeid, mask) +
+
+

Watch the specified bits from GPIO inputs on the device for changes

+
+ +Expand source code + +
def watchGPIOs(self, nodeid, mask):
+    """Watch the specified bits from GPIO inputs on the device for changes"""
+    logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}')
+    r = remote_hardware_pb2.HardwareMessage()
+    r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
+    r.gpio_mask = mask
+    self.iface.mask = mask
+    return self._sendHardware(nodeid, r)
+
+
+
+def writeGPIOs(self, nodeid, mask, vals) +
+
+

Write the specified vals bits to the device GPIOs. +Only bits in mask that +are 1 will be changed

+
+ +Expand source code + +
def writeGPIOs(self, nodeid, mask, vals):
+    """
+    Write the specified vals bits to the device GPIOs.  Only bits in mask that
+    are 1 will be changed
+    """
+    logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}')
+    r = remote_hardware_pb2.HardwareMessage()
+    r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
+    r.gpio_mask = mask
+    r.gpio_value = vals
+    return self._sendHardware(nodeid, r)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/remote_hardware_pb2.html b/docs/meshtastic/remote_hardware_pb2.html new file mode 100644 index 0000000..ff58876 --- /dev/null +++ b/docs/meshtastic/remote_hardware_pb2.html @@ -0,0 +1,822 @@ + + + + + + +meshtastic.remote_hardware_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.remote_hardware_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: remote_hardware.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='remote_hardware.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x15remote_hardware.proto\"\xca\x01\n\x0fHardwareMessage\x12\"\n\x03typ\x18\x01 \x01(\x0e\x32\x15.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42J\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+)
+
+
+
+_HARDWAREMESSAGE_TYPE = _descriptor.EnumDescriptor(
+  name='Type',
+  full_name='HardwareMessage.Type',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNSET', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='WRITE_GPIOS', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='WATCH_GPIOS', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='GPIOS_CHANGED', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='READ_GPIOS', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='READ_GPIOS_REPLY', index=5, number=5,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=120,
+  serialized_end=228,
+)
+_sym_db.RegisterEnumDescriptor(_HARDWAREMESSAGE_TYPE)
+
+
+_HARDWAREMESSAGE = _descriptor.Descriptor(
+  name='HardwareMessage',
+  full_name='HardwareMessage',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='typ', full_name='HardwareMessage.typ', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gpio_mask', full_name='HardwareMessage.gpio_mask', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='gpio_value', full_name='HardwareMessage.gpio_value', index=2,
+      number=3, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _HARDWAREMESSAGE_TYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=26,
+  serialized_end=228,
+)
+
+_HARDWAREMESSAGE.fields_by_name['typ'].enum_type = _HARDWAREMESSAGE_TYPE
+_HARDWAREMESSAGE_TYPE.containing_type = _HARDWAREMESSAGE
+DESCRIPTOR.message_types_by_name['HardwareMessage'] = _HARDWAREMESSAGE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_message.Message,), {
+  'DESCRIPTOR' : _HARDWAREMESSAGE,
+  '__module__' : 'remote_hardware_pb2'
+  # @@protoc_insertion_point(class_scope:HardwareMessage)
+  })
+_sym_db.RegisterMessage(HardwareMessage)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class HardwareMessage +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var GPIOS_CHANGED
+
+
+
+
var GPIO_MASK_FIELD_NUMBER
+
+
+
+
var GPIO_VALUE_FIELD_NUMBER
+
+
+
+
var READ_GPIOS
+
+
+
+
var READ_GPIOS_REPLY
+
+
+
+
var TYP_FIELD_NUMBER
+
+
+
+
var Type
+
+
+
+
var UNSET
+
+
+
+
var WATCH_GPIOS
+
+
+
+
var WRITE_GPIOS
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var gpio_mask
+
+

Getter for gpio_mask.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var gpio_value
+
+

Getter for gpio_value.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var typ
+
+

Getter for typ.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/serial_interface.html b/docs/meshtastic/serial_interface.html new file mode 100644 index 0000000..8f7ef3e --- /dev/null +++ b/docs/meshtastic/serial_interface.html @@ -0,0 +1,254 @@ + + + + + + +meshtastic.serial_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.serial_interface

+
+
+

Serial interface class

+
+ +Expand source code + +
""" Serial interface class
+"""
+import logging
+import platform
+import os
+import stat
+import serial
+
+import meshtastic.util
+from .stream_interface import StreamInterface
+
+class SerialInterface(StreamInterface):
+    """Interface class for meshtastic devices over a serial link"""
+
+    def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
+        """Constructor, opens a connection to a specified serial port, or if unspecified try to
+        find one Meshtastic device by probing
+
+        Keyword Arguments:
+            devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
+            debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
+        """
+
+        if devPath is None:
+            ports = meshtastic.util.findPorts()
+            logging.debug(f"ports:{ports}")
+            if len(ports) == 0:
+                meshtastic.util.our_exit("Warning: No Meshtastic devices detected.")
+            elif len(ports) > 1:
+                message = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
+                message += f"  Ports detected:{ports}"
+                meshtastic.util.our_exit(message)
+            else:
+                devPath = ports[0]
+
+        logging.debug(f"Connecting to {devPath}")
+
+        # Note: we provide None for port here, because we will be opening it later
+        self.stream = serial.Serial(
+            None, 921600, exclusive=True, timeout=0.5, write_timeout=0)
+
+        # rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
+        self.stream.port = devPath
+
+        # HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
+        # mode, set RTS to false so that the device platform won't be reset spuriously.
+        # Linux does this properly, so don't apply this hack on Linux (because it makes the reset button not work).
+        if self._hostPlatformAlwaysDrivesUartRts():
+            self.stream.rts = False
+        self.stream.open()
+
+        StreamInterface.__init__(
+            self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
+
+    """true if platform driving the serial port is Windows Subsystem for Linux 1."""
+    def _isWsl1(self):
+        # WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control,
+        # e.g. /init.  We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom
+        # serial driver for the CP21xx still exhibits the buggy behavior.
+        # WSL2 is not covered here, as it does not (as of 2021-May-25) support the appropriate functionality to
+        # share or pass-through serial ports.
+        try:
+            # Claims to be Linux, but has /dev/lxss; must be WSL 1
+            return platform.system() == 'Linux' and stat.S_ISCHR(os.stat('/dev/lxss').st_mode)
+        except:
+            # Couldn't stat /dev/lxss special device; not WSL1
+            return False
+
+    def _hostPlatformAlwaysDrivesUartRts(self):
+        # OS-X/Windows seems to have a bug in its CP21xx serial drivers.  It ignores that we asked for no RTSCTS
+        # control and will always drive RTS either high or low (rather than letting the CP102 leave
+        # it as an open-collector floating pin).
+        # TODO: When WSL2 supports USB passthrough, this will get messier.  If/when WSL2 gets virtual serial
+        # ports that "share" the Windows serial port (and thus the Windows drivers), this code will need to be
+        # updated to reflect that as well -- or if T-Beams get made with an alternate USB to UART bridge that has
+        # a less buggy driver.
+        return platform.system() != 'Linux' or self._isWsl1()
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class SerialInterface +(devPath=None, debugOut=None, noProto=False, connectNow=True) +
+
+

Interface class for meshtastic devices over a serial link

+

Constructor, opens a connection to a specified serial port, or if unspecified try to +find one Meshtastic device by probing

+

Keyword Arguments: +devPath {string} – A filepath to a device, i.e. /dev/ttyUSB0 (default: {None}) +debugOut {stream} – If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})

+
+ +Expand source code + +
class SerialInterface(StreamInterface):
+    """Interface class for meshtastic devices over a serial link"""
+
+    def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
+        """Constructor, opens a connection to a specified serial port, or if unspecified try to
+        find one Meshtastic device by probing
+
+        Keyword Arguments:
+            devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None})
+            debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None})
+        """
+
+        if devPath is None:
+            ports = meshtastic.util.findPorts()
+            logging.debug(f"ports:{ports}")
+            if len(ports) == 0:
+                meshtastic.util.our_exit("Warning: No Meshtastic devices detected.")
+            elif len(ports) > 1:
+                message = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
+                message += f"  Ports detected:{ports}"
+                meshtastic.util.our_exit(message)
+            else:
+                devPath = ports[0]
+
+        logging.debug(f"Connecting to {devPath}")
+
+        # Note: we provide None for port here, because we will be opening it later
+        self.stream = serial.Serial(
+            None, 921600, exclusive=True, timeout=0.5, write_timeout=0)
+
+        # rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset
+        self.stream.port = devPath
+
+        # HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance
+        # mode, set RTS to false so that the device platform won't be reset spuriously.
+        # Linux does this properly, so don't apply this hack on Linux (because it makes the reset button not work).
+        if self._hostPlatformAlwaysDrivesUartRts():
+            self.stream.rts = False
+        self.stream.open()
+
+        StreamInterface.__init__(
+            self, debugOut=debugOut, noProto=noProto, connectNow=connectNow)
+
+    """true if platform driving the serial port is Windows Subsystem for Linux 1."""
+    def _isWsl1(self):
+        # WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control,
+        # e.g. /init.  We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom
+        # serial driver for the CP21xx still exhibits the buggy behavior.
+        # WSL2 is not covered here, as it does not (as of 2021-May-25) support the appropriate functionality to
+        # share or pass-through serial ports.
+        try:
+            # Claims to be Linux, but has /dev/lxss; must be WSL 1
+            return platform.system() == 'Linux' and stat.S_ISCHR(os.stat('/dev/lxss').st_mode)
+        except:
+            # Couldn't stat /dev/lxss special device; not WSL1
+            return False
+
+    def _hostPlatformAlwaysDrivesUartRts(self):
+        # OS-X/Windows seems to have a bug in its CP21xx serial drivers.  It ignores that we asked for no RTSCTS
+        # control and will always drive RTS either high or low (rather than letting the CP102 leave
+        # it as an open-collector floating pin).
+        # TODO: When WSL2 supports USB passthrough, this will get messier.  If/when WSL2 gets virtual serial
+        # ports that "share" the Windows serial port (and thus the Windows drivers), this code will need to be
+        # updated to reflect that as well -- or if T-Beams get made with an alternate USB to UART bridge that has
+        # a less buggy driver.
+        return platform.system() != 'Linux' or self._isWsl1()
+
+

Ancestors

+ +

Inherited members

+ +
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/storeforward_pb2.html b/docs/meshtastic/storeforward_pb2.html new file mode 100644 index 0000000..c053c6d --- /dev/null +++ b/docs/meshtastic/storeforward_pb2.html @@ -0,0 +1,1165 @@ + + + + + + +meshtastic.storeforward_pb2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.storeforward_pb2

+
+
+
+ +Expand source code + +
# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: storeforward.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='storeforward.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto',
+  serialized_pb=b'\n\x12storeforward.proto\"\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xf7\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3'
+)
+
+
+
+_STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor(
+  name='RequestResponse',
+  full_name='StoreAndForward.RequestResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNSET', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTER_ERROR', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTER_HEARTBEAT', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTER_PING', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTER_PONG', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTER_BUSY', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ROUTER_HISTORY', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CLIENT_ERROR', index=7, number=101,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CLIENT_HISTORY', index=8, number=102,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CLIENT_STATS', index=9, number=103,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CLIENT_PING', index=10, number=104,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CLIENT_PONG', index=11, number=105,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CLIENT_ABORT', index=12, number=106,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=554,
+  serialized_end=801,
+)
+_sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE)
+
+
+_STOREANDFORWARD_STATISTICS = _descriptor.Descriptor(
+  name='Statistics',
+  full_name='StoreAndForward.Statistics',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='messages_total', full_name='StoreAndForward.Statistics.messages_total', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='messages_saved', full_name='StoreAndForward.Statistics.messages_saved', index=1,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='messages_max', full_name='StoreAndForward.Statistics.messages_max', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='up_time', full_name='StoreAndForward.Statistics.up_time', index=3,
+      number=4, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='requests', full_name='StoreAndForward.Statistics.requests', index=4,
+      number=5, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='requests_history', full_name='StoreAndForward.Statistics.requests_history', index=5,
+      number=6, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='heartbeat', full_name='StoreAndForward.Statistics.heartbeat', index=6,
+      number=7, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='return_max', full_name='StoreAndForward.Statistics.return_max', index=7,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='return_window', full_name='StoreAndForward.Statistics.return_window', index=8,
+      number=9, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=223,
+  serialized_end=428,
+)
+
+_STOREANDFORWARD_HISTORY = _descriptor.Descriptor(
+  name='History',
+  full_name='StoreAndForward.History',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='history_messages', full_name='StoreAndForward.History.history_messages', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='window', full_name='StoreAndForward.History.window', index=1,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='last_request', full_name='StoreAndForward.History.last_request', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=430,
+  serialized_end=503,
+)
+
+_STOREANDFORWARD_HEARTBEAT = _descriptor.Descriptor(
+  name='Heartbeat',
+  full_name='StoreAndForward.Heartbeat',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='period', full_name='StoreAndForward.Heartbeat.period', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='secondary', full_name='StoreAndForward.Heartbeat.secondary', index=1,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=505,
+  serialized_end=551,
+)
+
+_STOREANDFORWARD = _descriptor.Descriptor(
+  name='StoreAndForward',
+  full_name='StoreAndForward',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='rr', full_name='StoreAndForward.rr', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='stats', full_name='StoreAndForward.stats', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='history', full_name='StoreAndForward.history', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='heartbeat', full_name='StoreAndForward.heartbeat', index=3,
+      number=4, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, _STOREANDFORWARD_HEARTBEAT, ],
+  enum_types=[
+    _STOREANDFORWARD_REQUESTRESPONSE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=23,
+  serialized_end=801,
+)
+
+_STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD
+_STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD
+_STOREANDFORWARD_HEARTBEAT.containing_type = _STOREANDFORWARD
+_STOREANDFORWARD.fields_by_name['rr'].enum_type = _STOREANDFORWARD_REQUESTRESPONSE
+_STOREANDFORWARD.fields_by_name['stats'].message_type = _STOREANDFORWARD_STATISTICS
+_STOREANDFORWARD.fields_by_name['history'].message_type = _STOREANDFORWARD_HISTORY
+_STOREANDFORWARD.fields_by_name['heartbeat'].message_type = _STOREANDFORWARD_HEARTBEAT
+_STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD
+DESCRIPTOR.message_types_by_name['StoreAndForward'] = _STOREANDFORWARD
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_message.Message,), {
+
+  'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), {
+    'DESCRIPTOR' : _STOREANDFORWARD_STATISTICS,
+    '__module__' : 'storeforward_pb2'
+    # @@protoc_insertion_point(class_scope:StoreAndForward.Statistics)
+    })
+  ,
+
+  'History' : _reflection.GeneratedProtocolMessageType('History', (_message.Message,), {
+    'DESCRIPTOR' : _STOREANDFORWARD_HISTORY,
+    '__module__' : 'storeforward_pb2'
+    # @@protoc_insertion_point(class_scope:StoreAndForward.History)
+    })
+  ,
+
+  'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
+    'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT,
+    '__module__' : 'storeforward_pb2'
+    # @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
+    })
+  ,
+  'DESCRIPTOR' : _STOREANDFORWARD,
+  '__module__' : 'storeforward_pb2'
+  # @@protoc_insertion_point(class_scope:StoreAndForward)
+  })
+_sym_db.RegisterMessage(StoreAndForward)
+_sym_db.RegisterMessage(StoreAndForward.Statistics)
+_sym_db.RegisterMessage(StoreAndForward.History)
+_sym_db.RegisterMessage(StoreAndForward.Heartbeat)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class StoreAndForward +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CLIENT_ABORT
+
+
+
+
var CLIENT_ERROR
+
+
+
+
var CLIENT_HISTORY
+
+
+
+
var CLIENT_PING
+
+
+
+
var CLIENT_PONG
+
+
+
+
var CLIENT_STATS
+
+
+
+
var DESCRIPTOR
+
+
+
+
var HEARTBEAT_FIELD_NUMBER
+
+
+
+
var HISTORY_FIELD_NUMBER
+
+
+
+
var Heartbeat
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
var History
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
var ROUTER_BUSY
+
+
+
+
var ROUTER_ERROR
+
+
+
+
var ROUTER_HEARTBEAT
+
+
+
+
var ROUTER_HISTORY
+
+
+
+
var ROUTER_PING
+
+
+
+
var ROUTER_PONG
+
+
+
+
var RR_FIELD_NUMBER
+
+
+
+
var RequestResponse
+
+
+
+
var STATS_FIELD_NUMBER
+
+
+
+
var Statistics
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
var UNSET
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var heartbeat
+
+

Getter for heartbeat.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var history
+
+

Getter for history.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var rr
+
+

Getter for rr.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var stats
+
+

Getter for stats.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/stream_interface.html b/docs/meshtastic/stream_interface.html new file mode 100644 index 0000000..bdec6d7 --- /dev/null +++ b/docs/meshtastic/stream_interface.html @@ -0,0 +1,519 @@ + + + + + + +meshtastic.stream_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.stream_interface

+
+
+

Stream Interface base class

+
+ +Expand source code + +
"""Stream Interface base class
+"""
+import logging
+import threading
+import time
+import traceback
+import serial
+
+
+from .mesh_interface import MeshInterface
+from .util import stripnl
+
+
+START1 = 0x94
+START2 = 0xc3
+HEADER_LEN = 4
+MAX_TO_FROM_RADIO_SIZE = 512
+
+
+class StreamInterface(MeshInterface):
+    """Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
+
+    def __init__(self, debugOut=None, noProto=False, connectNow=True):
+        """Constructor, opens a connection to self.stream
+
+        Keyword Arguments:
+            debugOut {stream} -- If a stream is provided, any debug serial output from the
+                                 device will be emitted to that stream. (default: {None})
+
+        Raises:
+            Exception: [description]
+            Exception: [description]
+        """
+
+        if not hasattr(self, 'stream') and not noProto:
+            raise Exception(
+                "StreamInterface is now abstract (to update existing code create SerialInterface instead)")
+        self._rxBuf = bytes()  # empty
+        self._wantExit = False
+
+        # FIXME, figure out why daemon=True causes reader thread to exit too early
+        self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
+
+        MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
+
+        # Start the reader thread after superclass constructor completes init
+        if connectNow:
+            self.connect()
+            if not noProto:
+                self.waitForConfig()
+
+    def connect(self):
+        """Connect to our radio
+
+        Normally this is called automatically by the constructor, but if you
+        passed in connectNow=False you can manually start the reading thread later.
+        """
+
+        # Send some bogus UART characters to force a sleeping device to wake, and
+        # if the reading statemachine was parsing a bad packet make sure
+        # we write enought start bytes to force it to resync (we don't use START1
+        # because we want to ensure it is looking for START1)
+        p = bytearray([START2] * 32)
+        self._writeBytes(p)
+        time.sleep(0.1)  # wait 100ms to give device time to start running
+
+        self._rxThread.start()
+
+        self._startConfig()
+
+        if not self.noProto:  # Wait for the db download if using the protocol
+            self._waitConnected()
+
+    def _disconnected(self):
+        """We override the superclass implementation to close our port"""
+        MeshInterface._disconnected(self)
+
+        logging.debug("Closing our port")
+        # pylint: disable=E0203
+        if not self.stream is None:
+            # pylint: disable=E0203
+            self.stream.close()
+            # pylint: disable=W0201
+            self.stream = None
+
+    def _writeBytes(self, b):
+        """Write an array of bytes to our stream and flush"""
+        if self.stream:  # ignore writes when stream is closed
+            self.stream.write(b)
+            self.stream.flush()
+
+    def _readBytes(self, length):
+        """Read an array of bytes from our stream"""
+        if self.stream:
+            return self.stream.read(length)
+        else:
+            return None
+
+    def _sendToRadioImpl(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        logging.debug(f"Sending: {stripnl(toRadio)}")
+        b = toRadio.SerializeToString()
+        bufLen = len(b)
+        # We convert into a string, because the TCP code doesn't work with byte arrays
+        header = bytes([START1, START2, (bufLen >> 8) & 0xff,  bufLen & 0xff])
+        logging.debug(f'sending header:{header} b:{b}')
+        self._writeBytes(header + b)
+
+    def close(self):
+        """Close a connection to the device"""
+        logging.debug("Closing stream")
+        MeshInterface.close(self)
+        # pyserial cancel_read doesn't seem to work, therefore we ask the
+        # reader thread to close things for us
+        self._wantExit = True
+        if self._rxThread != threading.current_thread():
+            self._rxThread.join()  # wait for it to exit
+
+    def __reader(self):
+        """The reader thread that reads bytes from our stream"""
+        logging.debug('in __reader()')
+        empty = bytes()
+
+        try:
+            while not self._wantExit:
+                logging.debug("reading character")
+                b = self._readBytes(1)
+                logging.debug("In reader loop")
+                #logging.debug(f"read returned {b}")
+                if len(b) > 0:
+                    c = b[0]
+                    #logging.debug(f'c:{c}')
+                    ptr = len(self._rxBuf)
+
+                    # Assume we want to append this byte, fixme use bytearray instead
+                    self._rxBuf = self._rxBuf + b
+
+                    if ptr == 0:  # looking for START1
+                        if c != START1:
+                            self._rxBuf = empty  # failed to find start
+                            if self.debugOut is not None:
+                                try:
+                                    self.debugOut.write(b.decode("utf-8"))
+                                except:
+                                    self.debugOut.write('?')
+
+                    elif ptr == 1:  # looking for START2
+                        if c != START2:
+                            self._rxBuf = empty  # failed to find start2
+                    elif ptr >= HEADER_LEN - 1:  # we've at least got a header
+                        #logging.debug('at least we received a header')
+                        # big endian length follows header
+                        packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
+
+                        if ptr == HEADER_LEN - 1:  # we _just_ finished reading the header, validate length
+                            if packetlen > MAX_TO_FROM_RADIO_SIZE:
+                                self._rxBuf = empty  # length was out out bounds, restart
+
+                        if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN:
+                            try:
+                                self._handleFromRadio(self._rxBuf[HEADER_LEN:])
+                            except Exception as ex:
+                                logging.error(f"Error while handling message from radio {ex}")
+                                traceback.print_exc()
+                            self._rxBuf = empty
+                else:
+                    # logging.debug(f"timeout")
+                    pass
+        except serial.SerialException as ex:
+            if not self._wantExit:  # We might intentionally get an exception during shutdown
+                logging.warning(f"Meshtastic serial port disconnected, disconnecting... {ex}")
+        except OSError as ex:
+            if not self._wantExit:  # We might intentionally get an exception during shutdown
+                logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}")
+        except Exception as ex:
+            logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}")
+        finally:
+            logging.debug("reader is exiting")
+            self._disconnected()
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class StreamInterface +(debugOut=None, noProto=False, connectNow=True) +
+
+

Interface class for meshtastic devices over a stream link (serial, TCP, etc)

+

Constructor, opens a connection to self.stream

+

Keyword Arguments: +debugOut {stream} – If a stream is provided, any debug serial output from the +device will be emitted to that stream. (default: {None})

+

Raises

+
+
Exception
+
[description]
+
Exception
+
[description]
+
+
+ +Expand source code + +
class StreamInterface(MeshInterface):
+    """Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
+
+    def __init__(self, debugOut=None, noProto=False, connectNow=True):
+        """Constructor, opens a connection to self.stream
+
+        Keyword Arguments:
+            debugOut {stream} -- If a stream is provided, any debug serial output from the
+                                 device will be emitted to that stream. (default: {None})
+
+        Raises:
+            Exception: [description]
+            Exception: [description]
+        """
+
+        if not hasattr(self, 'stream') and not noProto:
+            raise Exception(
+                "StreamInterface is now abstract (to update existing code create SerialInterface instead)")
+        self._rxBuf = bytes()  # empty
+        self._wantExit = False
+
+        # FIXME, figure out why daemon=True causes reader thread to exit too early
+        self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
+
+        MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
+
+        # Start the reader thread after superclass constructor completes init
+        if connectNow:
+            self.connect()
+            if not noProto:
+                self.waitForConfig()
+
+    def connect(self):
+        """Connect to our radio
+
+        Normally this is called automatically by the constructor, but if you
+        passed in connectNow=False you can manually start the reading thread later.
+        """
+
+        # Send some bogus UART characters to force a sleeping device to wake, and
+        # if the reading statemachine was parsing a bad packet make sure
+        # we write enought start bytes to force it to resync (we don't use START1
+        # because we want to ensure it is looking for START1)
+        p = bytearray([START2] * 32)
+        self._writeBytes(p)
+        time.sleep(0.1)  # wait 100ms to give device time to start running
+
+        self._rxThread.start()
+
+        self._startConfig()
+
+        if not self.noProto:  # Wait for the db download if using the protocol
+            self._waitConnected()
+
+    def _disconnected(self):
+        """We override the superclass implementation to close our port"""
+        MeshInterface._disconnected(self)
+
+        logging.debug("Closing our port")
+        # pylint: disable=E0203
+        if not self.stream is None:
+            # pylint: disable=E0203
+            self.stream.close()
+            # pylint: disable=W0201
+            self.stream = None
+
+    def _writeBytes(self, b):
+        """Write an array of bytes to our stream and flush"""
+        if self.stream:  # ignore writes when stream is closed
+            self.stream.write(b)
+            self.stream.flush()
+
+    def _readBytes(self, length):
+        """Read an array of bytes from our stream"""
+        if self.stream:
+            return self.stream.read(length)
+        else:
+            return None
+
+    def _sendToRadioImpl(self, toRadio):
+        """Send a ToRadio protobuf to the device"""
+        logging.debug(f"Sending: {stripnl(toRadio)}")
+        b = toRadio.SerializeToString()
+        bufLen = len(b)
+        # We convert into a string, because the TCP code doesn't work with byte arrays
+        header = bytes([START1, START2, (bufLen >> 8) & 0xff,  bufLen & 0xff])
+        logging.debug(f'sending header:{header} b:{b}')
+        self._writeBytes(header + b)
+
+    def close(self):
+        """Close a connection to the device"""
+        logging.debug("Closing stream")
+        MeshInterface.close(self)
+        # pyserial cancel_read doesn't seem to work, therefore we ask the
+        # reader thread to close things for us
+        self._wantExit = True
+        if self._rxThread != threading.current_thread():
+            self._rxThread.join()  # wait for it to exit
+
+    def __reader(self):
+        """The reader thread that reads bytes from our stream"""
+        logging.debug('in __reader()')
+        empty = bytes()
+
+        try:
+            while not self._wantExit:
+                logging.debug("reading character")
+                b = self._readBytes(1)
+                logging.debug("In reader loop")
+                #logging.debug(f"read returned {b}")
+                if len(b) > 0:
+                    c = b[0]
+                    #logging.debug(f'c:{c}')
+                    ptr = len(self._rxBuf)
+
+                    # Assume we want to append this byte, fixme use bytearray instead
+                    self._rxBuf = self._rxBuf + b
+
+                    if ptr == 0:  # looking for START1
+                        if c != START1:
+                            self._rxBuf = empty  # failed to find start
+                            if self.debugOut is not None:
+                                try:
+                                    self.debugOut.write(b.decode("utf-8"))
+                                except:
+                                    self.debugOut.write('?')
+
+                    elif ptr == 1:  # looking for START2
+                        if c != START2:
+                            self._rxBuf = empty  # failed to find start2
+                    elif ptr >= HEADER_LEN - 1:  # we've at least got a header
+                        #logging.debug('at least we received a header')
+                        # big endian length follows header
+                        packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
+
+                        if ptr == HEADER_LEN - 1:  # we _just_ finished reading the header, validate length
+                            if packetlen > MAX_TO_FROM_RADIO_SIZE:
+                                self._rxBuf = empty  # length was out out bounds, restart
+
+                        if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN:
+                            try:
+                                self._handleFromRadio(self._rxBuf[HEADER_LEN:])
+                            except Exception as ex:
+                                logging.error(f"Error while handling message from radio {ex}")
+                                traceback.print_exc()
+                            self._rxBuf = empty
+                else:
+                    # logging.debug(f"timeout")
+                    pass
+        except serial.SerialException as ex:
+            if not self._wantExit:  # We might intentionally get an exception during shutdown
+                logging.warning(f"Meshtastic serial port disconnected, disconnecting... {ex}")
+        except OSError as ex:
+            if not self._wantExit:  # We might intentionally get an exception during shutdown
+                logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}")
+        except Exception as ex:
+            logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}")
+        finally:
+            logging.debug("reader is exiting")
+            self._disconnected()
+
+

Ancestors

+ +

Subclasses

+ +

Methods

+
+
+def close(self) +
+
+

Close a connection to the device

+
+ +Expand source code + +
def close(self):
+    """Close a connection to the device"""
+    logging.debug("Closing stream")
+    MeshInterface.close(self)
+    # pyserial cancel_read doesn't seem to work, therefore we ask the
+    # reader thread to close things for us
+    self._wantExit = True
+    if self._rxThread != threading.current_thread():
+        self._rxThread.join()  # wait for it to exit
+
+
+
+def connect(self) +
+
+

Connect to our radio

+

Normally this is called automatically by the constructor, but if you +passed in connectNow=False you can manually start the reading thread later.

+
+ +Expand source code + +
def connect(self):
+    """Connect to our radio
+
+    Normally this is called automatically by the constructor, but if you
+    passed in connectNow=False you can manually start the reading thread later.
+    """
+
+    # Send some bogus UART characters to force a sleeping device to wake, and
+    # if the reading statemachine was parsing a bad packet make sure
+    # we write enought start bytes to force it to resync (we don't use START1
+    # because we want to ensure it is looking for START1)
+    p = bytearray([START2] * 32)
+    self._writeBytes(p)
+    time.sleep(0.1)  # wait 100ms to give device time to start running
+
+    self._rxThread.start()
+
+    self._startConfig()
+
+    if not self.noProto:  # Wait for the db download if using the protocol
+        self._waitConnected()
+
+
+
+

Inherited members

+ +
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tcp_interface.html b/docs/meshtastic/tcp_interface.html new file mode 100644 index 0000000..1ea7d18 --- /dev/null +++ b/docs/meshtastic/tcp_interface.html @@ -0,0 +1,251 @@ + + + + + + +meshtastic.tcp_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tcp_interface

+
+
+

TCPInterface class for interfacing with http endpoint

+
+ +Expand source code + +
"""TCPInterface class for interfacing with http endpoint
+"""
+import logging
+import socket
+from typing import AnyStr
+
+from .stream_interface import StreamInterface
+
+class TCPInterface(StreamInterface):
+    """Interface class for meshtastic devices over a TCP link"""
+
+    def __init__(self, hostname: AnyStr, debugOut=None, noProto=False,
+                 connectNow=True, portNumber=4403):
+        """Constructor, opens a connection to a specified IP address/hostname
+
+        Keyword Arguments:
+            hostname {string} -- Hostname/IP address of the device to connect to
+        """
+
+        # Instead of wrapping as a stream, we use the native socket API
+        # self.stream = sock.makefile('rw')
+        self.stream = None
+
+        self.hostname = hostname
+        self.portNumber = portNumber
+
+        if connectNow:
+            logging.debug(f"Connecting to {hostname}")
+            server_address = (hostname, portNumber)
+            sock = socket.create_connection(server_address)
+            self.socket = sock
+        else:
+            self.socket = None
+
+        StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
+                                 connectNow=connectNow)
+
+    def myConnect(self):
+        """Connect to socket"""
+        server_address = (self.hostname, self.portNumber)
+        sock = socket.create_connection(server_address)
+        self.socket = sock
+
+    def close(self):
+        """Close a connection to the device"""
+        logging.debug("Closing TCP stream")
+        StreamInterface.close(self)
+        # Sometimes the socket read might be blocked in the reader thread.
+        # Therefore we force the shutdown by closing the socket here
+        self._wantExit = True
+        if not self.socket is None:
+            try:
+                self.socket.shutdown(socket.SHUT_RDWR)
+            except:
+                pass  # Ignore errors in shutdown, because we might have a race with the server
+            self.socket.close()
+
+    def _writeBytes(self, b):
+        """Write an array of bytes to our stream and flush"""
+        self.socket.send(b)
+
+    def _readBytes(self, length):
+        """Read an array of bytes from our stream"""
+        return self.socket.recv(length)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class TCPInterface +(hostname: ~AnyStr, debugOut=None, noProto=False, connectNow=True, portNumber=4403) +
+
+

Interface class for meshtastic devices over a TCP link

+

Constructor, opens a connection to a specified IP address/hostname

+

Keyword Arguments: +hostname {string} – Hostname/IP address of the device to connect to

+
+ +Expand source code + +
class TCPInterface(StreamInterface):
+    """Interface class for meshtastic devices over a TCP link"""
+
+    def __init__(self, hostname: AnyStr, debugOut=None, noProto=False,
+                 connectNow=True, portNumber=4403):
+        """Constructor, opens a connection to a specified IP address/hostname
+
+        Keyword Arguments:
+            hostname {string} -- Hostname/IP address of the device to connect to
+        """
+
+        # Instead of wrapping as a stream, we use the native socket API
+        # self.stream = sock.makefile('rw')
+        self.stream = None
+
+        self.hostname = hostname
+        self.portNumber = portNumber
+
+        if connectNow:
+            logging.debug(f"Connecting to {hostname}")
+            server_address = (hostname, portNumber)
+            sock = socket.create_connection(server_address)
+            self.socket = sock
+        else:
+            self.socket = None
+
+        StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto,
+                                 connectNow=connectNow)
+
+    def myConnect(self):
+        """Connect to socket"""
+        server_address = (self.hostname, self.portNumber)
+        sock = socket.create_connection(server_address)
+        self.socket = sock
+
+    def close(self):
+        """Close a connection to the device"""
+        logging.debug("Closing TCP stream")
+        StreamInterface.close(self)
+        # Sometimes the socket read might be blocked in the reader thread.
+        # Therefore we force the shutdown by closing the socket here
+        self._wantExit = True
+        if not self.socket is None:
+            try:
+                self.socket.shutdown(socket.SHUT_RDWR)
+            except:
+                pass  # Ignore errors in shutdown, because we might have a race with the server
+            self.socket.close()
+
+    def _writeBytes(self, b):
+        """Write an array of bytes to our stream and flush"""
+        self.socket.send(b)
+
+    def _readBytes(self, length):
+        """Read an array of bytes from our stream"""
+        return self.socket.recv(length)
+
+

Ancestors

+ +

Methods

+
+
+def myConnect(self) +
+
+

Connect to socket

+
+ +Expand source code + +
def myConnect(self):
+    """Connect to socket"""
+    server_address = (self.hostname, self.portNumber)
+    sock = socket.create_connection(server_address)
+    self.socket = sock
+
+
+
+

Inherited members

+ +
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/test.html b/docs/meshtastic/test.html new file mode 100644 index 0000000..0374767 --- /dev/null +++ b/docs/meshtastic/test.html @@ -0,0 +1,544 @@ + + + + + + +meshtastic.test API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.test

+
+
+

With two radios connected serially, send and receive test +messages and report back if successful.

+
+ +Expand source code + +
"""With two radios connected serially, send and receive test
+   messages and report back if successful.
+"""
+import logging
+import time
+import sys
+import traceback
+from dotmap import DotMap
+from pubsub import pub
+import meshtastic.util
+from .__init__ import BROADCAST_NUM
+from .serial_interface import SerialInterface
+from .tcp_interface import TCPInterface
+
+
+"""The interfaces we are using for our tests"""
+interfaces = None
+
+"""A list of all packets we received while the current test was running"""
+receivedPackets = None
+
+testsRunning = False
+
+testNumber = 0
+
+sendingInterface = None
+
+
+def onReceive(packet, interface):
+    """Callback invoked when a packet arrives"""
+    if sendingInterface == interface:
+        pass
+        # print("Ignoring sending interface")
+    else:
+        # print(f"From {interface.stream.port}: {packet}")
+        p = DotMap(packet)
+
+        if p.decoded.portnum == "TEXT_MESSAGE_APP":
+            # We only care a about clear text packets
+            if receivedPackets is not None:
+                receivedPackets.append(p)
+
+
+def onNode(node):
+    """Callback invoked when the node DB changes"""
+    print(f"Node changed: {node}")
+
+
+def subscribe():
+    """Subscribe to the topics the user probably wants to see, prints output to stdout"""
+
+    pub.subscribe(onNode, "meshtastic.node")
+
+
+def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False):
+    """
+    Sends one test packet between two nodes and then returns success or failure
+
+    Arguments:
+        fromInterface {[type]} -- [description]
+        toInterface {[type]} -- [description]
+
+    Returns:
+        boolean -- True for success
+    """
+    global receivedPackets
+    receivedPackets = []
+    fromNode = fromInterface.myInfo.my_node_num
+
+    if isBroadcast:
+        toNode = BROADCAST_NUM
+    else:
+        toNode = toInterface.myInfo.my_node_num
+
+    logging.debug(
+        f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
+    global sendingInterface
+    sendingInterface = fromInterface
+    if not asBinary:
+        fromInterface.sendText(f"Test {testNumber}", toNode, wantAck=wantAck)
+    else:
+        fromInterface.sendData((f"Binary {testNumber}").encode(
+            "utf-8"), toNode, wantAck=wantAck)
+    for _ in range(60):  # max of 60 secs before we timeout
+        time.sleep(1)
+        if  len(receivedPackets) >= 1:
+            return True
+    return False  # Failed to send
+
+
+def runTests(numTests=50, wantAck=False, maxFailures=0):
+    """Run the tests."""
+    logging.info(f"Running {numTests} tests with wantAck={wantAck}")
+    numFail = 0
+    numSuccess = 0
+    for _ in range(numTests):
+        global testNumber
+        testNumber = testNumber + 1
+        isBroadcast = True
+        # asBinary=(i % 2 == 0)
+        success = testSend(
+            interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck)
+        if not success:
+            numFail = numFail + 1
+            logging.error(
+                f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)")
+        else:
+            numSuccess = numSuccess + 1
+            logging.info(
+                f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far")
+
+        time.sleep(1)
+
+    if numFail > maxFailures:
+        logging.error("Too many failures! Test failed!")
+        return False
+    return True
+
+
+def testThread(numTests=50):
+    """Test thread"""
+    logging.info("Found devices, starting tests...")
+    result = runTests(numTests, wantAck=True)
+    if result:
+        # Run another test
+        # Allow a few dropped packets
+        result = runTests(numTests, wantAck=False, maxFailures=1)
+    return result
+
+
+def onConnection(topic=pub.AUTO_TOPIC):
+    """Callback invoked when we connect/disconnect from a radio"""
+    print(f"Connection changed: {topic.getName()}")
+
+
+def openDebugLog(portName):
+    """Open the debug log file"""
+    debugname = "log" + portName.replace("/", "_")
+    logging.info(f"Writing serial debugging to {debugname}")
+    return open(debugname, 'w+', buffering=1, encoding='utf8')
+
+
+def testAll(numTests=5):
+    """
+    Run a series of tests using devices we can find.
+    This is called from the cli with the "--test" option.
+
+    """
+    ports = meshtastic.util.findPorts()
+    if len(ports) < 2:
+        meshtastic.util.our_exit("Warning: Must have at least two devices connected to USB.")
+
+    pub.subscribe(onConnection, "meshtastic.connection")
+    pub.subscribe(onReceive, "meshtastic.receive")
+    global interfaces
+    interfaces = list(map(lambda port: SerialInterface(
+        port, debugOut=openDebugLog(port), connectNow=True), ports))
+
+    logging.info("Ports opened, starting test")
+    result = testThread(numTests)
+
+    for i in interfaces:
+        i.close()
+
+    return result
+
+
+def testSimulator():
+    """
+    Assume that someone has launched meshtastic-native as a simulated node.
+    Talk to that node over TCP, do some operations and if they are successful
+    exit the process with a success code, else exit with a non zero exit code.
+
+    Run with
+    python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
+    """
+    logging.basicConfig(level=logging.DEBUG)
+    logging.info("Connecting to simulator on localhost!")
+    try:
+        iface = TCPInterface("localhost")
+        iface.showInfo()
+        iface.localNode.showInfo()
+        iface.localNode.exitSimulator()
+        iface.close()
+        logging.info("Integration test successful!")
+    except:
+        print("Error while testing simulator:", sys.exc_info()[0])
+        traceback.print_exc()
+        sys.exit(1)
+    sys.exit(0)
+
+
+
+
+
+

Global variables

+
+
var interfaces
+
+

A list of all packets we received while the current test was running

+
+
+
+
+

Functions

+
+
+def onConnection(topic=pubsub.core.callables.AUTO_TOPIC) +
+
+

Callback invoked when we connect/disconnect from a radio

+
+ +Expand source code + +
def onConnection(topic=pub.AUTO_TOPIC):
+    """Callback invoked when we connect/disconnect from a radio"""
+    print(f"Connection changed: {topic.getName()}")
+
+
+
+def onNode(node) +
+
+

Callback invoked when the node DB changes

+
+ +Expand source code + +
def onNode(node):
+    """Callback invoked when the node DB changes"""
+    print(f"Node changed: {node}")
+
+
+
+def onReceive(packet, interface) +
+
+

Callback invoked when a packet arrives

+
+ +Expand source code + +
def onReceive(packet, interface):
+    """Callback invoked when a packet arrives"""
+    if sendingInterface == interface:
+        pass
+        # print("Ignoring sending interface")
+    else:
+        # print(f"From {interface.stream.port}: {packet}")
+        p = DotMap(packet)
+
+        if p.decoded.portnum == "TEXT_MESSAGE_APP":
+            # We only care a about clear text packets
+            if receivedPackets is not None:
+                receivedPackets.append(p)
+
+
+
+def openDebugLog(portName) +
+
+

Open the debug log file

+
+ +Expand source code + +
def openDebugLog(portName):
+    """Open the debug log file"""
+    debugname = "log" + portName.replace("/", "_")
+    logging.info(f"Writing serial debugging to {debugname}")
+    return open(debugname, 'w+', buffering=1, encoding='utf8')
+
+
+
+def runTests(numTests=50, wantAck=False, maxFailures=0) +
+
+

Run the tests.

+
+ +Expand source code + +
def runTests(numTests=50, wantAck=False, maxFailures=0):
+    """Run the tests."""
+    logging.info(f"Running {numTests} tests with wantAck={wantAck}")
+    numFail = 0
+    numSuccess = 0
+    for _ in range(numTests):
+        global testNumber
+        testNumber = testNumber + 1
+        isBroadcast = True
+        # asBinary=(i % 2 == 0)
+        success = testSend(
+            interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck)
+        if not success:
+            numFail = numFail + 1
+            logging.error(
+                f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)")
+        else:
+            numSuccess = numSuccess + 1
+            logging.info(
+                f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far")
+
+        time.sleep(1)
+
+    if numFail > maxFailures:
+        logging.error("Too many failures! Test failed!")
+        return False
+    return True
+
+
+
+def subscribe() +
+
+

Subscribe to the topics the user probably wants to see, prints output to stdout

+
+ +Expand source code + +
def subscribe():
+    """Subscribe to the topics the user probably wants to see, prints output to stdout"""
+
+    pub.subscribe(onNode, "meshtastic.node")
+
+
+
+def testAll(numTests=5) +
+
+

Run a series of tests using devices we can find. +This is called from the cli with the "–test" option.

+
+ +Expand source code + +
def testAll(numTests=5):
+    """
+    Run a series of tests using devices we can find.
+    This is called from the cli with the "--test" option.
+
+    """
+    ports = meshtastic.util.findPorts()
+    if len(ports) < 2:
+        meshtastic.util.our_exit("Warning: Must have at least two devices connected to USB.")
+
+    pub.subscribe(onConnection, "meshtastic.connection")
+    pub.subscribe(onReceive, "meshtastic.receive")
+    global interfaces
+    interfaces = list(map(lambda port: SerialInterface(
+        port, debugOut=openDebugLog(port), connectNow=True), ports))
+
+    logging.info("Ports opened, starting test")
+    result = testThread(numTests)
+
+    for i in interfaces:
+        i.close()
+
+    return result
+
+
+
+def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False) +
+
+

Sends one test packet between two nodes and then returns success or failure

+

Arguments

+

fromInterface {[type]} – [description] +toInterface {[type]} – [description]

+

Returns

+

boolean – True for success

+
+ +Expand source code + +
def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False):
+    """
+    Sends one test packet between two nodes and then returns success or failure
+
+    Arguments:
+        fromInterface {[type]} -- [description]
+        toInterface {[type]} -- [description]
+
+    Returns:
+        boolean -- True for success
+    """
+    global receivedPackets
+    receivedPackets = []
+    fromNode = fromInterface.myInfo.my_node_num
+
+    if isBroadcast:
+        toNode = BROADCAST_NUM
+    else:
+        toNode = toInterface.myInfo.my_node_num
+
+    logging.debug(
+        f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
+    global sendingInterface
+    sendingInterface = fromInterface
+    if not asBinary:
+        fromInterface.sendText(f"Test {testNumber}", toNode, wantAck=wantAck)
+    else:
+        fromInterface.sendData((f"Binary {testNumber}").encode(
+            "utf-8"), toNode, wantAck=wantAck)
+    for _ in range(60):  # max of 60 secs before we timeout
+        time.sleep(1)
+        if  len(receivedPackets) >= 1:
+            return True
+    return False  # Failed to send
+
+
+
+def testSimulator() +
+
+

Assume that someone has launched meshtastic-native as a simulated node. +Talk to that node over TCP, do some operations and if they are successful +exit the process with a success code, else exit with a non zero exit code.

+

Run with +python3 -c 'from meshtastic.test import testSimulator; testSimulator()'

+
+ +Expand source code + +
def testSimulator():
+    """
+    Assume that someone has launched meshtastic-native as a simulated node.
+    Talk to that node over TCP, do some operations and if they are successful
+    exit the process with a success code, else exit with a non zero exit code.
+
+    Run with
+    python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
+    """
+    logging.basicConfig(level=logging.DEBUG)
+    logging.info("Connecting to simulator on localhost!")
+    try:
+        iface = TCPInterface("localhost")
+        iface.showInfo()
+        iface.localNode.showInfo()
+        iface.localNode.exitSimulator()
+        iface.close()
+        logging.info("Integration test successful!")
+    except:
+        print("Error while testing simulator:", sys.exc_info()[0])
+        traceback.print_exc()
+        sys.exit(1)
+    sys.exit(0)
+
+
+
+def testThread(numTests=50) +
+
+

Test thread

+
+ +Expand source code + +
def testThread(numTests=50):
+    """Test thread"""
+    logging.info("Found devices, starting tests...")
+    result = runTests(numTests, wantAck=True)
+    if result:
+        # Run another test
+        # Allow a few dropped packets
+        result = runTests(numTests, wantAck=False, maxFailures=1)
+    return result
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/conftest.html b/docs/meshtastic/tests/conftest.html new file mode 100644 index 0000000..efb42ff --- /dev/null +++ b/docs/meshtastic/tests/conftest.html @@ -0,0 +1,199 @@ + + + + + + +meshtastic.tests.conftest API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.conftest

+
+
+

Common pytest code (place for fixtures).

+
+ +Expand source code + +
"""Common pytest code (place for fixtures)."""
+
+import argparse
+
+from unittest.mock import MagicMock
+import pytest
+
+from meshtastic.__main__ import Globals
+from ..mesh_interface import MeshInterface
+
+
+@pytest.fixture
+def reset_globals():
+    """Fixture to reset globals."""
+    parser = None
+    parser = argparse.ArgumentParser()
+    Globals.getInstance().reset()
+    Globals.getInstance().set_parser(parser)
+
+
+@pytest.fixture
+def iface_with_nodes():
+    """Fixture to setup some nodes."""
+    nodesById = {
+            '!9388f81c': {
+                'num': 2475227164,
+                'user': {
+                    'id': '!9388f81c',
+                    'longName': 'Unknown f81c',
+                    'shortName': '?1C',
+                    'macaddr': 'RBeTiPgc',
+                    'hwModel': 'TBEAM'
+                    },
+                'position': {},
+                'lastHeard': 1640204888
+                }
+            }
+
+    nodesByNum = {
+            2475227164: {
+                'num': 2475227164,
+                'user': {
+                    'id': '!9388f81c',
+                    'longName': 'Unknown f81c',
+                    'shortName': '?1C',
+                    'macaddr': 'RBeTiPgc',
+                    'hwModel': 'TBEAM'
+                    },
+                'position': {
+                    'time': 1640206266
+                    },
+                'lastHeard': 1640206266
+                }
+            }
+    iface = MeshInterface(noProto=True)
+    iface.nodes = nodesById
+    iface.nodesByNum = nodesByNum
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+    iface.myInfo.my_node_num = 2475227164
+    return iface
+
+
+
+
+
+
+
+

Functions

+
+
+def iface_with_nodes() +
+
+

Fixture to setup some nodes.

+
+ +Expand source code + +
@pytest.fixture
+def iface_with_nodes():
+    """Fixture to setup some nodes."""
+    nodesById = {
+            '!9388f81c': {
+                'num': 2475227164,
+                'user': {
+                    'id': '!9388f81c',
+                    'longName': 'Unknown f81c',
+                    'shortName': '?1C',
+                    'macaddr': 'RBeTiPgc',
+                    'hwModel': 'TBEAM'
+                    },
+                'position': {},
+                'lastHeard': 1640204888
+                }
+            }
+
+    nodesByNum = {
+            2475227164: {
+                'num': 2475227164,
+                'user': {
+                    'id': '!9388f81c',
+                    'longName': 'Unknown f81c',
+                    'shortName': '?1C',
+                    'macaddr': 'RBeTiPgc',
+                    'hwModel': 'TBEAM'
+                    },
+                'position': {
+                    'time': 1640206266
+                    },
+                'lastHeard': 1640206266
+                }
+            }
+    iface = MeshInterface(noProto=True)
+    iface.nodes = nodesById
+    iface.nodesByNum = nodesByNum
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+    iface.myInfo.my_node_num = 2475227164
+    return iface
+
+
+
+def reset_globals() +
+
+

Fixture to reset globals.

+
+ +Expand source code + +
@pytest.fixture
+def reset_globals():
+    """Fixture to reset globals."""
+    parser = None
+    parser = argparse.ArgumentParser()
+    Globals.getInstance().reset()
+    Globals.getInstance().set_parser(parser)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/index.html b/docs/meshtastic/tests/index.html new file mode 100644 index 0000000..b755e53 --- /dev/null +++ b/docs/meshtastic/tests/index.html @@ -0,0 +1,142 @@ + + + + + + +meshtastic.tests API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests

+
+
+
+
+

Sub-modules

+
+
meshtastic.tests.conftest
+
+

Common pytest code (place for fixtures).

+
+
meshtastic.tests.test_ble_interface
+
+

Meshtastic unit tests for ble_interface.py

+
+
meshtastic.tests.test_examples
+
+

Meshtastic test that the examples run as expected. +We assume you have a python virtual environment in current directory. +If not, you need to run: …

+
+
meshtastic.tests.test_globals
+
+

Meshtastic unit tests for globals.py

+
+
meshtastic.tests.test_int
+
+

Meshtastic integration tests

+
+
meshtastic.tests.test_main
+
+

Meshtastic unit tests for main.py

+
+
meshtastic.tests.test_mesh_interface
+
+

Meshtastic unit tests for mesh_interface.py

+
+
meshtastic.tests.test_node
+
+

Meshtastic unit tests for node.py

+
+
meshtastic.tests.test_remote_hardware
+
+

Meshtastic unit tests for remote_hardware.py

+
+
meshtastic.tests.test_serial_interface
+
+

Meshtastic unit tests for serial_interface.py

+
+
meshtastic.tests.test_smoke1
+
+

Meshtastic smoke tests with a single device via USB

+
+
meshtastic.tests.test_smoke2
+
+

Meshtastic smoke tests with 2 devices connected via USB

+
+
meshtastic.tests.test_smoke_wifi
+
+

Meshtastic smoke tests a device setup with wifi …

+
+
meshtastic.tests.test_stream_interface
+
+

Meshtastic unit tests for stream_interface.py

+
+
meshtastic.tests.test_tcp_interface
+
+

Meshtastic unit tests for tcp_interface.py

+
+
meshtastic.tests.test_util
+
+

Meshtastic unit tests for util.py

+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_ble_interface.html b/docs/meshtastic/tests/test_ble_interface.html new file mode 100644 index 0000000..ae87dd6 --- /dev/null +++ b/docs/meshtastic/tests/test_ble_interface.html @@ -0,0 +1,95 @@ + + + + + + +meshtastic.tests.test_ble_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_ble_interface

+
+
+

Meshtastic unit tests for ble_interface.py

+
+ +Expand source code + +
"""Meshtastic unit tests for ble_interface.py"""
+
+
+import pytest
+
+from ..ble_interface import BLEInterface
+
+@pytest.mark.unit
+def test_BLEInterface():
+    """Test that we can instantiate a BLEInterface"""
+    iface = BLEInterface('foo', debugOut=True, noProto=True)
+    iface.close()
+
+
+
+
+
+
+
+

Functions

+
+
+def test_BLEInterface() +
+
+

Test that we can instantiate a BLEInterface

+
+ +Expand source code + +
@pytest.mark.unit
+def test_BLEInterface():
+    """Test that we can instantiate a BLEInterface"""
+    iface = BLEInterface('foo', debugOut=True, noProto=True)
+    iface.close()
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_examples.html b/docs/meshtastic/tests/test_examples.html new file mode 100644 index 0000000..6d35c14 --- /dev/null +++ b/docs/meshtastic/tests/test_examples.html @@ -0,0 +1,132 @@ + + + + + + +meshtastic.tests.test_examples API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_examples

+
+
+

Meshtastic test that the examples run as expected. +We assume you have a python virtual environment in current directory. +If not, you need to run: "python3 -m venv venv", "source venv/bin/activate", "pip install ."

+
+ +Expand source code + +
"""Meshtastic test that the examples run as expected.
+   We assume you have a python virtual environment in current directory.
+   If not, you need to run: "python3 -m venv venv", "source venv/bin/activate", "pip install ."
+"""
+import subprocess
+
+import pytest
+
+@pytest.mark.examples
+def test_examples_hello_world_serial_no_arg():
+    """Test hello_world_serial without any args"""
+    return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py')
+    assert return_value == 3
+
+
+@pytest.mark.examples
+def test_examples_hello_world_serial_with_arg(capsys):
+    """Test hello_world_serial with arg"""
+    return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py hello')
+    assert return_value == 1
+    _, err = capsys.readouterr()
+    assert err == ''
+    # TODO: Why does this not work?
+    # assert out == 'Warning: No Meshtastic devices detected.'
+
+
+
+
+
+
+
+

Functions

+
+
+def test_examples_hello_world_serial_no_arg() +
+
+

Test hello_world_serial without any args

+
+ +Expand source code + +
@pytest.mark.examples
+def test_examples_hello_world_serial_no_arg():
+    """Test hello_world_serial without any args"""
+    return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py')
+    assert return_value == 3
+
+
+
+def test_examples_hello_world_serial_with_arg(capsys) +
+
+

Test hello_world_serial with arg

+
+ +Expand source code + +
@pytest.mark.examples
+def test_examples_hello_world_serial_with_arg(capsys):
+    """Test hello_world_serial with arg"""
+    return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py hello')
+    assert return_value == 1
+    _, err = capsys.readouterr()
+    assert err == ''
+    # TODO: Why does this not work?
+    # assert out == 'Warning: No Meshtastic devices detected.'
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_globals.html b/docs/meshtastic/tests/test_globals.html new file mode 100644 index 0000000..df7d4e9 --- /dev/null +++ b/docs/meshtastic/tests/test_globals.html @@ -0,0 +1,130 @@ + + + + + + +meshtastic.tests.test_globals API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_globals

+
+
+

Meshtastic unit tests for globals.py

+
+ +Expand source code + +
"""Meshtastic unit tests for globals.py
+"""
+
+import pytest
+
+from ..globals import Globals
+
+
+@pytest.mark.unit
+def test_globals_get_instaance():
+    """Test that we can instantiate a Globals instance"""
+    ourglobals = Globals.getInstance()
+    ourglobals2 = Globals.getInstance()
+    assert ourglobals == ourglobals2
+
+
+@pytest.mark.unit
+def test_globals_there_can_be_only_one():
+    """Test that we can cannot create two Globals instances"""
+    # if we have an instance, delete it
+    Globals.getInstance()
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        # try to create another instance
+        Globals()
+    assert pytest_wrapped_e.type == Exception
+
+
+
+
+
+
+
+

Functions

+
+
+def test_globals_get_instaance() +
+
+

Test that we can instantiate a Globals instance

+
+ +Expand source code + +
@pytest.mark.unit
+def test_globals_get_instaance():
+    """Test that we can instantiate a Globals instance"""
+    ourglobals = Globals.getInstance()
+    ourglobals2 = Globals.getInstance()
+    assert ourglobals == ourglobals2
+
+
+
+def test_globals_there_can_be_only_one() +
+
+

Test that we can cannot create two Globals instances

+
+ +Expand source code + +
@pytest.mark.unit
+def test_globals_there_can_be_only_one():
+    """Test that we can cannot create two Globals instances"""
+    # if we have an instance, delete it
+    Globals.getInstance()
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        # try to create another instance
+        Globals()
+    assert pytest_wrapped_e.type == Exception
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_int.html b/docs/meshtastic/tests/test_int.html new file mode 100644 index 0000000..4811aad --- /dev/null +++ b/docs/meshtastic/tests/test_int.html @@ -0,0 +1,177 @@ + + + + + + +meshtastic.tests.test_int API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_int

+
+
+

Meshtastic integration tests

+
+ +Expand source code + +
"""Meshtastic integration tests"""
+import re
+import subprocess
+
+import pytest
+
+
+@pytest.mark.int
+def test_int_no_args():
+    """Test without any args"""
+    return_value, out = subprocess.getstatusoutput('meshtastic')
+    assert re.match(r'usage: meshtastic', out)
+    assert return_value == 1
+
+
+@pytest.mark.int
+def test_int_version():
+    """Test '--version'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --version')
+    assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
+    assert return_value == 0
+
+
+@pytest.mark.int
+def test_int_help():
+    """Test '--help'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --help')
+    assert re.match(r'usage: meshtastic ', out)
+    assert return_value == 0
+
+
+@pytest.mark.int
+def test_int_support():
+    """Test '--support'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --support')
+    assert re.search(r'System', out)
+    assert re.search(r'Python', out)
+    assert return_value == 0
+
+
+
+
+
+
+
+

Functions

+
+
+def test_int_help() +
+
+

Test '–help'.

+
+ +Expand source code + +
@pytest.mark.int
+def test_int_help():
+    """Test '--help'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --help')
+    assert re.match(r'usage: meshtastic ', out)
+    assert return_value == 0
+
+
+
+def test_int_no_args() +
+
+

Test without any args

+
+ +Expand source code + +
@pytest.mark.int
+def test_int_no_args():
+    """Test without any args"""
+    return_value, out = subprocess.getstatusoutput('meshtastic')
+    assert re.match(r'usage: meshtastic', out)
+    assert return_value == 1
+
+
+
+def test_int_support() +
+
+

Test '–support'.

+
+ +Expand source code + +
@pytest.mark.int
+def test_int_support():
+    """Test '--support'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --support')
+    assert re.search(r'System', out)
+    assert re.search(r'Python', out)
+    assert return_value == 0
+
+
+
+def test_int_version() +
+
+

Test '–version'.

+
+ +Expand source code + +
@pytest.mark.int
+def test_int_version():
+    """Test '--version'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --version')
+    assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
+    assert return_value == 0
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_main.html b/docs/meshtastic/tests/test_main.html new file mode 100644 index 0000000..c9a0f51 --- /dev/null +++ b/docs/meshtastic/tests/test_main.html @@ -0,0 +1,4552 @@ + + + + + + +meshtastic.tests.test_main API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_main

+
+
+

Meshtastic unit tests for main.py

+
+ +Expand source code + +
"""Meshtastic unit tests for __main__.py"""
+# pylint: disable=C0302
+
+import sys
+import os
+import re
+import logging
+
+from unittest.mock import patch, MagicMock
+import pytest
+
+from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, export_config, getPref, setPref
+#from ..radioconfig_pb2 import UserPreferences
+import meshtastic.radioconfig_pb2
+from ..serial_interface import SerialInterface
+from ..tcp_interface import TCPInterface
+from ..ble_interface import BLEInterface
+from ..node import Node
+from ..channel_pb2 import Channel
+from ..remote_hardware import onGPIOreceive
+
+
+@pytest.mark.unit
+def test_main_init_parser_no_args(capsys, reset_globals):
+    """Test no arguments"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+    initParser()
+    out, err = capsys.readouterr()
+    assert out == ''
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_init_parser_version(capsys, reset_globals):
+    """Test --version"""
+    sys.argv = ['', '--version']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        initParser()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    out, err = capsys.readouterr()
+    assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_main_version(capsys, reset_globals):
+    """Test --version"""
+    sys.argv = ['', '--version']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    out, err = capsys.readouterr()
+    assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_main_no_args(reset_globals):
+    """Test with no args"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_main_support(capsys, reset_globals):
+    """Test --support"""
+    sys.argv = ['', '--support']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    out, err = capsys.readouterr()
+    assert re.search(r'System', out, re.MULTILINE)
+    assert re.search(r'Platform', out, re.MULTILINE)
+    assert re.search(r'Machine', out, re.MULTILINE)
+    assert re.search(r'Executable', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=[])
+def test_main_ch_index_no_devices(patched_find_ports, capsys, reset_globals):
+    """Test --ch-index 1"""
+    sys.argv = ['', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert Globals.getInstance().get_channel_index() == 1
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
+    assert err == ''
+    patched_find_ports.assert_called()
+
+
+@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=[])
+def test_main_test_no_ports(patched_find_ports, reset_globals):
+    """Test --test with no hardware"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    assert Globals.getInstance().get_target_node() is None
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    patched_find_ports.assert_called()
+
+
+@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1'])
+def test_main_test_one_port(patched_find_ports, reset_globals):
+    """Test --test with one fake port"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    assert Globals.getInstance().get_target_node() is None
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    patched_find_ports.assert_called()
+
+
+@pytest.mark.unit
+@patch('meshtastic.test.testAll', return_value=True)
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1', '/dev/ttyFake2'])
+def test_main_test_two_ports_success(patched_find_ports, patched_test_all, reset_globals):
+    """Test --test two fake ports and testAll() is a simulated success"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    # TODO: why does this fail? patched_find_ports.assert_called()
+    patched_test_all.assert_called()
+
+
+@pytest.mark.unit
+@patch('meshtastic.test.testAll', return_value=False)
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1', '/dev/ttyFake2'])
+def test_main_test_two_ports_fails(patched_find_ports, patched_test_all, reset_globals):
+    """Test --test two fake ports and testAll() is a simulated failure"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    # TODO: why does this fail? patched_find_ports.assert_called()
+    patched_test_all.assert_called()
+
+
+@pytest.mark.unit
+def test_main_info(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_info_with_tcp_interface(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--host', 'meshtastic.local']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=TCPInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_info_with_ble_interface(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--ble', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=BLEInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.ble_interface.BLEInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_no_proto(capsys, reset_globals):
+    """Test --noproto (using --info for output)"""
+    sys.argv = ['', '--info', '--noproto']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+
+    # Override the time.sleep so there is no loop
+    def my_sleep(amount):
+        sys.exit(0)
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with patch('time.sleep', side_effect=my_sleep):
+            with pytest.raises(SystemExit) as pytest_wrapped_e:
+                main()
+            assert pytest_wrapped_e.type == SystemExit
+            assert pytest_wrapped_e.value.code == 0
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+            assert err == ''
+
+
+@pytest.mark.unit
+def test_main_info_with_seriallog_stdout(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--seriallog', 'stdout']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_info_with_seriallog_output_txt(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--seriallog', 'output.txt']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+    # do some cleanup
+    os.remove('output.txt')
+
+
+@pytest.mark.unit
+def test_main_qr(capsys, reset_globals):
+    """Test --qr"""
+    sys.argv = ['', '--qr']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    # TODO: could mock/check url
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Primary channel URL', out, re.MULTILINE)
+        # if a qr code is generated it will have lots of these
+        assert re.search(r'\[7m', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_nodes(capsys, reset_globals):
+    """Test --nodes"""
+    sys.argv = ['', '--nodes']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showNodes():
+        print('inside mocked showNodes')
+    iface.showNodes.side_effect = mock_showNodes
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showNodes', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_set_owner_to_bob(capsys, reset_globals):
+    """Test --set-owner bob"""
+    sys.argv = ['', '--set-owner', 'bob']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Setting device owner to bob', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_set_ham_to_KI123(capsys, reset_globals):
+    """Test --set-ham KI123"""
+    sys.argv = ['', '--set-ham', 'KI123']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_turnOffEncryptionOnPrimaryChannel():
+        print('inside mocked turnOffEncryptionOnPrimaryChannel')
+    def mock_setOwner(name, is_licensed):
+        print('inside mocked setOwner')
+    mocked_node.turnOffEncryptionOnPrimaryChannel.side_effect = mock_turnOffEncryptionOnPrimaryChannel
+    mocked_node.setOwner.side_effect = mock_setOwner
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Setting Ham ID to KI123', out, re.MULTILINE)
+        assert re.search(r'inside mocked setOwner', out, re.MULTILINE)
+        assert re.search(r'inside mocked turnOffEncryptionOnPrimaryChannel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_reboot(capsys, reset_globals):
+    """Test --reboot"""
+    sys.argv = ['', '--reboot']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_reboot():
+        print('inside mocked reboot')
+    mocked_node.reboot.side_effect = mock_reboot
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked reboot', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_sendtext(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendText(text, dest, wantAck, channelIndex):
+        print('inside mocked sendText')
+    iface.sendText.side_effect = mock_sendText
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending text message', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendText', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_sendtext_with_channel(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendText(text, dest, wantAck, channelIndex):
+        print('inside mocked sendText')
+    iface.sendText.side_effect = mock_sendText
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending text message', out, re.MULTILINE)
+        assert re.search(r'on channelIndex:1', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendText', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_sendtext_with_invalid_channel(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getChannelByChannelIndex.return_value = None
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        iface.getNode.return_value.getChannelByChannelIndex.return_value = None
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'is not a valid channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_sendtext_with_invalid_channel_nine(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello', '--ch-index', '9']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        iface.getNode.return_value.getChannelByChannelIndex.return_value = None
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'is not a valid channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_sendtext_with_dest(capsys, reset_globals):
+    """Test --sendtext with --dest"""
+    sys.argv = ['', '--sendtext', 'hello', '--dest', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendText(text, dest, wantAck, channelIndex):
+        print('inside mocked sendText')
+    iface.sendText.side_effect = mock_sendText
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending text message', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendText', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_sendping(capsys, reset_globals):
+    """Test --sendping"""
+    sys.argv = ['', '--sendping']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendData(payload, dest, portNum, wantAck, wantResponse):
+        print('inside mocked sendData')
+    iface.sendData.side_effect = mock_sendData
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending ping message', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendData', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_setlat(capsys, reset_globals):
+    """Test --sendlat"""
+    sys.argv = ['', '--setlat', '37.5']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_writeConfig():
+        print('inside mocked writeConfig')
+    mocked_node.writeConfig.side_effect = mock_writeConfig
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendPosition(lat, lon, alt):
+        print('inside mocked sendPosition')
+    iface.sendPosition.side_effect = mock_sendPosition
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Fixing latitude', out, re.MULTILINE)
+        assert re.search(r'Setting device position', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendPosition', out, re.MULTILINE)
+        # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_setlon(capsys, reset_globals):
+    """Test --setlon"""
+    sys.argv = ['', '--setlon', '-122.1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_writeConfig():
+        print('inside mocked writeConfig')
+    mocked_node.writeConfig.side_effect = mock_writeConfig
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendPosition(lat, lon, alt):
+        print('inside mocked sendPosition')
+    iface.sendPosition.side_effect = mock_sendPosition
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Fixing longitude', out, re.MULTILINE)
+        assert re.search(r'Setting device position', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendPosition', out, re.MULTILINE)
+        # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_setalt(capsys, reset_globals):
+    """Test --setalt"""
+    sys.argv = ['', '--setalt', '51']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_writeConfig():
+        print('inside mocked writeConfig')
+    mocked_node.writeConfig.side_effect = mock_writeConfig
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendPosition(lat, lon, alt):
+        print('inside mocked sendPosition')
+    iface.sendPosition.side_effect = mock_sendPosition
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Fixing altitude', out, re.MULTILINE)
+        assert re.search(r'Setting device position', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendPosition', out, re.MULTILINE)
+        # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_set_team_valid(capsys, reset_globals):
+    """Test --set-team"""
+    sys.argv = ['', '--set-team', 'CYAN']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_setOwner(team):
+        print('inside mocked setOwner')
+    mocked_node.setOwner.side_effect = mock_setOwner
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with patch('meshtastic.mesh_pb2.Team') as mm:
+            mm.Name.return_value = 'FAKENAME'
+            mm.Value.return_value = 'FAKEVAL'
+            main()
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'Setting team to', out, re.MULTILINE)
+            assert err == ''
+            mo.assert_called()
+            mm.Name.assert_called()
+            mm.Value.assert_called()
+
+
+@pytest.mark.unit
+def test_main_set_team_invalid(capsys, reset_globals):
+    """Test --set-team using an invalid team name"""
+    sys.argv = ['', '--set-team', 'NOTCYAN']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+
+    def throw_an_exception(exc):
+        raise ValueError("Fake exception.")
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with patch('meshtastic.mesh_pb2.Team') as mm:
+            mm.Value.side_effect = throw_an_exception
+            main()
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'ERROR: Team', out, re.MULTILINE)
+            assert err == ''
+            mo.assert_called()
+            mm.Value.assert_called()
+
+
+@pytest.mark.unit
+def test_main_seturl(capsys, reset_globals):
+    """Test --seturl (url used below is what is generated after a factory_reset)"""
+    sys.argv = ['', '--seturl', 'https://www.meshtastic.org/d/#CgUYAyIBAQ']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_set_valid(capsys, reset_globals):
+    """Test --set with valid field"""
+    sys.argv = ['', '--set', 'wifi_ssid', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Set wifi_ssid to foo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_set_with_invalid(capsys, reset_globals):
+    """Test --set with invalid field"""
+    sys.argv = ['', '--set', 'foo', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_user_prefs = MagicMock()
+    mocked_user_prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    mocked_node = MagicMock(autospec=Node)
+    mocked_node.radioConfig.preferences = ( mocked_user_prefs )
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+# TODO: write some negative --configure tests
+@pytest.mark.unit
+def test_main_configure(capsys, reset_globals):
+    """Test --configure with valid file"""
+    sys.argv = ['', '--configure', 'example_config.yaml']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Setting device owner', out, re.MULTILINE)
+        assert re.search(r'Setting channel url', out, re.MULTILINE)
+        assert re.search(r'Fixing altitude', out, re.MULTILINE)
+        assert re.search(r'Fixing latitude', out, re.MULTILINE)
+        assert re.search(r'Fixing longitude', out, re.MULTILINE)
+        assert re.search(r'Writing modified preferences', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_add_valid(capsys, reset_globals):
+    """Test --ch-add with valid channel name, and that channel name does not already exist"""
+    sys.argv = ['', '--ch-add', 'testing']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_channel = MagicMock(autospec=Channel)
+    # TODO: figure out how to get it to print the channel name instead of MagicMock
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = False
+    # set it up so we have free channels
+    mocked_node.getDisabledChannel.return_value = mocked_channel
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_add_invalid_name_too_long(capsys, reset_globals):
+    """Test --ch-add with invalid channel name, name too long"""
+    sys.argv = ['', '--ch-add', 'testingtestingtesting']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_channel = MagicMock(autospec=Channel)
+    # TODO: figure out how to get it to print the channel name instead of MagicMock
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = False
+    # set it up so we have free channels
+    mocked_node.getDisabledChannel.return_value = mocked_channel
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Channel name must be shorter', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_add_but_name_already_exists(capsys, reset_globals):
+    """Test --ch-add with a channel name that already exists"""
+    sys.argv = ['', '--ch-add', 'testing']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = True
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: This node already has', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_add_but_no_more_channels(capsys, reset_globals):
+    """Test --ch-add with but there are no more channels"""
+    sys.argv = ['', '--ch-add', 'testing']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = False
+    # set it up so we have free channels
+    mocked_node.getDisabledChannel.return_value = None
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: No free channels were found', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_del(capsys, reset_globals):
+    """Test --ch-del with valid secondary channel to be deleted"""
+    sys.argv = ['', '--ch-del', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Deleting channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_del_no_ch_index_specified(capsys, reset_globals):
+    """Test --ch-del without a valid ch-index"""
+    sys.argv = ['', '--ch-del']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_del_primary_channel(capsys, reset_globals):
+    """Test --ch-del on ch-index=0"""
+    sys.argv = ['', '--ch-del', '--ch-index', '0']
+    Globals.getInstance().set_args(sys.argv)
+    Globals.getInstance().set_channel_index(1)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_enable_valid_secondary_channel(capsys, reset_globals):
+    """Test --ch-enable with --ch-index"""
+    sys.argv = ['', '--ch-enable', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Writing modified channels', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() == 1
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_disable_valid_secondary_channel(capsys, reset_globals):
+    """Test --ch-disable with --ch-index"""
+    sys.argv = ['', '--ch-disable', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Writing modified channels', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() == 1
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_enable_without_a_ch_index(capsys, reset_globals):
+    """Test --ch-enable without --ch-index"""
+    sys.argv = ['', '--ch-enable']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() is None
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_enable_primary_channel(capsys, reset_globals):
+    """Test --ch-enable with --ch-index = 0"""
+    sys.argv = ['', '--ch-enable', '--ch-index', '0']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Cannot enable/disable PRIMARY', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() == 0
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_range_options(capsys, reset_globals):
+    """Test changing the various range options."""
+    range_options = ['--ch-longslow', '--ch-longfast', '--ch-mediumslow',
+                     '--ch-mediumfast', '--ch-shortslow', '--ch-shortfast']
+    for range_option in range_options:
+        sys.argv = ['', f"{range_option}" ]
+        Globals.getInstance().set_args(sys.argv)
+
+        mocked_node = MagicMock(autospec=Node)
+
+        iface = MagicMock(autospec=SerialInterface)
+        iface.getNode.return_value = mocked_node
+
+        with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+            main()
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'Writing modified channels', out, re.MULTILINE)
+            assert err == ''
+            mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_ch_longsfast_on_non_primary_channel(capsys, reset_globals):
+    """Test --ch-longfast --ch-index 1"""
+    sys.argv = ['', '--ch-longfast', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Standard channel settings', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+# PositionFlags:
+# Misc info that might be helpful (this info will grow stale, just
+# a snapshot of the values.) The radioconfig_pb2.PositionFlags.Name and bit values are:
+# POS_UNDEFINED 0
+# POS_ALTITUDE 1
+# POS_ALT_MSL 2
+# POS_GEO_SEP 4
+# POS_DOP 8
+# POS_HVDOP 16
+# POS_BATTERY 32
+# POS_SATINVIEW 64
+# POS_SEQ_NOS 128
+# POS_TIMESTAMP 256
+
+@pytest.mark.unit
+def test_main_pos_fields_no_args(capsys, reset_globals):
+    """Test --pos-fields no args (which shows settings)"""
+    sys.argv = ['', '--pos-fields']
+    Globals.getInstance().set_args(sys.argv)
+
+    pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+        with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
+            # kind of cheating here, we are setting up the node
+            mocked_node = MagicMock(autospec=Node)
+            anode = mocked_node()
+            anode.radioConfig.preferences.position_flags = 35
+            Globals.getInstance().set_target_node(anode)
+
+            mrc.values.return_value = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256]
+            # Note: When you use side_effect and a list, each call will use a value from the front of the list then
+            # remove that value from the list. If there are three values in the list, we expect it to be called
+            # three times.
+            mrc.Name.side_effect = [ 'POS_ALTITUDE', 'POS_ALT_MSL', 'POS_BATTERY' ]
+
+            main()
+
+            mrc.Name.assert_called()
+            mrc.values.assert_called()
+            mo.assert_called()
+
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'POS_ALTITUDE POS_ALT_MSL POS_BATTERY', out, re.MULTILINE)
+            assert err == ''
+
+
+@pytest.mark.unit
+def test_main_pos_fields_arg_of_zero(capsys, reset_globals):
+    """Test --pos-fields an arg of 0 (which shows list)"""
+    sys.argv = ['', '--pos-fields', '0']
+    Globals.getInstance().set_args(sys.argv)
+
+    pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+        with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
+
+            def throw_value_error_exception(exc):
+                raise ValueError()
+            mrc.Value.side_effect = throw_value_error_exception
+            mrc.keys.return_value = [ 'POS_UNDEFINED', 'POS_ALTITUDE', 'POS_ALT_MSL',
+                                      'POS_GEO_SEP', 'POS_DOP', 'POS_HVDOP', 'POS_BATTERY',
+                                      'POS_SATINVIEW', 'POS_SEQ_NOS', 'POS_TIMESTAMP']
+
+            main()
+
+            mrc.Value.assert_called()
+            mrc.keys.assert_called()
+            mo.assert_called()
+
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'ERROR: supported position fields are:', out, re.MULTILINE)
+            assert re.search(r"['POS_UNDEFINED', 'POS_ALTITUDE', 'POS_ALT_MSL', 'POS_GEO_SEP',"\
+                              "'POS_DOP', 'POS_HVDOP', 'POS_BATTERY', 'POS_SATINVIEW', 'POS_SEQ_NOS',"\
+                              "'POS_TIMESTAMP']", out, re.MULTILINE)
+            assert err == ''
+
+
+@pytest.mark.unit
+def test_main_pos_fields_valid_values(capsys, reset_globals):
+    """Test --pos-fields with valid values"""
+    sys.argv = ['', '--pos-fields', 'POS_GEO_SEP', 'POS_ALT_MSL']
+    Globals.getInstance().set_args(sys.argv)
+
+    pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+        with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
+
+            mrc.Value.side_effect = [ 4, 2 ]
+
+            main()
+
+            mrc.Value.assert_called()
+            mo.assert_called()
+
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'Setting position fields to 6', out, re.MULTILINE)
+            assert re.search(r'Set position_flags to 6', out, re.MULTILINE)
+            assert re.search(r'Writing modified preferences to device', out, re.MULTILINE)
+            assert err == ''
+
+
+@pytest.mark.unit
+def test_main_get_with_valid_values(capsys, reset_globals):
+    """Test --get with valid values (with string, number, boolean)"""
+    sys.argv = ['', '--get', 'ls_secs', '--get', 'wifi_ssid', '--get', 'fixed_position']
+    Globals.getInstance().set_args(sys.argv)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+
+        # kind of cheating here, we are setting up the node
+        mocked_node = MagicMock(autospec=Node)
+        anode = mocked_node()
+        anode.radioConfig.preferences.wifi_ssid = 'foo'
+        anode.radioConfig.preferences.ls_secs = 300
+        anode.radioConfig.preferences.fixed_position = False
+        Globals.getInstance().set_target_node(anode)
+
+        main()
+
+        mo.assert_called()
+
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'ls_secs: 300', out, re.MULTILINE)
+        assert re.search(r'wifi_ssid: foo', out, re.MULTILINE)
+        assert re.search(r'fixed_position: False', out, re.MULTILINE)
+        assert err == ''
+
+
+@pytest.mark.unit
+def test_main_get_with_invalid(capsys, reset_globals):
+    """Test --get with invalid field"""
+    sys.argv = ['', '--get', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_user_prefs = MagicMock()
+    mocked_user_prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    mocked_node = MagicMock(autospec=Node)
+    mocked_node.radioConfig.preferences = ( mocked_user_prefs )
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+        assert re.search(r'Choices in sorted order are', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_setchan(capsys, reset_globals):
+    """Test --setchan (deprecated)"""
+    sys.argv = ['', '--setchan', 'a', 'b']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_main_onReceive_empty(caplog, reset_globals):
+    """Test onReceive"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+    iface = MagicMock(autospec=SerialInterface)
+    packet = {'decoded': 'foo'}
+    with caplog.at_level(logging.DEBUG):
+        onReceive(packet, iface)
+    assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
+
+
+#    TODO: use this captured position app message (might want/need in the future)
+#    packet = {
+#            'to': 4294967295,
+#            'decoded': {
+#                'portnum': 'POSITION_APP',
+#                'payload': "M69\306a"
+#                },
+#            'id': 334776976,
+#            'hop_limit': 3
+#            }
+
+@pytest.mark.unit
+def test_main_onReceive_with_sendtext(caplog, reset_globals):
+    """Test onReceive with sendtext
+       The entire point of this test is to make sure the interface.close() call
+       is made in onReceive().
+    """
+    sys.argv = ['', '--sendtext', 'hello']
+    Globals.getInstance().set_args(sys.argv)
+
+    # Note: 'TEXT_MESSAGE_APP' value is 1
+    packet = {
+            'to': 4294967295,
+            'decoded': {
+                'portnum': 1,
+                'payload': "hello"
+                },
+            'id': 334776977,
+            'hop_limit': 3,
+            'want_ack': True
+            }
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.myInfo.my_node_num = 4294967295
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with caplog.at_level(logging.DEBUG):
+            main()
+            onReceive(packet, iface)
+        assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_onReceive_with_reply(caplog, capsys, reset_globals):
+    """Test onReceive with a reply
+       To capture: on one device run '--sendtext aaa --reply' and on another
+       device run '--sendtext bbb --reply', then back to the first device and
+       run '--sendtext aaa2 --reply'. You should now see a "Sending reply" message.
+    """
+    sys.argv = ['', '--sendtext', 'hello', '--reply']
+    Globals.getInstance().set_args(sys.argv)
+
+    # Note: 'TEXT_MESSAGE_APP' value is 1
+
+    send_packet = {
+            'to': 4294967295,
+            'decoded': {
+                'portnum': 1,
+                'payload': "hello"
+                },
+            'id': 334776977,
+            'hop_limit': 3,
+            'want_ack': True
+            }
+
+    reply_packet = {
+            'from': 682968668,
+            'to': 4294967295,
+            'decoded': {
+                'portnum': 'TEXT_MESSAGE_APP',
+                'payload': b'bbb',
+                'text': 'bbb'
+                },
+            'id': 1709936182,
+            'rxTime': 1640381999,
+            'rxSnr': 6.0,
+            'hopLimit': 3,
+            'raw': 'faked',
+            'fromId': '!28b5465c',
+            'toId': '^all'
+            }
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.myInfo.my_node_num = 4294967295
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with caplog.at_level(logging.DEBUG):
+            main()
+            onReceive(send_packet, iface)
+            onReceive(reply_packet, iface)
+        assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
+        out, err = capsys.readouterr()
+        assert re.search(r'got msg ', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_onConnection(reset_globals, capsys):
+    """Test onConnection"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+    iface = MagicMock(autospec=SerialInterface)
+    class TempTopic:
+        """ temp class for topic """
+        def getName(self):
+            """ return the fake name of a topic"""
+            return 'foo'
+    mytopic = TempTopic()
+    onConnection(iface, mytopic)
+    out, err = capsys.readouterr()
+    assert re.search(r'Connection changed: foo', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_export_config(reset_globals, capsys):
+    """Test export_config() function directly"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.getLongName.return_value = 'foo'
+        mo.localNode.getURL.return_value = 'bar'
+        mo.getMyNodeInfo().get.return_value = { 'latitudeI': 1100000000, 'longitudeI': 1200000000,
+                                                'altitude': 100, 'batteryLevel': 34, 'latitude': 110.0,
+                                                'longitude': 120.0}
+        mo.localNode.radioConfig.preferences = """phone_timeout_secs: 900
+ls_secs: 300
+position_broadcast_smart: true
+fixed_position: true
+position_flags: 35"""
+        export_config(mo)
+    out, err = capsys.readouterr()
+    assert re.search(r'owner: foo', out, re.MULTILINE)
+    assert re.search(r'channel_url: bar', out, re.MULTILINE)
+    assert re.search(r'location:', out, re.MULTILINE)
+    assert re.search(r'lat: 110.0', out, re.MULTILINE)
+    assert re.search(r'lon: 120.0', out, re.MULTILINE)
+    assert re.search(r'alt: 100', out, re.MULTILINE)
+    assert re.search(r'user_prefs:', out, re.MULTILINE)
+    assert re.search(r'phone_timeout_secs: 900', out, re.MULTILINE)
+    assert re.search(r'ls_secs: 300', out, re.MULTILINE)
+    assert re.search(r"position_broadcast_smart: 'true'", out, re.MULTILINE)
+    assert re.search(r"fixed_position: 'true'", out, re.MULTILINE)
+    assert re.search(r"position_flags: 35", out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_export_config_called_from_main(capsys, reset_globals):
+    """Test --export-config"""
+    sys.argv = ['', '--export-config']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'# start of Meshtastic configure yaml', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+@pytest.mark.unit
+def test_main_gpio_rd_no_gpio_channel(capsys, reset_globals):
+    """Test --gpio_rd with no named gpio channel"""
+    sys.argv = ['', '--gpio-rd', '0x10']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.getChannelByName.return_value = None
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Warning: No channel named', out)
+        assert err == ''
+
+
+@pytest.mark.unit
+def test_main_gpio_rd_no_dest(capsys, reset_globals):
+    """Test --gpio_rd with a named gpio channel but no dest was specified"""
+    sys.argv = ['', '--gpio-rd', '0x2000']
+    Globals.getInstance().set_args(sys.argv)
+
+    channel = Channel(index=1, role=1)
+    channel.settings.modem_config = 3
+    channel.settings.psk = b'\x01'
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.getChannelByName.return_value = channel
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Warning: Must use a destination node ID', out)
+        assert err == ''
+
+
+@pytest.mark.unit
+def test_main_gpio_rd(caplog, capsys, reset_globals):
+    """Test --gpio_rd with a named gpio channel"""
+    # Note: On the Heltec v2.1, there is a GPIO pin GPIO 13 that does not have a
+    # red arrow (meaning ok to use for our purposes)
+    # See https://resource.heltec.cn/download/WiFi_LoRa_32/WIFI_LoRa_32_V2.pdf
+    # To find out the mask for GPIO 13, let us assign n as 13.
+    # 1. Subtract 1 from n (n is now 12)
+    # 2. Find the 2^n or 2^12 (4096)
+    # 3. Convert 4096 decimal to hex (0x1000)
+    # You can use python:
+    # >>> print(hex(2**12))
+    # 0x1000
+    sys.argv = ['', '--gpio-rd', '0x1000', '--dest', '!1234']
+    Globals.getInstance().set_args(sys.argv)
+
+    channel = Channel(index=1, role=1)
+    channel.settings.modem_config = 3
+    channel.settings.psk = b'\x01'
+
+    packet = {
+
+            'from': 682968668,
+            'to': 682968612,
+            'channel': 1,
+            'decoded': {
+                'portnum': 'REMOTE_HARDWARE_APP',
+                'payload': b'\x08\x05\x18\x80 ',
+                'requestId': 1629980484,
+                'remotehw': {
+                    'typ': 'READ_GPIOS_REPLY',
+                    'gpioValue': '4096',
+                    'raw': 'faked',
+                    'id': 1693085229,
+                    'rxTime': 1640294262,
+                    'rxSnr': 4.75,
+                    'hopLimit': 3,
+                    'wantAck': True,
+                    }
+                }
+            }
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.getChannelByName.return_value = channel
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with caplog.at_level(logging.DEBUG):
+            main()
+            onGPIOreceive(packet, mo)
+    assert re.search(r'readGPIOs nodeid:!1234 mask:4096', caplog.text, re.MULTILINE)
+    out, err = capsys.readouterr()
+    assert re.search(r'Connected to radio', out, re.MULTILINE)
+    assert re.search(r'Reading GPIO mask 0x1000 ', out, re.MULTILINE)
+    assert re.search(r'Received RemoteHardware typ=READ_GPIOS_REPLY, gpio_value=4096', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_getPref_valid_field(capsys, reset_globals):
+    """Test getPref() with a valid field"""
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs'
+    prefs.wifi_ssid = 'foo'
+    prefs.ls_secs = 300
+    prefs.fixed_position = False
+
+    getPref(prefs, 'ls_secs')
+    out, err = capsys.readouterr()
+    assert re.search(r'ls_secs: 300', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_getPref_invalid_field(capsys, reset_globals):
+    """Test getPref() with an invalid field"""
+
+    class Field:
+        """Simple class for testing."""
+
+        def __init__(self, name):
+            """constructor"""
+            self.name = name
+
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    # Note: This is a subset of the real fields
+    ls_secs_field = Field('ls_secs')
+    is_router = Field('is_router')
+    fixed_position = Field('fixed_position')
+
+    fields = [ ls_secs_field, is_router, fixed_position ]
+    prefs.DESCRIPTOR.fields = fields
+
+    getPref(prefs, 'foo')
+
+    out, err = capsys.readouterr()
+    assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+    # ensure they are sorted
+    assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_setPref_valid_field(capsys, reset_globals):
+    """Test setPref() with a valid field"""
+
+    class Field:
+        """Simple class for testing."""
+
+        def __init__(self, name, enum_type):
+            """constructor"""
+            self.name = name
+            self.enum_type = enum_type
+
+    ls_secs_field = Field('ls_secs', 'int')
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = ls_secs_field
+
+    setPref(prefs, 'ls_secs', '300')
+    out, err = capsys.readouterr()
+    assert re.search(r'Set ls_secs to 300', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_main_setPref_invalid_field(capsys, reset_globals):
+    """Test setPref() with a invalid field"""
+
+
+    class Field:
+        """Simple class for testing."""
+
+        def __init__(self, name):
+            """constructor"""
+            self.name = name
+
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    # Note: This is a subset of the real fields
+    ls_secs_field = Field('ls_secs')
+    is_router = Field('is_router')
+    fixed_position = Field('fixed_position')
+
+    fields = [ ls_secs_field, is_router, fixed_position ]
+    prefs.DESCRIPTOR.fields = fields
+
+    setPref(prefs, 'foo', '300')
+    out, err = capsys.readouterr()
+    assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+    # ensure they are sorted
+    assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
+    assert err == ''
+
+
+
+
+
+
+
+

Functions

+
+
+def test_main_ch_add_but_name_already_exists(capsys, reset_globals) +
+
+

Test –ch-add with a channel name that already exists

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_add_but_name_already_exists(capsys, reset_globals):
+    """Test --ch-add with a channel name that already exists"""
+    sys.argv = ['', '--ch-add', 'testing']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = True
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: This node already has', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_add_but_no_more_channels(capsys, reset_globals) +
+
+

Test –ch-add with but there are no more channels

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_add_but_no_more_channels(capsys, reset_globals):
+    """Test --ch-add with but there are no more channels"""
+    sys.argv = ['', '--ch-add', 'testing']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = False
+    # set it up so we have free channels
+    mocked_node.getDisabledChannel.return_value = None
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: No free channels were found', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_add_invalid_name_too_long(capsys, reset_globals) +
+
+

Test –ch-add with invalid channel name, name too long

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_add_invalid_name_too_long(capsys, reset_globals):
+    """Test --ch-add with invalid channel name, name too long"""
+    sys.argv = ['', '--ch-add', 'testingtestingtesting']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_channel = MagicMock(autospec=Channel)
+    # TODO: figure out how to get it to print the channel name instead of MagicMock
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = False
+    # set it up so we have free channels
+    mocked_node.getDisabledChannel.return_value = mocked_channel
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Channel name must be shorter', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_add_valid(capsys, reset_globals) +
+
+

Test –ch-add with valid channel name, and that channel name does not already exist

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_add_valid(capsys, reset_globals):
+    """Test --ch-add with valid channel name, and that channel name does not already exist"""
+    sys.argv = ['', '--ch-add', 'testing']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_channel = MagicMock(autospec=Channel)
+    # TODO: figure out how to get it to print the channel name instead of MagicMock
+
+    mocked_node = MagicMock(autospec=Node)
+    # set it up so we do not already have a channel named this
+    mocked_node.getChannelByName.return_value = False
+    # set it up so we have free channels
+    mocked_node.getDisabledChannel.return_value = mocked_channel
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_del(capsys, reset_globals) +
+
+

Test –ch-del with valid secondary channel to be deleted

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_del(capsys, reset_globals):
+    """Test --ch-del with valid secondary channel to be deleted"""
+    sys.argv = ['', '--ch-del', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Deleting channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_del_no_ch_index_specified(capsys, reset_globals) +
+
+

Test –ch-del without a valid ch-index

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_del_no_ch_index_specified(capsys, reset_globals):
+    """Test --ch-del without a valid ch-index"""
+    sys.argv = ['', '--ch-del']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_del_primary_channel(capsys, reset_globals) +
+
+

Test –ch-del on ch-index=0

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_del_primary_channel(capsys, reset_globals):
+    """Test --ch-del on ch-index=0"""
+    sys.argv = ['', '--ch-del', '--ch-index', '0']
+    Globals.getInstance().set_args(sys.argv)
+    Globals.getInstance().set_channel_index(1)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_disable_valid_secondary_channel(capsys, reset_globals) +
+
+

Test –ch-disable with –ch-index

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_disable_valid_secondary_channel(capsys, reset_globals):
+    """Test --ch-disable with --ch-index"""
+    sys.argv = ['', '--ch-disable', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Writing modified channels', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() == 1
+        mo.assert_called()
+
+
+
+def test_main_ch_enable_primary_channel(capsys, reset_globals) +
+
+

Test –ch-enable with –ch-index = 0

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_enable_primary_channel(capsys, reset_globals):
+    """Test --ch-enable with --ch-index = 0"""
+    sys.argv = ['', '--ch-enable', '--ch-index', '0']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Cannot enable/disable PRIMARY', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() == 0
+        mo.assert_called()
+
+
+
+def test_main_ch_enable_valid_secondary_channel(capsys, reset_globals) +
+
+

Test –ch-enable with –ch-index

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_enable_valid_secondary_channel(capsys, reset_globals):
+    """Test --ch-enable with --ch-index"""
+    sys.argv = ['', '--ch-enable', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Writing modified channels', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() == 1
+        mo.assert_called()
+
+
+
+def test_main_ch_enable_without_a_ch_index(capsys, reset_globals) +
+
+

Test –ch-enable without –ch-index

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_enable_without_a_ch_index(capsys, reset_globals):
+    """Test --ch-enable without --ch-index"""
+    sys.argv = ['', '--ch-enable']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+        assert err == ''
+        assert Globals.getInstance().get_channel_index() is None
+        mo.assert_called()
+
+
+
+def test_main_ch_index_no_devices(patched_find_ports, capsys, reset_globals) +
+
+

Test –ch-index 1

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=[])
+def test_main_ch_index_no_devices(patched_find_ports, capsys, reset_globals):
+    """Test --ch-index 1"""
+    sys.argv = ['', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert Globals.getInstance().get_channel_index() == 1
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
+    assert err == ''
+    patched_find_ports.assert_called()
+
+
+
+def test_main_ch_longsfast_on_non_primary_channel(capsys, reset_globals) +
+
+

Test –ch-longfast –ch-index 1

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_longsfast_on_non_primary_channel(capsys, reset_globals):
+    """Test --ch-longfast --ch-index 1"""
+    sys.argv = ['', '--ch-longfast', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Warning: Standard channel settings', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_ch_range_options(capsys, reset_globals) +
+
+

Test changing the various range options.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_ch_range_options(capsys, reset_globals):
+    """Test changing the various range options."""
+    range_options = ['--ch-longslow', '--ch-longfast', '--ch-mediumslow',
+                     '--ch-mediumfast', '--ch-shortslow', '--ch-shortfast']
+    for range_option in range_options:
+        sys.argv = ['', f"{range_option}" ]
+        Globals.getInstance().set_args(sys.argv)
+
+        mocked_node = MagicMock(autospec=Node)
+
+        iface = MagicMock(autospec=SerialInterface)
+        iface.getNode.return_value = mocked_node
+
+        with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+            main()
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'Writing modified channels', out, re.MULTILINE)
+            assert err == ''
+            mo.assert_called()
+
+
+
+def test_main_configure(capsys, reset_globals) +
+
+

Test –configure with valid file

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_configure(capsys, reset_globals):
+    """Test --configure with valid file"""
+    sys.argv = ['', '--configure', 'example_config.yaml']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Setting device owner', out, re.MULTILINE)
+        assert re.search(r'Setting channel url', out, re.MULTILINE)
+        assert re.search(r'Fixing altitude', out, re.MULTILINE)
+        assert re.search(r'Fixing latitude', out, re.MULTILINE)
+        assert re.search(r'Fixing longitude', out, re.MULTILINE)
+        assert re.search(r'Writing modified preferences', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_export_config(reset_globals, capsys) +
+
+

Test export_config() function directly

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_export_config(reset_globals, capsys):
+    """Test export_config() function directly"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.getLongName.return_value = 'foo'
+        mo.localNode.getURL.return_value = 'bar'
+        mo.getMyNodeInfo().get.return_value = { 'latitudeI': 1100000000, 'longitudeI': 1200000000,
+                                                'altitude': 100, 'batteryLevel': 34, 'latitude': 110.0,
+                                                'longitude': 120.0}
+        mo.localNode.radioConfig.preferences = """phone_timeout_secs: 900
+ls_secs: 300
+position_broadcast_smart: true
+fixed_position: true
+position_flags: 35"""
+        export_config(mo)
+    out, err = capsys.readouterr()
+    assert re.search(r'owner: foo', out, re.MULTILINE)
+    assert re.search(r'channel_url: bar', out, re.MULTILINE)
+    assert re.search(r'location:', out, re.MULTILINE)
+    assert re.search(r'lat: 110.0', out, re.MULTILINE)
+    assert re.search(r'lon: 120.0', out, re.MULTILINE)
+    assert re.search(r'alt: 100', out, re.MULTILINE)
+    assert re.search(r'user_prefs:', out, re.MULTILINE)
+    assert re.search(r'phone_timeout_secs: 900', out, re.MULTILINE)
+    assert re.search(r'ls_secs: 300', out, re.MULTILINE)
+    assert re.search(r"position_broadcast_smart: 'true'", out, re.MULTILINE)
+    assert re.search(r"fixed_position: 'true'", out, re.MULTILINE)
+    assert re.search(r"position_flags: 35", out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_export_config_called_from_main(capsys, reset_globals) +
+
+

Test –export-config

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_export_config_called_from_main(capsys, reset_globals):
+    """Test --export-config"""
+    sys.argv = ['', '--export-config']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'# start of Meshtastic configure yaml', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_getPref_invalid_field(capsys, reset_globals) +
+
+

Test getPref() with an invalid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_getPref_invalid_field(capsys, reset_globals):
+    """Test getPref() with an invalid field"""
+
+    class Field:
+        """Simple class for testing."""
+
+        def __init__(self, name):
+            """constructor"""
+            self.name = name
+
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    # Note: This is a subset of the real fields
+    ls_secs_field = Field('ls_secs')
+    is_router = Field('is_router')
+    fixed_position = Field('fixed_position')
+
+    fields = [ ls_secs_field, is_router, fixed_position ]
+    prefs.DESCRIPTOR.fields = fields
+
+    getPref(prefs, 'foo')
+
+    out, err = capsys.readouterr()
+    assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+    # ensure they are sorted
+    assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_getPref_valid_field(capsys, reset_globals) +
+
+

Test getPref() with a valid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_getPref_valid_field(capsys, reset_globals):
+    """Test getPref() with a valid field"""
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs'
+    prefs.wifi_ssid = 'foo'
+    prefs.ls_secs = 300
+    prefs.fixed_position = False
+
+    getPref(prefs, 'ls_secs')
+    out, err = capsys.readouterr()
+    assert re.search(r'ls_secs: 300', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_get_with_invalid(capsys, reset_globals) +
+
+

Test –get with invalid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_get_with_invalid(capsys, reset_globals):
+    """Test --get with invalid field"""
+    sys.argv = ['', '--get', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_user_prefs = MagicMock()
+    mocked_user_prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    mocked_node = MagicMock(autospec=Node)
+    mocked_node.radioConfig.preferences = ( mocked_user_prefs )
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+        assert re.search(r'Choices in sorted order are', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_get_with_valid_values(capsys, reset_globals) +
+
+

Test –get with valid values (with string, number, boolean)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_get_with_valid_values(capsys, reset_globals):
+    """Test --get with valid values (with string, number, boolean)"""
+    sys.argv = ['', '--get', 'ls_secs', '--get', 'wifi_ssid', '--get', 'fixed_position']
+    Globals.getInstance().set_args(sys.argv)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+
+        # kind of cheating here, we are setting up the node
+        mocked_node = MagicMock(autospec=Node)
+        anode = mocked_node()
+        anode.radioConfig.preferences.wifi_ssid = 'foo'
+        anode.radioConfig.preferences.ls_secs = 300
+        anode.radioConfig.preferences.fixed_position = False
+        Globals.getInstance().set_target_node(anode)
+
+        main()
+
+        mo.assert_called()
+
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'ls_secs: 300', out, re.MULTILINE)
+        assert re.search(r'wifi_ssid: foo', out, re.MULTILINE)
+        assert re.search(r'fixed_position: False', out, re.MULTILINE)
+        assert err == ''
+
+
+
+def test_main_gpio_rd(caplog, capsys, reset_globals) +
+
+

Test –gpio_rd with a named gpio channel

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_gpio_rd(caplog, capsys, reset_globals):
+    """Test --gpio_rd with a named gpio channel"""
+    # Note: On the Heltec v2.1, there is a GPIO pin GPIO 13 that does not have a
+    # red arrow (meaning ok to use for our purposes)
+    # See https://resource.heltec.cn/download/WiFi_LoRa_32/WIFI_LoRa_32_V2.pdf
+    # To find out the mask for GPIO 13, let us assign n as 13.
+    # 1. Subtract 1 from n (n is now 12)
+    # 2. Find the 2^n or 2^12 (4096)
+    # 3. Convert 4096 decimal to hex (0x1000)
+    # You can use python:
+    # >>> print(hex(2**12))
+    # 0x1000
+    sys.argv = ['', '--gpio-rd', '0x1000', '--dest', '!1234']
+    Globals.getInstance().set_args(sys.argv)
+
+    channel = Channel(index=1, role=1)
+    channel.settings.modem_config = 3
+    channel.settings.psk = b'\x01'
+
+    packet = {
+
+            'from': 682968668,
+            'to': 682968612,
+            'channel': 1,
+            'decoded': {
+                'portnum': 'REMOTE_HARDWARE_APP',
+                'payload': b'\x08\x05\x18\x80 ',
+                'requestId': 1629980484,
+                'remotehw': {
+                    'typ': 'READ_GPIOS_REPLY',
+                    'gpioValue': '4096',
+                    'raw': 'faked',
+                    'id': 1693085229,
+                    'rxTime': 1640294262,
+                    'rxSnr': 4.75,
+                    'hopLimit': 3,
+                    'wantAck': True,
+                    }
+                }
+            }
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.getChannelByName.return_value = channel
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with caplog.at_level(logging.DEBUG):
+            main()
+            onGPIOreceive(packet, mo)
+    assert re.search(r'readGPIOs nodeid:!1234 mask:4096', caplog.text, re.MULTILINE)
+    out, err = capsys.readouterr()
+    assert re.search(r'Connected to radio', out, re.MULTILINE)
+    assert re.search(r'Reading GPIO mask 0x1000 ', out, re.MULTILINE)
+    assert re.search(r'Received RemoteHardware typ=READ_GPIOS_REPLY, gpio_value=4096', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_gpio_rd_no_dest(capsys, reset_globals) +
+
+

Test –gpio_rd with a named gpio channel but no dest was specified

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_gpio_rd_no_dest(capsys, reset_globals):
+    """Test --gpio_rd with a named gpio channel but no dest was specified"""
+    sys.argv = ['', '--gpio-rd', '0x2000']
+    Globals.getInstance().set_args(sys.argv)
+
+    channel = Channel(index=1, role=1)
+    channel.settings.modem_config = 3
+    channel.settings.psk = b'\x01'
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.getChannelByName.return_value = channel
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Warning: Must use a destination node ID', out)
+        assert err == ''
+
+
+
+def test_main_gpio_rd_no_gpio_channel(capsys, reset_globals) +
+
+

Test –gpio_rd with no named gpio channel

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_gpio_rd_no_gpio_channel(capsys, reset_globals):
+    """Test --gpio_rd with no named gpio channel"""
+    sys.argv = ['', '--gpio-rd', '0x10']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.getChannelByName.return_value = None
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Warning: No channel named', out)
+        assert err == ''
+
+
+
+def test_main_info(capsys, reset_globals) +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_info(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_info_with_ble_interface(capsys, reset_globals) +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_info_with_ble_interface(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--ble', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=BLEInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.ble_interface.BLEInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_info_with_seriallog_output_txt(capsys, reset_globals) +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_info_with_seriallog_output_txt(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--seriallog', 'output.txt']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+    # do some cleanup
+    os.remove('output.txt')
+
+
+
+def test_main_info_with_seriallog_stdout(capsys, reset_globals) +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_info_with_seriallog_stdout(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--seriallog', 'stdout']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_info_with_tcp_interface(capsys, reset_globals) +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_info_with_tcp_interface(capsys, reset_globals):
+    """Test --info"""
+    sys.argv = ['', '--info', '--host', 'meshtastic.local']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=TCPInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+    with patch('meshtastic.tcp_interface.TCPInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_init_parser_no_args(capsys, reset_globals) +
+
+

Test no arguments

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_init_parser_no_args(capsys, reset_globals):
+    """Test no arguments"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+    initParser()
+    out, err = capsys.readouterr()
+    assert out == ''
+    assert err == ''
+
+
+
+def test_main_init_parser_version(capsys, reset_globals) +
+
+

Test –version

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_init_parser_version(capsys, reset_globals):
+    """Test --version"""
+    sys.argv = ['', '--version']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        initParser()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    out, err = capsys.readouterr()
+    assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
+    assert err == ''
+
+
+
+def test_main_main_no_args(reset_globals) +
+
+

Test with no args

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_main_no_args(reset_globals):
+    """Test with no args"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_main_main_version(capsys, reset_globals) +
+
+

Test –version

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_main_version(capsys, reset_globals):
+    """Test --version"""
+    sys.argv = ['', '--version']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    out, err = capsys.readouterr()
+    assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out)
+    assert err == ''
+
+
+
+def test_main_no_proto(capsys, reset_globals) +
+
+

Test –noproto (using –info for output)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_no_proto(capsys, reset_globals):
+    """Test --noproto (using --info for output)"""
+    sys.argv = ['', '--info', '--noproto']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showInfo():
+        print('inside mocked showInfo')
+    iface.showInfo.side_effect = mock_showInfo
+
+    # Override the time.sleep so there is no loop
+    def my_sleep(amount):
+        sys.exit(0)
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with patch('time.sleep', side_effect=my_sleep):
+            with pytest.raises(SystemExit) as pytest_wrapped_e:
+                main()
+            assert pytest_wrapped_e.type == SystemExit
+            assert pytest_wrapped_e.value.code == 0
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
+            assert err == ''
+
+
+
+def test_main_nodes(capsys, reset_globals) +
+
+

Test –nodes

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_nodes(capsys, reset_globals):
+    """Test --nodes"""
+    sys.argv = ['', '--nodes']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_showNodes():
+        print('inside mocked showNodes')
+    iface.showNodes.side_effect = mock_showNodes
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked showNodes', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_onConnection(reset_globals, capsys) +
+
+

Test onConnection

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_onConnection(reset_globals, capsys):
+    """Test onConnection"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+    iface = MagicMock(autospec=SerialInterface)
+    class TempTopic:
+        """ temp class for topic """
+        def getName(self):
+            """ return the fake name of a topic"""
+            return 'foo'
+    mytopic = TempTopic()
+    onConnection(iface, mytopic)
+    out, err = capsys.readouterr()
+    assert re.search(r'Connection changed: foo', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_onReceive_empty(caplog, reset_globals) +
+
+

Test onReceive

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_onReceive_empty(caplog, reset_globals):
+    """Test onReceive"""
+    sys.argv = ['']
+    Globals.getInstance().set_args(sys.argv)
+    iface = MagicMock(autospec=SerialInterface)
+    packet = {'decoded': 'foo'}
+    with caplog.at_level(logging.DEBUG):
+        onReceive(packet, iface)
+    assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
+
+
+
+def test_main_onReceive_with_reply(caplog, capsys, reset_globals) +
+
+

Test onReceive with a reply +To capture: on one device run '–sendtext aaa –reply' and on another +device run '–sendtext bbb –reply', then back to the first device and +run '–sendtext aaa2 –reply'. You should now see a "Sending reply" message.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_onReceive_with_reply(caplog, capsys, reset_globals):
+    """Test onReceive with a reply
+       To capture: on one device run '--sendtext aaa --reply' and on another
+       device run '--sendtext bbb --reply', then back to the first device and
+       run '--sendtext aaa2 --reply'. You should now see a "Sending reply" message.
+    """
+    sys.argv = ['', '--sendtext', 'hello', '--reply']
+    Globals.getInstance().set_args(sys.argv)
+
+    # Note: 'TEXT_MESSAGE_APP' value is 1
+
+    send_packet = {
+            'to': 4294967295,
+            'decoded': {
+                'portnum': 1,
+                'payload': "hello"
+                },
+            'id': 334776977,
+            'hop_limit': 3,
+            'want_ack': True
+            }
+
+    reply_packet = {
+            'from': 682968668,
+            'to': 4294967295,
+            'decoded': {
+                'portnum': 'TEXT_MESSAGE_APP',
+                'payload': b'bbb',
+                'text': 'bbb'
+                },
+            'id': 1709936182,
+            'rxTime': 1640381999,
+            'rxSnr': 6.0,
+            'hopLimit': 3,
+            'raw': 'faked',
+            'fromId': '!28b5465c',
+            'toId': '^all'
+            }
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.myInfo.my_node_num = 4294967295
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with caplog.at_level(logging.DEBUG):
+            main()
+            onReceive(send_packet, iface)
+            onReceive(reply_packet, iface)
+        assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
+        out, err = capsys.readouterr()
+        assert re.search(r'got msg ', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_onReceive_with_sendtext(caplog, reset_globals) +
+
+

Test onReceive with sendtext +The entire point of this test is to make sure the interface.close() call +is made in onReceive().

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_onReceive_with_sendtext(caplog, reset_globals):
+    """Test onReceive with sendtext
+       The entire point of this test is to make sure the interface.close() call
+       is made in onReceive().
+    """
+    sys.argv = ['', '--sendtext', 'hello']
+    Globals.getInstance().set_args(sys.argv)
+
+    # Note: 'TEXT_MESSAGE_APP' value is 1
+    packet = {
+            'to': 4294967295,
+            'decoded': {
+                'portnum': 1,
+                'payload': "hello"
+                },
+            'id': 334776977,
+            'hop_limit': 3,
+            'want_ack': True
+            }
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.myInfo.my_node_num = 4294967295
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with caplog.at_level(logging.DEBUG):
+            main()
+            onReceive(packet, iface)
+        assert re.search(r'in onReceive', caplog.text, re.MULTILINE)
+        mo.assert_called()
+
+
+
+def test_main_pos_fields_arg_of_zero(capsys, reset_globals) +
+
+

Test –pos-fields an arg of 0 (which shows list)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_pos_fields_arg_of_zero(capsys, reset_globals):
+    """Test --pos-fields an arg of 0 (which shows list)"""
+    sys.argv = ['', '--pos-fields', '0']
+    Globals.getInstance().set_args(sys.argv)
+
+    pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+        with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
+
+            def throw_value_error_exception(exc):
+                raise ValueError()
+            mrc.Value.side_effect = throw_value_error_exception
+            mrc.keys.return_value = [ 'POS_UNDEFINED', 'POS_ALTITUDE', 'POS_ALT_MSL',
+                                      'POS_GEO_SEP', 'POS_DOP', 'POS_HVDOP', 'POS_BATTERY',
+                                      'POS_SATINVIEW', 'POS_SEQ_NOS', 'POS_TIMESTAMP']
+
+            main()
+
+            mrc.Value.assert_called()
+            mrc.keys.assert_called()
+            mo.assert_called()
+
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'ERROR: supported position fields are:', out, re.MULTILINE)
+            assert re.search(r"['POS_UNDEFINED', 'POS_ALTITUDE', 'POS_ALT_MSL', 'POS_GEO_SEP',"\
+                              "'POS_DOP', 'POS_HVDOP', 'POS_BATTERY', 'POS_SATINVIEW', 'POS_SEQ_NOS',"\
+                              "'POS_TIMESTAMP']", out, re.MULTILINE)
+            assert err == ''
+
+
+
+def test_main_pos_fields_no_args(capsys, reset_globals) +
+
+

Test –pos-fields no args (which shows settings)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_pos_fields_no_args(capsys, reset_globals):
+    """Test --pos-fields no args (which shows settings)"""
+    sys.argv = ['', '--pos-fields']
+    Globals.getInstance().set_args(sys.argv)
+
+    pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+        with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
+            # kind of cheating here, we are setting up the node
+            mocked_node = MagicMock(autospec=Node)
+            anode = mocked_node()
+            anode.radioConfig.preferences.position_flags = 35
+            Globals.getInstance().set_target_node(anode)
+
+            mrc.values.return_value = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256]
+            # Note: When you use side_effect and a list, each call will use a value from the front of the list then
+            # remove that value from the list. If there are three values in the list, we expect it to be called
+            # three times.
+            mrc.Name.side_effect = [ 'POS_ALTITUDE', 'POS_ALT_MSL', 'POS_BATTERY' ]
+
+            main()
+
+            mrc.Name.assert_called()
+            mrc.values.assert_called()
+            mo.assert_called()
+
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'POS_ALTITUDE POS_ALT_MSL POS_BATTERY', out, re.MULTILINE)
+            assert err == ''
+
+
+
+def test_main_pos_fields_valid_values(capsys, reset_globals) +
+
+

Test –pos-fields with valid values

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_pos_fields_valid_values(capsys, reset_globals):
+    """Test --pos-fields with valid values"""
+    sys.argv = ['', '--pos-fields', 'POS_GEO_SEP', 'POS_ALT_MSL']
+    Globals.getInstance().set_args(sys.argv)
+
+    pos_flags = MagicMock(autospec=meshtastic.radioconfig_pb2.PositionFlags)
+
+    with patch('meshtastic.serial_interface.SerialInterface') as mo:
+        with patch('meshtastic.radioconfig_pb2.PositionFlags', return_value=pos_flags) as mrc:
+
+            mrc.Value.side_effect = [ 4, 2 ]
+
+            main()
+
+            mrc.Value.assert_called()
+            mo.assert_called()
+
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'Setting position fields to 6', out, re.MULTILINE)
+            assert re.search(r'Set position_flags to 6', out, re.MULTILINE)
+            assert re.search(r'Writing modified preferences to device', out, re.MULTILINE)
+            assert err == ''
+
+
+
+def test_main_qr(capsys, reset_globals) +
+
+

Test –qr

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_qr(capsys, reset_globals):
+    """Test --qr"""
+    sys.argv = ['', '--qr']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    # TODO: could mock/check url
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Primary channel URL', out, re.MULTILINE)
+        # if a qr code is generated it will have lots of these
+        assert re.search(r'\[7m', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_reboot(capsys, reset_globals) +
+
+

Test –reboot

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_reboot(capsys, reset_globals):
+    """Test --reboot"""
+    sys.argv = ['', '--reboot']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_reboot():
+        print('inside mocked reboot')
+    mocked_node.reboot.side_effect = mock_reboot
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'inside mocked reboot', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_sendping(capsys, reset_globals) +
+
+

Test –sendping

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_sendping(capsys, reset_globals):
+    """Test --sendping"""
+    sys.argv = ['', '--sendping']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendData(payload, dest, portNum, wantAck, wantResponse):
+        print('inside mocked sendData')
+    iface.sendData.side_effect = mock_sendData
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending ping message', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendData', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_sendtext(capsys, reset_globals) +
+
+

Test –sendtext

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_sendtext(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendText(text, dest, wantAck, channelIndex):
+        print('inside mocked sendText')
+    iface.sendText.side_effect = mock_sendText
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending text message', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendText', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_sendtext_with_channel(capsys, reset_globals) +
+
+

Test –sendtext

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_sendtext_with_channel(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendText(text, dest, wantAck, channelIndex):
+        print('inside mocked sendText')
+    iface.sendText.side_effect = mock_sendText
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending text message', out, re.MULTILINE)
+        assert re.search(r'on channelIndex:1', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendText', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_sendtext_with_dest(capsys, reset_globals) +
+
+

Test –sendtext with –dest

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_sendtext_with_dest(capsys, reset_globals):
+    """Test --sendtext with --dest"""
+    sys.argv = ['', '--sendtext', 'hello', '--dest', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendText(text, dest, wantAck, channelIndex):
+        print('inside mocked sendText')
+    iface.sendText.side_effect = mock_sendText
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Sending text message', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendText', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_sendtext_with_invalid_channel(capsys, reset_globals) +
+
+

Test –sendtext

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_sendtext_with_invalid_channel(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getChannelByChannelIndex.return_value = None
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        iface.getNode.return_value.getChannelByChannelIndex.return_value = None
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'is not a valid channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_sendtext_with_invalid_channel_nine(capsys, reset_globals) +
+
+

Test –sendtext

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_sendtext_with_invalid_channel_nine(capsys, reset_globals):
+    """Test --sendtext"""
+    sys.argv = ['', '--sendtext', 'hello', '--ch-index', '9']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        iface.getNode.return_value.getChannelByChannelIndex.return_value = None
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'is not a valid channel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_setPref_invalid_field(capsys, reset_globals) +
+
+

Test setPref() with a invalid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_setPref_invalid_field(capsys, reset_globals):
+    """Test setPref() with a invalid field"""
+
+
+    class Field:
+        """Simple class for testing."""
+
+        def __init__(self, name):
+            """constructor"""
+            self.name = name
+
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    # Note: This is a subset of the real fields
+    ls_secs_field = Field('ls_secs')
+    is_router = Field('is_router')
+    fixed_position = Field('fixed_position')
+
+    fields = [ ls_secs_field, is_router, fixed_position ]
+    prefs.DESCRIPTOR.fields = fields
+
+    setPref(prefs, 'foo', '300')
+    out, err = capsys.readouterr()
+    assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+    # ensure they are sorted
+    assert re.search(r'fixed_position\s+is_router\s+ls_secs', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_setPref_valid_field(capsys, reset_globals) +
+
+

Test setPref() with a valid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_setPref_valid_field(capsys, reset_globals):
+    """Test setPref() with a valid field"""
+
+    class Field:
+        """Simple class for testing."""
+
+        def __init__(self, name, enum_type):
+            """constructor"""
+            self.name = name
+            self.enum_type = enum_type
+
+    ls_secs_field = Field('ls_secs', 'int')
+    prefs = MagicMock()
+    prefs.DESCRIPTOR.fields_by_name.get.return_value = ls_secs_field
+
+    setPref(prefs, 'ls_secs', '300')
+    out, err = capsys.readouterr()
+    assert re.search(r'Set ls_secs to 300', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_set_ham_to_KI123(capsys, reset_globals) +
+
+

Test –set-ham KI123

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_set_ham_to_KI123(capsys, reset_globals):
+    """Test --set-ham KI123"""
+    sys.argv = ['', '--set-ham', 'KI123']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_turnOffEncryptionOnPrimaryChannel():
+        print('inside mocked turnOffEncryptionOnPrimaryChannel')
+    def mock_setOwner(name, is_licensed):
+        print('inside mocked setOwner')
+    mocked_node.turnOffEncryptionOnPrimaryChannel.side_effect = mock_turnOffEncryptionOnPrimaryChannel
+    mocked_node.setOwner.side_effect = mock_setOwner
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Setting Ham ID to KI123', out, re.MULTILINE)
+        assert re.search(r'inside mocked setOwner', out, re.MULTILINE)
+        assert re.search(r'inside mocked turnOffEncryptionOnPrimaryChannel', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_set_owner_to_bob(capsys, reset_globals) +
+
+

Test –set-owner bob

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_set_owner_to_bob(capsys, reset_globals):
+    """Test --set-owner bob"""
+    sys.argv = ['', '--set-owner', 'bob']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Setting device owner to bob', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_set_team_invalid(capsys, reset_globals) +
+
+

Test –set-team using an invalid team name

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_set_team_invalid(capsys, reset_globals):
+    """Test --set-team using an invalid team name"""
+    sys.argv = ['', '--set-team', 'NOTCYAN']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+
+    def throw_an_exception(exc):
+        raise ValueError("Fake exception.")
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with patch('meshtastic.mesh_pb2.Team') as mm:
+            mm.Value.side_effect = throw_an_exception
+            main()
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'ERROR: Team', out, re.MULTILINE)
+            assert err == ''
+            mo.assert_called()
+            mm.Value.assert_called()
+
+
+
+def test_main_set_team_valid(capsys, reset_globals) +
+
+

Test –set-team

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_set_team_valid(capsys, reset_globals):
+    """Test --set-team"""
+    sys.argv = ['', '--set-team', 'CYAN']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_setOwner(team):
+        print('inside mocked setOwner')
+    mocked_node.setOwner.side_effect = mock_setOwner
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with patch('meshtastic.mesh_pb2.Team') as mm:
+            mm.Name.return_value = 'FAKENAME'
+            mm.Value.return_value = 'FAKEVAL'
+            main()
+            out, err = capsys.readouterr()
+            assert re.search(r'Connected to radio', out, re.MULTILINE)
+            assert re.search(r'Setting team to', out, re.MULTILINE)
+            assert err == ''
+            mo.assert_called()
+            mm.Name.assert_called()
+            mm.Value.assert_called()
+
+
+
+def test_main_set_valid(capsys, reset_globals) +
+
+

Test –set with valid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_set_valid(capsys, reset_globals):
+    """Test --set with valid field"""
+    sys.argv = ['', '--set', 'wifi_ssid', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Set wifi_ssid to foo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_set_with_invalid(capsys, reset_globals) +
+
+

Test –set with invalid field

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_set_with_invalid(capsys, reset_globals):
+    """Test --set with invalid field"""
+    sys.argv = ['', '--set', 'foo', 'foo']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_user_prefs = MagicMock()
+    mocked_user_prefs.DESCRIPTOR.fields_by_name.get.return_value = None
+
+    mocked_node = MagicMock(autospec=Node)
+    mocked_node.radioConfig.preferences = ( mocked_user_prefs )
+
+    iface = MagicMock(autospec=SerialInterface)
+    iface.getNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_setalt(capsys, reset_globals) +
+
+

Test –setalt

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_setalt(capsys, reset_globals):
+    """Test --setalt"""
+    sys.argv = ['', '--setalt', '51']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_writeConfig():
+        print('inside mocked writeConfig')
+    mocked_node.writeConfig.side_effect = mock_writeConfig
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendPosition(lat, lon, alt):
+        print('inside mocked sendPosition')
+    iface.sendPosition.side_effect = mock_sendPosition
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Fixing altitude', out, re.MULTILINE)
+        assert re.search(r'Setting device position', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendPosition', out, re.MULTILINE)
+        # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_setchan(capsys, reset_globals) +
+
+

Test –setchan (deprecated)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_setchan(capsys, reset_globals):
+    """Test --setchan (deprecated)"""
+    sys.argv = ['', '--setchan', 'a', 'b']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            main()
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_main_setlat(capsys, reset_globals) +
+
+

Test –sendlat

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_setlat(capsys, reset_globals):
+    """Test --sendlat"""
+    sys.argv = ['', '--setlat', '37.5']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_writeConfig():
+        print('inside mocked writeConfig')
+    mocked_node.writeConfig.side_effect = mock_writeConfig
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendPosition(lat, lon, alt):
+        print('inside mocked sendPosition')
+    iface.sendPosition.side_effect = mock_sendPosition
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Fixing latitude', out, re.MULTILINE)
+        assert re.search(r'Setting device position', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendPosition', out, re.MULTILINE)
+        # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_setlon(capsys, reset_globals) +
+
+

Test –setlon

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_setlon(capsys, reset_globals):
+    """Test --setlon"""
+    sys.argv = ['', '--setlon', '-122.1']
+    Globals.getInstance().set_args(sys.argv)
+
+    mocked_node = MagicMock(autospec=Node)
+    def mock_writeConfig():
+        print('inside mocked writeConfig')
+    mocked_node.writeConfig.side_effect = mock_writeConfig
+
+    iface = MagicMock(autospec=SerialInterface)
+    def mock_sendPosition(lat, lon, alt):
+        print('inside mocked sendPosition')
+    iface.sendPosition.side_effect = mock_sendPosition
+    iface.localNode.return_value = mocked_node
+
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert re.search(r'Fixing longitude', out, re.MULTILINE)
+        assert re.search(r'Setting device position', out, re.MULTILINE)
+        assert re.search(r'inside mocked sendPosition', out, re.MULTILINE)
+        # TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_seturl(capsys, reset_globals) +
+
+

Test –seturl (url used below is what is generated after a factory_reset)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_seturl(capsys, reset_globals):
+    """Test --seturl (url used below is what is generated after a factory_reset)"""
+    sys.argv = ['', '--seturl', 'https://www.meshtastic.org/d/#CgUYAyIBAQ']
+    Globals.getInstance().set_args(sys.argv)
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        main()
+        out, err = capsys.readouterr()
+        assert re.search(r'Connected to radio', out, re.MULTILINE)
+        assert err == ''
+        mo.assert_called()
+
+
+
+def test_main_support(capsys, reset_globals) +
+
+

Test –support

+
+ +Expand source code + +
@pytest.mark.unit
+def test_main_support(capsys, reset_globals):
+    """Test --support"""
+    sys.argv = ['', '--support']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    out, err = capsys.readouterr()
+    assert re.search(r'System', out, re.MULTILINE)
+    assert re.search(r'Platform', out, re.MULTILINE)
+    assert re.search(r'Machine', out, re.MULTILINE)
+    assert re.search(r'Executable', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_main_test_no_ports(patched_find_ports, reset_globals) +
+
+

Test –test with no hardware

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=[])
+def test_main_test_no_ports(patched_find_ports, reset_globals):
+    """Test --test with no hardware"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    assert Globals.getInstance().get_target_node() is None
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    patched_find_ports.assert_called()
+
+
+
+def test_main_test_one_port(patched_find_ports, reset_globals) +
+
+

Test –test with one fake port

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1'])
+def test_main_test_one_port(patched_find_ports, reset_globals):
+    """Test --test with one fake port"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    assert Globals.getInstance().get_target_node() is None
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    patched_find_ports.assert_called()
+
+
+
+def test_main_test_two_ports_fails(patched_find_ports, patched_test_all, reset_globals) +
+
+

Test –test two fake ports and testAll() is a simulated failure

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.test.testAll', return_value=False)
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1', '/dev/ttyFake2'])
+def test_main_test_two_ports_fails(patched_find_ports, patched_test_all, reset_globals):
+    """Test --test two fake ports and testAll() is a simulated failure"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    # TODO: why does this fail? patched_find_ports.assert_called()
+    patched_test_all.assert_called()
+
+
+
+def test_main_test_two_ports_success(patched_find_ports, patched_test_all, reset_globals) +
+
+

Test –test two fake ports and testAll() is a simulated success

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.test.testAll', return_value=True)
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyFake1', '/dev/ttyFake2'])
+def test_main_test_two_ports_success(patched_find_ports, patched_test_all, reset_globals):
+    """Test --test two fake ports and testAll() is a simulated success"""
+    sys.argv = ['', '--test']
+    Globals.getInstance().set_args(sys.argv)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        main()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+    # TODO: why does this fail? patched_find_ports.assert_called()
+    patched_test_all.assert_called()
+
+
+
+
+
+

Classes

+
+
+class Channel +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var DISABLED
+
+
+
+
var INDEX_FIELD_NUMBER
+
+
+
+
var PRIMARY
+
+
+
+
var ROLE_FIELD_NUMBER
+
+
+
+
var Role
+
+
+
+
var SECONDARY
+
+
+
+
var SETTINGS_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var index
+
+

Getter for index.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var role
+
+

Getter for role.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var settings
+
+

Getter for settings.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_mesh_interface.html b/docs/meshtastic/tests/test_mesh_interface.html new file mode 100644 index 0000000..06b366e --- /dev/null +++ b/docs/meshtastic/tests/test_mesh_interface.html @@ -0,0 +1,1290 @@ + + + + + + +meshtastic.tests.test_mesh_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_mesh_interface

+
+
+

Meshtastic unit tests for mesh_interface.py

+
+ +Expand source code + +
"""Meshtastic unit tests for mesh_interface.py"""
+
+import re
+import logging
+
+from unittest.mock import patch, MagicMock
+import pytest
+
+from ..mesh_interface import MeshInterface
+from ..node import Node
+from .. import mesh_pb2
+from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR
+
+
+@pytest.mark.unit
+def test_MeshInterface(capsys, reset_globals):
+    """Test that we can instantiate a MeshInterface"""
+    iface = MeshInterface(noProto=True)
+    anode = Node('foo', 'bar')
+
+    nodes = {
+        '!9388f81c': {
+            'num': 2475227164,
+            'user': {
+                'id': '!9388f81c',
+                'longName': 'Unknown f81c',
+                'shortName': '?1C',
+                'macaddr': 'RBeTiPgc',
+                'hwModel': 'TBEAM'
+            },
+            'position': {},
+            'lastHeard': 1640204888
+        }
+    }
+
+    iface.nodesByNum = {1: anode }
+    iface.nodes = nodes
+
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+
+    iface.showInfo()
+    iface.localNode.showInfo()
+    iface.showNodes()
+    iface.sendText('hello')
+    iface.close()
+    out, err = capsys.readouterr()
+    assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
+    assert re.search(r'Nodes', out, re.MULTILINE)
+    assert re.search(r'Preferences', out, re.MULTILINE)
+    assert re.search(r'Channels', out, re.MULTILINE)
+    assert re.search(r'Primary channel URL', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_getMyUser(reset_globals, iface_with_nodes):
+    """Test getMyUser()"""
+    iface = iface_with_nodes
+
+    iface.myInfo.my_node_num = 2475227164
+    myuser = iface.getMyUser()
+    print(f'myuser:{myuser}')
+    assert myuser is not None
+    assert myuser["id"] == '!9388f81c'
+
+
+@pytest.mark.unit
+def test_getLongName(reset_globals, iface_with_nodes):
+    """Test getLongName()"""
+    iface = iface_with_nodes
+    iface.myInfo.my_node_num = 2475227164
+    mylongname = iface.getLongName()
+    assert mylongname == 'Unknown f81c'
+
+
+@pytest.mark.unit
+def test_getShortName(reset_globals, iface_with_nodes):
+    """Test getShortName()."""
+    iface = iface_with_nodes
+    iface.myInfo.my_node_num = 2475227164
+    myshortname = iface.getShortName()
+    assert myshortname == '?1C'
+
+
+@pytest.mark.unit
+def test_handlePacketFromRadio_no_from(capsys, reset_globals):
+    """Test _handlePacketFromRadio with no 'from' in the mesh packet."""
+    iface = MeshInterface(noProto=True)
+    meshPacket = mesh_pb2.MeshPacket()
+    iface._handlePacketFromRadio(meshPacket)
+    out, err = capsys.readouterr()
+    assert re.search(r'Device returned a packet we sent, ignoring', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals):
+    """Test _handlePacketFromRadio with a portnum
+       Since we have an attribute called 'from', we cannot simply 'set' it.
+       Had to implement a hack just to be able to test some code.
+    """
+    iface = MeshInterface(noProto=True)
+    meshPacket = mesh_pb2.MeshPacket()
+    meshPacket.decoded.payload = b''
+    meshPacket.decoded.portnum = 1
+    with caplog.at_level(logging.WARNING):
+        iface._handlePacketFromRadio(meshPacket, hack=True)
+    assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_handlePacketFromRadio_no_portnum(caplog, reset_globals):
+    """Test _handlePacketFromRadio without a portnum"""
+    iface = MeshInterface(noProto=True)
+    meshPacket = mesh_pb2.MeshPacket()
+    meshPacket.decoded.payload = b''
+    with caplog.at_level(logging.WARNING):
+        iface._handlePacketFromRadio(meshPacket, hack=True)
+    assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_getNode_with_local(reset_globals):
+    """Test getNode"""
+    iface = MeshInterface(noProto=True)
+    anode = iface.getNode(LOCAL_ADDR)
+    assert anode == iface.localNode
+
+
+@pytest.mark.unit
+def test_getNode_not_local(reset_globals, caplog):
+    """Test getNode not local"""
+    iface = MeshInterface(noProto=True)
+    anode = MagicMock(autospec=Node)
+    with caplog.at_level(logging.DEBUG):
+        with patch('meshtastic.node.Node', return_value=anode):
+            another_node = iface.getNode('bar2')
+            assert another_node != iface.localNode
+    assert re.search(r'About to requestConfig', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_getNode_not_local_timeout(reset_globals, capsys):
+    """Test getNode not local, simulate timeout"""
+    iface = MeshInterface(noProto=True)
+    anode = MagicMock(autospec=Node)
+    anode.waitForConfig.return_value = False
+    with patch('meshtastic.node.Node', return_value=anode):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            iface.getNode('bar2')
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.match(r'Error: Timed out waiting for node config', out)
+        assert err == ''
+
+
+@pytest.mark.unit
+def test_sendPosition(reset_globals, caplog):
+    """Test sendPosition"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface.sendPosition()
+    iface.close()
+    assert re.search(r'p.time:', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_handleFromRadio_empty_payload(reset_globals, caplog):
+    """Test _handleFromRadio"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._handleFromRadio(b'')
+    iface.close()
+    assert re.search(r'Unexpected FromRadio payload', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_handleFromRadio_with_my_info(reset_globals, caplog):
+    """Test _handleFromRadio with my_info"""
+    # Note: I captured the '--debug --info' for the bytes below.
+    # It "translates" to this:
+    # my_info {
+    #  my_node_num: 682584012
+    #  num_bands: 13
+    #  firmware_version: "1.2.49.5354c49"
+    #  reboot_count: 13
+    #  bitrate: 17.088470458984375
+    #  message_timeout_msec: 300000
+    #  min_app_version: 20200
+    #  max_channels: 8
+    # }
+    from_radio_bytes = b'\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._handleFromRadio(from_radio_bytes)
+    iface.close()
+    assert re.search(r'Received myinfo', caplog.text, re.MULTILINE)
+    assert re.search(r'num_bands: 13', caplog.text, re.MULTILINE)
+    assert re.search(r'max_channels: 8', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys):
+    """Test _handleFromRadio with node_info"""
+    # Note: I captured the '--debug --info' for the bytes below.
+    # It "translates" to this:
+    # node_info {
+    #  num: 682584012
+    #  user {
+    #    id: "!28af67cc"
+    #    long_name: "Unknown 67cc"
+    #    short_name: "?CC"
+    #    macaddr: "$o(\257g\314"
+    #    hw_model: HELTEC_V2_1
+    #  }
+    #  position {
+    #    }
+    #  }
+
+    from_radio_bytes = b'"2\x08\xcc\xcf\xbd\xc5\x02\x12(\n\t!28af67cc\x12\x0cUnknown 67cc\x1a\x03?CC"\x06$o(\xafg\xcc0\n\x1a\x00'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._startConfig()
+        iface._handleFromRadio(from_radio_bytes)
+        assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE)
+        assert re.search(r'682584012', caplog.text, re.MULTILINE)
+        assert re.search(r'HELTEC_V2_1', caplog.text, re.MULTILINE)
+        # validate some of showNodes() output
+        iface.showNodes()
+        out, err = capsys.readouterr()
+        assert re.search(r' 1 ', out, re.MULTILINE)
+        assert re.search(r'│ Unknown 67cc │ ', out, re.MULTILINE)
+        assert re.search(r'│ !28af67cc │ N/A   │ N/A         │ N/A', out, re.MULTILINE)
+        assert err == ''
+        iface.close()
+
+
+@pytest.mark.unit
+def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys):
+    """Test _handleFromRadio with node_info"""
+    # Note: Captured the '--debug --info' for the bytes below.
+    # pylint: disable=C0301
+    from_radio_bytes = b'"=\x08\x80\xf8\xc8\xf6\x07\x12"\n\t!7ed23c00\x12\x07TBeam 1\x1a\x02T1"\x06\x94\xb9~\xd2<\x000\x04\x1a\x07 ]MN\x01\xbea%\xad\x01\xbea=\x00\x00,A'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._startConfig()
+        iface._handleFromRadio(from_radio_bytes)
+        assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE)
+        assert re.search(r'TBeam 1', caplog.text, re.MULTILINE)
+        assert re.search(r'2127707136', caplog.text, re.MULTILINE)
+        # validate some of showNodes() output
+        iface.showNodes()
+        out, err = capsys.readouterr()
+        assert re.search(r' 1 ', out, re.MULTILINE)
+        assert re.search(r'│ TBeam 1 │ ', out, re.MULTILINE)
+        assert re.search(r'│ !7ed23c00 │', out, re.MULTILINE)
+        assert err == ''
+        iface.close()
+
+
+@pytest.mark.unit
+def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplog, capsys):
+    """Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
+    # Note: Captured the '--debug --info' for the bytes below.
+    from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._startConfig()
+        iface._handleFromRadio(from_radio_bytes)
+
+
+@pytest.mark.unit
+def test_MeshInterface_sendToRadioImpl(caplog, reset_globals):
+    """Test _sendToRadioImp()"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._sendToRadioImpl('foo')
+    assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+@pytest.mark.unit
+def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals):
+    """Test sendToRadio()"""
+    iface = MeshInterface()
+    with caplog.at_level(logging.DEBUG):
+        iface._sendToRadioImpl('foo')
+    assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+@pytest.mark.unit
+def test_sendData_too_long(caplog, reset_globals):
+    """Test when data payload is too big"""
+    iface = MeshInterface(noProto=True)
+    some_large_text = b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    with caplog.at_level(logging.DEBUG):
+        with pytest.raises(Exception) as pytest_wrapped_e:
+            iface.sendData(some_large_text)
+            assert re.search('Data payload too big', caplog.text, re.MULTILINE)
+        assert pytest_wrapped_e.type == Exception
+    iface.close()
+
+
+@pytest.mark.unit
+def test_sendData_unknown_app(capsys, reset_globals):
+    """Test sendData when unknown app"""
+    iface = MeshInterface(noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        iface.sendData(b'hello', portNum=0)
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: A non-zero port number', out, re.MULTILINE)
+    assert err == ''
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_sendPosition_with_a_position(caplog, reset_globals):
+    """Test sendPosition when lat/long/alt"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface.sendPosition(latitude=40.8, longitude=-111.86, altitude=201)
+        assert re.search(r'p.latitude_i:408', caplog.text, re.MULTILINE)
+        assert re.search(r'p.longitude_i:-11186', caplog.text, re.MULTILINE)
+        assert re.search(r'p.altitude:201', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_sendPacket_with_no_destination(capsys, reset_globals):
+    """Test _sendPacket()"""
+    iface = MeshInterface(noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        iface._sendPacket(b'', destinationId=None)
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: destinationId must not be None', out, re.MULTILINE)
+    assert err == ''
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_as_int(caplog, reset_globals):
+    """Test _sendPacket() with int as a destination"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=123)
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals):
+    """Test _sendPacket() with int as a destination"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId='!1234')
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals):
+    """Test _sendPacket() with BROADCAST_ADDR as a destination"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=BROADCAST_ADDR)
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globals):
+    """Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
+    iface = MeshInterface(noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: No myInfo', out, re.MULTILINE)
+    assert err == ''
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_globals):
+    """Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
+    iface = MeshInterface(noProto=True)
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals, iface_with_nodes):
+    """Test _sendPacket() with '' as a destination with myInfo"""
+    iface = iface_with_nodes
+    meshPacket = mesh_pb2.MeshPacket()
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        iface._sendPacket(meshPacket, destinationId='')
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.match(r'Warning: NodeId  not found in DB', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_globals, iface_with_nodes):
+    """Test _sendPacket() with '' as a destination with myInfo"""
+    iface = iface_with_nodes
+    iface.nodes = None
+    meshPacket = mesh_pb2.MeshPacket()
+    with caplog.at_level(logging.WARNING):
+        iface._sendPacket(meshPacket, destinationId='')
+    assert re.search(r'Warning: There were no self.nodes.', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_getMyNodeInfo(reset_globals):
+    """Test getMyNodeInfo()"""
+    iface = MeshInterface(noProto=True)
+    anode = iface.getNode(LOCAL_ADDR)
+    iface.nodesByNum = {1: anode }
+    assert iface.nodesByNum.get(1) == anode
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+    iface.myInfo.my_node_num = 1
+    myinfo = iface.getMyNodeInfo()
+    assert myinfo == anode
+
+
+@pytest.mark.unit
+def test_generatePacketId(capsys, reset_globals):
+    """Test _generatePacketId() when no currentPacketId (not connected)"""
+    iface = MeshInterface(noProto=True)
+    # not sure when this condition would ever happen... but we can simulate it
+    iface.currentPacketId = None
+    assert iface.currentPacketId is None
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        iface._generatePacketId()
+        out, err = capsys.readouterr()
+        assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE)
+        assert err == ''
+    assert pytest_wrapped_e.type == Exception
+
+
+
+
+
+
+
+

Functions

+
+
+def test_MeshInterface(capsys, reset_globals) +
+
+

Test that we can instantiate a MeshInterface

+
+ +Expand source code + +
@pytest.mark.unit
+def test_MeshInterface(capsys, reset_globals):
+    """Test that we can instantiate a MeshInterface"""
+    iface = MeshInterface(noProto=True)
+    anode = Node('foo', 'bar')
+
+    nodes = {
+        '!9388f81c': {
+            'num': 2475227164,
+            'user': {
+                'id': '!9388f81c',
+                'longName': 'Unknown f81c',
+                'shortName': '?1C',
+                'macaddr': 'RBeTiPgc',
+                'hwModel': 'TBEAM'
+            },
+            'position': {},
+            'lastHeard': 1640204888
+        }
+    }
+
+    iface.nodesByNum = {1: anode }
+    iface.nodes = nodes
+
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+
+    iface.showInfo()
+    iface.localNode.showInfo()
+    iface.showNodes()
+    iface.sendText('hello')
+    iface.close()
+    out, err = capsys.readouterr()
+    assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
+    assert re.search(r'Nodes', out, re.MULTILINE)
+    assert re.search(r'Preferences', out, re.MULTILINE)
+    assert re.search(r'Channels', out, re.MULTILINE)
+    assert re.search(r'Primary channel URL', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_MeshInterface_sendToRadioImpl(caplog, reset_globals) +
+
+

Test _sendToRadioImp()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_MeshInterface_sendToRadioImpl(caplog, reset_globals):
+    """Test _sendToRadioImp()"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._sendToRadioImpl('foo')
+    assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+
+def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals) +
+
+

Test sendToRadio()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_MeshInterface_sendToRadio_no_proto(caplog, reset_globals):
+    """Test sendToRadio()"""
+    iface = MeshInterface()
+    with caplog.at_level(logging.DEBUG):
+        iface._sendToRadioImpl('foo')
+    assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+
+def test_generatePacketId(capsys, reset_globals) +
+
+

Test _generatePacketId() when no currentPacketId (not connected)

+
+ +Expand source code + +
@pytest.mark.unit
+def test_generatePacketId(capsys, reset_globals):
+    """Test _generatePacketId() when no currentPacketId (not connected)"""
+    iface = MeshInterface(noProto=True)
+    # not sure when this condition would ever happen... but we can simulate it
+    iface.currentPacketId = None
+    assert iface.currentPacketId is None
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        iface._generatePacketId()
+        out, err = capsys.readouterr()
+        assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE)
+        assert err == ''
+    assert pytest_wrapped_e.type == Exception
+
+
+
+def test_getLongName(reset_globals, iface_with_nodes) +
+
+

Test getLongName()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getLongName(reset_globals, iface_with_nodes):
+    """Test getLongName()"""
+    iface = iface_with_nodes
+    iface.myInfo.my_node_num = 2475227164
+    mylongname = iface.getLongName()
+    assert mylongname == 'Unknown f81c'
+
+
+
+def test_getMyNodeInfo(reset_globals) +
+
+

Test getMyNodeInfo()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getMyNodeInfo(reset_globals):
+    """Test getMyNodeInfo()"""
+    iface = MeshInterface(noProto=True)
+    anode = iface.getNode(LOCAL_ADDR)
+    iface.nodesByNum = {1: anode }
+    assert iface.nodesByNum.get(1) == anode
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+    iface.myInfo.my_node_num = 1
+    myinfo = iface.getMyNodeInfo()
+    assert myinfo == anode
+
+
+
+def test_getMyUser(reset_globals, iface_with_nodes) +
+
+

Test getMyUser()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getMyUser(reset_globals, iface_with_nodes):
+    """Test getMyUser()"""
+    iface = iface_with_nodes
+
+    iface.myInfo.my_node_num = 2475227164
+    myuser = iface.getMyUser()
+    print(f'myuser:{myuser}')
+    assert myuser is not None
+    assert myuser["id"] == '!9388f81c'
+
+
+
+def test_getNode_not_local(reset_globals, caplog) +
+
+

Test getNode not local

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getNode_not_local(reset_globals, caplog):
+    """Test getNode not local"""
+    iface = MeshInterface(noProto=True)
+    anode = MagicMock(autospec=Node)
+    with caplog.at_level(logging.DEBUG):
+        with patch('meshtastic.node.Node', return_value=anode):
+            another_node = iface.getNode('bar2')
+            assert another_node != iface.localNode
+    assert re.search(r'About to requestConfig', caplog.text, re.MULTILINE)
+
+
+
+def test_getNode_not_local_timeout(reset_globals, capsys) +
+
+

Test getNode not local, simulate timeout

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getNode_not_local_timeout(reset_globals, capsys):
+    """Test getNode not local, simulate timeout"""
+    iface = MeshInterface(noProto=True)
+    anode = MagicMock(autospec=Node)
+    anode.waitForConfig.return_value = False
+    with patch('meshtastic.node.Node', return_value=anode):
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            iface.getNode('bar2')
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.match(r'Error: Timed out waiting for node config', out)
+        assert err == ''
+
+
+
+def test_getNode_with_local(reset_globals) +
+
+

Test getNode

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getNode_with_local(reset_globals):
+    """Test getNode"""
+    iface = MeshInterface(noProto=True)
+    anode = iface.getNode(LOCAL_ADDR)
+    assert anode == iface.localNode
+
+
+
+def test_getShortName(reset_globals, iface_with_nodes) +
+
+

Test getShortName().

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getShortName(reset_globals, iface_with_nodes):
+    """Test getShortName()."""
+    iface = iface_with_nodes
+    iface.myInfo.my_node_num = 2475227164
+    myshortname = iface.getShortName()
+    assert myshortname == '?1C'
+
+
+
+def test_handleFromRadio_empty_payload(reset_globals, caplog) +
+
+

Test _handleFromRadio

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handleFromRadio_empty_payload(reset_globals, caplog):
+    """Test _handleFromRadio"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._handleFromRadio(b'')
+    iface.close()
+    assert re.search(r'Unexpected FromRadio payload', caplog.text, re.MULTILINE)
+
+
+
+def test_handleFromRadio_with_my_info(reset_globals, caplog) +
+
+

Test _handleFromRadio with my_info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handleFromRadio_with_my_info(reset_globals, caplog):
+    """Test _handleFromRadio with my_info"""
+    # Note: I captured the '--debug --info' for the bytes below.
+    # It "translates" to this:
+    # my_info {
+    #  my_node_num: 682584012
+    #  num_bands: 13
+    #  firmware_version: "1.2.49.5354c49"
+    #  reboot_count: 13
+    #  bitrate: 17.088470458984375
+    #  message_timeout_msec: 300000
+    #  min_app_version: 20200
+    #  max_channels: 8
+    # }
+    from_radio_bytes = b'\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._handleFromRadio(from_radio_bytes)
+    iface.close()
+    assert re.search(r'Received myinfo', caplog.text, re.MULTILINE)
+    assert re.search(r'num_bands: 13', caplog.text, re.MULTILINE)
+    assert re.search(r'max_channels: 8', caplog.text, re.MULTILINE)
+
+
+
+def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys) +
+
+

Test _handleFromRadio with node_info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handleFromRadio_with_node_info(reset_globals, caplog, capsys):
+    """Test _handleFromRadio with node_info"""
+    # Note: I captured the '--debug --info' for the bytes below.
+    # It "translates" to this:
+    # node_info {
+    #  num: 682584012
+    #  user {
+    #    id: "!28af67cc"
+    #    long_name: "Unknown 67cc"
+    #    short_name: "?CC"
+    #    macaddr: "$o(\257g\314"
+    #    hw_model: HELTEC_V2_1
+    #  }
+    #  position {
+    #    }
+    #  }
+
+    from_radio_bytes = b'"2\x08\xcc\xcf\xbd\xc5\x02\x12(\n\t!28af67cc\x12\x0cUnknown 67cc\x1a\x03?CC"\x06$o(\xafg\xcc0\n\x1a\x00'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._startConfig()
+        iface._handleFromRadio(from_radio_bytes)
+        assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE)
+        assert re.search(r'682584012', caplog.text, re.MULTILINE)
+        assert re.search(r'HELTEC_V2_1', caplog.text, re.MULTILINE)
+        # validate some of showNodes() output
+        iface.showNodes()
+        out, err = capsys.readouterr()
+        assert re.search(r' 1 ', out, re.MULTILINE)
+        assert re.search(r'│ Unknown 67cc │ ', out, re.MULTILINE)
+        assert re.search(r'│ !28af67cc │ N/A   │ N/A         │ N/A', out, re.MULTILINE)
+        assert err == ''
+        iface.close()
+
+
+
+def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys) +
+
+

Test _handleFromRadio with node_info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handleFromRadio_with_node_info_tbeam1(reset_globals, caplog, capsys):
+    """Test _handleFromRadio with node_info"""
+    # Note: Captured the '--debug --info' for the bytes below.
+    # pylint: disable=C0301
+    from_radio_bytes = b'"=\x08\x80\xf8\xc8\xf6\x07\x12"\n\t!7ed23c00\x12\x07TBeam 1\x1a\x02T1"\x06\x94\xb9~\xd2<\x000\x04\x1a\x07 ]MN\x01\xbea%\xad\x01\xbea=\x00\x00,A'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._startConfig()
+        iface._handleFromRadio(from_radio_bytes)
+        assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE)
+        assert re.search(r'TBeam 1', caplog.text, re.MULTILINE)
+        assert re.search(r'2127707136', caplog.text, re.MULTILINE)
+        # validate some of showNodes() output
+        iface.showNodes()
+        out, err = capsys.readouterr()
+        assert re.search(r' 1 ', out, re.MULTILINE)
+        assert re.search(r'│ TBeam 1 │ ', out, re.MULTILINE)
+        assert re.search(r'│ !7ed23c00 │', out, re.MULTILINE)
+        assert err == ''
+        iface.close()
+
+
+
+def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplog, capsys) +
+
+

Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handleFromRadio_with_node_info_tbeam_with_bad_data(reset_globals, caplog, capsys):
+    """Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
+    # Note: Captured the '--debug --info' for the bytes below.
+    from_radio_bytes = b'"\x17\x08\xdc\x8a\x8a\xae\x02\x12\x08"\x06\x00\x00\x00\x00\x00\x00\x1a\x00=\x00\x00\xb8@'
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface._startConfig()
+        iface._handleFromRadio(from_radio_bytes)
+
+
+
+def test_handlePacketFromRadio_no_from(capsys, reset_globals) +
+
+

Test _handlePacketFromRadio with no 'from' in the mesh packet.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handlePacketFromRadio_no_from(capsys, reset_globals):
+    """Test _handlePacketFromRadio with no 'from' in the mesh packet."""
+    iface = MeshInterface(noProto=True)
+    meshPacket = mesh_pb2.MeshPacket()
+    iface._handlePacketFromRadio(meshPacket)
+    out, err = capsys.readouterr()
+    assert re.search(r'Device returned a packet we sent, ignoring', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_handlePacketFromRadio_no_portnum(caplog, reset_globals) +
+
+

Test _handlePacketFromRadio without a portnum

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handlePacketFromRadio_no_portnum(caplog, reset_globals):
+    """Test _handlePacketFromRadio without a portnum"""
+    iface = MeshInterface(noProto=True)
+    meshPacket = mesh_pb2.MeshPacket()
+    meshPacket.decoded.payload = b''
+    with caplog.at_level(logging.WARNING):
+        iface._handlePacketFromRadio(meshPacket, hack=True)
+    assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
+
+
+
+def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals) +
+
+

Test _handlePacketFromRadio with a portnum +Since we have an attribute called 'from', we cannot simply 'set' it. +Had to implement a hack just to be able to test some code.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_handlePacketFromRadio_with_a_portnum(caplog, reset_globals):
+    """Test _handlePacketFromRadio with a portnum
+       Since we have an attribute called 'from', we cannot simply 'set' it.
+       Had to implement a hack just to be able to test some code.
+    """
+    iface = MeshInterface(noProto=True)
+    meshPacket = mesh_pb2.MeshPacket()
+    meshPacket.decoded.payload = b''
+    meshPacket.decoded.portnum = 1
+    with caplog.at_level(logging.WARNING):
+        iface._handlePacketFromRadio(meshPacket, hack=True)
+    assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE)
+
+
+
+def test_sendData_too_long(caplog, reset_globals) +
+
+

Test when data payload is too big

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendData_too_long(caplog, reset_globals):
+    """Test when data payload is too big"""
+    iface = MeshInterface(noProto=True)
+    some_large_text = b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    some_large_text += b'This is a long text that will be too long for send text.'
+    with caplog.at_level(logging.DEBUG):
+        with pytest.raises(Exception) as pytest_wrapped_e:
+            iface.sendData(some_large_text)
+            assert re.search('Data payload too big', caplog.text, re.MULTILINE)
+        assert pytest_wrapped_e.type == Exception
+    iface.close()
+
+
+
+def test_sendData_unknown_app(capsys, reset_globals) +
+
+

Test sendData when unknown app

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendData_unknown_app(capsys, reset_globals):
+    """Test sendData when unknown app"""
+    iface = MeshInterface(noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        iface.sendData(b'hello', portNum=0)
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: A non-zero port number', out, re.MULTILINE)
+    assert err == ''
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals) +
+
+

Test _sendPacket() with BROADCAST_ADDR as a destination

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog, reset_globals):
+    """Test _sendPacket() with BROADCAST_ADDR as a destination"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=BROADCAST_ADDR)
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+
+def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globals) +
+
+

Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys, reset_globals):
+    """Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
+    iface = MeshInterface(noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: No myInfo', out, re.MULTILINE)
+    assert err == ''
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_globals) +
+
+

Test _sendPacket() with LOCAL_ADDR as a destination with myInfo

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog, reset_globals):
+    """Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
+    iface = MeshInterface(noProto=True)
+    myInfo = MagicMock()
+    iface.myInfo = myInfo
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+
+def test_sendPacket_with_destination_as_int(caplog, reset_globals) +
+
+

Test _sendPacket() with int as a destination

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_as_int(caplog, reset_globals):
+    """Test _sendPacket() with int as a destination"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId=123)
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+
+def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals, iface_with_nodes) +
+
+

Test _sendPacket() with '' as a destination with myInfo

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_is_blank_with_nodes(capsys, reset_globals, iface_with_nodes):
+    """Test _sendPacket() with '' as a destination with myInfo"""
+    iface = iface_with_nodes
+    meshPacket = mesh_pb2.MeshPacket()
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        iface._sendPacket(meshPacket, destinationId='')
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.match(r'Warning: NodeId  not found in DB', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_globals, iface_with_nodes) +
+
+

Test _sendPacket() with '' as a destination with myInfo

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_is_blank_without_nodes(caplog, reset_globals, iface_with_nodes):
+    """Test _sendPacket() with '' as a destination with myInfo"""
+    iface = iface_with_nodes
+    iface.nodes = None
+    meshPacket = mesh_pb2.MeshPacket()
+    with caplog.at_level(logging.WARNING):
+        iface._sendPacket(meshPacket, destinationId='')
+    assert re.search(r'Warning: There were no self.nodes.', caplog.text, re.MULTILINE)
+
+
+
+def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals) +
+
+

Test _sendPacket() with int as a destination

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_destination_starting_with_a_bang(caplog, reset_globals):
+    """Test _sendPacket() with int as a destination"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        meshPacket = mesh_pb2.MeshPacket()
+        iface._sendPacket(meshPacket, destinationId='!1234')
+        assert re.search(r'Not sending packet', caplog.text, re.MULTILINE)
+
+
+
+def test_sendPacket_with_no_destination(capsys, reset_globals) +
+
+

Test _sendPacket()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPacket_with_no_destination(capsys, reset_globals):
+    """Test _sendPacket()"""
+    iface = MeshInterface(noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        iface._sendPacket(b'', destinationId=None)
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: destinationId must not be None', out, re.MULTILINE)
+    assert err == ''
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_sendPosition(reset_globals, caplog) +
+
+

Test sendPosition

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPosition(reset_globals, caplog):
+    """Test sendPosition"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface.sendPosition()
+    iface.close()
+    assert re.search(r'p.time:', caplog.text, re.MULTILINE)
+
+
+
+def test_sendPosition_with_a_position(caplog, reset_globals) +
+
+

Test sendPosition when lat/long/alt

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendPosition_with_a_position(caplog, reset_globals):
+    """Test sendPosition when lat/long/alt"""
+    iface = MeshInterface(noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        iface.sendPosition(latitude=40.8, longitude=-111.86, altitude=201)
+        assert re.search(r'p.latitude_i:408', caplog.text, re.MULTILINE)
+        assert re.search(r'p.longitude_i:-11186', caplog.text, re.MULTILINE)
+        assert re.search(r'p.altitude:201', caplog.text, re.MULTILINE)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_node.html b/docs/meshtastic/tests/test_node.html new file mode 100644 index 0000000..e5196ed --- /dev/null +++ b/docs/meshtastic/tests/test_node.html @@ -0,0 +1,4143 @@ + + + + + + +meshtastic.tests.test_node API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_node

+
+
+

Meshtastic unit tests for node.py

+
+ +Expand source code + +
"""Meshtastic unit tests for node.py"""
+
+import re
+import logging
+
+from unittest.mock import patch, MagicMock
+import pytest
+
+from ..node import Node
+from ..serial_interface import SerialInterface
+from ..admin_pb2 import AdminMessage
+from ..channel_pb2 import Channel
+from ..radioconfig_pb2 import RadioConfig
+
+
+@pytest.mark.unit
+def test_node(capsys):
+    """Test that we can instantiate a Node"""
+    anode = Node('foo', 'bar')
+    radioConfig = RadioConfig()
+    anode.radioConfig = radioConfig
+    anode.showChannels()
+    anode.showInfo()
+    out, err = capsys.readouterr()
+    assert re.search(r'Preferences', out)
+    assert re.search(r'Channels', out)
+    assert re.search(r'Primary channel URL', out)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_node_reqquestConfig():
+    """Test run requestConfig"""
+    iface = MagicMock(autospec=SerialInterface)
+    amesg = MagicMock(autospec=AdminMessage)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg):
+            anode = Node(mo, 'bar')
+            anode.requestConfig()
+
+
+@pytest.mark.unit
+def test_setOwner_and_team(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='Test123', short_name='123', team=1)
+    assert re.search(r'p.set_owner.long_name:Test123:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:123:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:1', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_setOwner_no_short_name(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='Test123')
+    assert re.search(r'p.set_owner.long_name:Test123:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:Tst:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_setOwner_no_short_name_and_long_name_is_short(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='Tnt')
+    assert re.search(r'p.set_owner.long_name:Tnt:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:Tnt:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_setOwner_no_short_name_and_long_name_has_words(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='A B C', is_licensed=True)
+    assert re.search(r'p.set_owner.long_name:A B C:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:ABC:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:True', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_exitSimulator(caplog):
+    """Test exitSimulator"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.exitSimulator()
+    assert re.search(r'in exitSimulator', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_reboot(caplog):
+    """Test reboot"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.reboot()
+    assert re.search(r'Telling node to reboot', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_setURL_empty_url():
+    """Test reboot"""
+    anode = Node('foo', 'bar', noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode.setURL('')
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_setURL_valid_URL(caplog):
+    """Test setURL"""
+    iface = MagicMock(autospec=SerialInterface)
+    url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
+    with caplog.at_level(logging.DEBUG):
+        anode = Node(iface, 'bar', noProto=True)
+        anode.radioConfig = 'baz'
+        channels = ['zoo']
+        anode.channels = channels
+        anode.setURL(url)
+    assert re.search(r'Channel i:0', caplog.text, re.MULTILINE)
+    assert re.search(r'modem_config: Bw125Cr48Sf4096', caplog.text, re.MULTILINE)
+    assert re.search(r'psk: "\\001"', caplog.text, re.MULTILINE)
+    assert re.search(r'role: PRIMARY', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_setURL_valid_URL_but_no_settings(caplog):
+    """Test setURL"""
+    iface = MagicMock(autospec=SerialInterface)
+    url = "https://www.meshtastic.org/d/#"
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode = Node(iface, 'bar', noProto=True)
+        anode.radioConfig = 'baz'
+        anode.setURL(url)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_showChannels(capsys):
+    """Test showChannels"""
+    anode = Node('foo', 'bar')
+
+    # primary channel
+    # role: 0=Disabled, 1=Primary, 2=Secondary
+    # modem_config: 0-5
+    # role: 0=Disabled, 1=Primary, 2=Secondary
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testing'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    anode.showChannels()
+    out, err = capsys.readouterr()
+    assert re.search(r'Channels:', out, re.MULTILINE)
+    # primary channel
+    assert re.search(r'Primary channel URL', out, re.MULTILINE)
+    assert re.search(r'PRIMARY psk=default ', out, re.MULTILINE)
+    assert re.search(r'"modemConfig": "Bw125Cr48Sf4096"', out, re.MULTILINE)
+    assert re.search(r'"psk": "AQ=="', out, re.MULTILINE)
+    # secondary channel
+    assert re.search(r'SECONDARY psk=secret ', out, re.MULTILINE)
+    assert re.search(r'"psk": "ipR5DsbJHjWREkCmMKi0M4cA8ksO539Bes31sJAwqDQ="', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_getChannelByChannelIndex():
+    """Test getChannelByChannelIndex()"""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1) # primary channel
+    channel2 = Channel(index=2, role=2) # secondary channel
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+
+    # test primary
+    assert anode.getChannelByChannelIndex(0) is not None
+    # test secondary
+    assert anode.getChannelByChannelIndex(1) is not None
+    # test disabled
+    assert anode.getChannelByChannelIndex(2) is not None
+    # test invalid values
+    assert anode.getChannelByChannelIndex(-1) is None
+    assert anode.getChannelByChannelIndex(9) is None
+
+
+@pytest.mark.unit
+def test_deleteChannel_try_to_delete_primary_channel(capsys):
+    """Try to delete primary channel."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    # no secondary channels
+    channel2 = Channel(index=2, role=0)
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode.deleteChannel(0)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: Only SECONDARY channels can be deleted', out, re.MULTILINE)
+    assert err == ''
+
+@pytest.mark.unit
+def test_deleteChannel_secondary():
+    """Try to delete a secondary channel."""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testing'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        anode.channels = channels
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'testing'
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+        anode.deleteChannel(1)
+
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == ''
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+
+@pytest.mark.unit
+def test_deleteChannel_secondary_with_admin_channel_after_testing():
+    """Try to delete a secondary channel where there is an admin channel."""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testing'
+
+    channel3 = Channel(index=3, role=2)
+    channel3.settings.name = 'admin'
+
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        assert mo.localNode == anode
+
+        anode.channels = channels
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'testing'
+        assert channels[2].settings.name == 'admin'
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+        anode.deleteChannel(1)
+
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'admin'
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+
+@pytest.mark.unit
+def test_deleteChannel_secondary_with_admin_channel_before_testing():
+    """Try to delete a secondary channel where there is an admin channel."""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=2)
+    channel3.settings.name = 'testing'
+
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        anode.channels = channels
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'admin'
+        assert channels[2].settings.name == 'testing'
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+        anode.deleteChannel(2)
+
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'admin'
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+
+@pytest.mark.unit
+def test_getChannelByName(capsys):
+    """Get a channel by the name."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getChannelByName('admin')
+    assert ch.index == 2
+
+
+@pytest.mark.unit
+def test_getChannelByName_invalid_name(capsys):
+    """Get a channel by the name but one that is not present."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getChannelByName('testing')
+    assert ch is None
+
+
+@pytest.mark.unit
+def test_getDisabledChannel(capsys):
+    """Get the first disabled channel."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testingA'
+
+    channel3 = Channel(index=3, role=2)
+    channel3.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel3.settings.name = 'testingB'
+
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getDisabledChannel()
+    assert ch.index == 4
+
+
+@pytest.mark.unit
+def test_getDisabledChannel_where_all_channels_are_used(capsys):
+    """Get the first disabled channel."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel3 = Channel(index=3, role=2)
+    channel4 = Channel(index=4, role=2)
+    channel5 = Channel(index=5, role=2)
+    channel6 = Channel(index=6, role=2)
+    channel7 = Channel(index=7, role=2)
+    channel8 = Channel(index=8, role=2)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getDisabledChannel()
+    assert ch is None
+
+
+@pytest.mark.unit
+def test_getAdminChannelIndex(capsys):
+    """Get the 'admin' channel index."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    i = anode._getAdminChannelIndex()
+    assert i == 2
+
+
+@pytest.mark.unit
+def test_getAdminChannelIndex_when_no_admin_named_channel(capsys):
+    """Get the 'admin' channel when there is not one."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=0)
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    i = anode._getAdminChannelIndex()
+    assert i == 0
+
+
+# TODO: should we check if we need to turn it off?
+@pytest.mark.unit
+def test_turnOffEncryptionOnPrimaryChannel(capsys):
+    """Turn off encryption when there is a psk."""
+    anode = Node('foo', 'bar', noProto=True)
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    # value from using "--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b "
+    channel1.settings.psk = b'\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++'
+
+    channel2 = Channel(index=2, role=0)
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    anode.turnOffEncryptionOnPrimaryChannel()
+    out, err = capsys.readouterr()
+    assert re.search(r'Writing modified channels to device', out)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_writeConfig_with_no_radioConfig(capsys):
+    """Test writeConfig with no radioConfig."""
+    anode = Node('foo', 'bar', noProto=True)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode.writeConfig()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Error: No RadioConfig has been read', out)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_writeConfig(caplog):
+    """Test writeConfig"""
+    anode = Node('foo', 'bar', noProto=True)
+    radioConfig = RadioConfig()
+    anode.radioConfig = radioConfig
+
+    with caplog.at_level(logging.DEBUG):
+        anode.writeConfig()
+    assert re.search(r'Wrote config', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_requestChannel_not_localNode(caplog):
+    """Test _requestChannel()"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+        with caplog.at_level(logging.DEBUG):
+            anode._requestChannel(0)
+            assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_requestChannel_localNode(caplog):
+    """Test _requestChannel()"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        with caplog.at_level(logging.DEBUG):
+            anode._requestChannel(0)
+            assert re.search(r'Requesting channel 0', caplog.text, re.MULTILINE)
+            assert not re.search(r'from remote node', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_onResponseRequestChannel(caplog):
+    """Test onResponseRequestChannel()"""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    msg1 = MagicMock(autospec=AdminMessage)
+    msg1.get_channel_response = channel1
+
+    msg2 = MagicMock(autospec=AdminMessage)
+    channel2 = Channel(index=2, role=0) # disabled
+    msg2.get_channel_response = channel2
+
+    # default primary channel
+    packet1 = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b':\t\x12\x05\x18\x03"\x01\x01\x18\x01',
+            'requestId': 2615094405,
+            'admin': {
+                'getChannelResponse': {
+                    'settings': {
+                        'modemConfig': 'Bw125Cr48Sf4096',
+                        'psk': 'AQ=='
+                    },
+                    'role': 'PRIMARY'
+                },
+                'raw': msg1,
+            }
+        },
+        'id': 1692918436,
+        'hopLimit': 3,
+        'priority':
+        'RELIABLE',
+        'raw': 'fake',
+        'fromId': '!9388f81c',
+        'toId': '!9388f81c'
+        }
+
+    # no other channels
+    packet2 = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b':\x04\x08\x02\x12\x00',
+            'requestId': 743049663,
+            'admin': {
+                'getChannelResponse': {
+                    'index': 2,
+                    'settings': {}
+                },
+                'raw': msg2,
+            }
+        },
+        'id': 1692918456,
+        'rxTime': 1640202239,
+        'hopLimit': 3,
+        'priority': 'RELIABLE',
+        'raw': 'faked',
+        'fromId': '!9388f81c',
+        'toId': '!9388f81c'
+    }
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        radioConfig = RadioConfig()
+        anode.radioConfig = radioConfig
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        with caplog.at_level(logging.DEBUG):
+            anode.requestConfig()
+            anode.onResponseRequestChannel(packet1)
+            assert re.search(r'Received channel', caplog.text, re.MULTILINE)
+            anode.onResponseRequestChannel(packet2)
+            assert re.search(r'Received channel', caplog.text, re.MULTILINE)
+            assert re.search(r'Finished downloading channels', caplog.text, re.MULTILINE)
+            assert len(anode.channels) == 8
+            assert anode.channels[0].settings.modem_config == 3
+            assert anode.channels[1].settings.name == ''
+            assert anode.channels[2].settings.name == ''
+            assert anode.channels[3].settings.name == ''
+            assert anode.channels[4].settings.name == ''
+            assert anode.channels[5].settings.name == ''
+            assert anode.channels[6].settings.name == ''
+            assert anode.channels[7].settings.name == ''
+
+
+@pytest.mark.unit
+def test_onResponseRequestSetting(caplog):
+    """Test onResponseRequestSetting()"""
+    # Note: Split out the get_radio_response to a MagicMock
+    # so it could be "returned" (not really sure how to do that
+    # in a python dict.
+    amsg = MagicMock(autospec=AdminMessage)
+    amsg.get_radio_response = """{
+  preferences {
+    phone_timeout_secs: 900
+    ls_secs: 300
+    position_broadcast_smart: true
+    position_flags: 35
+  }
+}"""
+    packet = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b'*\x0e\n\x0c0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#',
+            'requestId': 3145147848,
+            'admin': {
+                'getRadioResponse': {
+                    'preferences': {
+                        'phoneTimeoutSecs': 900,
+                        'lsSecs': 300,
+                        'positionBroadcastSmart': True,
+                        'positionFlags': 35
+                     }
+                },
+                'raw': amsg
+            },
+            'id': 365963704,
+            'rxTime': 1640195197,
+            'hopLimit': 3,
+            'priority': 'RELIABLE',
+            'raw': 'faked',
+            'fromId': '!9388f81c',
+            'toId': '!9388f81c'
+        }
+    }
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        radioConfig = RadioConfig()
+        anode.radioConfig = radioConfig
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        with caplog.at_level(logging.DEBUG):
+            anode.onResponseRequestSettings(packet)
+            assert re.search(r'Received radio config, now fetching channels..', caplog.text, re.MULTILINE)
+
+
+@pytest.mark.unit
+def test_onResponseRequestSetting_with_error(capsys):
+    """Test onResponseRequestSetting() with an error"""
+    packet = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b'*\x0e\n\x0c0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#',
+            'requestId': 3145147848,
+            'routing': {
+                'errorReason': 'some made up error',
+            },
+            'admin': {
+                'getRadioResponse': {
+                    'preferences': {
+                        'phoneTimeoutSecs': 900,
+                        'lsSecs': 300,
+                        'positionBroadcastSmart': True,
+                        'positionFlags': 35
+                     }
+                },
+            },
+            'id': 365963704,
+            'rxTime': 1640195197,
+            'hopLimit': 3,
+            'priority': 'RELIABLE',
+            'fromId': '!9388f81c',
+            'toId': '!9388f81c'
+        }
+    }
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        radioConfig = RadioConfig()
+        anode.radioConfig = radioConfig
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        anode.onResponseRequestSettings(packet)
+        out, err = capsys.readouterr()
+        assert re.search(r'Error on response', out)
+        assert err == ''
+
+
+
+
+
+
+
+

Functions

+
+
+def test_deleteChannel_secondary() +
+
+

Try to delete a secondary channel.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_deleteChannel_secondary():
+    """Try to delete a secondary channel."""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testing'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        anode.channels = channels
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'testing'
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+        anode.deleteChannel(1)
+
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == ''
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+
+
+def test_deleteChannel_secondary_with_admin_channel_after_testing() +
+
+

Try to delete a secondary channel where there is an admin channel.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_deleteChannel_secondary_with_admin_channel_after_testing():
+    """Try to delete a secondary channel where there is an admin channel."""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testing'
+
+    channel3 = Channel(index=3, role=2)
+    channel3.settings.name = 'admin'
+
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        assert mo.localNode == anode
+
+        anode.channels = channels
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'testing'
+        assert channels[2].settings.name == 'admin'
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+        anode.deleteChannel(1)
+
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'admin'
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+
+
+def test_deleteChannel_secondary_with_admin_channel_before_testing() +
+
+

Try to delete a secondary channel where there is an admin channel.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_deleteChannel_secondary_with_admin_channel_before_testing():
+    """Try to delete a secondary channel where there is an admin channel."""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=2)
+    channel3.settings.name = 'testing'
+
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        anode.channels = channels
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'admin'
+        assert channels[2].settings.name == 'testing'
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+        anode.deleteChannel(2)
+
+        assert len(anode.channels) == 8
+        assert channels[0].settings.modem_config == 3
+        assert channels[1].settings.name == 'admin'
+        assert channels[2].settings.name == ''
+        assert channels[3].settings.name == ''
+        assert channels[4].settings.name == ''
+        assert channels[5].settings.name == ''
+        assert channels[6].settings.name == ''
+        assert channels[7].settings.name == ''
+
+
+
+def test_deleteChannel_try_to_delete_primary_channel(capsys) +
+
+

Try to delete primary channel.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_deleteChannel_try_to_delete_primary_channel(capsys):
+    """Try to delete primary channel."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    # no secondary channels
+    channel2 = Channel(index=2, role=0)
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode.deleteChannel(0)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: Only SECONDARY channels can be deleted', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_exitSimulator(caplog) +
+
+

Test exitSimulator

+
+ +Expand source code + +
@pytest.mark.unit
+def test_exitSimulator(caplog):
+    """Test exitSimulator"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.exitSimulator()
+    assert re.search(r'in exitSimulator', caplog.text, re.MULTILINE)
+
+
+
+def test_getAdminChannelIndex(capsys) +
+
+

Get the 'admin' channel index.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getAdminChannelIndex(capsys):
+    """Get the 'admin' channel index."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    i = anode._getAdminChannelIndex()
+    assert i == 2
+
+
+
+def test_getAdminChannelIndex_when_no_admin_named_channel(capsys) +
+
+

Get the 'admin' channel when there is not one.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getAdminChannelIndex_when_no_admin_named_channel(capsys):
+    """Get the 'admin' channel when there is not one."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=0)
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    i = anode._getAdminChannelIndex()
+    assert i == 0
+
+
+
+def test_getChannelByChannelIndex() +
+
+

Test getChannelByChannelIndex()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getChannelByChannelIndex():
+    """Test getChannelByChannelIndex()"""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1) # primary channel
+    channel2 = Channel(index=2, role=2) # secondary channel
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+
+    # test primary
+    assert anode.getChannelByChannelIndex(0) is not None
+    # test secondary
+    assert anode.getChannelByChannelIndex(1) is not None
+    # test disabled
+    assert anode.getChannelByChannelIndex(2) is not None
+    # test invalid values
+    assert anode.getChannelByChannelIndex(-1) is None
+    assert anode.getChannelByChannelIndex(9) is None
+
+
+
+def test_getChannelByName(capsys) +
+
+

Get a channel by the name.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getChannelByName(capsys):
+    """Get a channel by the name."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getChannelByName('admin')
+    assert ch.index == 2
+
+
+
+def test_getChannelByName_invalid_name(capsys) +
+
+

Get a channel by the name but one that is not present.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getChannelByName_invalid_name(capsys):
+    """Get a channel by the name but one that is not present."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'admin'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getChannelByName('testing')
+    assert ch is None
+
+
+
+def test_getDisabledChannel(capsys) +
+
+

Get the first disabled channel.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getDisabledChannel(capsys):
+    """Get the first disabled channel."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testingA'
+
+    channel3 = Channel(index=3, role=2)
+    channel3.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel3.settings.name = 'testingB'
+
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getDisabledChannel()
+    assert ch.index == 4
+
+
+
+def test_getDisabledChannel_where_all_channels_are_used(capsys) +
+
+

Get the first disabled channel.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_getDisabledChannel_where_all_channels_are_used(capsys):
+    """Get the first disabled channel."""
+    anode = Node('foo', 'bar')
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel3 = Channel(index=3, role=2)
+    channel4 = Channel(index=4, role=2)
+    channel5 = Channel(index=5, role=2)
+    channel6 = Channel(index=6, role=2)
+    channel7 = Channel(index=7, role=2)
+    channel8 = Channel(index=8, role=2)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    ch = anode.getDisabledChannel()
+    assert ch is None
+
+
+
+def test_node(capsys) +
+
+

Test that we can instantiate a Node

+
+ +Expand source code + +
@pytest.mark.unit
+def test_node(capsys):
+    """Test that we can instantiate a Node"""
+    anode = Node('foo', 'bar')
+    radioConfig = RadioConfig()
+    anode.radioConfig = radioConfig
+    anode.showChannels()
+    anode.showInfo()
+    out, err = capsys.readouterr()
+    assert re.search(r'Preferences', out)
+    assert re.search(r'Channels', out)
+    assert re.search(r'Primary channel URL', out)
+    assert err == ''
+
+
+
+def test_node_reqquestConfig() +
+
+

Test run requestConfig

+
+ +Expand source code + +
@pytest.mark.unit
+def test_node_reqquestConfig():
+    """Test run requestConfig"""
+    iface = MagicMock(autospec=SerialInterface)
+    amesg = MagicMock(autospec=AdminMessage)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with patch('meshtastic.admin_pb2.AdminMessage', return_value=amesg):
+            anode = Node(mo, 'bar')
+            anode.requestConfig()
+
+
+
+def test_onResponseRequestChannel(caplog) +
+
+

Test onResponseRequestChannel()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_onResponseRequestChannel(caplog):
+    """Test onResponseRequestChannel()"""
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    msg1 = MagicMock(autospec=AdminMessage)
+    msg1.get_channel_response = channel1
+
+    msg2 = MagicMock(autospec=AdminMessage)
+    channel2 = Channel(index=2, role=0) # disabled
+    msg2.get_channel_response = channel2
+
+    # default primary channel
+    packet1 = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b':\t\x12\x05\x18\x03"\x01\x01\x18\x01',
+            'requestId': 2615094405,
+            'admin': {
+                'getChannelResponse': {
+                    'settings': {
+                        'modemConfig': 'Bw125Cr48Sf4096',
+                        'psk': 'AQ=='
+                    },
+                    'role': 'PRIMARY'
+                },
+                'raw': msg1,
+            }
+        },
+        'id': 1692918436,
+        'hopLimit': 3,
+        'priority':
+        'RELIABLE',
+        'raw': 'fake',
+        'fromId': '!9388f81c',
+        'toId': '!9388f81c'
+        }
+
+    # no other channels
+    packet2 = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b':\x04\x08\x02\x12\x00',
+            'requestId': 743049663,
+            'admin': {
+                'getChannelResponse': {
+                    'index': 2,
+                    'settings': {}
+                },
+                'raw': msg2,
+            }
+        },
+        'id': 1692918456,
+        'rxTime': 1640202239,
+        'hopLimit': 3,
+        'priority': 'RELIABLE',
+        'raw': 'faked',
+        'fromId': '!9388f81c',
+        'toId': '!9388f81c'
+    }
+
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        radioConfig = RadioConfig()
+        anode.radioConfig = radioConfig
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        with caplog.at_level(logging.DEBUG):
+            anode.requestConfig()
+            anode.onResponseRequestChannel(packet1)
+            assert re.search(r'Received channel', caplog.text, re.MULTILINE)
+            anode.onResponseRequestChannel(packet2)
+            assert re.search(r'Received channel', caplog.text, re.MULTILINE)
+            assert re.search(r'Finished downloading channels', caplog.text, re.MULTILINE)
+            assert len(anode.channels) == 8
+            assert anode.channels[0].settings.modem_config == 3
+            assert anode.channels[1].settings.name == ''
+            assert anode.channels[2].settings.name == ''
+            assert anode.channels[3].settings.name == ''
+            assert anode.channels[4].settings.name == ''
+            assert anode.channels[5].settings.name == ''
+            assert anode.channels[6].settings.name == ''
+            assert anode.channels[7].settings.name == ''
+
+
+
+def test_onResponseRequestSetting(caplog) +
+
+

Test onResponseRequestSetting()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_onResponseRequestSetting(caplog):
+    """Test onResponseRequestSetting()"""
+    # Note: Split out the get_radio_response to a MagicMock
+    # so it could be "returned" (not really sure how to do that
+    # in a python dict.
+    amsg = MagicMock(autospec=AdminMessage)
+    amsg.get_radio_response = """{
+  preferences {
+    phone_timeout_secs: 900
+    ls_secs: 300
+    position_broadcast_smart: true
+    position_flags: 35
+  }
+}"""
+    packet = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b'*\x0e\n\x0c0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#',
+            'requestId': 3145147848,
+            'admin': {
+                'getRadioResponse': {
+                    'preferences': {
+                        'phoneTimeoutSecs': 900,
+                        'lsSecs': 300,
+                        'positionBroadcastSmart': True,
+                        'positionFlags': 35
+                     }
+                },
+                'raw': amsg
+            },
+            'id': 365963704,
+            'rxTime': 1640195197,
+            'hopLimit': 3,
+            'priority': 'RELIABLE',
+            'raw': 'faked',
+            'fromId': '!9388f81c',
+            'toId': '!9388f81c'
+        }
+    }
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        radioConfig = RadioConfig()
+        anode.radioConfig = radioConfig
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        with caplog.at_level(logging.DEBUG):
+            anode.onResponseRequestSettings(packet)
+            assert re.search(r'Received radio config, now fetching channels..', caplog.text, re.MULTILINE)
+
+
+
+def test_onResponseRequestSetting_with_error(capsys) +
+
+

Test onResponseRequestSetting() with an error

+
+ +Expand source code + +
@pytest.mark.unit
+def test_onResponseRequestSetting_with_error(capsys):
+    """Test onResponseRequestSetting() with an error"""
+    packet = {
+        'from': 2475227164,
+        'to': 2475227164,
+        'decoded': {
+            'portnum': 'ADMIN_APP',
+            'payload': b'*\x0e\n\x0c0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#',
+            'requestId': 3145147848,
+            'routing': {
+                'errorReason': 'some made up error',
+            },
+            'admin': {
+                'getRadioResponse': {
+                    'preferences': {
+                        'phoneTimeoutSecs': 900,
+                        'lsSecs': 300,
+                        'positionBroadcastSmart': True,
+                        'positionFlags': 35
+                     }
+                },
+            },
+            'id': 365963704,
+            'rxTime': 1640195197,
+            'hopLimit': 3,
+            'priority': 'RELIABLE',
+            'fromId': '!9388f81c',
+            'toId': '!9388f81c'
+        }
+    }
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        radioConfig = RadioConfig()
+        anode.radioConfig = radioConfig
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        anode.onResponseRequestSettings(packet)
+        out, err = capsys.readouterr()
+        assert re.search(r'Error on response', out)
+        assert err == ''
+
+
+
+def test_reboot(caplog) +
+
+

Test reboot

+
+ +Expand source code + +
@pytest.mark.unit
+def test_reboot(caplog):
+    """Test reboot"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.reboot()
+    assert re.search(r'Telling node to reboot', caplog.text, re.MULTILINE)
+
+
+
+def test_requestChannel_localNode(caplog) +
+
+

Test _requestChannel()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_requestChannel_localNode(caplog):
+    """Test _requestChannel()"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+
+        # Note: Have to do this next line because every call to MagicMock object/method returns a new magic mock
+        mo.localNode = anode
+
+        with caplog.at_level(logging.DEBUG):
+            anode._requestChannel(0)
+            assert re.search(r'Requesting channel 0', caplog.text, re.MULTILINE)
+            assert not re.search(r'from remote node', caplog.text, re.MULTILINE)
+
+
+
+def test_requestChannel_not_localNode(caplog) +
+
+

Test _requestChannel()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_requestChannel_not_localNode(caplog):
+    """Test _requestChannel()"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        mo.myInfo.max_channels = 8
+        anode = Node(mo, 'bar', noProto=True)
+        with caplog.at_level(logging.DEBUG):
+            anode._requestChannel(0)
+            assert re.search(r'Requesting channel 0 info from remote node', caplog.text, re.MULTILINE)
+
+
+
+def test_setOwner_and_team(caplog) +
+
+

Test setOwner

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setOwner_and_team(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='Test123', short_name='123', team=1)
+    assert re.search(r'p.set_owner.long_name:Test123:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:123:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:1', caplog.text, re.MULTILINE)
+
+
+
+def test_setOwner_no_short_name(caplog) +
+
+

Test setOwner

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setOwner_no_short_name(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='Test123')
+    assert re.search(r'p.set_owner.long_name:Test123:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:Tst:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
+
+
+
+def test_setOwner_no_short_name_and_long_name_has_words(caplog) +
+
+

Test setOwner

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setOwner_no_short_name_and_long_name_has_words(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='A B C', is_licensed=True)
+    assert re.search(r'p.set_owner.long_name:A B C:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:ABC:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:True', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
+
+
+
+def test_setOwner_no_short_name_and_long_name_is_short(caplog) +
+
+

Test setOwner

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setOwner_no_short_name_and_long_name_is_short(caplog):
+    """Test setOwner"""
+    anode = Node('foo', 'bar', noProto=True)
+    with caplog.at_level(logging.DEBUG):
+        anode.setOwner(long_name ='Tnt')
+    assert re.search(r'p.set_owner.long_name:Tnt:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.short_name:Tnt:', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.is_licensed:False', caplog.text, re.MULTILINE)
+    assert re.search(r'p.set_owner.team:0', caplog.text, re.MULTILINE)
+
+
+
+def test_setURL_empty_url() +
+
+

Test reboot

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setURL_empty_url():
+    """Test reboot"""
+    anode = Node('foo', 'bar', noProto=True)
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode.setURL('')
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_setURL_valid_URL(caplog) +
+
+

Test setURL

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setURL_valid_URL(caplog):
+    """Test setURL"""
+    iface = MagicMock(autospec=SerialInterface)
+    url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
+    with caplog.at_level(logging.DEBUG):
+        anode = Node(iface, 'bar', noProto=True)
+        anode.radioConfig = 'baz'
+        channels = ['zoo']
+        anode.channels = channels
+        anode.setURL(url)
+    assert re.search(r'Channel i:0', caplog.text, re.MULTILINE)
+    assert re.search(r'modem_config: Bw125Cr48Sf4096', caplog.text, re.MULTILINE)
+    assert re.search(r'psk: "\\001"', caplog.text, re.MULTILINE)
+    assert re.search(r'role: PRIMARY', caplog.text, re.MULTILINE)
+
+
+
+def test_setURL_valid_URL_but_no_settings(caplog) +
+
+

Test setURL

+
+ +Expand source code + +
@pytest.mark.unit
+def test_setURL_valid_URL_but_no_settings(caplog):
+    """Test setURL"""
+    iface = MagicMock(autospec=SerialInterface)
+    url = "https://www.meshtastic.org/d/#"
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode = Node(iface, 'bar', noProto=True)
+        anode.radioConfig = 'baz'
+        anode.setURL(url)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_showChannels(capsys) +
+
+

Test showChannels

+
+ +Expand source code + +
@pytest.mark.unit
+def test_showChannels(capsys):
+    """Test showChannels"""
+    anode = Node('foo', 'bar')
+
+    # primary channel
+    # role: 0=Disabled, 1=Primary, 2=Secondary
+    # modem_config: 0-5
+    # role: 0=Disabled, 1=Primary, 2=Secondary
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    channel1.settings.psk = b'\x01'
+
+    channel2 = Channel(index=2, role=2)
+    channel2.settings.psk = b'\x8a\x94y\x0e\xc6\xc9\x1e5\x91\x12@\xa60\xa8\xb43\x87\x00\xf2K\x0e\xe7\x7fAz\xcd\xf5\xb0\x900\xa84'
+    channel2.settings.name = 'testing'
+
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    anode.showChannels()
+    out, err = capsys.readouterr()
+    assert re.search(r'Channels:', out, re.MULTILINE)
+    # primary channel
+    assert re.search(r'Primary channel URL', out, re.MULTILINE)
+    assert re.search(r'PRIMARY psk=default ', out, re.MULTILINE)
+    assert re.search(r'"modemConfig": "Bw125Cr48Sf4096"', out, re.MULTILINE)
+    assert re.search(r'"psk": "AQ=="', out, re.MULTILINE)
+    # secondary channel
+    assert re.search(r'SECONDARY psk=secret ', out, re.MULTILINE)
+    assert re.search(r'"psk": "ipR5DsbJHjWREkCmMKi0M4cA8ksO539Bes31sJAwqDQ="', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_turnOffEncryptionOnPrimaryChannel(capsys) +
+
+

Turn off encryption when there is a psk.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_turnOffEncryptionOnPrimaryChannel(capsys):
+    """Turn off encryption when there is a psk."""
+    anode = Node('foo', 'bar', noProto=True)
+
+    channel1 = Channel(index=1, role=1)
+    channel1.settings.modem_config = 3
+    # value from using "--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b "
+    channel1.settings.psk = b'\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++\x1a\x1a\x1a\x1a++++'
+
+    channel2 = Channel(index=2, role=0)
+    channel3 = Channel(index=3, role=0)
+    channel4 = Channel(index=4, role=0)
+    channel5 = Channel(index=5, role=0)
+    channel6 = Channel(index=6, role=0)
+    channel7 = Channel(index=7, role=0)
+    channel8 = Channel(index=8, role=0)
+
+    channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]
+
+    anode.channels = channels
+    anode.turnOffEncryptionOnPrimaryChannel()
+    out, err = capsys.readouterr()
+    assert re.search(r'Writing modified channels to device', out)
+    assert err == ''
+
+
+
+def test_writeConfig(caplog) +
+
+

Test writeConfig

+
+ +Expand source code + +
@pytest.mark.unit
+def test_writeConfig(caplog):
+    """Test writeConfig"""
+    anode = Node('foo', 'bar', noProto=True)
+    radioConfig = RadioConfig()
+    anode.radioConfig = radioConfig
+
+    with caplog.at_level(logging.DEBUG):
+        anode.writeConfig()
+    assert re.search(r'Wrote config', caplog.text, re.MULTILINE)
+
+
+
+def test_writeConfig_with_no_radioConfig(capsys) +
+
+

Test writeConfig with no radioConfig.

+
+ +Expand source code + +
@pytest.mark.unit
+def test_writeConfig_with_no_radioConfig(capsys):
+    """Test writeConfig with no radioConfig."""
+    anode = Node('foo', 'bar', noProto=True)
+
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        anode.writeConfig()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Error: No RadioConfig has been read', out)
+    assert err == ''
+
+
+
+
+
+

Classes

+
+
+class AdminMessage +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var CONFIRM_SET_CHANNEL_FIELD_NUMBER
+
+
+
+
var CONFIRM_SET_RADIO_FIELD_NUMBER
+
+
+
+
var DESCRIPTOR
+
+
+
+
var EXIT_SIMULATOR_FIELD_NUMBER
+
+
+
+
var GET_CHANNEL_REQUEST_FIELD_NUMBER
+
+
+
+
var GET_CHANNEL_RESPONSE_FIELD_NUMBER
+
+
+
+
var GET_OWNER_REQUEST_FIELD_NUMBER
+
+
+
+
var GET_OWNER_RESPONSE_FIELD_NUMBER
+
+
+
+
var GET_RADIO_REQUEST_FIELD_NUMBER
+
+
+
+
var GET_RADIO_RESPONSE_FIELD_NUMBER
+
+
+
+
var REBOOT_SECONDS_FIELD_NUMBER
+
+
+
+
var SET_CHANNEL_FIELD_NUMBER
+
+
+
+
var SET_OWNER_FIELD_NUMBER
+
+
+
+
var SET_RADIO_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var confirm_set_channel
+
+

Getter for confirm_set_channel.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var confirm_set_radio
+
+

Getter for confirm_set_radio.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var exit_simulator
+
+

Getter for exit_simulator.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_channel_request
+
+

Getter for get_channel_request.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_channel_response
+
+

Getter for get_channel_response.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var get_owner_request
+
+

Getter for get_owner_request.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_owner_response
+
+

Getter for get_owner_response.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var get_radio_request
+
+

Getter for get_radio_request.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var get_radio_response
+
+

Getter for get_radio_response.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var reboot_seconds
+
+

Getter for reboot_seconds.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var set_channel
+
+

Getter for set_channel.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var set_owner
+
+

Getter for set_owner.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
var set_radio
+
+

Getter for set_radio.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class Channel +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var DISABLED
+
+
+
+
var INDEX_FIELD_NUMBER
+
+
+
+
var PRIMARY
+
+
+
+
var ROLE_FIELD_NUMBER
+
+
+
+
var Role
+
+
+
+
var SECONDARY
+
+
+
+
var SETTINGS_FIELD_NUMBER
+
+
+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var index
+
+

Getter for index.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var role
+
+

Getter for role.

+
+ +Expand source code + +
def getter(self):
+  # TODO(protobuf-team): This may be broken since there may not be
+  # default_value.  Combine with has_default_value somehow.
+  return self._fields.get(field, default_value)
+
+
+
var settings
+
+

Getter for settings.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+class RadioConfig +(**kwargs) +
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+

Ancestors

+
    +
  • google.protobuf.message.Message
  • +
+

Class variables

+
+
var DESCRIPTOR
+
+
+
+
var PREFERENCES_FIELD_NUMBER
+
+
+
+
var UserPreferences
+
+

Abstract base class for protocol messages.

+

Protocol message classes are almost always generated by the protocol +compiler. +These generated types subclass Message and implement the methods +shown below.

+
+
+

Static methods

+
+
+def FromString(s) +
+
+
+
+ +Expand source code + +
def FromString(s):
+  message = cls()
+  message.MergeFromString(s)
+  return message
+
+
+
+def RegisterExtension(extension_handle) +
+
+
+
+ +Expand source code + +
def RegisterExtension(extension_handle):
+  extension_handle.containing_type = cls.DESCRIPTOR
+  # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available.
+  # pylint: disable=protected-access
+  cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle)
+  _AttachFieldHelpers(cls, extension_handle)
+
+
+
+

Instance variables

+
+
var preferences
+
+

Getter for preferences.

+
+ +Expand source code + +
def getter(self):
+  field_value = self._fields.get(field)
+  if field_value is None:
+    # Construct a new object to represent this field.
+    field_value = field._default_constructor(self)
+
+    # Atomically check if another thread has preempted us and, if not, swap
+    # in the new object we just created.  If someone has preempted us, we
+    # take that object and discard ours.
+    # WARNING:  We are relying on setdefault() being atomic.  This is true
+    #   in CPython but we haven't investigated others.  This warning appears
+    #   in several other locations in this file.
+    field_value = self._fields.setdefault(field, field_value)
+  return field_value
+
+
+
+

Methods

+
+
+def ByteSize(self) +
+
+
+
+ +Expand source code + +
def ByteSize(self):
+  if not self._cached_byte_size_dirty:
+    return self._cached_byte_size
+
+  size = 0
+  descriptor = self.DESCRIPTOR
+  if descriptor.GetOptions().map_entry:
+    # Fields of map entry should always be serialized.
+    size = descriptor.fields_by_name['key']._sizer(self.key)
+    size += descriptor.fields_by_name['value']._sizer(self.value)
+  else:
+    for field_descriptor, field_value in self.ListFields():
+      size += field_descriptor._sizer(field_value)
+    for tag_bytes, value_bytes in self._unknown_fields:
+      size += len(tag_bytes) + len(value_bytes)
+
+  self._cached_byte_size = size
+  self._cached_byte_size_dirty = False
+  self._listener_for_children.dirty = False
+  return size
+
+
+
+def Clear(self) +
+
+
+
+ +Expand source code + +
def _Clear(self):
+  # Clear fields.
+  self._fields = {}
+  self._unknown_fields = ()
+  # pylint: disable=protected-access
+  if self._unknown_field_set is not None:
+    self._unknown_field_set._clear()
+    self._unknown_field_set = None
+
+  self._oneofs = {}
+  self._Modified()
+
+
+
+def ClearField(self, field_name) +
+
+
+
+ +Expand source code + +
def ClearField(self, field_name):
+  try:
+    field = message_descriptor.fields_by_name[field_name]
+  except KeyError:
+    try:
+      field = message_descriptor.oneofs_by_name[field_name]
+      if field in self._oneofs:
+        field = self._oneofs[field]
+      else:
+        return
+    except KeyError:
+      raise ValueError('Protocol message %s has no "%s" field.' %
+                       (message_descriptor.name, field_name))
+
+  if field in self._fields:
+    # To match the C++ implementation, we need to invalidate iterators
+    # for map fields when ClearField() happens.
+    if hasattr(self._fields[field], 'InvalidateIterators'):
+      self._fields[field].InvalidateIterators()
+
+    # Note:  If the field is a sub-message, its listener will still point
+    #   at us.  That's fine, because the worst than can happen is that it
+    #   will call _Modified() and invalidate our byte size.  Big deal.
+    del self._fields[field]
+
+    if self._oneofs.get(field.containing_oneof, None) is field:
+      del self._oneofs[field.containing_oneof]
+
+  # Always call _Modified() -- even if nothing was changed, this is
+  # a mutating method, and thus calling it should cause the field to become
+  # present in the parent message.
+  self._Modified()
+
+
+
+def DiscardUnknownFields(self) +
+
+
+
+ +Expand source code + +
def _DiscardUnknownFields(self):
+  self._unknown_fields = []
+  self._unknown_field_set = None      # pylint: disable=protected-access
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            value[key].DiscardUnknownFields()
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for sub_message in value:
+          sub_message.DiscardUnknownFields()
+      else:
+        value.DiscardUnknownFields()
+
+
+
+def FindInitializationErrors(self) +
+
+

Finds required fields which are not initialized.

+

Returns

+

A list of strings. +Each string is a path to an uninitialized field from +the top-level message, e.g. "foo.bar[5].baz".

+
+ +Expand source code + +
def FindInitializationErrors(self):
+  """Finds required fields which are not initialized.
+
+  Returns:
+    A list of strings.  Each string is a path to an uninitialized field from
+    the top-level message, e.g. "foo.bar[5].baz".
+  """
+
+  errors = []  # simplify things
+
+  for field in required_fields:
+    if not self.HasField(field.name):
+      errors.append(field.name)
+
+  for field, value in self.ListFields():
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.is_extension:
+        name = '(%s)' % field.full_name
+      else:
+        name = field.name
+
+      if _IsMapField(field):
+        if _IsMessageMapField(field):
+          for key in value:
+            element = value[key]
+            prefix = '%s[%s].' % (name, key)
+            sub_errors = element.FindInitializationErrors()
+            errors += [prefix + error for error in sub_errors]
+        else:
+          # ScalarMaps can't have any initialization errors.
+          pass
+      elif field.label == _FieldDescriptor.LABEL_REPEATED:
+        for i in range(len(value)):
+          element = value[i]
+          prefix = '%s[%d].' % (name, i)
+          sub_errors = element.FindInitializationErrors()
+          errors += [prefix + error for error in sub_errors]
+      else:
+        prefix = name + '.'
+        sub_errors = value.FindInitializationErrors()
+        errors += [prefix + error for error in sub_errors]
+
+  return errors
+
+
+
+def HasField(self, field_name) +
+
+
+
+ +Expand source code + +
def HasField(self, field_name):
+  try:
+    field = hassable_fields[field_name]
+  except KeyError:
+    raise ValueError(error_msg % (message_descriptor.full_name, field_name))
+
+  if isinstance(field, descriptor_mod.OneofDescriptor):
+    try:
+      return HasField(self, self._oneofs[field].name)
+    except KeyError:
+      return False
+  else:
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      value = self._fields.get(field)
+      return value is not None and value._is_present_in_parent
+    else:
+      return field in self._fields
+
+
+
+def IsInitialized(self, errors=None) +
+
+

Checks if all required fields of a message are set.

+

Args

+
+
errors
+
A list which, if provided, will be populated with the field +paths of all missing required fields.
+
+

Returns

+

True iff the specified message has all required fields set.

+
+ +Expand source code + +
def IsInitialized(self, errors=None):
+  """Checks if all required fields of a message are set.
+
+  Args:
+    errors:  A list which, if provided, will be populated with the field
+             paths of all missing required fields.
+
+  Returns:
+    True iff the specified message has all required fields set.
+  """
+
+  # Performance is critical so we avoid HasField() and ListFields().
+
+  for field in required_fields:
+    if (field not in self._fields or
+        (field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE and
+         not self._fields[field]._is_present_in_parent)):
+      if errors is not None:
+        errors.extend(self.FindInitializationErrors())
+      return False
+
+  for field, value in list(self._fields.items()):  # dict can change size!
+    if field.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE:
+      if field.label == _FieldDescriptor.LABEL_REPEATED:
+        if (field.message_type.has_options and
+            field.message_type.GetOptions().map_entry):
+          continue
+        for element in value:
+          if not element.IsInitialized():
+            if errors is not None:
+              errors.extend(self.FindInitializationErrors())
+            return False
+      elif value._is_present_in_parent and not value.IsInitialized():
+        if errors is not None:
+          errors.extend(self.FindInitializationErrors())
+        return False
+
+  return True
+
+
+
+def ListFields(self) +
+
+
+
+ +Expand source code + +
def ListFields(self):
+  all_fields = [item for item in self._fields.items() if _IsPresent(item)]
+  all_fields.sort(key = lambda item: item[0].number)
+  return all_fields
+
+
+
+def MergeFrom(self, msg) +
+
+
+
+ +Expand source code + +
def MergeFrom(self, msg):
+  if not isinstance(msg, cls):
+    raise TypeError(
+        'Parameter to MergeFrom() must be instance of same class: '
+        'expected %s got %s.' % (_FullyQualifiedClassName(cls),
+                                 _FullyQualifiedClassName(msg.__class__)))
+
+  assert msg is not self
+  self._Modified()
+
+  fields = self._fields
+
+  for field, value in msg._fields.items():
+    if field.label == LABEL_REPEATED:
+      field_value = fields.get(field)
+      if field_value is None:
+        # Construct a new object to represent this field.
+        field_value = field._default_constructor(self)
+        fields[field] = field_value
+      field_value.MergeFrom(value)
+    elif field.cpp_type == CPPTYPE_MESSAGE:
+      if value._is_present_in_parent:
+        field_value = fields.get(field)
+        if field_value is None:
+          # Construct a new object to represent this field.
+          field_value = field._default_constructor(self)
+          fields[field] = field_value
+        field_value.MergeFrom(value)
+    else:
+      self._fields[field] = value
+      if field.containing_oneof:
+        self._UpdateOneofState(field)
+
+  if msg._unknown_fields:
+    if not self._unknown_fields:
+      self._unknown_fields = []
+    self._unknown_fields.extend(msg._unknown_fields)
+    # pylint: disable=protected-access
+    if self._unknown_field_set is None:
+      self._unknown_field_set = containers.UnknownFieldSet()
+    self._unknown_field_set._extend(msg._unknown_field_set)
+
+
+
+def MergeFromString(self, serialized) +
+
+
+
+ +Expand source code + +
def MergeFromString(self, serialized):
+  serialized = memoryview(serialized)
+  length = len(serialized)
+  try:
+    if self._InternalParse(serialized, 0, length) != length:
+      # The only reason _InternalParse would return early is if it
+      # encountered an end-group tag.
+      raise message_mod.DecodeError('Unexpected end-group tag.')
+  except (IndexError, TypeError):
+    # Now ord(buf[p:p+1]) == ord('') gets TypeError.
+    raise message_mod.DecodeError('Truncated message.')
+  except struct.error as e:
+    raise message_mod.DecodeError(e)
+  return length   # Return this for legacy reasons.
+
+
+
+def SerializePartialToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializePartialToString(self, **kwargs):
+  out = BytesIO()
+  self._InternalSerialize(out.write, **kwargs)
+  return out.getvalue()
+
+
+
+def SerializeToString(self, **kwargs) +
+
+
+
+ +Expand source code + +
def SerializeToString(self, **kwargs):
+  # Check if the message has all of its required fields set.
+  if not self.IsInitialized():
+    raise message_mod.EncodeError(
+        'Message %s is missing required fields: %s' % (
+        self.DESCRIPTOR.full_name, ','.join(self.FindInitializationErrors())))
+  return self.SerializePartialToString(**kwargs)
+
+
+
+def SetInParent(self) +
+
+

Sets the _cached_byte_size_dirty bit to true, +and propagates this to our listener iff this was a state change.

+
+ +Expand source code + +
def Modified(self):
+  """Sets the _cached_byte_size_dirty bit to true,
+  and propagates this to our listener iff this was a state change.
+  """
+
+  # Note:  Some callers check _cached_byte_size_dirty before calling
+  #   _Modified() as an extra optimization.  So, if this method is ever
+  #   changed such that it does stuff even when _cached_byte_size_dirty is
+  #   already true, the callers need to be updated.
+  if not self._cached_byte_size_dirty:
+    self._cached_byte_size_dirty = True
+    self._listener_for_children.dirty = True
+    self._is_present_in_parent = True
+    self._listener.Modified()
+
+
+
+def UnknownFields(self) +
+
+
+
+ +Expand source code + +
def _UnknownFields(self):
+  if self._unknown_field_set is None:  # pylint: disable=protected-access
+    # pylint: disable=protected-access
+    self._unknown_field_set = containers.UnknownFieldSet()
+  return self._unknown_field_set    # pylint: disable=protected-access
+
+
+
+def WhichOneof(self, oneof_name) +
+
+

Returns the name of the currently set field inside a oneof, or None.

+
+ +Expand source code + +
def WhichOneof(self, oneof_name):
+  """Returns the name of the currently set field inside a oneof, or None."""
+  try:
+    field = message_descriptor.oneofs_by_name[oneof_name]
+  except KeyError:
+    raise ValueError(
+        'Protocol message has no oneof "%s" field.' % oneof_name)
+
+  nested_field = self._oneofs.get(field, None)
+  if nested_field is not None and self.HasField(nested_field.name):
+    return nested_field.name
+  else:
+    return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_remote_hardware.html b/docs/meshtastic/tests/test_remote_hardware.html new file mode 100644 index 0000000..9f5ca0f --- /dev/null +++ b/docs/meshtastic/tests/test_remote_hardware.html @@ -0,0 +1,304 @@ + + + + + + +meshtastic.tests.test_remote_hardware API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_remote_hardware

+
+
+

Meshtastic unit tests for remote_hardware.py

+
+ +Expand source code + +
"""Meshtastic unit tests for remote_hardware.py"""
+
+import logging
+import re
+
+from unittest.mock import patch, MagicMock
+import pytest
+
+from ..remote_hardware import RemoteHardwareClient, onGPIOreceive
+from ..serial_interface import SerialInterface
+
+
+@pytest.mark.unit
+def test_RemoteHardwareClient():
+    """Test that we can instantiate a RemoteHardwareClient instance"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    assert rhw.iface == iface
+    iface.close()
+
+
+@pytest.mark.unit
+def test_onGPIOreceive(capsys):
+    """Test onGPIOreceive"""
+    iface = MagicMock(autospec=SerialInterface)
+    packet = {'decoded': {'remotehw': {'typ': 'foo', 'gpioValue': '4096' }}}
+    onGPIOreceive(packet, iface)
+    out, err = capsys.readouterr()
+    assert re.search(r'Received RemoteHardware', out)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_RemoteHardwareClient_no_gpio_channel(capsys):
+    """Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            RemoteHardwareClient(mo)
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Warning: No channel named', out)
+        assert err == ""
+
+
+@pytest.mark.unit
+def test_readGPIOs(caplog):
+    """Test readGPIOs"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    with caplog.at_level(logging.DEBUG):
+        rhw.readGPIOs('0x10', 123)
+    assert re.search(r'readGPIOs', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+@pytest.mark.unit
+def test_writeGPIOs(caplog):
+    """Test writeGPIOs"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    with caplog.at_level(logging.DEBUG):
+        rhw.writeGPIOs('0x10', 123, 1)
+    assert re.search(r'writeGPIOs', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+@pytest.mark.unit
+def test_watchGPIOs(caplog):
+    """Test watchGPIOs"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    with caplog.at_level(logging.DEBUG):
+        rhw.watchGPIOs('0x10', 123)
+    assert re.search(r'watchGPIOs', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+@pytest.mark.unit
+def test_sendHardware_no_nodeid():
+    """Test sending no nodeid to _sendHardware()"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            rhw = RemoteHardwareClient(mo)
+            rhw._sendHardware(None, None)
+        assert pytest_wrapped_e.type == SystemExit
+
+
+
+
+
+
+
+

Functions

+
+
+def test_RemoteHardwareClient() +
+
+

Test that we can instantiate a RemoteHardwareClient instance

+
+ +Expand source code + +
@pytest.mark.unit
+def test_RemoteHardwareClient():
+    """Test that we can instantiate a RemoteHardwareClient instance"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    assert rhw.iface == iface
+    iface.close()
+
+
+
+def test_RemoteHardwareClient_no_gpio_channel(capsys) +
+
+

Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'

+
+ +Expand source code + +
@pytest.mark.unit
+def test_RemoteHardwareClient_no_gpio_channel(capsys):
+    """Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        mo.localNode.getChannelByName.return_value = None
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            RemoteHardwareClient(mo)
+        assert pytest_wrapped_e.type == SystemExit
+        assert pytest_wrapped_e.value.code == 1
+        out, err = capsys.readouterr()
+        assert re.search(r'Warning: No channel named', out)
+        assert err == ""
+
+
+
+def test_onGPIOreceive(capsys) +
+
+

Test onGPIOreceive

+
+ +Expand source code + +
@pytest.mark.unit
+def test_onGPIOreceive(capsys):
+    """Test onGPIOreceive"""
+    iface = MagicMock(autospec=SerialInterface)
+    packet = {'decoded': {'remotehw': {'typ': 'foo', 'gpioValue': '4096' }}}
+    onGPIOreceive(packet, iface)
+    out, err = capsys.readouterr()
+    assert re.search(r'Received RemoteHardware', out)
+    assert err == ''
+
+
+
+def test_readGPIOs(caplog) +
+
+

Test readGPIOs

+
+ +Expand source code + +
@pytest.mark.unit
+def test_readGPIOs(caplog):
+    """Test readGPIOs"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    with caplog.at_level(logging.DEBUG):
+        rhw.readGPIOs('0x10', 123)
+    assert re.search(r'readGPIOs', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+
+def test_sendHardware_no_nodeid() +
+
+

Test sending no nodeid to _sendHardware()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_sendHardware_no_nodeid():
+    """Test sending no nodeid to _sendHardware()"""
+    iface = MagicMock(autospec=SerialInterface)
+    with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
+        with pytest.raises(SystemExit) as pytest_wrapped_e:
+            rhw = RemoteHardwareClient(mo)
+            rhw._sendHardware(None, None)
+        assert pytest_wrapped_e.type == SystemExit
+
+
+
+def test_watchGPIOs(caplog) +
+
+

Test watchGPIOs

+
+ +Expand source code + +
@pytest.mark.unit
+def test_watchGPIOs(caplog):
+    """Test watchGPIOs"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    with caplog.at_level(logging.DEBUG):
+        rhw.watchGPIOs('0x10', 123)
+    assert re.search(r'watchGPIOs', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+
+def test_writeGPIOs(caplog) +
+
+

Test writeGPIOs

+
+ +Expand source code + +
@pytest.mark.unit
+def test_writeGPIOs(caplog):
+    """Test writeGPIOs"""
+    iface = MagicMock(autospec=SerialInterface)
+    rhw = RemoteHardwareClient(iface)
+    with caplog.at_level(logging.DEBUG):
+        rhw.writeGPIOs('0x10', 123, 1)
+    assert re.search(r'writeGPIOs', caplog.text, re.MULTILINE)
+    iface.close()
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_serial_interface.html b/docs/meshtastic/tests/test_serial_interface.html new file mode 100644 index 0000000..f3c8981 --- /dev/null +++ b/docs/meshtastic/tests/test_serial_interface.html @@ -0,0 +1,186 @@ + + + + + + +meshtastic.tests.test_serial_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_serial_interface

+
+
+

Meshtastic unit tests for serial_interface.py

+
+ +Expand source code + +
"""Meshtastic unit tests for serial_interface.py"""
+
+import re
+
+
+from unittest.mock import patch
+import pytest
+
+from ..serial_interface import SerialInterface
+
+@pytest.mark.unit
+@patch('serial.Serial')
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake'])
+def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
+    """Test that we can instantiate a SerialInterface with a single port"""
+    iface = SerialInterface(noProto=True)
+    iface.showInfo()
+    iface.localNode.showInfo()
+    iface.close()
+    mocked_findPorts.assert_called()
+    mocked_serial.assert_called()
+
+
+@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=[])
+def test_SerialInterface_no_ports(mocked_findPorts, capsys):
+    """Test that we can instantiate a SerialInterface with no ports"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        SerialInterface(noProto=True)
+    mocked_findPorts.assert_called()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake1', '/dev/ttyUSBfake2'])
+def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
+    """Test that we can instantiate a SerialInterface with two ports"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        SerialInterface(noProto=True)
+    mocked_findPorts.assert_called()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: Multiple serial ports were detected', out, re.MULTILINE)
+    assert err == ''
+
+
+
+
+
+
+
+

Functions

+
+
+def test_SerialInterface_multiple_ports(mocked_findPorts, capsys) +
+
+

Test that we can instantiate a SerialInterface with two ports

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake1', '/dev/ttyUSBfake2'])
+def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
+    """Test that we can instantiate a SerialInterface with two ports"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        SerialInterface(noProto=True)
+    mocked_findPorts.assert_called()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: Multiple serial ports were detected', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_SerialInterface_no_ports(mocked_findPorts, capsys) +
+
+

Test that we can instantiate a SerialInterface with no ports

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('meshtastic.util.findPorts', return_value=[])
+def test_SerialInterface_no_ports(mocked_findPorts, capsys):
+    """Test that we can instantiate a SerialInterface with no ports"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        SerialInterface(noProto=True)
+    mocked_findPorts.assert_called()
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+    out, err = capsys.readouterr()
+    assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE)
+    assert err == ''
+
+
+
+def test_SerialInterface_single_port(mocked_findPorts, mocked_serial) +
+
+

Test that we can instantiate a SerialInterface with a single port

+
+ +Expand source code + +
@pytest.mark.unit
+@patch('serial.Serial')
+@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake'])
+def test_SerialInterface_single_port(mocked_findPorts, mocked_serial):
+    """Test that we can instantiate a SerialInterface with a single port"""
+    iface = SerialInterface(noProto=True)
+    iface.showInfo()
+    iface.localNode.showInfo()
+    iface.close()
+    mocked_findPorts.assert_called()
+    mocked_serial.assert_called()
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_smoke1.html b/docs/meshtastic/tests/test_smoke1.html new file mode 100644 index 0000000..e3a32c6 --- /dev/null +++ b/docs/meshtastic/tests/test_smoke1.html @@ -0,0 +1,1822 @@ + + + + + + +meshtastic.tests.test_smoke1 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_smoke1

+
+
+

Meshtastic smoke tests with a single device via USB

+
+ +Expand source code + +
"""Meshtastic smoke tests with a single device via USB"""
+import re
+import subprocess
+import time
+import os
+
+# Do not like using hard coded sleeps, but it probably makes
+# sense to pause for the radio at apprpriate times
+import pytest
+
+from ..util import findPorts
+
+# seconds to pause after running a meshtastic command
+PAUSE_AFTER_COMMAND = 2
+PAUSE_AFTER_REBOOT = 7
+
+
+@pytest.mark.smoke1
+def test_smoke1_reboot():
+    """Test reboot"""
+    return_value, _ = subprocess.getstatusoutput('meshtastic --reboot')
+    assert return_value == 0
+    # pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
+    time.sleep(18)
+
+
+@pytest.mark.smoke1
+def test_smoke1_info():
+    """Test --info"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert re.search(r'^My info', out, re.MULTILINE)
+    assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
+    assert re.search(r'^Preferences', out, re.MULTILINE)
+    assert re.search(r'^Channels', out, re.MULTILINE)
+    assert re.search(r'^  PRIMARY', out, re.MULTILINE)
+    assert re.search(r'^Primary channel URL', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_sendping():
+    """Test --sendping"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --sendping')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Sending ping message', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_get_with_invalid_setting():
+    """Test '--get a_bad_setting'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --get a_bad_setting')
+    assert re.search(r'Choices in sorted order', out)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_set_with_invalid_setting():
+    """Test '--set a_bad_setting'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set a_bad_setting foo')
+    assert re.search(r'Choices in sorted order', out)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_ch_set_with_invalid_settingpatch_find_ports():
+    """Test '--ch-set with a_bad_setting'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set invalid_setting foo --ch-index 0')
+    assert re.search(r'Choices in sorted order', out)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_pos_fields():
+    """Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting position fields to 35', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'POS_ALTITUDE', out, re.MULTILINE)
+    assert re.search(r'POS_ALT_MSL', out, re.MULTILINE)
+    assert re.search(r'POS_BATTERY', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_test_with_arg_but_no_hardware():
+    """Test --test
+       Note: Since only one device is connected, it will not do much.
+    """
+    return_value, out = subprocess.getstatusoutput('meshtastic --test')
+    assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE)
+    assert return_value == 1
+
+
+@pytest.mark.smoke1
+def test_smoke1_debug():
+    """Test --debug"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info --debug')
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert re.search(r'^DEBUG file', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_seriallog_to_file():
+    """Test --seriallog to a file creates a file"""
+    filename = 'tmpoutput.txt'
+    if os.path.exists(f"{filename}"):
+        os.remove(f"{filename}")
+    return_value, _ = subprocess.getstatusoutput(f'meshtastic --info --seriallog {filename}')
+    assert os.path.exists(f"{filename}")
+    assert return_value == 0
+    os.remove(f"{filename}")
+
+
+@pytest.mark.smoke1
+def test_smoke1_qr():
+    """Test --qr"""
+    filename = 'tmpqr'
+    if os.path.exists(f"{filename}"):
+        os.remove(f"{filename}")
+    return_value, _ = subprocess.getstatusoutput(f'meshtastic --qr > {filename}')
+    assert os.path.exists(f"{filename}")
+    # not really testing that a valid qr code is created, just that the file size
+    # is reasonably big enough for a qr code
+    assert os.stat(f"{filename}").st_size > 20000
+    assert return_value == 0
+    os.remove(f"{filename}")
+
+
+@pytest.mark.smoke1
+def test_smoke1_nodes():
+    """Test --nodes"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --nodes')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^│   N │ User', out, re.MULTILINE)
+    assert re.search(r'^│   1 │', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_send_hello():
+    """Test --sendtext hello"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --sendtext hello')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_port():
+    """Test --port"""
+    # first, get the ports
+    ports = findPorts()
+    # hopefully there is just one
+    assert len(ports) == 1
+    port = ports[0]
+    return_value, out = subprocess.getstatusoutput(f'meshtastic --port {port} --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_is_router_true():
+    """Test --set is_router true"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set is_router true')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set is_router to true', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
+    assert re.search(r'^is_router: True', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_location_info():
+    """Test --setlat, --setlon and --setalt """
+    return_value, out = subprocess.getstatusoutput('meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Fixing altitude', out, re.MULTILINE)
+    assert re.search(r'^Fixing latitude', out, re.MULTILINE)
+    assert re.search(r'^Fixing longitude', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out2 = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'1337', out2, re.MULTILINE)
+    assert re.search(r'32.7767', out2, re.MULTILINE)
+    assert re.search(r'-96.797', out2, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_is_router_false():
+    """Test --set is_router false"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set is_router false')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set is_router to false', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
+    assert re.search(r'^is_router: False', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_owner():
+    """Test --set-owner name"""
+    # make sure the owner is not Joe
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Bob')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'Owner: Joe', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Joe')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Owner: Joe', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_team():
+    """Test --set-team """
+    # unset the team
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-team CLEAR')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting team to CLEAR', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-team CYAN')
+    assert re.search(r'Setting team to CYAN', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'CYAN', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_values():
+    """Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
+       --ch-shortslow, and --ch-shortfast arguments
+    """
+    exp = {
+            '--ch-longslow': 'Bw125Cr48Sf4096',
+            '--ch-longfast': 'Bw31_25Cr48Sf512',
+            '--ch-mediumslow': 'Bw250Cr46Sf2048',
+            '--ch-mediumfast': 'Bw250Cr47Sf1024',
+            # for some reason, this value does not show any modemConfig
+            '--ch-shortslow': '{ "psk',
+            '--ch-shortfast': 'Bw500Cr45Sf128'
+          }
+
+    for key, val in exp.items():
+        print(key, val)
+        return_value, out = subprocess.getstatusoutput(f'meshtastic {key}')
+        assert re.match(r'Connected to radio', out)
+        assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+        assert return_value == 0
+        # pause for the radio (might reboot)
+        time.sleep(PAUSE_AFTER_REBOOT)
+        return_value, out = subprocess.getstatusoutput('meshtastic --info')
+        assert re.search(val, out, re.MULTILINE)
+        assert return_value == 0
+        # pause for the radio
+        time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_set_name():
+    """Test --ch-set name"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'MyChannel', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set name to MyChannel', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'MyChannel', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_set_downlink_and_uplink():
+    """Test -ch-set downlink_enabled X and --ch-set uplink_enabled X"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'uplinkEnabled', out, re.MULTILINE)
+    assert not re.search(r'downlinkEnabled', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE)
+    assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'uplinkEnabled', out, re.MULTILINE)
+    assert re.search(r'downlinkEnabled', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_add_and_ch_del():
+    """Test --ch-add"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
+    assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-index 1 --ch-del')
+    assert re.search(r'Deleting channel 1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    # make sure the secondar channel is not there
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert not re.search(r'SECONDARY', out, re.MULTILINE)
+    assert not re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_enable_and_disable():
+    """Test --ch-enable and --ch-disable"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
+    assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    # ensure they need to specify a --ch-index
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable')
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'DISABLED', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_del_a_disabled_non_primary_channel():
+    """Test --ch-del will work on a disabled non-primary channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
+    assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    # ensure they need to specify a --ch-index
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable')
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert not re.search(r'DISABLED', out, re.MULTILINE)
+    assert not re.search(r'SECONDARY', out, re.MULTILINE)
+    assert not re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_attempt_to_delete_primary_channel():
+    """Test that we cannot delete the PRIMARY channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 0')
+    assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_attempt_to_disable_primary_channel():
+    """Test that we cannot disable the PRIMARY channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 0')
+    assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_attempt_to_enable_primary_channel():
+    """Test that we cannot enable the PRIMARY channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 0')
+    assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_ensure_ch_del_second_of_three_channels():
+    """Test that when we delete the 2nd of 3 channels, that it deletes the correct channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing2', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing2', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_ensure_ch_del_third_of_three_channels():
+    """Test that when we delete the 3rd of 3 channels, that it deletes the correct channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing2', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 2')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_ch_set_modem_config():
+    """Test --ch-set modem_config"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512')
+    assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512 --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set modem_config to Bw31_25Cr48Sf512', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_seturl_default():
+    """Test --seturl with default value"""
+    # set some channel value so we no longer have a default channel
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo --ch-index 0')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    # ensure we no longer have a default primary channel
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search('CgUYAyIBAQ', out, re.MULTILINE)
+    assert return_value == 0
+    url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
+    return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search('CgUYAyIBAQ', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_seturl_invalid_url():
+    """Test --seturl with invalid url"""
+    # Note: This url is no longer a valid url.
+    url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
+    return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
+    assert re.match(r'Connected to radio', out)
+    assert re.search('Warning: There were no settings', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+@pytest.mark.smoke1
+def test_smoke1_configure():
+    """Test --configure"""
+    _ , out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml")
+    assert re.match(r'Connected to radio', out)
+    assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE)
+    assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE)
+    assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE)
+    assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE)
+    assert re.search('^Setting device position', out, re.MULTILINE)
+    assert re.search('^Set region to 1', out, re.MULTILINE)
+    assert re.search('^Set is_always_powered to true', out, re.MULTILINE)
+    assert re.search('^Set send_owner_interval to 2', out, re.MULTILINE)
+    assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE)
+    assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE)
+    assert re.search('^Writing modified preferences to device', out, re.MULTILINE)
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_ham():
+    """Test --set-ham
+       Note: Do a factory reset after this setting so it is very short-lived.
+    """
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-ham KI1234')
+    assert re.search(r'Setting Ham ID', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Owner: KI1234', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_set_wifi_settings():
+    """Test --set wifi_ssid and --set wifi_password"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE)
+    assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --get wifi_ssid --get wifi_password')
+    assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE)
+    assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE)
+    assert return_value == 0
+
+
+@pytest.mark.smoke1
+def test_smoke1_factory_reset():
+    """Test factory reset"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set factory_reset true')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set factory_reset to true', out, re.MULTILINE)
+    assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE)
+    assert return_value == 0
+    # NOTE: The radio may not be responsive after this, may need to do a manual reboot
+    # by pressing the button
+
+
+
+
+
+
+
+

Functions

+
+
+def test_ch_set_with_invalid_settingpatch_find_ports() +
+
+

Test '–ch-set with a_bad_setting'.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_ch_set_with_invalid_settingpatch_find_ports():
+    """Test '--ch-set with a_bad_setting'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set invalid_setting foo --ch-index 0')
+    assert re.search(r'Choices in sorted order', out)
+    assert return_value == 0
+
+
+
+def test_get_with_invalid_setting() +
+
+

Test '–get a_bad_setting'.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_get_with_invalid_setting():
+    """Test '--get a_bad_setting'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --get a_bad_setting')
+    assert re.search(r'Choices in sorted order', out)
+    assert return_value == 0
+
+
+
+def test_set_with_invalid_setting() +
+
+

Test '–set a_bad_setting'.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_set_with_invalid_setting():
+    """Test '--set a_bad_setting'."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set a_bad_setting foo')
+    assert re.search(r'Choices in sorted order', out)
+    assert return_value == 0
+
+
+
+def test_smoke1_attempt_to_delete_primary_channel() +
+
+

Test that we cannot delete the PRIMARY channel.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_attempt_to_delete_primary_channel():
+    """Test that we cannot delete the PRIMARY channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 0')
+    assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_attempt_to_disable_primary_channel() +
+
+

Test that we cannot disable the PRIMARY channel.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_attempt_to_disable_primary_channel():
+    """Test that we cannot disable the PRIMARY channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 0')
+    assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_attempt_to_enable_primary_channel() +
+
+

Test that we cannot enable the PRIMARY channel.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_attempt_to_enable_primary_channel():
+    """Test that we cannot enable the PRIMARY channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 0')
+    assert re.search(r'Warning: Cannot enable', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_ch_add_and_ch_del() +
+
+

Test –ch-add

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_add_and_ch_del():
+    """Test --ch-add"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
+    assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-index 1 --ch-del')
+    assert re.search(r'Deleting channel 1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    # make sure the secondar channel is not there
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert not re.search(r'SECONDARY', out, re.MULTILINE)
+    assert not re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_ch_del_a_disabled_non_primary_channel() +
+
+

Test –ch-del will work on a disabled non-primary channel.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_del_a_disabled_non_primary_channel():
+    """Test --ch-del will work on a disabled non-primary channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
+    assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    # ensure they need to specify a --ch-index
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable')
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert not re.search(r'DISABLED', out, re.MULTILINE)
+    assert not re.search(r'SECONDARY', out, re.MULTILINE)
+    assert not re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_ch_enable_and_disable() +
+
+

Test –ch-enable and –ch-disable

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_enable_and_disable():
+    """Test --ch-enable and --ch-disable"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing')
+    assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    # ensure they need to specify a --ch-index
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable')
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'DISABLED', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+ +
+

Test -ch-set downlink_enabled X and –ch-set uplink_enabled X

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_set_downlink_and_uplink():
+    """Test -ch-set downlink_enabled X and --ch-set uplink_enabled X"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'uplinkEnabled', out, re.MULTILINE)
+    assert not re.search(r'downlinkEnabled', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE)
+    assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'uplinkEnabled', out, re.MULTILINE)
+    assert re.search(r'downlinkEnabled', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_ch_set_modem_config() +
+
+

Test –ch-set modem_config

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_set_modem_config():
+    """Test --ch-set modem_config"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512')
+    assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512 --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set modem_config to Bw31_25Cr48Sf512', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_ch_set_name() +
+
+

Test –ch-set name

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_set_name():
+    """Test --ch-set name"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'MyChannel', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel --ch-index 0')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set name to MyChannel', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'MyChannel', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_ch_values() +
+
+

Test –ch-longslow, –ch-longfast, –ch-mediumslow, –ch-mediumsfast, +–ch-shortslow, and –ch-shortfast arguments

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ch_values():
+    """Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
+       --ch-shortslow, and --ch-shortfast arguments
+    """
+    exp = {
+            '--ch-longslow': 'Bw125Cr48Sf4096',
+            '--ch-longfast': 'Bw31_25Cr48Sf512',
+            '--ch-mediumslow': 'Bw250Cr46Sf2048',
+            '--ch-mediumfast': 'Bw250Cr47Sf1024',
+            # for some reason, this value does not show any modemConfig
+            '--ch-shortslow': '{ "psk',
+            '--ch-shortfast': 'Bw500Cr45Sf128'
+          }
+
+    for key, val in exp.items():
+        print(key, val)
+        return_value, out = subprocess.getstatusoutput(f'meshtastic {key}')
+        assert re.match(r'Connected to radio', out)
+        assert re.search(r'Writing modified channels to device', out, re.MULTILINE)
+        assert return_value == 0
+        # pause for the radio (might reboot)
+        time.sleep(PAUSE_AFTER_REBOOT)
+        return_value, out = subprocess.getstatusoutput('meshtastic --info')
+        assert re.search(val, out, re.MULTILINE)
+        assert return_value == 0
+        # pause for the radio
+        time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_configure() +
+
+

Test –configure

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_configure():
+    """Test --configure"""
+    _ , out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml")
+    assert re.match(r'Connected to radio', out)
+    assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE)
+    assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE)
+    assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE)
+    assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE)
+    assert re.search('^Setting device position', out, re.MULTILINE)
+    assert re.search('^Set region to 1', out, re.MULTILINE)
+    assert re.search('^Set is_always_powered to true', out, re.MULTILINE)
+    assert re.search('^Set send_owner_interval to 2', out, re.MULTILINE)
+    assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE)
+    assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE)
+    assert re.search('^Writing modified preferences to device', out, re.MULTILINE)
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+
+
+
+def test_smoke1_debug() +
+
+

Test –debug

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_debug():
+    """Test --debug"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info --debug')
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert re.search(r'^DEBUG file', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_ensure_ch_del_second_of_three_channels() +
+
+

Test that when we delete the 2nd of 3 channels, that it deletes the correct channel.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ensure_ch_del_second_of_three_channels():
+    """Test that when we delete the 2nd of 3 channels, that it deletes the correct channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing2', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing2', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_ensure_ch_del_third_of_three_channels() +
+
+

Test that when we delete the 3rd of 3 channels, that it deletes the correct channel.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_ensure_ch_del_third_of_three_channels():
+    """Test that when we delete the 3rd of 3 channels, that it deletes the correct channel."""
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'SECONDARY', out, re.MULTILINE)
+    assert re.search(r'testing1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing2', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 2')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'testing1', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1')
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_factory_reset() +
+
+

Test factory reset

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_factory_reset():
+    """Test factory reset"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set factory_reset true')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set factory_reset to true', out, re.MULTILINE)
+    assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE)
+    assert return_value == 0
+    # NOTE: The radio may not be responsive after this, may need to do a manual reboot
+    # by pressing the button
+
+
+
+def test_smoke1_info() +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_info():
+    """Test --info"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert re.search(r'^My info', out, re.MULTILINE)
+    assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
+    assert re.search(r'^Preferences', out, re.MULTILINE)
+    assert re.search(r'^Channels', out, re.MULTILINE)
+    assert re.search(r'^  PRIMARY', out, re.MULTILINE)
+    assert re.search(r'^Primary channel URL', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_nodes() +
+
+

Test –nodes

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_nodes():
+    """Test --nodes"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --nodes')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^│   N │ User', out, re.MULTILINE)
+    assert re.search(r'^│   1 │', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_port() +
+
+

Test –port

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_port():
+    """Test --port"""
+    # first, get the ports
+    ports = findPorts()
+    # hopefully there is just one
+    assert len(ports) == 1
+    port = ports[0]
+    return_value, out = subprocess.getstatusoutput(f'meshtastic --port {port} --info')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_pos_fields() +
+
+

Test –pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_pos_fields():
+    """Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting position fields to 35', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'POS_ALTITUDE', out, re.MULTILINE)
+    assert re.search(r'POS_ALT_MSL', out, re.MULTILINE)
+    assert re.search(r'POS_BATTERY', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_qr() +
+
+

Test –qr

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_qr():
+    """Test --qr"""
+    filename = 'tmpqr'
+    if os.path.exists(f"{filename}"):
+        os.remove(f"{filename}")
+    return_value, _ = subprocess.getstatusoutput(f'meshtastic --qr > {filename}')
+    assert os.path.exists(f"{filename}")
+    # not really testing that a valid qr code is created, just that the file size
+    # is reasonably big enough for a qr code
+    assert os.stat(f"{filename}").st_size > 20000
+    assert return_value == 0
+    os.remove(f"{filename}")
+
+
+
+def test_smoke1_reboot() +
+
+

Test reboot

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_reboot():
+    """Test reboot"""
+    return_value, _ = subprocess.getstatusoutput('meshtastic --reboot')
+    assert return_value == 0
+    # pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
+    time.sleep(18)
+
+
+
+def test_smoke1_send_hello() +
+
+

Test –sendtext hello

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_send_hello():
+    """Test --sendtext hello"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --sendtext hello')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_sendping() +
+
+

Test –sendping

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_sendping():
+    """Test --sendping"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --sendping')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Sending ping message', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_seriallog_to_file() +
+
+

Test –seriallog to a file creates a file

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_seriallog_to_file():
+    """Test --seriallog to a file creates a file"""
+    filename = 'tmpoutput.txt'
+    if os.path.exists(f"{filename}"):
+        os.remove(f"{filename}")
+    return_value, _ = subprocess.getstatusoutput(f'meshtastic --info --seriallog {filename}')
+    assert os.path.exists(f"{filename}")
+    assert return_value == 0
+    os.remove(f"{filename}")
+
+
+
+def test_smoke1_set_ham() +
+
+

Test –set-ham +Note: Do a factory reset after this setting so it is very short-lived.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_ham():
+    """Test --set-ham
+       Note: Do a factory reset after this setting so it is very short-lived.
+    """
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-ham KI1234')
+    assert re.search(r'Setting Ham ID', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Owner: KI1234', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_set_is_router_false() +
+
+

Test –set is_router false

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_is_router_false():
+    """Test --set is_router false"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set is_router false')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set is_router to false', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
+    assert re.search(r'^is_router: False', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_set_is_router_true() +
+
+

Test –set is_router true

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_is_router_true():
+    """Test --set is_router true"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set is_router true')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set is_router to true', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
+    assert re.search(r'^is_router: True', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_set_location_info() +
+
+

Test –setlat, –setlon and –setalt

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_location_info():
+    """Test --setlat, --setlon and --setalt """
+    return_value, out = subprocess.getstatusoutput('meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Fixing altitude', out, re.MULTILINE)
+    assert re.search(r'^Fixing latitude', out, re.MULTILINE)
+    assert re.search(r'^Fixing longitude', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out2 = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'1337', out2, re.MULTILINE)
+    assert re.search(r'32.7767', out2, re.MULTILINE)
+    assert re.search(r'-96.797', out2, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_set_owner() +
+
+

Test –set-owner name

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_owner():
+    """Test --set-owner name"""
+    # make sure the owner is not Joe
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Bob')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search(r'Owner: Joe', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Joe')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Owner: Joe', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_set_team() +
+
+

Test –set-team

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_team():
+    """Test --set-team """
+    # unset the team
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-team CLEAR')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Setting team to CLEAR', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    return_value, out = subprocess.getstatusoutput('meshtastic --set-team CYAN')
+    assert re.search(r'Setting team to CYAN', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_REBOOT)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'CYAN', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_set_wifi_settings() +
+
+

Test –set wifi_ssid and –set wifi_password

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_set_wifi_settings():
+    """Test --set wifi_ssid and --set wifi_password"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"')
+    assert re.match(r'Connected to radio', out)
+    assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE)
+    assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --get wifi_ssid --get wifi_password')
+    assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE)
+    assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_seturl_default() +
+
+

Test –seturl with default value

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_seturl_default():
+    """Test --seturl with default value"""
+    # set some channel value so we no longer have a default channel
+    return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo --ch-index 0')
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    # ensure we no longer have a default primary channel
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert not re.search('CgUYAyIBAQ', out, re.MULTILINE)
+    assert return_value == 0
+    url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
+    return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
+    assert re.match(r'Connected to radio', out)
+    assert return_value == 0
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search('CgUYAyIBAQ', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+def test_smoke1_seturl_invalid_url() +
+
+

Test –seturl with invalid url

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_seturl_invalid_url():
+    """Test --seturl with invalid url"""
+    # Note: This url is no longer a valid url.
+    url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
+    return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
+    assert re.match(r'Connected to radio', out)
+    assert re.search('Warning: There were no settings', out, re.MULTILINE)
+    assert return_value == 1
+    # pause for the radio
+    time.sleep(PAUSE_AFTER_COMMAND)
+
+
+
+def test_smoke1_test_with_arg_but_no_hardware() +
+
+

Test –test +Note: Since only one device is connected, it will not do much.

+
+ +Expand source code + +
@pytest.mark.smoke1
+def test_smoke1_test_with_arg_but_no_hardware():
+    """Test --test
+       Note: Since only one device is connected, it will not do much.
+    """
+    return_value, out = subprocess.getstatusoutput('meshtastic --test')
+    assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE)
+    assert return_value == 1
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_smoke2.html b/docs/meshtastic/tests/test_smoke2.html new file mode 100644 index 0000000..2cfb2e7 --- /dev/null +++ b/docs/meshtastic/tests/test_smoke2.html @@ -0,0 +1,127 @@ + + + + + + +meshtastic.tests.test_smoke2 API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_smoke2

+
+
+

Meshtastic smoke tests with 2 devices connected via USB

+
+ +Expand source code + +
"""Meshtastic smoke tests with 2 devices connected via USB"""
+import re
+import subprocess
+
+import pytest
+
+
+@pytest.mark.smoke2
+def test_smoke2_info():
+    """Test --info with 2 devices connected serially"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Warning: Multiple', out, re.MULTILINE)
+    assert return_value == 1
+
+
+@pytest.mark.smoke2
+def test_smoke2_test():
+    """Test --test"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --test')
+    assert re.search(r'Writing serial debugging', out, re.MULTILINE)
+    assert re.search(r'Ports opened', out, re.MULTILINE)
+    assert re.search(r'Running 5 tests', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+
+
+
+
+

Functions

+
+
+def test_smoke2_info() +
+
+

Test –info with 2 devices connected serially

+
+ +Expand source code + +
@pytest.mark.smoke2
+def test_smoke2_info():
+    """Test --info with 2 devices connected serially"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info')
+    assert re.search(r'Warning: Multiple', out, re.MULTILINE)
+    assert return_value == 1
+
+
+
+def test_smoke2_test() +
+
+

Test –test

+
+ +Expand source code + +
@pytest.mark.smoke2
+def test_smoke2_test():
+    """Test --test"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --test')
+    assert re.search(r'Writing serial debugging', out, re.MULTILINE)
+    assert re.search(r'Ports opened', out, re.MULTILINE)
+    assert re.search(r'Running 5 tests', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_smoke_wifi.html b/docs/meshtastic/tests/test_smoke_wifi.html new file mode 100644 index 0000000..3e2e28e --- /dev/null +++ b/docs/meshtastic/tests/test_smoke_wifi.html @@ -0,0 +1,115 @@ + + + + + + +meshtastic.tests.test_smoke_wifi API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_smoke_wifi

+
+
+

Meshtastic smoke tests a device setup with wifi.

+

Need to have run the following on an esp32 device: +meshtastic –set wifi_ssid 'foo' –set wifi_password 'sekret'

+
+ +Expand source code + +
"""Meshtastic smoke tests a device setup with wifi.
+
+   Need to have run the following on an esp32 device:
+      meshtastic --set wifi_ssid 'foo' --set wifi_password 'sekret'
+"""
+import re
+import subprocess
+
+import pytest
+
+
+@pytest.mark.smokewifi
+def test_smokewifi_info():
+    """Test --info"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info --host meshtastic.local')
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert re.search(r'^My info', out, re.MULTILINE)
+    assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
+    assert re.search(r'^Preferences', out, re.MULTILINE)
+    assert re.search(r'^Channels', out, re.MULTILINE)
+    assert re.search(r'^  PRIMARY', out, re.MULTILINE)
+    assert re.search(r'^Primary channel URL', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+
+
+
+
+

Functions

+
+
+def test_smokewifi_info() +
+
+

Test –info

+
+ +Expand source code + +
@pytest.mark.smokewifi
+def test_smokewifi_info():
+    """Test --info"""
+    return_value, out = subprocess.getstatusoutput('meshtastic --info --host meshtastic.local')
+    assert re.search(r'^Owner', out, re.MULTILINE)
+    assert re.search(r'^My info', out, re.MULTILINE)
+    assert re.search(r'^Nodes in mesh', out, re.MULTILINE)
+    assert re.search(r'^Preferences', out, re.MULTILINE)
+    assert re.search(r'^Channels', out, re.MULTILINE)
+    assert re.search(r'^  PRIMARY', out, re.MULTILINE)
+    assert re.search(r'^Primary channel URL', out, re.MULTILINE)
+    assert return_value == 0
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_stream_interface.html b/docs/meshtastic/tests/test_stream_interface.html new file mode 100644 index 0000000..6702fd9 --- /dev/null +++ b/docs/meshtastic/tests/test_stream_interface.html @@ -0,0 +1,246 @@ + + + + + + +meshtastic.tests.test_stream_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_stream_interface

+
+
+

Meshtastic unit tests for stream_interface.py

+
+ +Expand source code + +
"""Meshtastic unit tests for stream_interface.py"""
+
+import logging
+import re
+
+from unittest.mock import MagicMock
+import pytest
+
+from ..stream_interface import StreamInterface
+
+
+@pytest.mark.unit
+def test_StreamInterface():
+    """Test that we cannot instantiate a StreamInterface based on noProto"""
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        StreamInterface()
+    assert pytest_wrapped_e.type == Exception
+
+
+# Note: This takes a bit, so moving from unit to slow
+@pytest.mark.unitslow
+def test_StreamInterface_with_noProto(caplog, reset_globals):
+    """Test that we can instantiate a StreamInterface based on nonProto
+       and we can read/write bytes from a mocked stream
+    """
+    stream = MagicMock()
+    test_data = b'hello'
+    stream.read.return_value = test_data
+    with caplog.at_level(logging.DEBUG):
+        iface = StreamInterface(noProto=True, connectNow=False)
+        iface.stream = stream
+        iface._writeBytes(test_data)
+        data = iface._readBytes(len(test_data))
+        assert data == test_data
+
+
+# Note: This takes a bit, so moving from unit to slow
+# Tip: If you want to see the print output, run with '-s' flag:
+#      pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
+@pytest.mark.unitslow
+def test_sendToRadioImpl(caplog, reset_globals):
+    """Test _sendToRadioImpl()"""
+
+#    def add_header(b):
+#        """Add header stuffs for radio"""
+#        bufLen = len(b)
+#        header = bytes([START1, START2, (bufLen >> 8) & 0xff,  bufLen & 0xff])
+#        return header + b
+
+    # captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named "gpio")
+    raw_1_my_info = b'\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
+    raw_2_node_info = b'"9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C"\x06$o(\xb5F\\0\n\x1a\x02 1%M<\xc6a'
+    # pylint: disable=C0301
+    raw_3_node_info = b'"C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24"\x06$o(\xb5F$0\n\x1a\x07 5MH<\xc6a%G<\xc6a=\x00\x00\xc0@'
+    raw_4_complete = b'@\xcf\xe5\xd1\x8c\x0e'
+    # pylint: disable=C0301
+    raw_5_prefs = b'Z6\r\\F\xb5(\x15\\F\xb5("\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M<\xc6aP\x03`F'
+    # pylint: disable=C0301
+    raw_6_channel0 = b'Z.\r\\F\xb5(\x15\\F\xb5("\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01"\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M<\xc6aP\x03`F'
+    # pylint: disable=C0301
+    raw_7_channel1 = b'ZS\r\\F\xb5(\x15\\F\xb5("9\x08\x06\x120:.\x08\x01\x12(" \xb4&\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4"\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M<\xc6aP\x03`F'
+    raw_8_channel2 = b'Z)\r\\F\xb5(\x15\\F\xb5("\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M<\xc6aP\x03`F'
+    raw_blank = b''
+
+    test_data = b'hello'
+    stream = MagicMock()
+    #stream.read.return_value = add_header(test_data)
+    stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete,
+                                raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2,
+                                raw_blank, raw_blank]
+    toRadio = MagicMock()
+    toRadio.SerializeToString.return_value = test_data
+    with caplog.at_level(logging.DEBUG):
+        iface = StreamInterface(noProto=True, connectNow=False)
+        iface.stream = stream
+        iface.connect()
+        iface._sendToRadioImpl(toRadio)
+        assert re.search(r'Sending: ', caplog.text, re.MULTILINE)
+        assert re.search(r'reading character', caplog.text, re.MULTILINE)
+        assert re.search(r'In reader loop', caplog.text, re.MULTILINE)
+        print(caplog.text)
+
+
+
+
+
+
+
+

Functions

+
+
+def test_StreamInterface() +
+
+

Test that we cannot instantiate a StreamInterface based on noProto

+
+ +Expand source code + +
@pytest.mark.unit
+def test_StreamInterface():
+    """Test that we cannot instantiate a StreamInterface based on noProto"""
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        StreamInterface()
+    assert pytest_wrapped_e.type == Exception
+
+
+
+def test_StreamInterface_with_noProto(caplog, reset_globals) +
+
+

Test that we can instantiate a StreamInterface based on nonProto +and we can read/write bytes from a mocked stream

+
+ +Expand source code + +
@pytest.mark.unitslow
+def test_StreamInterface_with_noProto(caplog, reset_globals):
+    """Test that we can instantiate a StreamInterface based on nonProto
+       and we can read/write bytes from a mocked stream
+    """
+    stream = MagicMock()
+    test_data = b'hello'
+    stream.read.return_value = test_data
+    with caplog.at_level(logging.DEBUG):
+        iface = StreamInterface(noProto=True, connectNow=False)
+        iface.stream = stream
+        iface._writeBytes(test_data)
+        data = iface._readBytes(len(test_data))
+        assert data == test_data
+
+
+
+def test_sendToRadioImpl(caplog, reset_globals) +
+
+

Test _sendToRadioImpl()

+
+ +Expand source code + +
@pytest.mark.unitslow
+def test_sendToRadioImpl(caplog, reset_globals):
+    """Test _sendToRadioImpl()"""
+
+#    def add_header(b):
+#        """Add header stuffs for radio"""
+#        bufLen = len(b)
+#        header = bytes([START1, START2, (bufLen >> 8) & 0xff,  bufLen & 0xff])
+#        return header + b
+
+    # captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named "gpio")
+    raw_1_my_info = b'\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
+    raw_2_node_info = b'"9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C"\x06$o(\xb5F\\0\n\x1a\x02 1%M<\xc6a'
+    # pylint: disable=C0301
+    raw_3_node_info = b'"C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24"\x06$o(\xb5F$0\n\x1a\x07 5MH<\xc6a%G<\xc6a=\x00\x00\xc0@'
+    raw_4_complete = b'@\xcf\xe5\xd1\x8c\x0e'
+    # pylint: disable=C0301
+    raw_5_prefs = b'Z6\r\\F\xb5(\x15\\F\xb5("\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M<\xc6aP\x03`F'
+    # pylint: disable=C0301
+    raw_6_channel0 = b'Z.\r\\F\xb5(\x15\\F\xb5("\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01"\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M<\xc6aP\x03`F'
+    # pylint: disable=C0301
+    raw_7_channel1 = b'ZS\r\\F\xb5(\x15\\F\xb5("9\x08\x06\x120:.\x08\x01\x12(" \xb4&\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4"\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M<\xc6aP\x03`F'
+    raw_8_channel2 = b'Z)\r\\F\xb5(\x15\\F\xb5("\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M<\xc6aP\x03`F'
+    raw_blank = b''
+
+    test_data = b'hello'
+    stream = MagicMock()
+    #stream.read.return_value = add_header(test_data)
+    stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete,
+                                raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2,
+                                raw_blank, raw_blank]
+    toRadio = MagicMock()
+    toRadio.SerializeToString.return_value = test_data
+    with caplog.at_level(logging.DEBUG):
+        iface = StreamInterface(noProto=True, connectNow=False)
+        iface.stream = stream
+        iface.connect()
+        iface._sendToRadioImpl(toRadio)
+        assert re.search(r'Sending: ', caplog.text, re.MULTILINE)
+        assert re.search(r'reading character', caplog.text, re.MULTILINE)
+        assert re.search(r'In reader loop', caplog.text, re.MULTILINE)
+        print(caplog.text)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_tcp_interface.html b/docs/meshtastic/tests/test_tcp_interface.html new file mode 100644 index 0000000..0753c11 --- /dev/null +++ b/docs/meshtastic/tests/test_tcp_interface.html @@ -0,0 +1,120 @@ + + + + + + +meshtastic.tests.test_tcp_interface API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_tcp_interface

+
+
+

Meshtastic unit tests for tcp_interface.py

+
+ +Expand source code + +
"""Meshtastic unit tests for tcp_interface.py"""
+
+import re
+
+from unittest.mock import patch
+import pytest
+
+from ..tcp_interface import TCPInterface
+
+
+@pytest.mark.unit
+def test_TCPInterface(capsys):
+    """Test that we can instantiate a TCPInterface"""
+    with patch('socket.socket') as mock_socket:
+        iface = TCPInterface(hostname='localhost', noProto=True)
+        iface.showInfo()
+        iface.localNode.showInfo()
+        out, err = capsys.readouterr()
+        assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
+        assert re.search(r'Nodes', out, re.MULTILINE)
+        assert re.search(r'Preferences', out, re.MULTILINE)
+        assert re.search(r'Channels', out, re.MULTILINE)
+        assert re.search(r'Primary channel URL', out, re.MULTILINE)
+        assert err == ''
+        assert mock_socket.called
+        iface.close()
+
+
+
+
+
+
+
+

Functions

+
+
+def test_TCPInterface(capsys) +
+
+

Test that we can instantiate a TCPInterface

+
+ +Expand source code + +
@pytest.mark.unit
+def test_TCPInterface(capsys):
+    """Test that we can instantiate a TCPInterface"""
+    with patch('socket.socket') as mock_socket:
+        iface = TCPInterface(hostname='localhost', noProto=True)
+        iface.showInfo()
+        iface.localNode.showInfo()
+        out, err = capsys.readouterr()
+        assert re.search(r'Owner: None \(None\)', out, re.MULTILINE)
+        assert re.search(r'Nodes', out, re.MULTILINE)
+        assert re.search(r'Preferences', out, re.MULTILINE)
+        assert re.search(r'Channels', out, re.MULTILINE)
+        assert re.search(r'Primary channel URL', out, re.MULTILINE)
+        assert err == ''
+        assert mock_socket.called
+        iface.close()
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tests/test_util.html b/docs/meshtastic/tests/test_util.html new file mode 100644 index 0000000..5892866 --- /dev/null +++ b/docs/meshtastic/tests/test_util.html @@ -0,0 +1,519 @@ + + + + + + +meshtastic.tests.test_util API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tests.test_util

+
+
+

Meshtastic unit tests for util.py

+
+ +Expand source code + +
"""Meshtastic unit tests for util.py"""
+
+import re
+import logging
+
+import pytest
+
+from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
+                             support_info, genPSK256, fromStr, fromPSK,
+                             quoteBooleans, catchAndIgnore)
+
+
+@pytest.mark.unit
+def test_genPSK256():
+    """Test genPSK256"""
+    assert genPSK256() != ''
+
+
+@pytest.mark.unit
+def test_fromStr():
+    """Test fromStr"""
+    assert fromStr('') == b''
+    assert fromStr('0x12') == b'\x12'
+    assert fromStr('t')
+    assert fromStr('T')
+    assert fromStr('true')
+    assert fromStr('True')
+    assert fromStr('yes')
+    assert fromStr('Yes')
+    assert fromStr('f') is False
+    assert fromStr('F') is False
+    assert fromStr('false') is False
+    assert fromStr('False') is False
+    assert fromStr('no') is False
+    assert fromStr('No') is False
+    assert fromStr('100.01') == 100.01
+    assert fromStr('123') == 123
+    assert fromStr('abc') == 'abc'
+
+
+@pytest.mark.unit
+def test_quoteBooleans():
+    """Test quoteBooleans"""
+    assert quoteBooleans('') == ''
+    assert quoteBooleans('foo') == 'foo'
+    assert quoteBooleans('true') == 'true'
+    assert quoteBooleans('false') == 'false'
+    assert quoteBooleans(': true') == ": 'true'"
+    assert quoteBooleans(': false') == ": 'false'"
+
+@pytest.mark.unit
+def test_fromPSK():
+    """Test fromPSK"""
+    assert fromPSK('random') != ''
+    assert fromPSK('none') == b'\x00'
+    assert fromPSK('default') == b'\x01'
+    assert fromPSK('simple22') == b'\x17'
+    assert fromPSK('trash') == 'trash'
+
+
+@pytest.mark.unit
+def test_stripnl():
+    """Test stripnl"""
+    assert stripnl('') == ''
+    assert stripnl('a\n') == 'a'
+    assert stripnl(' a \n ') == 'a'
+    assert stripnl('a\nb') == 'a b'
+
+
+@pytest.mark.unit
+def test_pskToString_empty_string():
+    """Test pskToString empty string"""
+    assert pskToString('') == 'unencrypted'
+
+
+@pytest.mark.unit
+def test_pskToString_string():
+    """Test pskToString string"""
+    assert pskToString('hunter123') == 'secret'
+
+
+@pytest.mark.unit
+def test_pskToString_one_byte_zero_value():
+    """Test pskToString one byte that is value of 0"""
+    assert pskToString(bytes([0x00])) == 'unencrypted'
+
+
+@pytest.mark.unit
+def test_pskToString_one_byte_non_zero_value():
+    """Test pskToString one byte that is non-zero"""
+    assert pskToString(bytes([0x01])) == 'default'
+
+
+@pytest.mark.unit
+def test_pskToString_many_bytes():
+    """Test pskToString many bytes"""
+    assert pskToString(bytes([0x02, 0x01])) == 'secret'
+
+
+@pytest.mark.unit
+def test_pskToString_simple():
+    """Test pskToString simple"""
+    assert pskToString(bytes([0x03])) == 'simple2'
+
+
+@pytest.mark.unit
+def test_our_exit_zero_return_value():
+    """Test our_exit with a zero return value"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        our_exit("Warning: Some message", 0)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+
+
+@pytest.mark.unit
+def test_our_exit_non_zero_return_value():
+    """Test our_exit with a non-zero return value"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        our_exit("Error: Some message", 1)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+@pytest.mark.unit
+def test_fixme():
+    """Test fixme()"""
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        fixme("some exception")
+    assert pytest_wrapped_e.type == Exception
+
+
+@pytest.mark.unit
+def test_support_info(capsys):
+    """Test support_info"""
+    support_info()
+    out, err = capsys.readouterr()
+    assert re.search(r'System', out, re.MULTILINE)
+    assert re.search(r'Platform', out, re.MULTILINE)
+    assert re.search(r'Machine', out, re.MULTILINE)
+    assert re.search(r'Executable', out, re.MULTILINE)
+    assert err == ''
+
+
+@pytest.mark.unit
+def test_catchAndIgnore(caplog):
+    """Test catchAndIgnore() does not actually throw an exception, but just logs"""
+    def some_closure():
+        raise Exception('foo')
+    with caplog.at_level(logging.DEBUG):
+        catchAndIgnore("something", some_closure)
+    assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)
+
+
+
+
+
+
+
+

Functions

+
+
+def test_catchAndIgnore(caplog) +
+
+

Test catchAndIgnore() does not actually throw an exception, but just logs

+
+ +Expand source code + +
@pytest.mark.unit
+def test_catchAndIgnore(caplog):
+    """Test catchAndIgnore() does not actually throw an exception, but just logs"""
+    def some_closure():
+        raise Exception('foo')
+    with caplog.at_level(logging.DEBUG):
+        catchAndIgnore("something", some_closure)
+    assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)
+
+
+
+def test_fixme() +
+
+

Test fixme()

+
+ +Expand source code + +
@pytest.mark.unit
+def test_fixme():
+    """Test fixme()"""
+    with pytest.raises(Exception) as pytest_wrapped_e:
+        fixme("some exception")
+    assert pytest_wrapped_e.type == Exception
+
+
+
+def test_fromPSK() +
+
+

Test fromPSK

+
+ +Expand source code + +
@pytest.mark.unit
+def test_fromPSK():
+    """Test fromPSK"""
+    assert fromPSK('random') != ''
+    assert fromPSK('none') == b'\x00'
+    assert fromPSK('default') == b'\x01'
+    assert fromPSK('simple22') == b'\x17'
+    assert fromPSK('trash') == 'trash'
+
+
+
+def test_fromStr() +
+
+

Test fromStr

+
+ +Expand source code + +
@pytest.mark.unit
+def test_fromStr():
+    """Test fromStr"""
+    assert fromStr('') == b''
+    assert fromStr('0x12') == b'\x12'
+    assert fromStr('t')
+    assert fromStr('T')
+    assert fromStr('true')
+    assert fromStr('True')
+    assert fromStr('yes')
+    assert fromStr('Yes')
+    assert fromStr('f') is False
+    assert fromStr('F') is False
+    assert fromStr('false') is False
+    assert fromStr('False') is False
+    assert fromStr('no') is False
+    assert fromStr('No') is False
+    assert fromStr('100.01') == 100.01
+    assert fromStr('123') == 123
+    assert fromStr('abc') == 'abc'
+
+
+
+def test_genPSK256() +
+
+

Test genPSK256

+
+ +Expand source code + +
@pytest.mark.unit
+def test_genPSK256():
+    """Test genPSK256"""
+    assert genPSK256() != ''
+
+
+
+def test_our_exit_non_zero_return_value() +
+
+

Test our_exit with a non-zero return value

+
+ +Expand source code + +
@pytest.mark.unit
+def test_our_exit_non_zero_return_value():
+    """Test our_exit with a non-zero return value"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        our_exit("Error: Some message", 1)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 1
+
+
+
+def test_our_exit_zero_return_value() +
+
+

Test our_exit with a zero return value

+
+ +Expand source code + +
@pytest.mark.unit
+def test_our_exit_zero_return_value():
+    """Test our_exit with a zero return value"""
+    with pytest.raises(SystemExit) as pytest_wrapped_e:
+        our_exit("Warning: Some message", 0)
+    assert pytest_wrapped_e.type == SystemExit
+    assert pytest_wrapped_e.value.code == 0
+
+
+
+def test_pskToString_empty_string() +
+
+

Test pskToString empty string

+
+ +Expand source code + +
@pytest.mark.unit
+def test_pskToString_empty_string():
+    """Test pskToString empty string"""
+    assert pskToString('') == 'unencrypted'
+
+
+
+def test_pskToString_many_bytes() +
+
+

Test pskToString many bytes

+
+ +Expand source code + +
@pytest.mark.unit
+def test_pskToString_many_bytes():
+    """Test pskToString many bytes"""
+    assert pskToString(bytes([0x02, 0x01])) == 'secret'
+
+
+
+def test_pskToString_one_byte_non_zero_value() +
+
+

Test pskToString one byte that is non-zero

+
+ +Expand source code + +
@pytest.mark.unit
+def test_pskToString_one_byte_non_zero_value():
+    """Test pskToString one byte that is non-zero"""
+    assert pskToString(bytes([0x01])) == 'default'
+
+
+
+def test_pskToString_one_byte_zero_value() +
+
+

Test pskToString one byte that is value of 0

+
+ +Expand source code + +
@pytest.mark.unit
+def test_pskToString_one_byte_zero_value():
+    """Test pskToString one byte that is value of 0"""
+    assert pskToString(bytes([0x00])) == 'unencrypted'
+
+
+
+def test_pskToString_simple() +
+
+

Test pskToString simple

+
+ +Expand source code + +
@pytest.mark.unit
+def test_pskToString_simple():
+    """Test pskToString simple"""
+    assert pskToString(bytes([0x03])) == 'simple2'
+
+
+
+def test_pskToString_string() +
+
+

Test pskToString string

+
+ +Expand source code + +
@pytest.mark.unit
+def test_pskToString_string():
+    """Test pskToString string"""
+    assert pskToString('hunter123') == 'secret'
+
+
+
+def test_quoteBooleans() +
+
+

Test quoteBooleans

+
+ +Expand source code + +
@pytest.mark.unit
+def test_quoteBooleans():
+    """Test quoteBooleans"""
+    assert quoteBooleans('') == ''
+    assert quoteBooleans('foo') == 'foo'
+    assert quoteBooleans('true') == 'true'
+    assert quoteBooleans('false') == 'false'
+    assert quoteBooleans(': true') == ": 'true'"
+    assert quoteBooleans(': false') == ": 'false'"
+
+
+
+def test_stripnl() +
+
+

Test stripnl

+
+ +Expand source code + +
@pytest.mark.unit
+def test_stripnl():
+    """Test stripnl"""
+    assert stripnl('') == ''
+    assert stripnl('a\n') == 'a'
+    assert stripnl(' a \n ') == 'a'
+    assert stripnl('a\nb') == 'a b'
+
+
+
+def test_support_info(capsys) +
+
+

Test support_info

+
+ +Expand source code + +
@pytest.mark.unit
+def test_support_info(capsys):
+    """Test support_info"""
+    support_info()
+    out, err = capsys.readouterr()
+    assert re.search(r'System', out, re.MULTILINE)
+    assert re.search(r'Platform', out, re.MULTILINE)
+    assert re.search(r'Machine', out, re.MULTILINE)
+    assert re.search(r'Executable', out, re.MULTILINE)
+    assert err == ''
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/tunnel.html b/docs/meshtastic/tunnel.html new file mode 100644 index 0000000..27414d5 --- /dev/null +++ b/docs/meshtastic/tunnel.html @@ -0,0 +1,615 @@ + + + + + + +meshtastic.tunnel API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.tunnel

+
+
+

Code for IP tunnel over a mesh

+

Note python-pytuntap was too buggy

+

using pip3 install pytap2

+

make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root

+

sudo ip tuntap del mode tun tun0

+

sudo bin/run.sh –port /dev/ttyUSB0 –setch-shortfast

+

sudo bin/run.sh –port /dev/ttyUSB0 –tunnel –debug

+

ssh -Y root@192.168.10.151 (or dietpi), default password p

+

ncat -e /bin/cat -k -u -l 1235

+

ncat -u 10.115.64.152 1235

+

ping -c 1 -W 20 10.115.64.152

+

ping -i 30 -W 30 10.115.64.152

+

FIXME: use a more optimal MTU

+
+ +Expand source code + +
"""Code for IP tunnel over a mesh
+
+# Note python-pytuntap was too buggy
+# using pip3 install pytap2
+# make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root
+# sudo ip tuntap del mode tun tun0
+# sudo bin/run.sh --port /dev/ttyUSB0 --setch-shortfast
+# sudo bin/run.sh --port /dev/ttyUSB0 --tunnel --debug
+# ssh -Y root@192.168.10.151 (or dietpi), default password p
+# ncat -e /bin/cat -k -u -l 1235
+# ncat -u 10.115.64.152 1235
+# ping -c 1 -W 20 10.115.64.152
+# ping -i 30 -W 30 10.115.64.152
+
+# FIXME: use a more optimal MTU
+"""
+
+import logging
+import threading
+from pubsub import pub
+
+from pytap2 import TapDevice
+
+from . import portnums_pb2
+
+# A new non standard log level that is lower level than DEBUG
+LOG_TRACE = 5
+
+# fixme - find a way to move onTunnelReceive inside of the class
+tunnelInstance = None
+
+"""A list of chatty UDP services we should never accidentally
+forward to our slow network"""
+udpBlacklist = {
+    1900,  # SSDP
+    5353,  # multicast DNS
+}
+
+"""A list of TCP services to block"""
+tcpBlacklist = {}
+
+"""A list of protocols we ignore"""
+protocolBlacklist = {
+    0x02,  # IGMP
+    0x80,  # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment
+}
+
+
+def hexstr(barray):
+    """Print a string of hex digits"""
+    return ":".join('{:02x}'.format(x) for x in barray)
+
+
+def ipstr(barray):
+    """Print a string of ip digits"""
+    return ".".join('{}'.format(x) for x in barray)
+
+
+def readnet_u16(p, offset):
+    """Read big endian u16 (network byte order)"""
+    return p[offset] * 256 + p[offset + 1]
+
+
+def onTunnelReceive(packet, interface):
+    """Callback for received tunneled messages from mesh
+
+    FIXME figure out how to do closures with methods in python"""
+    tunnelInstance.onReceive(packet)
+
+
+class Tunnel:
+    """A TUN based IP tunnel over meshtastic"""
+
+    def __init__(self, iface, subnet=None, netmask="255.255.0.0"):
+        """
+        Constructor
+
+        iface is the already open MeshInterface instance
+        subnet is used to construct our network number (normally 10.115.x.x)
+        """
+
+        if subnet is None:
+            subnet = "10.115"
+
+        self.iface = iface
+        self.subnetPrefix = subnet
+
+        global tunnelInstance
+        tunnelInstance = self
+
+        logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\
+                     "feature to work).  Mesh members:")
+
+        pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
+        myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
+
+        for node in self.iface.nodes.values():
+            nodeId = node["user"]["id"]
+            ip = self._nodeNumToIp(node["num"])
+            logging.info(f"Node { nodeId } has IP address { ip }")
+
+        logging.debug("creating TUN device with MTU=200")
+        # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
+        self.tun = TapDevice(name="mesh")
+        self.tun.up()
+        self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
+        logging.debug(f"starting TUN reader, our IP address is {myAddr}")
+        self._rxThread = threading.Thread(
+            target=self.__tunReader, args=(), daemon=True)
+        self._rxThread.start()
+
+    def onReceive(self, packet):
+        """onReceive"""
+        p = packet["decoded"]["payload"]
+        if packet["from"] == self.iface.myInfo.my_node_num:
+            logging.debug("Ignoring message we sent")
+        else:
+            logging.debug(
+                f"Received mesh tunnel message type={type(p)} len={len(p)}")
+            # we don't really need to check for filtering here (sender should have checked),
+            # but this provides useful debug printing on types of packets received
+            if not self._shouldFilterPacket(p):
+                self.tun.write(p)
+
+    def _shouldFilterPacket(self, p):
+        """Given a packet, decode it and return true if it should be ignored"""
+        protocol = p[8 + 1]
+        srcaddr = p[12:16]
+        destAddr = p[16:20]
+        subheader = 20
+        ignore = False  # Assume we will be forwarding the packet
+        if protocol in protocolBlacklist:
+            ignore = True
+            logging.log(
+                LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
+        elif protocol == 0x01:  # ICMP
+            icmpType = p[20]
+            icmpCode = p[21]
+            checksum = p[22:24]
+            # pylint: disable=line-too-long
+            logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}")
+            # reply to pings (swap src and dest but keep rest of packet unchanged)
+            #pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
+            # tap.write(pingback)
+        elif protocol == 0x11:  # UDP
+            srcport = readnet_u16(p, subheader)
+            destport = readnet_u16(p, subheader + 2)
+            if destport in udpBlacklist:
+                ignore = True
+                logging.log(
+                    LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
+            else:
+                logging.debug(
+                    f"forwarding udp srcport={srcport}, destport={destport}")
+        elif protocol == 0x06:  # TCP
+            srcport = readnet_u16(p, subheader)
+            destport = readnet_u16(p, subheader + 2)
+            if destport in tcpBlacklist:
+                ignore = True
+                logging.log(LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
+            else:
+                logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
+        else:
+            logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, "\
+                             "src={ipstr(srcaddr)}, dest={ipstr(destAddr)}")
+
+        return ignore
+
+    def __tunReader(self):
+        tap = self.tun
+        logging.debug("TUN reader running")
+        while True:
+            p = tap.read()
+            #logging.debug(f"IP packet received on TUN interface, type={type(p)}")
+            destAddr = p[16:20]
+
+            if not self._shouldFilterPacket(p):
+                self.sendPacket(destAddr, p)
+
+    def _ipToNodeId(self, ipAddr):
+        # We only consider the last 16 bits of the nodenum for IP address matching
+        ipBits = ipAddr[2] * 256 + ipAddr[3]
+
+        if ipBits == 0xffff:
+            return "^all"
+
+        for node in self.iface.nodes.values():
+            nodeNum = node["num"] & 0xffff
+            # logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}")
+            if (nodeNum) == ipBits:
+                return node["user"]["id"]
+        return None
+
+    def _nodeNumToIp(self, nodeNum):
+        return f"{self.subnetPrefix}.{(nodeNum >> 8) & 0xff}.{nodeNum & 0xff}"
+
+    def sendPacket(self, destAddr, p):
+        """Forward the provided IP packet into the mesh"""
+        nodeId = self._ipToNodeId(destAddr)
+        if nodeId is not None:
+            logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}")
+            self.iface.sendData(
+                p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
+        else:
+            logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}")
+
+    def close(self):
+        """Close"""
+        self.tun.close()
+
+
+
+
+
+

Global variables

+
+
var tcpBlacklist
+
+

A list of protocols we ignore

+
+
var tunnelInstance
+
+

A list of chatty UDP services we should never accidentally +forward to our slow network

+
+
var udpBlacklist
+
+

A list of TCP services to block

+
+
+
+
+

Functions

+
+
+def hexstr(barray) +
+
+

Print a string of hex digits

+
+ +Expand source code + +
def hexstr(barray):
+    """Print a string of hex digits"""
+    return ":".join('{:02x}'.format(x) for x in barray)
+
+
+
+def ipstr(barray) +
+
+

Print a string of ip digits

+
+ +Expand source code + +
def ipstr(barray):
+    """Print a string of ip digits"""
+    return ".".join('{}'.format(x) for x in barray)
+
+
+
+def onTunnelReceive(packet, interface) +
+
+

Callback for received tunneled messages from mesh

+

FIXME figure out how to do closures with methods in python

+
+ +Expand source code + +
def onTunnelReceive(packet, interface):
+    """Callback for received tunneled messages from mesh
+
+    FIXME figure out how to do closures with methods in python"""
+    tunnelInstance.onReceive(packet)
+
+
+
+def readnet_u16(p, offset) +
+
+

Read big endian u16 (network byte order)

+
+ +Expand source code + +
def readnet_u16(p, offset):
+    """Read big endian u16 (network byte order)"""
+    return p[offset] * 256 + p[offset + 1]
+
+
+
+
+
+

Classes

+
+
+class Tunnel +(iface, subnet=None, netmask='255.255.0.0') +
+
+

A TUN based IP tunnel over meshtastic

+

Constructor

+

iface is the already open MeshInterface instance +subnet is used to construct our network number (normally 10.115.x.x)

+
+ +Expand source code + +
class Tunnel:
+    """A TUN based IP tunnel over meshtastic"""
+
+    def __init__(self, iface, subnet=None, netmask="255.255.0.0"):
+        """
+        Constructor
+
+        iface is the already open MeshInterface instance
+        subnet is used to construct our network number (normally 10.115.x.x)
+        """
+
+        if subnet is None:
+            subnet = "10.115"
+
+        self.iface = iface
+        self.subnetPrefix = subnet
+
+        global tunnelInstance
+        tunnelInstance = self
+
+        logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\
+                     "feature to work).  Mesh members:")
+
+        pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
+        myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
+
+        for node in self.iface.nodes.values():
+            nodeId = node["user"]["id"]
+            ip = self._nodeNumToIp(node["num"])
+            logging.info(f"Node { nodeId } has IP address { ip }")
+
+        logging.debug("creating TUN device with MTU=200")
+        # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
+        self.tun = TapDevice(name="mesh")
+        self.tun.up()
+        self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200)
+        logging.debug(f"starting TUN reader, our IP address is {myAddr}")
+        self._rxThread = threading.Thread(
+            target=self.__tunReader, args=(), daemon=True)
+        self._rxThread.start()
+
+    def onReceive(self, packet):
+        """onReceive"""
+        p = packet["decoded"]["payload"]
+        if packet["from"] == self.iface.myInfo.my_node_num:
+            logging.debug("Ignoring message we sent")
+        else:
+            logging.debug(
+                f"Received mesh tunnel message type={type(p)} len={len(p)}")
+            # we don't really need to check for filtering here (sender should have checked),
+            # but this provides useful debug printing on types of packets received
+            if not self._shouldFilterPacket(p):
+                self.tun.write(p)
+
+    def _shouldFilterPacket(self, p):
+        """Given a packet, decode it and return true if it should be ignored"""
+        protocol = p[8 + 1]
+        srcaddr = p[12:16]
+        destAddr = p[16:20]
+        subheader = 20
+        ignore = False  # Assume we will be forwarding the packet
+        if protocol in protocolBlacklist:
+            ignore = True
+            logging.log(
+                LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}")
+        elif protocol == 0x01:  # ICMP
+            icmpType = p[20]
+            icmpCode = p[21]
+            checksum = p[22:24]
+            # pylint: disable=line-too-long
+            logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}")
+            # reply to pings (swap src and dest but keep rest of packet unchanged)
+            #pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
+            # tap.write(pingback)
+        elif protocol == 0x11:  # UDP
+            srcport = readnet_u16(p, subheader)
+            destport = readnet_u16(p, subheader + 2)
+            if destport in udpBlacklist:
+                ignore = True
+                logging.log(
+                    LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
+            else:
+                logging.debug(
+                    f"forwarding udp srcport={srcport}, destport={destport}")
+        elif protocol == 0x06:  # TCP
+            srcport = readnet_u16(p, subheader)
+            destport = readnet_u16(p, subheader + 2)
+            if destport in tcpBlacklist:
+                ignore = True
+                logging.log(LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
+            else:
+                logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
+        else:
+            logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, "\
+                             "src={ipstr(srcaddr)}, dest={ipstr(destAddr)}")
+
+        return ignore
+
+    def __tunReader(self):
+        tap = self.tun
+        logging.debug("TUN reader running")
+        while True:
+            p = tap.read()
+            #logging.debug(f"IP packet received on TUN interface, type={type(p)}")
+            destAddr = p[16:20]
+
+            if not self._shouldFilterPacket(p):
+                self.sendPacket(destAddr, p)
+
+    def _ipToNodeId(self, ipAddr):
+        # We only consider the last 16 bits of the nodenum for IP address matching
+        ipBits = ipAddr[2] * 256 + ipAddr[3]
+
+        if ipBits == 0xffff:
+            return "^all"
+
+        for node in self.iface.nodes.values():
+            nodeNum = node["num"] & 0xffff
+            # logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}")
+            if (nodeNum) == ipBits:
+                return node["user"]["id"]
+        return None
+
+    def _nodeNumToIp(self, nodeNum):
+        return f"{self.subnetPrefix}.{(nodeNum >> 8) & 0xff}.{nodeNum & 0xff}"
+
+    def sendPacket(self, destAddr, p):
+        """Forward the provided IP packet into the mesh"""
+        nodeId = self._ipToNodeId(destAddr)
+        if nodeId is not None:
+            logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}")
+            self.iface.sendData(
+                p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
+        else:
+            logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}")
+
+    def close(self):
+        """Close"""
+        self.tun.close()
+
+

Methods

+
+
+def close(self) +
+
+

Close

+
+ +Expand source code + +
def close(self):
+    """Close"""
+    self.tun.close()
+
+
+
+def onReceive(self, packet) +
+
+

onReceive

+
+ +Expand source code + +
def onReceive(self, packet):
+    """onReceive"""
+    p = packet["decoded"]["payload"]
+    if packet["from"] == self.iface.myInfo.my_node_num:
+        logging.debug("Ignoring message we sent")
+    else:
+        logging.debug(
+            f"Received mesh tunnel message type={type(p)} len={len(p)}")
+        # we don't really need to check for filtering here (sender should have checked),
+        # but this provides useful debug printing on types of packets received
+        if not self._shouldFilterPacket(p):
+            self.tun.write(p)
+
+
+
+def sendPacket(self, destAddr, p) +
+
+

Forward the provided IP packet into the mesh

+
+ +Expand source code + +
def sendPacket(self, destAddr, p):
+    """Forward the provided IP packet into the mesh"""
+    nodeId = self._ipToNodeId(destAddr)
+    if nodeId is not None:
+        logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}")
+        self.iface.sendData(
+            p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
+    else:
+        logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/meshtastic/util.html b/docs/meshtastic/util.html new file mode 100644 index 0000000..d7cb08b --- /dev/null +++ b/docs/meshtastic/util.html @@ -0,0 +1,688 @@ + + + + + + +meshtastic.util API documentation + + + + + + + + + + + +
+
+
+

Module meshtastic.util

+
+
+

Utility functions.

+
+ +Expand source code + +
"""Utility functions.
+"""
+import traceback
+from queue import Queue
+import os
+import sys
+import time
+import platform
+import logging
+import threading
+import serial
+import serial.tools.list_ports
+import pkg_resources
+
+"""Some devices such as a seger jlink we never want to accidentally open"""
+blacklistVids = dict.fromkeys([0x1366])
+
+
+def quoteBooleans(a_string):
+    """Quote booleans
+        given a string that contains ": true", replace with ": 'true'" (or false)
+    """
+    tmp = a_string.replace(": true", ": 'true'")
+    tmp = tmp.replace(": false", ": 'false'")
+    return tmp
+
+def genPSK256():
+    """Generate a random preshared key"""
+    return os.urandom(32)
+
+
+def fromPSK(valstr):
+    """A special version of fromStr that assumes the user is trying to set a PSK.
+    In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
+    """
+    if valstr == "random":
+        return genPSK256()
+    elif valstr == "none":
+        return bytes([0])  # Use the 'no encryption' PSK
+    elif valstr == "default":
+        return bytes([1])  # Use default channel psk
+    elif valstr.startswith("simple"):
+        # Use one of the single byte encodings
+        return bytes([int(valstr[6:]) + 1])
+    else:
+        return fromStr(valstr)
+
+
+def fromStr(valstr):
+    """Try to parse as int, float or bool (and fallback to a string as last resort)
+
+    Returns: an int, bool, float, str or byte array (for strings of hex digits)
+
+    Args:
+        valstr (string): A user provided string
+    """
+    if len(valstr) == 0:  # Treat an emptystring as an empty bytes
+        val = bytes()
+    elif valstr.startswith('0x'):
+        # if needed convert to string with asBytes.decode('utf-8')
+        val = bytes.fromhex(valstr[2:])
+    elif valstr.lower() in {"t", "true", "yes"}:
+        val = True
+    elif valstr.lower() in {"f", "false", "no"}:
+        val = False
+    else:
+        try:
+            val = int(valstr)
+        except ValueError:
+            try:
+                val = float(valstr)
+            except ValueError:
+                val = valstr  # Not a float or an int, assume string
+    return val
+
+
+def pskToString(psk: bytes):
+    """Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
+    if len(psk) == 0:
+        return "unencrypted"
+    elif len(psk) == 1:
+        b = psk[0]
+        if b == 0:
+            return "unencrypted"
+        elif b == 1:
+            return "default"
+        else:
+            return f"simple{b - 1}"
+    else:
+        return "secret"
+
+
+def stripnl(s):
+    """Remove newlines from a string (and remove extra whitespace)"""
+    s = str(s).replace("\n", " ")
+    return ' '.join(s.split())
+
+
+def fixme(message):
+    """Raise an exception for things that needs to be fixed"""
+    raise Exception(f"FIXME: {message}")
+
+
+def catchAndIgnore(reason, closure):
+    """Call a closure but if it throws an exception print it and continue"""
+    try:
+        closure()
+    except BaseException as ex:
+        logging.error(f"Exception thrown in {reason}: {ex}")
+
+
+def findPorts():
+    """Find all ports that might have meshtastic devices
+
+    Returns:
+        list -- a list of device paths
+    """
+    l = list(map(lambda port: port.device,
+                 filter(lambda port: port.vid is not None and port.vid not in blacklistVids,
+                        serial.tools.list_ports.comports())))
+    l.sort()
+    return l
+
+
+class dotdict(dict):
+    """dot.notation access to dictionary attributes"""
+    __getattr__ = dict.get
+    __setattr__ = dict.__setitem__
+    __delattr__ = dict.__delitem__
+
+
+class Timeout:
+    """Timeout class"""
+    def __init__(self, maxSecs=20):
+        self.expireTime = 0
+        self.sleepInterval = 0.1
+        self.expireTimeout = maxSecs
+
+    def reset(self):
+        """Restart the waitForSet timer"""
+        self.expireTime = time.time() + self.expireTimeout
+
+    def waitForSet(self, target, attrs=()):
+        """Block until the specified attributes are set. Returns True if config has been received."""
+        self.reset()
+        while time.time() < self.expireTime:
+            if all(map(lambda a: getattr(target, a, None), attrs)):
+                return True
+            time.sleep(self.sleepInterval)
+        return False
+
+
+class DeferredExecution():
+    """A thread that accepts closures to run, and runs them as they are received"""
+
+    def __init__(self, name=None):
+        self.queue = Queue()
+        self.thread = threading.Thread(target=self._run, args=(), name=name)
+        self.thread.daemon = True
+        self.thread.start()
+
+    def queueWork(self, runnable):
+        """ Queue up the work"""
+        self.queue.put(runnable)
+
+    def _run(self):
+        while True:
+            try:
+                o = self.queue.get()
+                o()
+            except:
+                logging.error(
+                    f"Unexpected error in deferred execution {sys.exc_info()[0]}")
+                print(traceback.format_exc())
+
+
+def our_exit(message, return_value = 1):
+    """Print the message and return a value.
+       return_value defaults to 1 (non-successful)
+    """
+    print(message)
+    sys.exit(return_value)
+
+
+def support_info():
+    """Print out info that helps troubleshooting of the cli."""
+    print('')
+    print('If having issues with meshtastic cli or python library')
+    print('or wish to make feature requests, visit:')
+    print('https://github.com/meshtastic/Meshtastic-python/issues')
+    print('When adding an issue, be sure to include the following info:')
+    print(' System: {0}'.format(platform.system()))
+    print('   Platform: {0}'.format(platform.platform()))
+    print('   Release: {0}'.format(platform.uname().release))
+    print('   Machine: {0}'.format(platform.uname().machine))
+    print('   Encoding (stdin): {0}'.format(sys.stdin.encoding))
+    print('   Encoding (stdout): {0}'.format(sys.stdout.encoding))
+    print(' meshtastic: v{0}'.format(pkg_resources.require('meshtastic')[0].version))
+    print(' Executable: {0}'.format(sys.argv[0]))
+    print(' Python: {0} {1} {2}'.format(platform.python_version(),
+          platform.python_implementation(), platform.python_compiler()))
+    print('')
+    print('Please add the output from the command: meshtastic --info')
+
+
+
+
+
+
+
+

Functions

+
+
+def catchAndIgnore(reason, closure) +
+
+

Call a closure but if it throws an exception print it and continue

+
+ +Expand source code + +
def catchAndIgnore(reason, closure):
+    """Call a closure but if it throws an exception print it and continue"""
+    try:
+        closure()
+    except BaseException as ex:
+        logging.error(f"Exception thrown in {reason}: {ex}")
+
+
+
+def findPorts() +
+
+

Find all ports that might have meshtastic devices

+

Returns

+

list – a list of device paths

+
+ +Expand source code + +
def findPorts():
+    """Find all ports that might have meshtastic devices
+
+    Returns:
+        list -- a list of device paths
+    """
+    l = list(map(lambda port: port.device,
+                 filter(lambda port: port.vid is not None and port.vid not in blacklistVids,
+                        serial.tools.list_ports.comports())))
+    l.sort()
+    return l
+
+
+
+def fixme(message) +
+
+

Raise an exception for things that needs to be fixed

+
+ +Expand source code + +
def fixme(message):
+    """Raise an exception for things that needs to be fixed"""
+    raise Exception(f"FIXME: {message}")
+
+
+
+def fromPSK(valstr) +
+
+

A special version of fromStr that assumes the user is trying to set a PSK. +In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN

+
+ +Expand source code + +
def fromPSK(valstr):
+    """A special version of fromStr that assumes the user is trying to set a PSK.
+    In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
+    """
+    if valstr == "random":
+        return genPSK256()
+    elif valstr == "none":
+        return bytes([0])  # Use the 'no encryption' PSK
+    elif valstr == "default":
+        return bytes([1])  # Use default channel psk
+    elif valstr.startswith("simple"):
+        # Use one of the single byte encodings
+        return bytes([int(valstr[6:]) + 1])
+    else:
+        return fromStr(valstr)
+
+
+
+def fromStr(valstr) +
+
+

Try to parse as int, float or bool (and fallback to a string as last resort)

+

Returns: an int, bool, float, str or byte array (for strings of hex digits)

+

Args

+
+
valstr : string
+
A user provided string
+
+
+ +Expand source code + +
def fromStr(valstr):
+    """Try to parse as int, float or bool (and fallback to a string as last resort)
+
+    Returns: an int, bool, float, str or byte array (for strings of hex digits)
+
+    Args:
+        valstr (string): A user provided string
+    """
+    if len(valstr) == 0:  # Treat an emptystring as an empty bytes
+        val = bytes()
+    elif valstr.startswith('0x'):
+        # if needed convert to string with asBytes.decode('utf-8')
+        val = bytes.fromhex(valstr[2:])
+    elif valstr.lower() in {"t", "true", "yes"}:
+        val = True
+    elif valstr.lower() in {"f", "false", "no"}:
+        val = False
+    else:
+        try:
+            val = int(valstr)
+        except ValueError:
+            try:
+                val = float(valstr)
+            except ValueError:
+                val = valstr  # Not a float or an int, assume string
+    return val
+
+
+
+def genPSK256() +
+
+

Generate a random preshared key

+
+ +Expand source code + +
def genPSK256():
+    """Generate a random preshared key"""
+    return os.urandom(32)
+
+
+
+def our_exit(message, return_value=1) +
+
+

Print the message and return a value. +return_value defaults to 1 (non-successful)

+
+ +Expand source code + +
def our_exit(message, return_value = 1):
+    """Print the message and return a value.
+       return_value defaults to 1 (non-successful)
+    """
+    print(message)
+    sys.exit(return_value)
+
+
+
+def pskToString(psk: bytes) +
+
+

Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string

+
+ +Expand source code + +
def pskToString(psk: bytes):
+    """Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
+    if len(psk) == 0:
+        return "unencrypted"
+    elif len(psk) == 1:
+        b = psk[0]
+        if b == 0:
+            return "unencrypted"
+        elif b == 1:
+            return "default"
+        else:
+            return f"simple{b - 1}"
+    else:
+        return "secret"
+
+
+
+def quoteBooleans(a_string) +
+
+

Quote booleans +given a string that contains ": true", replace with ": 'true'" (or false)

+
+ +Expand source code + +
def quoteBooleans(a_string):
+    """Quote booleans
+        given a string that contains ": true", replace with ": 'true'" (or false)
+    """
+    tmp = a_string.replace(": true", ": 'true'")
+    tmp = tmp.replace(": false", ": 'false'")
+    return tmp
+
+
+
+def stripnl(s) +
+
+

Remove newlines from a string (and remove extra whitespace)

+
+ +Expand source code + +
def stripnl(s):
+    """Remove newlines from a string (and remove extra whitespace)"""
+    s = str(s).replace("\n", " ")
+    return ' '.join(s.split())
+
+
+
+def support_info() +
+
+

Print out info that helps troubleshooting of the cli.

+
+ +Expand source code + +
def support_info():
+    """Print out info that helps troubleshooting of the cli."""
+    print('')
+    print('If having issues with meshtastic cli or python library')
+    print('or wish to make feature requests, visit:')
+    print('https://github.com/meshtastic/Meshtastic-python/issues')
+    print('When adding an issue, be sure to include the following info:')
+    print(' System: {0}'.format(platform.system()))
+    print('   Platform: {0}'.format(platform.platform()))
+    print('   Release: {0}'.format(platform.uname().release))
+    print('   Machine: {0}'.format(platform.uname().machine))
+    print('   Encoding (stdin): {0}'.format(sys.stdin.encoding))
+    print('   Encoding (stdout): {0}'.format(sys.stdout.encoding))
+    print(' meshtastic: v{0}'.format(pkg_resources.require('meshtastic')[0].version))
+    print(' Executable: {0}'.format(sys.argv[0]))
+    print(' Python: {0} {1} {2}'.format(platform.python_version(),
+          platform.python_implementation(), platform.python_compiler()))
+    print('')
+    print('Please add the output from the command: meshtastic --info')
+
+
+
+
+
+

Classes

+
+
+class DeferredExecution +(name=None) +
+
+

A thread that accepts closures to run, and runs them as they are received

+
+ +Expand source code + +
class DeferredExecution():
+    """A thread that accepts closures to run, and runs them as they are received"""
+
+    def __init__(self, name=None):
+        self.queue = Queue()
+        self.thread = threading.Thread(target=self._run, args=(), name=name)
+        self.thread.daemon = True
+        self.thread.start()
+
+    def queueWork(self, runnable):
+        """ Queue up the work"""
+        self.queue.put(runnable)
+
+    def _run(self):
+        while True:
+            try:
+                o = self.queue.get()
+                o()
+            except:
+                logging.error(
+                    f"Unexpected error in deferred execution {sys.exc_info()[0]}")
+                print(traceback.format_exc())
+
+

Methods

+
+
+def queueWork(self, runnable) +
+
+

Queue up the work

+
+ +Expand source code + +
def queueWork(self, runnable):
+    """ Queue up the work"""
+    self.queue.put(runnable)
+
+
+
+
+
+class Timeout +(maxSecs=20) +
+
+

Timeout class

+
+ +Expand source code + +
class Timeout:
+    """Timeout class"""
+    def __init__(self, maxSecs=20):
+        self.expireTime = 0
+        self.sleepInterval = 0.1
+        self.expireTimeout = maxSecs
+
+    def reset(self):
+        """Restart the waitForSet timer"""
+        self.expireTime = time.time() + self.expireTimeout
+
+    def waitForSet(self, target, attrs=()):
+        """Block until the specified attributes are set. Returns True if config has been received."""
+        self.reset()
+        while time.time() < self.expireTime:
+            if all(map(lambda a: getattr(target, a, None), attrs)):
+                return True
+            time.sleep(self.sleepInterval)
+        return False
+
+

Methods

+
+
+def reset(self) +
+
+

Restart the waitForSet timer

+
+ +Expand source code + +
def reset(self):
+    """Restart the waitForSet timer"""
+    self.expireTime = time.time() + self.expireTimeout
+
+
+
+def waitForSet(self, target, attrs=()) +
+
+

Block until the specified attributes are set. Returns True if config has been received.

+
+ +Expand source code + +
def waitForSet(self, target, attrs=()):
+    """Block until the specified attributes are set. Returns True if config has been received."""
+    self.reset()
+    while time.time() < self.expireTime:
+        if all(map(lambda a: getattr(target, a, None), attrs)):
+            return True
+        time.sleep(self.sleepInterval)
+    return False
+
+
+
+
+
+class dotdict +(*args, **kwargs) +
+
+

dot.notation access to dictionary attributes

+
+ +Expand source code + +
class dotdict(dict):
+    """dot.notation access to dictionary attributes"""
+    __getattr__ = dict.get
+    __setattr__ = dict.__setitem__
+    __delattr__ = dict.__delitem__
+
+

Ancestors

+
    +
  • builtins.dict
  • +
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/meshtastic/admin_pb2.py b/meshtastic/admin_pb2.py index f809323..dfbb18c 100644 --- a/meshtastic/admin_pb2.py +++ b/meshtastic/admin_pb2.py @@ -12,8 +12,8 @@ _sym_db = _symbol_database.Default() from . import channel_pb2 as channel__pb2 -from . import mesh_pb2 as mesh__pb2 from . import radioconfig_pb2 as radioconfig__pb2 +from . import mesh_pb2 as mesh__pb2 DESCRIPTOR = _descriptor.FileDescriptor( @@ -21,9 +21,9 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='', syntax='proto3', serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto', - serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\xfb\x02\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' + serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\rchannel.proto\x1a\x11radioconfig.proto\x1a\nmesh.proto\"\xbd\x03\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' , - dependencies=[channel__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,]) + dependencies=[channel__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,mesh__pb2.DESCRIPTOR,]) @@ -85,28 +85,42 @@ _ADMINMESSAGE = _descriptor.Descriptor( is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=7, + name='get_owner_request', full_name='AdminMessage.get_owner_request', index=7, + number=8, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='get_owner_response', full_name='AdminMessage.get_owner_response', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='confirm_set_channel', full_name='AdminMessage.confirm_set_channel', index=9, number=32, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=8, + name='confirm_set_radio', full_name='AdminMessage.confirm_set_radio', index=10, number=33, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='exit_simulator', full_name='AdminMessage.exit_simulator', index=9, + name='exit_simulator', full_name='AdminMessage.exit_simulator', index=11, number=34, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=10, + name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=12, number=35, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -128,7 +142,7 @@ _ADMINMESSAGE = _descriptor.Descriptor( index=0, containing_type=None, fields=[]), ], serialized_start=62, - serialized_end=441, + serialized_end=507, ) _ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG @@ -136,6 +150,7 @@ _ADMINMESSAGE.fields_by_name['set_owner'].message_type = mesh__pb2._USER _ADMINMESSAGE.fields_by_name['set_channel'].message_type = channel__pb2._CHANNEL _ADMINMESSAGE.fields_by_name['get_radio_response'].message_type = radioconfig__pb2._RADIOCONFIG _ADMINMESSAGE.fields_by_name['get_channel_response'].message_type = channel__pb2._CHANNEL +_ADMINMESSAGE.fields_by_name['get_owner_response'].message_type = mesh__pb2._USER _ADMINMESSAGE.oneofs_by_name['variant'].fields.append( _ADMINMESSAGE.fields_by_name['set_radio']) _ADMINMESSAGE.fields_by_name['set_radio'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] @@ -157,6 +172,12 @@ _ADMINMESSAGE.fields_by_name['get_channel_request'].containing_oneof = _ADMINMES _ADMINMESSAGE.oneofs_by_name['variant'].fields.append( _ADMINMESSAGE.fields_by_name['get_channel_response']) _ADMINMESSAGE.fields_by_name['get_channel_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] +_ADMINMESSAGE.oneofs_by_name['variant'].fields.append( + _ADMINMESSAGE.fields_by_name['get_owner_request']) +_ADMINMESSAGE.fields_by_name['get_owner_request'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] +_ADMINMESSAGE.oneofs_by_name['variant'].fields.append( + _ADMINMESSAGE.fields_by_name['get_owner_response']) +_ADMINMESSAGE.fields_by_name['get_owner_response'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] _ADMINMESSAGE.oneofs_by_name['variant'].fields.append( _ADMINMESSAGE.fields_by_name['confirm_set_channel']) _ADMINMESSAGE.fields_by_name['confirm_set_channel'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant'] diff --git a/meshtastic/mesh_pb2.py b/meshtastic/mesh_pb2.py index 76c4f92..75502b4 100644 --- a/meshtastic/mesh_pb2.py +++ b/meshtastic/mesh_pb2.py @@ -20,7 +20,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='', syntax='proto3', serialized_options=b'\n\023com.geeksville.meshB\nMeshProtosH\003Z!github.com/meshtastic/gomeshproto', - serialized_pb=b'\n\nmesh.proto\x1a\x0eportnums.proto\"\x94\x06\n\x08Position\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x15\n\rbattery_level\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\t \x01(\x07\x12,\n\x0flocation_source\x18\n \x01(\x0e\x32\x13.Position.LocSource\x12,\n\x0f\x61ltitude_source\x18\x0b \x01(\x0e\x32\x13.Position.AltSource\x12\x15\n\rpos_timestamp\x18\x0c \x01(\x07\x12\x17\n\x0fpos_time_millis\x18\r \x01(\x05\x12\x14\n\x0c\x61ltitude_hae\x18\x0e \x01(\x11\x12\x15\n\ralt_geoid_sep\x18\x0f \x01(\x11\x12\x0c\n\x04PDOP\x18\x10 \x01(\r\x12\x0c\n\x04HDOP\x18\x11 \x01(\r\x12\x0c\n\x04VDOP\x18\x12 \x01(\r\x12\x14\n\x0cgps_accuracy\x18\x13 \x01(\r\x12\x14\n\x0cground_speed\x18\x14 \x01(\r\x12\x14\n\x0cground_track\x18\x15 \x01(\r\x12\x13\n\x0b\x66ix_quality\x18\x16 \x01(\r\x12\x10\n\x08\x66ix_type\x18\x17 \x01(\r\x12\x14\n\x0csats_in_view\x18\x18 \x01(\r\x12\x11\n\tsensor_id\x18\x19 \x01(\r\x12\x17\n\x0fpos_next_update\x18( \x01(\r\x12\x16\n\x0epos_seq_number\x18) \x01(\r\"n\n\tLocSource\x12\x16\n\x12LOCSRC_UNSPECIFIED\x10\x00\x12\x17\n\x13LOCSRC_MANUAL_ENTRY\x10\x01\x12\x17\n\x13LOCSRC_GPS_INTERNAL\x10\x02\x12\x17\n\x13LOCSRC_GPS_EXTERNAL\x10\x03\"\x85\x01\n\tAltSource\x12\x16\n\x12\x41LTSRC_UNSPECIFIED\x10\x00\x12\x17\n\x13\x41LTSRC_MANUAL_ENTRY\x10\x01\x12\x17\n\x13\x41LTSRC_GPS_INTERNAL\x10\x02\x12\x17\n\x13\x41LTSRC_GPS_EXTERNAL\x10\x03\x12\x15\n\x11\x41LTSRC_BAROMETRIC\x10\x04J\x04\x08\x07\x10\x08J\x04\x08\x08\x10\t\"\xd7\x01\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x0f\n\x07macaddr\x18\x04 \x01(\x0c\x12 \n\x08hw_model\x18\x06 \x01(\x0e\x32\x0e.HardwareModel\x12\x13\n\x0bis_licensed\x18\x07 \x01(\x08\x12\x13\n\x04team\x18\x08 \x01(\x0e\x32\x05.Team\x12\x14\n\x0ctx_power_dbm\x18\n \x01(\r\x12\x14\n\x0c\x61nt_gain_dbi\x18\x0b \x01(\r\x12\x13\n\x0b\x61nt_azimuth\x18\x0c \x01(\r\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x02 \x03(\x07\"\xc5\x02\n\x07Routing\x12(\n\rroute_request\x18\x01 \x01(\x0b\x32\x0f.RouteDiscoveryH\x00\x12&\n\x0broute_reply\x18\x02 \x01(\x0b\x32\x0f.RouteDiscoveryH\x00\x12&\n\x0c\x65rror_reason\x18\x03 \x01(\x0e\x32\x0e.Routing.ErrorH\x00\"\xb4\x01\n\x05\x45rror\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08NO_ROUTE\x10\x01\x12\x0b\n\x07GOT_NAK\x10\x02\x12\x0b\n\x07TIMEOUT\x10\x03\x12\x10\n\x0cNO_INTERFACE\x10\x04\x12\x12\n\x0eMAX_RETRANSMIT\x10\x05\x12\x0e\n\nNO_CHANNEL\x10\x06\x12\r\n\tTOO_LARGE\x10\x07\x12\x0f\n\x0bNO_RESPONSE\x10\x08\x12\x0f\n\x0b\x42\x41\x44_REQUEST\x10 \x12\x12\n\x0eNOT_AUTHORIZED\x10!B\t\n\x07variant\"{\n\x04\x44\x61ta\x12\x19\n\x07portnum\x18\x01 \x01(\x0e\x32\x08.PortNum\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12\x15\n\rwant_response\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x65st\x18\x04 \x01(\x07\x12\x0e\n\x06source\x18\x05 \x01(\x07\x12\x12\n\nrequest_id\x18\x06 \x01(\x07\"\xe0\x02\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x07\x12\n\n\x02to\x18\x02 \x01(\x07\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\x12\x18\n\x07\x64\x65\x63oded\x18\x04 \x01(\x0b\x32\x05.DataH\x00\x12\x13\n\tencrypted\x18\x05 \x01(\x0cH\x00\x12\n\n\x02id\x18\x06 \x01(\x07\x12\x0f\n\x07rx_time\x18\x07 \x01(\x07\x12\x0e\n\x06rx_snr\x18\x08 \x01(\x02\x12\x11\n\thop_limit\x18\n \x01(\r\x12\x10\n\x08want_ack\x18\x0b \x01(\x08\x12&\n\x08priority\x18\x0c \x01(\x0e\x32\x14.MeshPacket.Priority\x12\x0f\n\x07rx_rssi\x18\r \x01(\x05\"[\n\x08Priority\x12\t\n\x05UNSET\x10\x00\x12\x07\n\x03MIN\x10\x01\x12\x0e\n\nBACKGROUND\x10\n\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10@\x12\x0c\n\x08RELIABLE\x10\x46\x12\x07\n\x03\x41\x43K\x10x\x12\x07\n\x03MAX\x10\x7f\x42\x10\n\x0epayloadVariant\"j\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x13\n\x04user\x18\x02 \x01(\x0b\x32\x05.User\x12\x1b\n\x08position\x18\x03 \x01(\x0b\x32\t.Position\x12\x0b\n\x03snr\x18\x07 \x01(\x02\x12\x12\n\nlast_heard\x18\x04 \x01(\x07\"\x8a\x03\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\r\x12\x0f\n\x07has_gps\x18\x02 \x01(\x08\x12\x11\n\tnum_bands\x18\x03 \x01(\r\x12\x14\n\x0cmax_channels\x18\x0f \x01(\r\x12\x12\n\x06region\x18\x04 \x01(\tB\x02\x18\x01\x12\x1f\n\x13hw_model_deprecated\x18\x05 \x01(\tB\x02\x18\x01\x12\x18\n\x10\x66irmware_version\x18\x06 \x01(\t\x12&\n\nerror_code\x18\x07 \x01(\x0e\x32\x12.CriticalErrorCode\x12\x15\n\rerror_address\x18\x08 \x01(\r\x12\x13\n\x0b\x65rror_count\x18\t \x01(\r\x12\x14\n\x0creboot_count\x18\n \x01(\r\x12\x0f\n\x07\x62itrate\x18\x0b \x01(\x02\x12\x1c\n\x14message_timeout_msec\x18\r \x01(\r\x12\x17\n\x0fmin_app_version\x18\x0e \x01(\r\x12\x15\n\rair_period_tx\x18\x10 \x03(\r\x12\x15\n\rair_period_rx\x18\x11 \x03(\r\"\xb5\x01\n\tLogRecord\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x07\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x1f\n\x05level\x18\x04 \x01(\x0e\x32\x10.LogRecord.Level\"X\n\x05Level\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x43RITICAL\x10\x32\x12\t\n\x05\x45RROR\x10(\x12\x0b\n\x07WARNING\x10\x1e\x12\x08\n\x04INFO\x10\x14\x12\t\n\x05\x44\x45\x42UG\x10\n\x12\t\n\x05TRACE\x10\x05\"\xe9\x01\n\tFromRadio\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x1d\n\x06packet\x18\x0b \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x1e\n\x07my_info\x18\x03 \x01(\x0b\x32\x0b.MyNodeInfoH\x00\x12\x1e\n\tnode_info\x18\x04 \x01(\x0b\x32\t.NodeInfoH\x00\x12 \n\nlog_record\x18\x07 \x01(\x0b\x32\n.LogRecordH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x08 \x01(\rH\x00\x12\x12\n\x08rebooted\x18\t \x01(\x08H\x00\x42\x10\n\x0epayloadVariantJ\x04\x08\x02\x10\x03J\x04\x08\x06\x10\x07\"\xe1\x01\n\x07ToRadio\x12\x1d\n\x06packet\x18\x02 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12&\n\tpeer_info\x18\x03 \x01(\x0b\x32\x11.ToRadio.PeerInfoH\x00\x12\x18\n\x0ewant_config_id\x18\x64 \x01(\rH\x00\x12\x14\n\ndisconnect\x18h \x01(\x08H\x00\x1a\x35\n\x08PeerInfo\x12\x13\n\x0b\x61pp_version\x18\x01 \x01(\r\x12\x14\n\x0cmqtt_gateway\x18\x02 \x01(\x08\x42\x10\n\x0epayloadVariantJ\x04\x08\x01\x10\x02J\x04\x08\x65\x10\x66J\x04\x08\x66\x10gJ\x04\x08g\x10h*\xac\x02\n\rHardwareModel\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08TLORA_V2\x10\x01\x12\x0c\n\x08TLORA_V1\x10\x02\x12\x12\n\x0eTLORA_V2_1_1p6\x10\x03\x12\t\n\x05TBEAM\x10\x04\x12\x0f\n\x0bHELTEC_V2_0\x10\x05\x12\x0c\n\x08TBEAM0p7\x10\x06\x12\n\n\x06T_ECHO\x10\x07\x12\x10\n\x0cTLORA_V1_1p3\x10\x08\x12\x0b\n\x07RAK4631\x10\t\x12\x0f\n\x0bHELTEC_V2_1\x10\n\x12\x11\n\rLORA_RELAY_V1\x10 \x12\x0e\n\nNRF52840DK\x10!\x12\x07\n\x03PPR\x10\"\x12\x0f\n\x0bGENIEBLOCKS\x10#\x12\x11\n\rNRF52_UNKNOWN\x10$\x12\r\n\tPORTDUINO\x10%\x12\x0f\n\x0b\x41NDROID_SIM\x10&\x12\n\n\x06\x44IY_V1\x10\'*\xb5\x01\n\x04Team\x12\t\n\x05\x43LEAR\x10\x00\x12\x08\n\x04\x43YAN\x10\x01\x12\t\n\x05WHITE\x10\x02\x12\n\n\x06YELLOW\x10\x03\x12\n\n\x06ORANGE\x10\x04\x12\x0b\n\x07MAGENTA\x10\x05\x12\x07\n\x03RED\x10\x06\x12\n\n\x06MAROON\x10\x07\x12\n\n\x06PURPLE\x10\x08\x12\r\n\tDARK_BLUE\x10\t\x12\x08\n\x04\x42LUE\x10\n\x12\x08\n\x04TEAL\x10\x0b\x12\t\n\x05GREEN\x10\x0c\x12\x0e\n\nDARK_GREEN\x10\r\x12\t\n\x05\x42ROWN\x10\x0e*.\n\tConstants\x12\n\n\x06Unused\x10\x00\x12\x15\n\x10\x44\x41TA_PAYLOAD_LEN\x10\xed\x01*\xe1\x01\n\x11\x43riticalErrorCode\x12\x08\n\x04None\x10\x00\x12\x0e\n\nTxWatchdog\x10\x01\x12\x12\n\x0eSleepEnterWait\x10\x02\x12\x0b\n\x07NoRadio\x10\x03\x12\x0f\n\x0bUnspecified\x10\x04\x12\x13\n\x0fUBloxInitFailed\x10\x05\x12\x0c\n\x08NoAXP192\x10\x06\x12\x17\n\x13InvalidRadioSetting\x10\x07\x12\x12\n\x0eTransmitFailed\x10\x08\x12\x0c\n\x08\x42rownout\x10\t\x12\x11\n\rSX1262Failure\x10\n\x12\x0f\n\x0bRadioSpiBug\x10\x0b\x42\x46\n\x13\x63om.geeksville.meshB\nMeshProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' + serialized_pb=b'\n\nmesh.proto\x1a\x0eportnums.proto\"\x94\x06\n\x08Position\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x15\n\rbattery_level\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\t \x01(\x07\x12,\n\x0flocation_source\x18\n \x01(\x0e\x32\x13.Position.LocSource\x12,\n\x0f\x61ltitude_source\x18\x0b \x01(\x0e\x32\x13.Position.AltSource\x12\x15\n\rpos_timestamp\x18\x0c \x01(\x07\x12\x17\n\x0fpos_time_millis\x18\r \x01(\x05\x12\x14\n\x0c\x61ltitude_hae\x18\x0e \x01(\x11\x12\x15\n\ralt_geoid_sep\x18\x0f \x01(\x11\x12\x0c\n\x04PDOP\x18\x10 \x01(\r\x12\x0c\n\x04HDOP\x18\x11 \x01(\r\x12\x0c\n\x04VDOP\x18\x12 \x01(\r\x12\x14\n\x0cgps_accuracy\x18\x13 \x01(\r\x12\x14\n\x0cground_speed\x18\x14 \x01(\r\x12\x14\n\x0cground_track\x18\x15 \x01(\r\x12\x13\n\x0b\x66ix_quality\x18\x16 \x01(\r\x12\x10\n\x08\x66ix_type\x18\x17 \x01(\r\x12\x14\n\x0csats_in_view\x18\x18 \x01(\r\x12\x11\n\tsensor_id\x18\x19 \x01(\r\x12\x17\n\x0fpos_next_update\x18( \x01(\r\x12\x16\n\x0epos_seq_number\x18) \x01(\r\"n\n\tLocSource\x12\x16\n\x12LOCSRC_UNSPECIFIED\x10\x00\x12\x17\n\x13LOCSRC_MANUAL_ENTRY\x10\x01\x12\x17\n\x13LOCSRC_GPS_INTERNAL\x10\x02\x12\x17\n\x13LOCSRC_GPS_EXTERNAL\x10\x03\"\x85\x01\n\tAltSource\x12\x16\n\x12\x41LTSRC_UNSPECIFIED\x10\x00\x12\x17\n\x13\x41LTSRC_MANUAL_ENTRY\x10\x01\x12\x17\n\x13\x41LTSRC_GPS_INTERNAL\x10\x02\x12\x17\n\x13\x41LTSRC_GPS_EXTERNAL\x10\x03\x12\x15\n\x11\x41LTSRC_BAROMETRIC\x10\x04J\x04\x08\x07\x10\x08J\x04\x08\x08\x10\t\"\xd7\x01\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x0f\n\x07macaddr\x18\x04 \x01(\x0c\x12 \n\x08hw_model\x18\x06 \x01(\x0e\x32\x0e.HardwareModel\x12\x13\n\x0bis_licensed\x18\x07 \x01(\x08\x12\x13\n\x04team\x18\x08 \x01(\x0e\x32\x05.Team\x12\x14\n\x0ctx_power_dbm\x18\n \x01(\r\x12\x14\n\x0c\x61nt_gain_dbi\x18\x0b \x01(\r\x12\x13\n\x0b\x61nt_azimuth\x18\x0c \x01(\r\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x02 \x03(\x07\"\xc5\x02\n\x07Routing\x12(\n\rroute_request\x18\x01 \x01(\x0b\x32\x0f.RouteDiscoveryH\x00\x12&\n\x0broute_reply\x18\x02 \x01(\x0b\x32\x0f.RouteDiscoveryH\x00\x12&\n\x0c\x65rror_reason\x18\x03 \x01(\x0e\x32\x0e.Routing.ErrorH\x00\"\xb4\x01\n\x05\x45rror\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08NO_ROUTE\x10\x01\x12\x0b\n\x07GOT_NAK\x10\x02\x12\x0b\n\x07TIMEOUT\x10\x03\x12\x10\n\x0cNO_INTERFACE\x10\x04\x12\x12\n\x0eMAX_RETRANSMIT\x10\x05\x12\x0e\n\nNO_CHANNEL\x10\x06\x12\r\n\tTOO_LARGE\x10\x07\x12\x0f\n\x0bNO_RESPONSE\x10\x08\x12\x0f\n\x0b\x42\x41\x44_REQUEST\x10 \x12\x12\n\x0eNOT_AUTHORIZED\x10!B\t\n\x07variant\"{\n\x04\x44\x61ta\x12\x19\n\x07portnum\x18\x01 \x01(\x0e\x32\x08.PortNum\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12\x15\n\rwant_response\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x65st\x18\x04 \x01(\x07\x12\x0e\n\x06source\x18\x05 \x01(\x07\x12\x12\n\nrequest_id\x18\x06 \x01(\x07\"\xf0\x03\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x07\x12\n\n\x02to\x18\x02 \x01(\x07\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\x12\x18\n\x07\x64\x65\x63oded\x18\x04 \x01(\x0b\x32\x05.DataH\x00\x12\x13\n\tencrypted\x18\x05 \x01(\x0cH\x00\x12\n\n\x02id\x18\x06 \x01(\x07\x12\x0f\n\x07rx_time\x18\x07 \x01(\x07\x12\x0e\n\x06rx_snr\x18\x08 \x01(\x02\x12\x11\n\thop_limit\x18\n \x01(\r\x12\x10\n\x08want_ack\x18\x0b \x01(\x08\x12&\n\x08priority\x18\x0c \x01(\x0e\x32\x14.MeshPacket.Priority\x12\x0f\n\x07rx_rssi\x18\r \x01(\x05\x12$\n\x07\x64\x65layed\x18\x0f \x01(\x0e\x32\x13.MeshPacket.Delayed\x12\x10\n\x08reply_id\x18\x10 \x01(\x07\x12\x12\n\nis_tapback\x18\x11 \x01(\x08\"[\n\x08Priority\x12\t\n\x05UNSET\x10\x00\x12\x07\n\x03MIN\x10\x01\x12\x0e\n\nBACKGROUND\x10\n\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10@\x12\x0c\n\x08RELIABLE\x10\x46\x12\x07\n\x03\x41\x43K\x10x\x12\x07\n\x03MAX\x10\x7f\"B\n\x07\x44\x65layed\x12\x0c\n\x08NO_DELAY\x10\x00\x12\x15\n\x11\x44\x45LAYED_BROADCAST\x10\x01\x12\x12\n\x0e\x44\x45LAYED_DIRECT\x10\x02\x42\x10\n\x0epayloadVariant\"j\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x13\n\x04user\x18\x02 \x01(\x0b\x32\x05.User\x12\x1b\n\x08position\x18\x03 \x01(\x0b\x32\t.Position\x12\x0b\n\x03snr\x18\x07 \x01(\x02\x12\x12\n\nlast_heard\x18\x04 \x01(\x07\"\xb9\x03\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\r\x12\x0f\n\x07has_gps\x18\x02 \x01(\x08\x12\x11\n\tnum_bands\x18\x03 \x01(\r\x12\x14\n\x0cmax_channels\x18\x0f \x01(\r\x12\x12\n\x06region\x18\x04 \x01(\tB\x02\x18\x01\x12\x1f\n\x13hw_model_deprecated\x18\x05 \x01(\tB\x02\x18\x01\x12\x18\n\x10\x66irmware_version\x18\x06 \x01(\t\x12&\n\nerror_code\x18\x07 \x01(\x0e\x32\x12.CriticalErrorCode\x12\x15\n\rerror_address\x18\x08 \x01(\r\x12\x13\n\x0b\x65rror_count\x18\t \x01(\r\x12\x14\n\x0creboot_count\x18\n \x01(\r\x12\x0f\n\x07\x62itrate\x18\x0b \x01(\x02\x12\x1c\n\x14message_timeout_msec\x18\r \x01(\r\x12\x17\n\x0fmin_app_version\x18\x0e \x01(\r\x12\x15\n\rair_period_tx\x18\x10 \x03(\r\x12\x15\n\rair_period_rx\x18\x11 \x03(\r\x12\x10\n\x08has_wifi\x18\x12 \x01(\x08\x12\x1b\n\x13\x63hannel_utilization\x18\x13 \x01(\x02\"\xb5\x01\n\tLogRecord\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x07\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x1f\n\x05level\x18\x04 \x01(\x0e\x32\x10.LogRecord.Level\"X\n\x05Level\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x43RITICAL\x10\x32\x12\t\n\x05\x45RROR\x10(\x12\x0b\n\x07WARNING\x10\x1e\x12\x08\n\x04INFO\x10\x14\x12\t\n\x05\x44\x45\x42UG\x10\n\x12\t\n\x05TRACE\x10\x05\"\xe9\x01\n\tFromRadio\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x1d\n\x06packet\x18\x0b \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x1e\n\x07my_info\x18\x03 \x01(\x0b\x32\x0b.MyNodeInfoH\x00\x12\x1e\n\tnode_info\x18\x04 \x01(\x0b\x32\t.NodeInfoH\x00\x12 \n\nlog_record\x18\x07 \x01(\x0b\x32\n.LogRecordH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x08 \x01(\rH\x00\x12\x12\n\x08rebooted\x18\t \x01(\x08H\x00\x42\x10\n\x0epayloadVariantJ\x04\x08\x02\x10\x03J\x04\x08\x06\x10\x07\"\xe1\x01\n\x07ToRadio\x12\x1d\n\x06packet\x18\x02 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12&\n\tpeer_info\x18\x03 \x01(\x0b\x32\x11.ToRadio.PeerInfoH\x00\x12\x18\n\x0ewant_config_id\x18\x64 \x01(\rH\x00\x12\x14\n\ndisconnect\x18h \x01(\x08H\x00\x1a\x35\n\x08PeerInfo\x12\x13\n\x0b\x61pp_version\x18\x01 \x01(\r\x12\x14\n\x0cmqtt_gateway\x18\x02 \x01(\x08\x42\x10\n\x0epayloadVariantJ\x04\x08\x01\x10\x02J\x04\x08\x65\x10\x66J\x04\x08\x66\x10gJ\x04\x08g\x10h*\xac\x02\n\rHardwareModel\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08TLORA_V2\x10\x01\x12\x0c\n\x08TLORA_V1\x10\x02\x12\x12\n\x0eTLORA_V2_1_1p6\x10\x03\x12\t\n\x05TBEAM\x10\x04\x12\x0f\n\x0bHELTEC_V2_0\x10\x05\x12\x0c\n\x08TBEAM0p7\x10\x06\x12\n\n\x06T_ECHO\x10\x07\x12\x10\n\x0cTLORA_V1_1p3\x10\x08\x12\x0b\n\x07RAK4631\x10\t\x12\x0f\n\x0bHELTEC_V2_1\x10\n\x12\x11\n\rLORA_RELAY_V1\x10 \x12\x0e\n\nNRF52840DK\x10!\x12\x07\n\x03PPR\x10\"\x12\x0f\n\x0bGENIEBLOCKS\x10#\x12\x11\n\rNRF52_UNKNOWN\x10$\x12\r\n\tPORTDUINO\x10%\x12\x0f\n\x0b\x41NDROID_SIM\x10&\x12\n\n\x06\x44IY_V1\x10\'*\xb5\x01\n\x04Team\x12\t\n\x05\x43LEAR\x10\x00\x12\x08\n\x04\x43YAN\x10\x01\x12\t\n\x05WHITE\x10\x02\x12\n\n\x06YELLOW\x10\x03\x12\n\n\x06ORANGE\x10\x04\x12\x0b\n\x07MAGENTA\x10\x05\x12\x07\n\x03RED\x10\x06\x12\n\n\x06MAROON\x10\x07\x12\n\n\x06PURPLE\x10\x08\x12\r\n\tDARK_BLUE\x10\t\x12\x08\n\x04\x42LUE\x10\n\x12\x08\n\x04TEAL\x10\x0b\x12\t\n\x05GREEN\x10\x0c\x12\x0e\n\nDARK_GREEN\x10\r\x12\t\n\x05\x42ROWN\x10\x0e*.\n\tConstants\x12\n\n\x06Unused\x10\x00\x12\x15\n\x10\x44\x41TA_PAYLOAD_LEN\x10\xed\x01*\xe1\x01\n\x11\x43riticalErrorCode\x12\x08\n\x04None\x10\x00\x12\x0e\n\nTxWatchdog\x10\x01\x12\x12\n\x0eSleepEnterWait\x10\x02\x12\x0b\n\x07NoRadio\x10\x03\x12\x0f\n\x0bUnspecified\x10\x04\x12\x13\n\x0fUBloxInitFailed\x10\x05\x12\x0c\n\x08NoAXP192\x10\x06\x12\x17\n\x13InvalidRadioSetting\x10\x07\x12\x12\n\x0eTransmitFailed\x10\x08\x12\x0c\n\x08\x42rownout\x10\t\x12\x11\n\rSX1262Failure\x10\n\x12\x0f\n\x0bRadioSpiBug\x10\x0b\x42\x46\n\x13\x63om.geeksville.meshB\nMeshProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' , dependencies=[portnums__pb2.DESCRIPTOR,]) @@ -109,8 +109,8 @@ _HARDWAREMODEL = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3034, - serialized_end=3334, + serialized_start=3225, + serialized_end=3525, ) _sym_db.RegisterEnumDescriptor(_HARDWAREMODEL) @@ -184,8 +184,8 @@ _TEAM = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3337, - serialized_end=3518, + serialized_start=3528, + serialized_end=3709, ) _sym_db.RegisterEnumDescriptor(_TEAM) @@ -207,8 +207,8 @@ _CONSTANTS = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3520, - serialized_end=3566, + serialized_start=3711, + serialized_end=3757, ) _sym_db.RegisterEnumDescriptor(_CONSTANTS) @@ -270,8 +270,8 @@ _CRITICALERRORCODE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3569, - serialized_end=3794, + serialized_start=3760, + serialized_end=3985, ) _sym_db.RegisterEnumDescriptor(_CRITICALERRORCODE) @@ -485,11 +485,37 @@ _MESHPACKET_PRIORITY = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=1769, - serialized_end=1860, + serialized_start=1845, + serialized_end=1936, ) _sym_db.RegisterEnumDescriptor(_MESHPACKET_PRIORITY) +_MESHPACKET_DELAYED = _descriptor.EnumDescriptor( + name='Delayed', + full_name='MeshPacket.Delayed', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NO_DELAY', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DELAYED_BROADCAST', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DELAYED_DIRECT', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1938, + serialized_end=2004, +) +_sym_db.RegisterEnumDescriptor(_MESHPACKET_DELAYED) + _LOGRECORD_LEVEL = _descriptor.EnumDescriptor( name='Level', full_name='LogRecord.Level', @@ -527,8 +553,8 @@ _LOGRECORD_LEVEL = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=2479, - serialized_end=2567, + serialized_start=2670, + serialized_end=2758, ) _sym_db.RegisterEnumDescriptor(_LOGRECORD_LEVEL) @@ -1051,12 +1077,34 @@ _MESHPACKET = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='delayed', full_name='MeshPacket.delayed', index=12, + number=15, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='reply_id', full_name='MeshPacket.reply_id', index=13, + number=16, type=7, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='is_tapback', full_name='MeshPacket.is_tapback', index=14, + number=17, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ _MESHPACKET_PRIORITY, + _MESHPACKET_DELAYED, ], serialized_options=None, is_extendable=False, @@ -1068,7 +1116,7 @@ _MESHPACKET = _descriptor.Descriptor( index=0, containing_type=None, fields=[]), ], serialized_start=1526, - serialized_end=1878, + serialized_end=2022, ) @@ -1126,8 +1174,8 @@ _NODEINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1880, - serialized_end=1986, + serialized_start=2024, + serialized_end=2130, ) @@ -1250,6 +1298,20 @@ _MYNODEINFO = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='has_wifi', full_name='MyNodeInfo.has_wifi', index=16, + number=18, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='channel_utilization', full_name='MyNodeInfo.channel_utilization', index=17, + number=19, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -1262,8 +1324,8 @@ _MYNODEINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1989, - serialized_end=2383, + serialized_start=2133, + serialized_end=2574, ) @@ -1315,8 +1377,8 @@ _LOGRECORD = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2386, - serialized_end=2567, + serialized_start=2577, + serialized_end=2758, ) @@ -1391,8 +1453,8 @@ _FROMRADIO = _descriptor.Descriptor( name='payloadVariant', full_name='FromRadio.payloadVariant', index=0, containing_type=None, fields=[]), ], - serialized_start=2570, - serialized_end=2803, + serialized_start=2761, + serialized_end=2994, ) @@ -1429,8 +1491,8 @@ _TORADIO_PEERINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2936, - serialized_end=2989, + serialized_start=3127, + serialized_end=3180, ) _TORADIO = _descriptor.Descriptor( @@ -1483,8 +1545,8 @@ _TORADIO = _descriptor.Descriptor( name='payloadVariant', full_name='ToRadio.payloadVariant', index=0, containing_type=None, fields=[]), ], - serialized_start=2806, - serialized_end=3031, + serialized_start=2997, + serialized_end=3222, ) _POSITION.fields_by_name['location_source'].enum_type = _POSITION_LOCSOURCE @@ -1509,7 +1571,9 @@ _ROUTING.fields_by_name['error_reason'].containing_oneof = _ROUTING.oneofs_by_na _DATA.fields_by_name['portnum'].enum_type = portnums__pb2._PORTNUM _MESHPACKET.fields_by_name['decoded'].message_type = _DATA _MESHPACKET.fields_by_name['priority'].enum_type = _MESHPACKET_PRIORITY +_MESHPACKET.fields_by_name['delayed'].enum_type = _MESHPACKET_DELAYED _MESHPACKET_PRIORITY.containing_type = _MESHPACKET +_MESHPACKET_DELAYED.containing_type = _MESHPACKET _MESHPACKET.oneofs_by_name['payloadVariant'].fields.append( _MESHPACKET.fields_by_name['decoded']) _MESHPACKET.fields_by_name['decoded'].containing_oneof = _MESHPACKET.oneofs_by_name['payloadVariant'] diff --git a/meshtastic/radioconfig_pb2.py b/meshtastic/radioconfig_pb2.py index 3ee457e..6d00423 100644 --- a/meshtastic/radioconfig_pb2.py +++ b/meshtastic/radioconfig_pb2.py @@ -19,7 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='', syntax='proto3', serialized_options=b'\n\023com.geeksville.meshB\021RadioConfigProtosH\003Z!github.com/meshtastic/gomeshproto', - serialized_pb=b'\n\x11radioconfig.proto\"\xa3\x13\n\x0bRadioConfig\x12\x31\n\x0bpreferences\x18\x01 \x01(\x0b\x32\x1c.RadioConfig.UserPreferences\x1a\xe0\x12\n\x0fUserPreferences\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12 \n\x18position_broadcast_smart\x18\x11 \x01(\x08\x12\x1b\n\x13send_owner_interval\x18\x02 \x01(\r\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x16\n\x0escreen_on_secs\x18\x05 \x01(\r\x12\x1a\n\x12phone_timeout_secs\x18\x06 \x01(\r\x12\x1d\n\x15phone_sds_timeout_sec\x18\x07 \x01(\r\x12\x1d\n\x15mesh_sds_timeout_secs\x18\x08 \x01(\r\x12\x10\n\x08sds_secs\x18\t \x01(\r\x12\x0f\n\x07ls_secs\x18\n \x01(\r\x12\x15\n\rmin_wake_secs\x18\x0b \x01(\r\x12\x11\n\twifi_ssid\x18\x0c \x01(\t\x12\x15\n\rwifi_password\x18\r \x01(\t\x12\x14\n\x0cwifi_ap_mode\x18\x0e \x01(\x08\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\x12&\n\x0e\x63harge_current\x18\x10 \x01(\x0e\x32\x0e.ChargeCurrent\x12\x11\n\tis_router\x18% \x01(\x08\x12\x14\n\x0cis_low_power\x18& \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\' \x01(\x08\x12\x17\n\x0fserial_disabled\x18( \x01(\x08\x12(\n\x0elocation_share\x18 \x01(\x0e\x32\x10.LocationSharing\x12$\n\rgps_operation\x18! \x01(\x0e\x32\r.GpsOperation\x12\x1b\n\x13gps_update_interval\x18\" \x01(\r\x12\x18\n\x10gps_attempt_time\x18$ \x01(\r\x12\x15\n\rgps_accept_2d\x18- \x01(\x08\x12\x13\n\x0bgps_max_dop\x18. \x01(\r\x12\x18\n\x10\x66requency_offset\x18) \x01(\x02\x12\x13\n\x0bmqtt_server\x18* \x01(\t\x12\x15\n\rmqtt_disabled\x18+ \x01(\x08\x12(\n\ngps_format\x18, \x01(\x0e\x32\x14.GpsCoordinateFormat\x12\x15\n\rfactory_reset\x18\x64 \x01(\x08\x12\x19\n\x11\x64\x65\x62ug_log_enabled\x18\x65 \x01(\x08\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x1c\n\x14serialplugin_enabled\x18x \x01(\x08\x12\x19\n\x11serialplugin_echo\x18y \x01(\x08\x12\x18\n\x10serialplugin_rxd\x18z \x01(\r\x12\x18\n\x10serialplugin_txd\x18{ \x01(\r\x12\x1c\n\x14serialplugin_timeout\x18| \x01(\r\x12\x19\n\x11serialplugin_mode\x18} \x01(\r\x12\'\n\x1f\x65xt_notification_plugin_enabled\x18~ \x01(\x08\x12)\n!ext_notification_plugin_output_ms\x18\x7f \x01(\r\x12\'\n\x1e\x65xt_notification_plugin_output\x18\x80\x01 \x01(\r\x12\'\n\x1e\x65xt_notification_plugin_active\x18\x81\x01 \x01(\x08\x12.\n%ext_notification_plugin_alert_message\x18\x82\x01 \x01(\x08\x12+\n\"ext_notification_plugin_alert_bell\x18\x83\x01 \x01(\x08\x12\"\n\x19range_test_plugin_enabled\x18\x84\x01 \x01(\x08\x12!\n\x18range_test_plugin_sender\x18\x85\x01 \x01(\r\x12\x1f\n\x16range_test_plugin_save\x18\x86\x01 \x01(\x08\x12%\n\x1cstore_forward_plugin_enabled\x18\x94\x01 \x01(\x08\x12\'\n\x1estore_forward_plugin_heartbeat\x18\x95\x01 \x01(\x08\x12%\n\x1cstore_forward_plugin_records\x18\x89\x01 \x01(\r\x12\x30\n\'store_forward_plugin_history_return_max\x18\x8a\x01 \x01(\r\x12\x33\n*store_forward_plugin_history_return_window\x18\x8b\x01 \x01(\r\x12=\n4environmental_measurement_plugin_measurement_enabled\x18\x8c\x01 \x01(\x08\x12\x38\n/environmental_measurement_plugin_screen_enabled\x18\x8d\x01 \x01(\x08\x12\x44\n;environmental_measurement_plugin_read_error_count_threshold\x18\x8e\x01 \x01(\r\x12\x39\n0environmental_measurement_plugin_update_interval\x18\x8f\x01 \x01(\r\x12;\n2environmental_measurement_plugin_recovery_interval\x18\x90\x01 \x01(\r\x12;\n2environmental_measurement_plugin_display_farenheit\x18\x91\x01 \x01(\x08\x12v\n,environmental_measurement_plugin_sensor_type\x18\x92\x01 \x01(\x0e\x32?.RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType\x12\x34\n+environmental_measurement_plugin_sensor_pin\x18\x93\x01 \x01(\r\x12\x17\n\x0eposition_flags\x18\x96\x01 \x01(\r\x12\x1a\n\x11is_always_powered\x18\x97\x01 \x01(\x08\x12\"\n\x19\x61uto_screen_carousel_secs\x18\x98\x01 \x01(\r\x12\'\n\x1eon_battery_shutdown_after_secs\x18\x99\x01 \x01(\r\"<\n\"EnvironmentalMeasurementSensorType\x12\t\n\x05\x44HT11\x10\x00\x12\x0b\n\x07\x44S18B20\x10\x01J\x06\x08\x88\x01\x10\x89\x01*f\n\nRegionCode\x12\t\n\x05Unset\x10\x00\x12\x06\n\x02US\x10\x01\x12\t\n\x05\x45U433\x10\x02\x12\t\n\x05\x45U865\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t*\xd1\x01\n\rChargeCurrent\x12\x0b\n\x07MAUnset\x10\x00\x12\t\n\x05MA100\x10\x01\x12\t\n\x05MA190\x10\x02\x12\t\n\x05MA280\x10\x03\x12\t\n\x05MA360\x10\x04\x12\t\n\x05MA450\x10\x05\x12\t\n\x05MA550\x10\x06\x12\t\n\x05MA630\x10\x07\x12\t\n\x05MA700\x10\x08\x12\t\n\x05MA780\x10\t\x12\t\n\x05MA880\x10\n\x12\t\n\x05MA960\x10\x0b\x12\n\n\x06MA1000\x10\x0c\x12\n\n\x06MA1080\x10\r\x12\n\n\x06MA1160\x10\x0e\x12\n\n\x06MA1240\x10\x0f\x12\n\n\x06MA1320\x10\x10*j\n\x0cGpsOperation\x12\x0e\n\nGpsOpUnset\x10\x00\x12\x13\n\x0fGpsOpStationary\x10\x01\x12\x0f\n\x0bGpsOpMobile\x10\x02\x12\x11\n\rGpsOpTimeOnly\x10\x03\x12\x11\n\rGpsOpDisabled\x10\x04*\x83\x01\n\x13GpsCoordinateFormat\x12\x10\n\x0cGpsFormatDec\x10\x00\x12\x10\n\x0cGpsFormatDMS\x10\x01\x12\x10\n\x0cGpsFormatUTM\x10\x02\x12\x11\n\rGpsFormatMGRS\x10\x03\x12\x10\n\x0cGpsFormatOLC\x10\x04\x12\x11\n\rGpsFormatOSGR\x10\x05*@\n\x0fLocationSharing\x12\x0c\n\x08LocUnset\x10\x00\x12\x0e\n\nLocEnabled\x10\x01\x12\x0f\n\x0bLocDisabled\x10\x02*\xbc\x01\n\rPositionFlags\x12\x11\n\rPOS_UNDEFINED\x10\x00\x12\x10\n\x0cPOS_ALTITUDE\x10\x01\x12\x0f\n\x0bPOS_ALT_MSL\x10\x02\x12\x0f\n\x0bPOS_GEO_SEP\x10\x04\x12\x0b\n\x07POS_DOP\x10\x08\x12\r\n\tPOS_HVDOP\x10\x10\x12\x0f\n\x0bPOS_BATTERY\x10 \x12\x11\n\rPOS_SATINVIEW\x10@\x12\x10\n\x0bPOS_SEQ_NOS\x10\x80\x01\x12\x12\n\rPOS_TIMESTAMP\x10\x80\x02\x42M\n\x13\x63om.geeksville.meshB\x11RadioConfigProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' + serialized_pb=b'\n\x11radioconfig.proto\"\xe7\x13\n\x0bRadioConfig\x12\x31\n\x0bpreferences\x18\x01 \x01(\x0b\x32\x1c.RadioConfig.UserPreferences\x1a\xa4\x13\n\x0fUserPreferences\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12 \n\x18position_broadcast_smart\x18\x11 \x01(\x08\x12\x1b\n\x13send_owner_interval\x18\x02 \x01(\r\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x16\n\x0escreen_on_secs\x18\x05 \x01(\r\x12\x1a\n\x12phone_timeout_secs\x18\x06 \x01(\r\x12\x1d\n\x15phone_sds_timeout_sec\x18\x07 \x01(\r\x12\x1d\n\x15mesh_sds_timeout_secs\x18\x08 \x01(\r\x12\x10\n\x08sds_secs\x18\t \x01(\r\x12\x0f\n\x07ls_secs\x18\n \x01(\r\x12\x15\n\rmin_wake_secs\x18\x0b \x01(\r\x12\x11\n\twifi_ssid\x18\x0c \x01(\t\x12\x15\n\rwifi_password\x18\r \x01(\t\x12\x14\n\x0cwifi_ap_mode\x18\x0e \x01(\x08\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\x12&\n\x0e\x63harge_current\x18\x10 \x01(\x0e\x32\x0e.ChargeCurrent\x12\x11\n\tis_router\x18% \x01(\x08\x12\x14\n\x0cis_low_power\x18& \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\' \x01(\x08\x12\x17\n\x0fserial_disabled\x18( \x01(\x08\x12(\n\x0elocation_share\x18 \x01(\x0e\x32\x10.LocationSharing\x12$\n\rgps_operation\x18! \x01(\x0e\x32\r.GpsOperation\x12\x1b\n\x13gps_update_interval\x18\" \x01(\r\x12\x18\n\x10gps_attempt_time\x18$ \x01(\r\x12\x15\n\rgps_accept_2d\x18- \x01(\x08\x12\x13\n\x0bgps_max_dop\x18. \x01(\r\x12\x18\n\x10\x66requency_offset\x18) \x01(\x02\x12\x13\n\x0bmqtt_server\x18* \x01(\t\x12\x15\n\rmqtt_disabled\x18+ \x01(\x08\x12(\n\ngps_format\x18, \x01(\x0e\x32\x14.GpsCoordinateFormat\x12\x15\n\rfactory_reset\x18\x64 \x01(\x08\x12\x19\n\x11\x64\x65\x62ug_log_enabled\x18\x65 \x01(\x08\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x1c\n\x14serialplugin_enabled\x18x \x01(\x08\x12\x19\n\x11serialplugin_echo\x18y \x01(\x08\x12\x18\n\x10serialplugin_rxd\x18z \x01(\r\x12\x18\n\x10serialplugin_txd\x18{ \x01(\r\x12\x1c\n\x14serialplugin_timeout\x18| \x01(\r\x12\x19\n\x11serialplugin_mode\x18} \x01(\r\x12\'\n\x1f\x65xt_notification_plugin_enabled\x18~ \x01(\x08\x12)\n!ext_notification_plugin_output_ms\x18\x7f \x01(\r\x12\'\n\x1e\x65xt_notification_plugin_output\x18\x80\x01 \x01(\r\x12\'\n\x1e\x65xt_notification_plugin_active\x18\x81\x01 \x01(\x08\x12.\n%ext_notification_plugin_alert_message\x18\x82\x01 \x01(\x08\x12+\n\"ext_notification_plugin_alert_bell\x18\x83\x01 \x01(\x08\x12\"\n\x19range_test_plugin_enabled\x18\x84\x01 \x01(\x08\x12!\n\x18range_test_plugin_sender\x18\x85\x01 \x01(\r\x12\x1f\n\x16range_test_plugin_save\x18\x86\x01 \x01(\x08\x12%\n\x1cstore_forward_plugin_enabled\x18\x94\x01 \x01(\x08\x12\'\n\x1estore_forward_plugin_heartbeat\x18\x95\x01 \x01(\x08\x12%\n\x1cstore_forward_plugin_records\x18\x89\x01 \x01(\r\x12\x30\n\'store_forward_plugin_history_return_max\x18\x8a\x01 \x01(\r\x12\x33\n*store_forward_plugin_history_return_window\x18\x8b\x01 \x01(\r\x12=\n4environmental_measurement_plugin_measurement_enabled\x18\x8c\x01 \x01(\x08\x12\x38\n/environmental_measurement_plugin_screen_enabled\x18\x8d\x01 \x01(\x08\x12\x44\n;environmental_measurement_plugin_read_error_count_threshold\x18\x8e\x01 \x01(\r\x12\x39\n0environmental_measurement_plugin_update_interval\x18\x8f\x01 \x01(\r\x12;\n2environmental_measurement_plugin_recovery_interval\x18\x90\x01 \x01(\r\x12;\n2environmental_measurement_plugin_display_farenheit\x18\x91\x01 \x01(\x08\x12v\n,environmental_measurement_plugin_sensor_type\x18\x92\x01 \x01(\x0e\x32?.RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType\x12\x34\n+environmental_measurement_plugin_sensor_pin\x18\x93\x01 \x01(\r\x12\x17\n\x0eposition_flags\x18\x96\x01 \x01(\r\x12\x1a\n\x11is_always_powered\x18\x97\x01 \x01(\x08\x12\"\n\x19\x61uto_screen_carousel_secs\x18\x98\x01 \x01(\r\x12\'\n\x1eon_battery_shutdown_after_secs\x18\x99\x01 \x01(\r\x12\x12\n\thop_limit\x18\x9a\x01 \x01(\r\x12\x16\n\rmqtt_username\x18\x9b\x01 \x01(\t\x12\x16\n\rmqtt_password\x18\x9c\x01 \x01(\t\"<\n\"EnvironmentalMeasurementSensorType\x12\t\n\x05\x44HT11\x10\x00\x12\x0b\n\x07\x44S18B20\x10\x01J\x06\x08\x88\x01\x10\x89\x01*f\n\nRegionCode\x12\t\n\x05Unset\x10\x00\x12\x06\n\x02US\x10\x01\x12\t\n\x05\x45U433\x10\x02\x12\t\n\x05\x45U865\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t*\xd1\x01\n\rChargeCurrent\x12\x0b\n\x07MAUnset\x10\x00\x12\t\n\x05MA100\x10\x01\x12\t\n\x05MA190\x10\x02\x12\t\n\x05MA280\x10\x03\x12\t\n\x05MA360\x10\x04\x12\t\n\x05MA450\x10\x05\x12\t\n\x05MA550\x10\x06\x12\t\n\x05MA630\x10\x07\x12\t\n\x05MA700\x10\x08\x12\t\n\x05MA780\x10\t\x12\t\n\x05MA880\x10\n\x12\t\n\x05MA960\x10\x0b\x12\n\n\x06MA1000\x10\x0c\x12\n\n\x06MA1080\x10\r\x12\n\n\x06MA1160\x10\x0e\x12\n\n\x06MA1240\x10\x0f\x12\n\n\x06MA1320\x10\x10*j\n\x0cGpsOperation\x12\x0e\n\nGpsOpUnset\x10\x00\x12\x13\n\x0fGpsOpStationary\x10\x01\x12\x0f\n\x0bGpsOpMobile\x10\x02\x12\x11\n\rGpsOpTimeOnly\x10\x03\x12\x11\n\rGpsOpDisabled\x10\x04*\x83\x01\n\x13GpsCoordinateFormat\x12\x10\n\x0cGpsFormatDec\x10\x00\x12\x10\n\x0cGpsFormatDMS\x10\x01\x12\x10\n\x0cGpsFormatUTM\x10\x02\x12\x11\n\rGpsFormatMGRS\x10\x03\x12\x10\n\x0cGpsFormatOLC\x10\x04\x12\x11\n\rGpsFormatOSGR\x10\x05*@\n\x0fLocationSharing\x12\x0c\n\x08LocUnset\x10\x00\x12\x0e\n\nLocEnabled\x10\x01\x12\x0f\n\x0bLocDisabled\x10\x02*\xbc\x01\n\rPositionFlags\x12\x11\n\rPOS_UNDEFINED\x10\x00\x12\x10\n\x0cPOS_ALTITUDE\x10\x01\x12\x0f\n\x0bPOS_ALT_MSL\x10\x02\x12\x0f\n\x0bPOS_GEO_SEP\x10\x04\x12\x0b\n\x07POS_DOP\x10\x08\x12\r\n\tPOS_HVDOP\x10\x10\x12\x0f\n\x0bPOS_BATTERY\x10 \x12\x11\n\rPOS_SATINVIEW\x10@\x12\x10\n\x0bPOS_SEQ_NOS\x10\x80\x01\x12\x12\n\rPOS_TIMESTAMP\x10\x80\x02\x42M\n\x13\x63om.geeksville.meshB\x11RadioConfigProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' ) _REGIONCODE = _descriptor.EnumDescriptor( @@ -71,8 +71,8 @@ _REGIONCODE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=2491, - serialized_end=2593, + serialized_start=2559, + serialized_end=2661, ) _sym_db.RegisterEnumDescriptor(_REGIONCODE) @@ -154,8 +154,8 @@ _CHARGECURRENT = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=2596, - serialized_end=2805, + serialized_start=2664, + serialized_end=2873, ) _sym_db.RegisterEnumDescriptor(_CHARGECURRENT) @@ -189,8 +189,8 @@ _GPSOPERATION = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=2807, - serialized_end=2913, + serialized_start=2875, + serialized_end=2981, ) _sym_db.RegisterEnumDescriptor(_GPSOPERATION) @@ -228,8 +228,8 @@ _GPSCOORDINATEFORMAT = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=2916, - serialized_end=3047, + serialized_start=2984, + serialized_end=3115, ) _sym_db.RegisterEnumDescriptor(_GPSCOORDINATEFORMAT) @@ -255,8 +255,8 @@ _LOCATIONSHARING = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3049, - serialized_end=3113, + serialized_start=3117, + serialized_end=3181, ) _sym_db.RegisterEnumDescriptor(_LOCATIONSHARING) @@ -310,8 +310,8 @@ _POSITIONFLAGS = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3116, - serialized_end=3304, + serialized_start=3184, + serialized_end=3372, ) _sym_db.RegisterEnumDescriptor(_POSITIONFLAGS) @@ -386,8 +386,8 @@ _RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE = _descriptor.En ], containing_type=None, serialized_options=None, - serialized_start=2421, - serialized_end=2481, + serialized_start=2489, + serialized_end=2549, ) _sym_db.RegisterEnumDescriptor(_RADIOCONFIG_USERPREFERENCES_ENVIRONMENTALMEASUREMENTSENSORTYPE) @@ -854,6 +854,27 @@ _RADIOCONFIG_USERPREFERENCES = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='hop_limit', full_name='RadioConfig.UserPreferences.hop_limit', index=65, + number=154, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mqtt_username', full_name='RadioConfig.UserPreferences.mqtt_username', index=66, + number=155, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mqtt_password', full_name='RadioConfig.UserPreferences.mqtt_password', index=67, + number=156, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -868,7 +889,7 @@ _RADIOCONFIG_USERPREFERENCES = _descriptor.Descriptor( oneofs=[ ], serialized_start=89, - serialized_end=2489, + serialized_end=2557, ) _RADIOCONFIG = _descriptor.Descriptor( @@ -898,7 +919,7 @@ _RADIOCONFIG = _descriptor.Descriptor( oneofs=[ ], serialized_start=22, - serialized_end=2489, + serialized_end=2557, ) _RADIOCONFIG_USERPREFERENCES.fields_by_name['region'].enum_type = _REGIONCODE diff --git a/meshtastic/storeforward_pb2.py b/meshtastic/storeforward_pb2.py index 1e214c0..5f781c0 100644 --- a/meshtastic/storeforward_pb2.py +++ b/meshtastic/storeforward_pb2.py @@ -18,7 +18,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='', syntax='proto3', serialized_options=b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto', - serialized_pb=b'\n\x12storeforward.proto\"\xe7\x04\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x1a\xc6\x01\n\nStatistics\x12\x15\n\rMessagesTotal\x18\x01 \x01(\r\x12\x15\n\rMessagesSaved\x18\x02 \x01(\r\x12\x13\n\x0bMessagesMax\x18\x03 \x01(\r\x12\x0e\n\x06UpTime\x18\x04 \x01(\r\x12\x10\n\x08Requests\x18\x05 \x01(\r\x12\x17\n\x0fRequestsHistory\x18\x06 \x01(\r\x12\x11\n\tHeartbeat\x18\x07 \x01(\x08\x12\x11\n\tReturnMax\x18\x08 \x01(\r\x12\x14\n\x0cReturnWindow\x18\t \x01(\r\x1a\x32\n\x07History\x12\x17\n\x0fHistoryMessages\x18\x01 \x01(\r\x12\x0e\n\x06Window\x18\x02 \x01(\r\"\xd1\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10iBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' + serialized_pb=b'\n\x12storeforward.proto\"\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xf7\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3' ) @@ -54,30 +54,38 @@ _STOREANDFORWARD_REQUESTRESPONSE = _descriptor.EnumDescriptor( serialized_options=None, type=None), _descriptor.EnumValueDescriptor( - name='CLIENT_ERROR', index=6, number=101, + name='ROUTER_HISTORY', index=6, number=6, serialized_options=None, type=None), _descriptor.EnumValueDescriptor( - name='CLIENT_HISTORY', index=7, number=102, + name='CLIENT_ERROR', index=7, number=101, serialized_options=None, type=None), _descriptor.EnumValueDescriptor( - name='CLIENT_STATS', index=8, number=103, + name='CLIENT_HISTORY', index=8, number=102, serialized_options=None, type=None), _descriptor.EnumValueDescriptor( - name='CLIENT_PING', index=9, number=104, + name='CLIENT_STATS', index=9, number=103, serialized_options=None, type=None), _descriptor.EnumValueDescriptor( - name='CLIENT_PONG', index=10, number=105, + name='CLIENT_PING', index=10, number=104, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='CLIENT_PONG', index=11, number=105, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='CLIENT_ABORT', index=12, number=106, serialized_options=None, type=None), ], containing_type=None, serialized_options=None, - serialized_start=429, - serialized_end=638, + serialized_start=554, + serialized_end=801, ) _sym_db.RegisterEnumDescriptor(_STOREANDFORWARD_REQUESTRESPONSE) @@ -90,63 +98,63 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor( containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='MessagesTotal', full_name='StoreAndForward.Statistics.MessagesTotal', index=0, + name='messages_total', full_name='StoreAndForward.Statistics.messages_total', index=0, number=1, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='MessagesSaved', full_name='StoreAndForward.Statistics.MessagesSaved', index=1, + name='messages_saved', full_name='StoreAndForward.Statistics.messages_saved', index=1, number=2, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='MessagesMax', full_name='StoreAndForward.Statistics.MessagesMax', index=2, + name='messages_max', full_name='StoreAndForward.Statistics.messages_max', index=2, number=3, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='UpTime', full_name='StoreAndForward.Statistics.UpTime', index=3, + name='up_time', full_name='StoreAndForward.Statistics.up_time', index=3, number=4, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='Requests', full_name='StoreAndForward.Statistics.Requests', index=4, + name='requests', full_name='StoreAndForward.Statistics.requests', index=4, number=5, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='RequestsHistory', full_name='StoreAndForward.Statistics.RequestsHistory', index=5, + name='requests_history', full_name='StoreAndForward.Statistics.requests_history', index=5, number=6, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='Heartbeat', full_name='StoreAndForward.Statistics.Heartbeat', index=6, + name='heartbeat', full_name='StoreAndForward.Statistics.heartbeat', index=6, number=7, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='ReturnMax', full_name='StoreAndForward.Statistics.ReturnMax', index=7, + name='return_max', full_name='StoreAndForward.Statistics.return_max', index=7, number=8, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='ReturnWindow', full_name='StoreAndForward.Statistics.ReturnWindow', index=8, + name='return_window', full_name='StoreAndForward.Statistics.return_window', index=8, number=9, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -164,8 +172,8 @@ _STOREANDFORWARD_STATISTICS = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=176, - serialized_end=374, + serialized_start=223, + serialized_end=428, ) _STOREANDFORWARD_HISTORY = _descriptor.Descriptor( @@ -176,14 +184,58 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor( containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='HistoryMessages', full_name='StoreAndForward.History.HistoryMessages', index=0, + name='history_messages', full_name='StoreAndForward.History.history_messages', index=0, number=1, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='Window', full_name='StoreAndForward.History.Window', index=1, + name='window', full_name='StoreAndForward.History.window', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='last_request', full_name='StoreAndForward.History.last_request', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=430, + serialized_end=503, +) + +_STOREANDFORWARD_HEARTBEAT = _descriptor.Descriptor( + name='Heartbeat', + full_name='StoreAndForward.Heartbeat', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='period', full_name='StoreAndForward.Heartbeat.period', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='secondary', full_name='StoreAndForward.Heartbeat.secondary', index=1, number=2, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -201,8 +253,8 @@ _STOREANDFORWARD_HISTORY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=376, - serialized_end=426, + serialized_start=505, + serialized_end=551, ) _STOREANDFORWARD = _descriptor.Descriptor( @@ -233,10 +285,17 @@ _STOREANDFORWARD = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='heartbeat', full_name='StoreAndForward.heartbeat', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], - nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, ], + nested_types=[_STOREANDFORWARD_STATISTICS, _STOREANDFORWARD_HISTORY, _STOREANDFORWARD_HEARTBEAT, ], enum_types=[ _STOREANDFORWARD_REQUESTRESPONSE, ], @@ -247,14 +306,16 @@ _STOREANDFORWARD = _descriptor.Descriptor( oneofs=[ ], serialized_start=23, - serialized_end=638, + serialized_end=801, ) _STOREANDFORWARD_STATISTICS.containing_type = _STOREANDFORWARD _STOREANDFORWARD_HISTORY.containing_type = _STOREANDFORWARD +_STOREANDFORWARD_HEARTBEAT.containing_type = _STOREANDFORWARD _STOREANDFORWARD.fields_by_name['rr'].enum_type = _STOREANDFORWARD_REQUESTRESPONSE _STOREANDFORWARD.fields_by_name['stats'].message_type = _STOREANDFORWARD_STATISTICS _STOREANDFORWARD.fields_by_name['history'].message_type = _STOREANDFORWARD_HISTORY +_STOREANDFORWARD.fields_by_name['heartbeat'].message_type = _STOREANDFORWARD_HEARTBEAT _STOREANDFORWARD_REQUESTRESPONSE.containing_type = _STOREANDFORWARD DESCRIPTOR.message_types_by_name['StoreAndForward'] = _STOREANDFORWARD _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -274,6 +335,13 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_ # @@protoc_insertion_point(class_scope:StoreAndForward.History) }) , + + 'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), { + 'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT, + '__module__' : 'storeforward_pb2' + # @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat) + }) + , 'DESCRIPTOR' : _STOREANDFORWARD, '__module__' : 'storeforward_pb2' # @@protoc_insertion_point(class_scope:StoreAndForward) @@ -281,6 +349,7 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_ _sym_db.RegisterMessage(StoreAndForward) _sym_db.RegisterMessage(StoreAndForward.Statistics) _sym_db.RegisterMessage(StoreAndForward.History) +_sym_db.RegisterMessage(StoreAndForward.Heartbeat) DESCRIPTOR._options = None