mirror of
https://github.com/meshtastic/python.git
synced 2026-09-08 19:39:14 -04:00
Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efb6d1c0e9 | ||
|
|
0539a9600f | ||
|
|
445e17ddc0 | ||
|
|
33840e2cee | ||
|
|
e45e59ad39 | ||
|
|
ae710478fb | ||
|
|
3b084d7df2 | ||
|
|
3f62c099ae | ||
|
|
a509bca86d | ||
|
|
dca0b1fced | ||
|
|
11b71e9587 | ||
|
|
ef9cdc34a4 | ||
|
|
19d066970e | ||
|
|
485d860911 | ||
|
|
e6f07123f6 | ||
|
|
f623732694 | ||
|
|
82220ba49b | ||
|
|
4339cda6bd | ||
|
|
5760fb3ad9 | ||
|
|
2281ea5b64 | ||
|
|
7950798378 | ||
|
|
bffaff1c71 | ||
|
|
ceca7b3b10 | ||
|
|
15438c8a6e | ||
|
|
2f9b285dd5 | ||
|
|
9b892711df | ||
|
|
1cd1aed33c | ||
|
|
ab14fb2a86 | ||
|
|
7a7353ca15 | ||
|
|
e2844cb8d6 | ||
|
|
441c6e80a1 | ||
|
|
b05e545396 | ||
|
|
b6a13d58fe | ||
|
|
8c5efb926c |
No files matched your search
@@ -0,0 +1,16 @@
|
||||
language: en-US
|
||||
reviews:
|
||||
profile: chill
|
||||
high_level_summary: true
|
||||
poem: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: false
|
||||
ignore_usernames:
|
||||
- renovate
|
||||
- renovate[bot]
|
||||
abort_on_close: true
|
||||
|
||||
path_filters:
|
||||
- "!/protobufs/**"
|
||||
- "!/meshtastic/protobuf/**"
|
||||
+54
-17
@@ -1,4 +1,7 @@
|
||||
name: CI
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -8,7 +11,29 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.12"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install --all-extras --with dev,powermon
|
||||
poetry run meshtastic --version
|
||||
|
||||
build:
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -17,10 +42,14 @@ jobs:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Python 3
|
||||
- name: Install Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Uninstall meshtastic
|
||||
run: |
|
||||
pip3 uninstall -y meshtastic
|
||||
@@ -32,15 +61,8 @@ jobs:
|
||||
run: |
|
||||
poetry install --all-extras --with dev,powermon
|
||||
poetry run meshtastic --version
|
||||
- name: Run pylint
|
||||
run: poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
|
||||
- name: Check types with mypy
|
||||
run: poetry run mypy meshtastic/
|
||||
- name: Run tests with pytest
|
||||
run: poetry run pytest --cov=meshtastic
|
||||
- name: Generate coverage report
|
||||
run: |
|
||||
poetry run pytest --cov=meshtastic --cov-report=xml
|
||||
- name: Run lint, type check, and tests in parallel
|
||||
run: make -j3 --output-sync=target ci
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
@@ -51,22 +73,37 @@ jobs:
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
validate:
|
||||
|
||||
simradio_testing:
|
||||
needs: [validate, build]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
channel:
|
||||
- beta
|
||||
- alpha
|
||||
- daily
|
||||
continue-on-error: ${{ matrix.channel == 'daily' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install
|
||||
poetry install --all-extras --with dev
|
||||
poetry run meshtastic --version
|
||||
- name: Install meshtasticd (${{ matrix.channel }}) from PPA
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:meshtastic/${{ matrix.channel }}
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y meshtasticd
|
||||
- name: Run firmware smoke tests
|
||||
run: poetry run pytest -m "smokevirt or smokemesh" -v
|
||||
timeout-minutes: 15
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Daily SimRadio Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 6 * * *
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
simradio_testing:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'meshtastic/python'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install meshtastic from local
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install poetry
|
||||
poetry install --all-extras --with dev
|
||||
poetry run meshtastic --version
|
||||
- name: Install meshtasticd (daily) from PPA
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:meshtastic/daily
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y meshtasticd
|
||||
- name: Run firmware smoke tests
|
||||
run: poetry run pytest -m "smokevirt or smokemesh" -v
|
||||
timeout-minutes: 15
|
||||
@@ -1,5 +1,15 @@
|
||||
name: "Update protobufs"
|
||||
on: workflow_dispatch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Protobufs version tag to sync to (e.g., v2.7.26). Defaults to the latest protobufs release."
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
repository_dispatch:
|
||||
types: [protobufs-release]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -14,6 +24,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
@@ -25,11 +36,44 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install poetry
|
||||
|
||||
- name: Update protobuf submodule
|
||||
- name: Determine target protobufs version
|
||||
id: version
|
||||
env:
|
||||
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||
PAYLOAD_VERSION: ${{ github.event.client_payload.version }}
|
||||
run: |
|
||||
cd protobufs
|
||||
git fetch --tags
|
||||
|
||||
if [ -n "${INPUT_VERSION}" ]; then
|
||||
target="${INPUT_VERSION}"
|
||||
elif [ -n "${PAYLOAD_VERSION}" ]; then
|
||||
target="${PAYLOAD_VERSION}"
|
||||
else
|
||||
target=$(git tag --list 'v*' --sort=-version:refname | head -n1)
|
||||
if [ -z "${target}" ]; then
|
||||
echo "Error: no release tags found in meshtastic/protobufs"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! git show-ref --verify --quiet "refs/tags/${target}"; then
|
||||
echo "Error: '${target}' is not a valid tag in meshtastic/protobufs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'version=%s\n' "${target}" >> "$GITHUB_OUTPUT"
|
||||
echo "Target protobufs version: ${target}"
|
||||
|
||||
- name: Update protobuf submodule to release
|
||||
env:
|
||||
TARGET_VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
git submodule sync --recursive
|
||||
git submodule update --init --recursive
|
||||
git submodule update --remote --recursive
|
||||
cd protobufs
|
||||
git checkout "${TARGET_VERSION}"
|
||||
cd ..
|
||||
|
||||
- name: Download nanopb
|
||||
run: |
|
||||
@@ -46,6 +90,8 @@ jobs:
|
||||
./bin/regen-protobufs.sh
|
||||
|
||||
- name: Commit update
|
||||
env:
|
||||
TARGET_VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'bot@noreply.github.com'
|
||||
@@ -53,7 +99,7 @@ jobs:
|
||||
git add protobufs
|
||||
git add meshtastic/protobuf
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
git commit -m "Update protobufs"
|
||||
git commit -m "protobufs: ${TARGET_VERSION}"
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit"
|
||||
|
||||
@@ -42,5 +42,19 @@ cov:
|
||||
examples: FORCE
|
||||
pytest -mexamples
|
||||
|
||||
# CI targets (run via poetry, executed in parallel by the CI workflow)
|
||||
.PHONY: ci-pylint ci-mypy ci-test ci
|
||||
|
||||
ci-pylint:
|
||||
poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$$"
|
||||
|
||||
ci-mypy:
|
||||
poetry run mypy meshtastic/
|
||||
|
||||
ci-test:
|
||||
poetry run pytest --cov=meshtastic --cov-report=xml
|
||||
|
||||
ci: ci-pylint ci-mypy ci-test
|
||||
|
||||
# Makefile hack to get the examples to always run
|
||||
FORCE: ;
|
||||
@@ -26,6 +26,13 @@ from typing import Any, Dict, List, Tuple
|
||||
# IntSize enum values from nanopb.proto
|
||||
INT_SIZE_ENUM = {8: "IS_8", 16: "IS_16", 32: "IS_32", 64: "IS_64"}
|
||||
|
||||
# FieldType enum names from nanopb.proto. Only FT_IGNORE carries meaning for a
|
||||
# Python client (the firmware omits the field entirely); the rest describe C
|
||||
# storage class, but are passed through so the descriptor mirrors the .options.
|
||||
FIELD_TYPE_ENUM = frozenset(
|
||||
{"FT_DEFAULT", "FT_CALLBACK", "FT_POINTER", "FT_STATIC", "FT_IGNORE", "FT_INLINE"}
|
||||
)
|
||||
|
||||
# Options that are valid proto FieldOptions and useful outside of C code generation.
|
||||
# We skip C-only options (anonymous_oneof, no_unions, skip_message, packed_struct,
|
||||
# packed_enum, mangle_names, callback_datatype, callback_function, descriptorsize,
|
||||
@@ -36,6 +43,7 @@ FIELD_OPTIONS = frozenset(
|
||||
"max_length",
|
||||
"max_count",
|
||||
"int_size",
|
||||
"type",
|
||||
"fixed_length",
|
||||
"fixed_count",
|
||||
"long_names",
|
||||
@@ -122,6 +130,10 @@ def format_nanopb_opts(opts: Dict[str, Any]) -> str:
|
||||
if k == "int_size":
|
||||
enum_val = INT_SIZE_ENUM.get(v, f"IS_{v}")
|
||||
parts.append(f"(nanopb).int_size = {enum_val}")
|
||||
elif k == "type":
|
||||
if v not in FIELD_TYPE_ENUM:
|
||||
raise ValueError(f"unknown nanopb field type {v!r}")
|
||||
parts.append(f"(nanopb).type = {v}")
|
||||
elif isinstance(v, bool):
|
||||
parts.append(f"(nanopb).{k} = {'true' if v else 'false'}")
|
||||
else:
|
||||
|
||||
+326
-115
@@ -5,9 +5,11 @@
|
||||
# later we can have a separate changelist to refactor main.py into smaller files
|
||||
# pylint: disable=R0917,C0302
|
||||
|
||||
from typing import List, Optional, Union
|
||||
from typing import Any, Callable, List, Optional, Union
|
||||
from types import ModuleType
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import argparse
|
||||
|
||||
argcomplete: Union[None, ModuleType] = None
|
||||
@@ -61,7 +63,7 @@ except ImportError as e:
|
||||
have_powermon = False
|
||||
powermon_exception = e
|
||||
meter = None
|
||||
from meshtastic.protobuf import admin_pb2, channel_pb2, config_pb2, portnums_pb2, mesh_pb2
|
||||
from meshtastic.protobuf import admin_pb2, channel_pb2, clientonly_pb2, config_pb2, portnums_pb2, mesh_pb2
|
||||
from meshtastic.version import get_active_version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -742,115 +744,105 @@ def onConnected(interface):
|
||||
printConfig(node.moduleConfig)
|
||||
|
||||
if args.configure:
|
||||
with open(args.configure[0], encoding="utf8") as file:
|
||||
configuration = yaml.safe_load(file)
|
||||
closeNow = True
|
||||
if args.dest != BROADCAST_ADDR:
|
||||
print("Configuring remote nodes is not supported.")
|
||||
return
|
||||
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).beginSettingsTransaction()
|
||||
filename = args.configure[0]
|
||||
fmt = getattr(args, "export_format", "auto")
|
||||
|
||||
if "owner" in configuration:
|
||||
# Validate owner name before setting
|
||||
owner_name = str(configuration["owner"]).strip()
|
||||
if not owner_name:
|
||||
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
|
||||
print(f"Setting device owner to {configuration['owner']}")
|
||||
waitForAckNak = True
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(configuration["owner"])
|
||||
time.sleep(0.5)
|
||||
def _seed_config(section: str, is_module: bool) -> Optional[Any]:
|
||||
"""Return the current local-node config section, if present."""
|
||||
config_obj = (
|
||||
interface.localNode.moduleConfig
|
||||
if is_module
|
||||
else interface.localNode.localConfig
|
||||
)
|
||||
return getattr(config_obj, section) if config_obj.HasField(section) else None
|
||||
|
||||
if "owner_short" in configuration:
|
||||
# Validate owner short name before setting
|
||||
owner_short_name = str(configuration["owner_short"]).strip()
|
||||
if not owner_short_name:
|
||||
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
|
||||
print(
|
||||
f"Setting device owner short to {configuration['owner_short']}"
|
||||
)
|
||||
waitForAckNak = True
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
||||
long_name=None, short_name=configuration["owner_short"]
|
||||
)
|
||||
time.sleep(0.5)
|
||||
profile = _read_profile(filename, fmt, seed_fn=_seed_config)
|
||||
|
||||
if "ownerShort" in configuration:
|
||||
# Validate owner short name before setting
|
||||
owner_short_name = str(configuration["ownerShort"]).strip()
|
||||
if not owner_short_name:
|
||||
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
|
||||
print(
|
||||
f"Setting device owner short to {configuration['ownerShort']}"
|
||||
)
|
||||
waitForAckNak = True
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
||||
long_name=None, short_name=configuration["ownerShort"]
|
||||
)
|
||||
time.sleep(0.5)
|
||||
closeNow = True
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).beginSettingsTransaction()
|
||||
|
||||
if "channel_url" in configuration:
|
||||
print("Setting channel url to", configuration["channel_url"])
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channel_url"])
|
||||
time.sleep(0.5)
|
||||
# Owner: combine long_name and short_name into a single setOwner call.
|
||||
# NOTE: is_licensed and is_unmessagable are not yet in DeviceProfile;
|
||||
# ref: https://github.com/meshtastic/protobufs/pull/971
|
||||
long_name = str(profile.long_name).strip() if profile.long_name else None
|
||||
short_name = str(profile.short_name).strip() if profile.short_name else None
|
||||
|
||||
if "channelUrl" in configuration:
|
||||
print("Setting channel url to", configuration["channelUrl"])
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channelUrl"])
|
||||
time.sleep(0.5)
|
||||
if long_name is not None and not long_name:
|
||||
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
|
||||
if short_name is not None and not short_name:
|
||||
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
|
||||
|
||||
if "canned_messages" in configuration:
|
||||
print("Setting canned message messages to", configuration["canned_messages"])
|
||||
interface.getNode(args.dest, **getNode_kwargs).set_canned_message(configuration["canned_messages"])
|
||||
time.sleep(0.5)
|
||||
if long_name or short_name:
|
||||
if long_name and short_name:
|
||||
print(f"Setting device owner to {long_name} and short name to {short_name}")
|
||||
elif long_name:
|
||||
print(f"Setting device owner to {long_name}")
|
||||
else:
|
||||
print(f"Setting device owner short to {short_name}")
|
||||
waitForAckNak = True
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
||||
long_name=long_name, short_name=short_name
|
||||
)
|
||||
time.sleep(0.5)
|
||||
|
||||
if "ringtone" in configuration:
|
||||
print("Setting ringtone to", configuration["ringtone"])
|
||||
interface.getNode(args.dest, **getNode_kwargs).set_ringtone(configuration["ringtone"])
|
||||
time.sleep(0.5)
|
||||
if profile.channel_url:
|
||||
print(f"Setting channel url to {profile.channel_url}")
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(profile.channel_url)
|
||||
time.sleep(0.5)
|
||||
|
||||
if "location" in configuration:
|
||||
alt = 0
|
||||
lat = 0.0
|
||||
lon = 0.0
|
||||
localConfig = interface.localNode.localConfig
|
||||
if profile.canned_messages:
|
||||
print(f"Setting canned message messages to {profile.canned_messages}")
|
||||
interface.getNode(args.dest, **getNode_kwargs).set_canned_message(profile.canned_messages)
|
||||
time.sleep(0.5)
|
||||
|
||||
if "alt" in configuration["location"]:
|
||||
alt = int(configuration["location"]["alt"] or 0)
|
||||
print(f"Fixing altitude at {alt} meters")
|
||||
if "lat" in configuration["location"]:
|
||||
lat = float(configuration["location"]["lat"] or 0)
|
||||
print(f"Fixing latitude at {lat} degrees")
|
||||
if "lon" in configuration["location"]:
|
||||
lon = float(configuration["location"]["lon"] or 0)
|
||||
print(f"Fixing longitude at {lon} degrees")
|
||||
if profile.ringtone:
|
||||
print(f"Setting ringtone to {profile.ringtone}")
|
||||
interface.getNode(args.dest, **getNode_kwargs).set_ringtone(profile.ringtone)
|
||||
time.sleep(0.5)
|
||||
|
||||
if profile.HasField("fixed_position"):
|
||||
# Only send the admin message when the position config
|
||||
# explicitly opts into fixed_position. The admin message
|
||||
# unconditionally enables the flag on the device, so we
|
||||
# require an explicit opt-in from the profile's config.
|
||||
if (
|
||||
profile.HasField("config")
|
||||
and profile.config.HasField("position")
|
||||
and profile.config.position.fixed_position
|
||||
):
|
||||
pos = profile.fixed_position
|
||||
lat = float(pos.latitude_i * Decimal("1e-7")) if pos.latitude_i else 0.0
|
||||
lon = float(pos.longitude_i * Decimal("1e-7")) if pos.longitude_i else 0.0
|
||||
alt = pos.altitude if pos.altitude else 0
|
||||
print(f"Fixing altitude at {alt} meters")
|
||||
print(f"Fixing latitude at {lat} degrees")
|
||||
print(f"Fixing longitude at {lon} degrees")
|
||||
print("Setting device position")
|
||||
interface.localNode.setFixedPosition(lat, lon, alt)
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setFixedPosition(lat, lon, alt)
|
||||
time.sleep(0.5)
|
||||
|
||||
if "config" in configuration:
|
||||
localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
|
||||
for section in configuration["config"]:
|
||||
traverseConfig(
|
||||
section, configuration["config"][section], localConfig
|
||||
)
|
||||
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
|
||||
meshtastic.util.camel_to_snake(section)
|
||||
)
|
||||
if profile.HasField("config"):
|
||||
localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
|
||||
for field in profile.config.DESCRIPTOR.fields:
|
||||
if field.message_type is not None and profile.config.HasField(field.name):
|
||||
getattr(localConfig, field.name).CopyFrom(getattr(profile.config, field.name))
|
||||
interface.getNode(args.dest, **getNode_kwargs).writeConfig(field.name)
|
||||
time.sleep(0.5)
|
||||
|
||||
if "module_config" in configuration:
|
||||
moduleConfig = interface.getNode(args.dest, **getNode_kwargs).moduleConfig
|
||||
for section in configuration["module_config"]:
|
||||
traverseConfig(
|
||||
section,
|
||||
configuration["module_config"][section],
|
||||
moduleConfig,
|
||||
)
|
||||
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
|
||||
meshtastic.util.camel_to_snake(section)
|
||||
)
|
||||
if profile.HasField("module_config"):
|
||||
moduleConfig = interface.getNode(args.dest, **getNode_kwargs).moduleConfig
|
||||
for field in profile.module_config.DESCRIPTOR.fields:
|
||||
if field.message_type is not None and profile.module_config.HasField(field.name):
|
||||
getattr(moduleConfig, field.name).CopyFrom(getattr(profile.module_config, field.name))
|
||||
interface.getNode(args.dest, **getNode_kwargs).writeConfig(field.name)
|
||||
time.sleep(0.5)
|
||||
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
|
||||
print("Writing modified configuration to device")
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
|
||||
print("Writing modified configuration to device")
|
||||
|
||||
if args.export_config:
|
||||
if args.dest != BROADCAST_ADDR:
|
||||
@@ -858,18 +850,40 @@ def onConnected(interface):
|
||||
return
|
||||
|
||||
closeNow = True
|
||||
config_txt = export_config(interface)
|
||||
|
||||
if args.export_config == "-":
|
||||
# Output to stdout (preserves legacy use of `> file.yaml`)
|
||||
print(config_txt)
|
||||
is_binary = False
|
||||
fmt = getattr(args, "export_format", "auto")
|
||||
if fmt in ("binary", "protobuf"):
|
||||
is_binary = True
|
||||
elif fmt == "yaml":
|
||||
is_binary = False
|
||||
else:
|
||||
try:
|
||||
with open(args.export_config, "w", encoding="utf-8") as f:
|
||||
f.write(config_txt)
|
||||
print(f"Exported configuration to {args.export_config}")
|
||||
except Exception as e:
|
||||
meshtastic.util.our_exit(f"ERROR: Failed to write config file: {e}")
|
||||
is_binary = args.export_config.endswith(".cfg")
|
||||
|
||||
if is_binary:
|
||||
config_bytes = export_profile(interface)
|
||||
if args.export_config == "-":
|
||||
sys.stdout.buffer.write(config_bytes)
|
||||
else:
|
||||
try:
|
||||
with open(args.export_config, "wb") as f:
|
||||
f.write(config_bytes)
|
||||
print(f"Exported profile to {args.export_config}")
|
||||
except Exception as e:
|
||||
meshtastic.util.our_exit(f"ERROR: Failed to write profile file: {e}")
|
||||
else:
|
||||
config_txt = export_config(interface)
|
||||
|
||||
if args.export_config == "-":
|
||||
# Output to stdout (preserves legacy use of `> file.yaml`)
|
||||
print(config_txt)
|
||||
else:
|
||||
try:
|
||||
with open(args.export_config, "w", encoding="utf-8") as f:
|
||||
f.write(config_txt)
|
||||
print(f"Exported configuration to {args.export_config}")
|
||||
except Exception as e:
|
||||
meshtastic.util.our_exit(f"ERROR: Failed to write config file: {e}")
|
||||
|
||||
if args.ch_set_url:
|
||||
closeNow = True
|
||||
@@ -955,9 +969,15 @@ def onConnected(interface):
|
||||
if args.ch_longslow:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.LONG_SLOW)
|
||||
|
||||
if args.ch_longmod:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.LONG_MODERATE)
|
||||
|
||||
if args.ch_longfast:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.LONG_FAST)
|
||||
|
||||
if args.ch_longturbo:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.LONG_TURBO)
|
||||
|
||||
if args.ch_medslow:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.MEDIUM_SLOW)
|
||||
|
||||
@@ -970,6 +990,9 @@ def onConnected(interface):
|
||||
if args.ch_shortfast:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.SHORT_FAST)
|
||||
|
||||
if args.ch_shortturbo:
|
||||
setSimpleConfig(config_pb2.Config.LoRaConfig.ModemPreset.SHORT_TURBO)
|
||||
|
||||
if args.ch_set or args.ch_enable or args.ch_disable:
|
||||
closeNow = True
|
||||
|
||||
@@ -1263,7 +1286,7 @@ def export_config(interface) -> str:
|
||||
lat = None
|
||||
lon = None
|
||||
alt = None
|
||||
if pos:
|
||||
if pos and interface.localNode.localConfig.position.fixed_position:
|
||||
lat = pos.get("latitude")
|
||||
lon = pos.get("longitude")
|
||||
alt = pos.get("altitude")
|
||||
@@ -1307,7 +1330,7 @@ def export_config(interface) -> str:
|
||||
for i in range(len(prefs[pref]['adminKey'])):
|
||||
prefs[pref]['adminKey'][i] = 'base64:' + prefs[pref]['adminKey'][i]
|
||||
if mt_config.camel_case:
|
||||
configObj["config"] = config #Identical command here and 2 lines below?
|
||||
configObj["config"] = prefs
|
||||
else:
|
||||
configObj["config"] = config
|
||||
|
||||
@@ -1332,6 +1355,162 @@ def export_config(interface) -> str:
|
||||
config_txt += yaml.dump(configObj)
|
||||
return config_txt
|
||||
|
||||
def _set_if_populated(profile, field_name, value):
|
||||
if value is None:
|
||||
return
|
||||
val = str(value).strip()
|
||||
if val:
|
||||
setattr(profile, field_name, val)
|
||||
|
||||
|
||||
def _profile_from_yaml(
|
||||
configuration: dict,
|
||||
seed_fn: Optional[Callable[[str, bool], Optional[Any]]] = None,
|
||||
) -> clientonly_pb2.DeviceProfile:
|
||||
"""Convert a YAML config dict to a DeviceProfile protobuf for uniform import.
|
||||
|
||||
If seed_fn is provided, it is called for each config/module_config section
|
||||
before applying YAML values. It should return the current protobuf message
|
||||
for that section (or None) so that unmentioned fields are preserved.
|
||||
"""
|
||||
profile = clientonly_pb2.DeviceProfile()
|
||||
if "owner" in configuration:
|
||||
_set_if_populated(profile, "long_name", configuration["owner"])
|
||||
if "owner_short" in configuration:
|
||||
_set_if_populated(profile, "short_name", configuration["owner_short"])
|
||||
elif "ownerShort" in configuration:
|
||||
_set_if_populated(profile, "short_name", configuration["ownerShort"])
|
||||
if "channel_url" in configuration:
|
||||
_set_if_populated(profile, "channel_url", configuration["channel_url"])
|
||||
elif "channelUrl" in configuration:
|
||||
_set_if_populated(profile, "channel_url", configuration["channelUrl"])
|
||||
if "canned_messages" in configuration:
|
||||
_set_if_populated(profile, "canned_messages", configuration["canned_messages"])
|
||||
if "ringtone" in configuration:
|
||||
_set_if_populated(profile, "ringtone", configuration["ringtone"])
|
||||
loc = configuration.get("location")
|
||||
if loc:
|
||||
lat = float(loc.get("lat", 0) or 0)
|
||||
lon = float(loc.get("lon", 0) or 0)
|
||||
alt = int(loc.get("alt", 0) or 0)
|
||||
if lat or lon or alt:
|
||||
profile.fixed_position.latitude_i = int(Decimal(str(lat)) * Decimal("1e7"))
|
||||
profile.fixed_position.longitude_i = int(Decimal(str(lon)) * Decimal("1e7"))
|
||||
profile.fixed_position.altitude = alt
|
||||
if "config" in configuration:
|
||||
for section in configuration["config"]:
|
||||
section_snake = meshtastic.util.camel_to_snake(section)
|
||||
if seed_fn is not None:
|
||||
seeded = seed_fn(section_snake, False)
|
||||
if seeded is not None:
|
||||
getattr(profile.config, section_snake).CopyFrom(seeded)
|
||||
traverseConfig(section, configuration["config"][section], profile.config)
|
||||
if "module_config" in configuration:
|
||||
for section in configuration["module_config"]:
|
||||
section_snake = meshtastic.util.camel_to_snake(section)
|
||||
if seed_fn is not None:
|
||||
seeded = seed_fn(section_snake, True)
|
||||
if seeded is not None:
|
||||
getattr(profile.module_config, section_snake).CopyFrom(seeded)
|
||||
traverseConfig(section, configuration["module_config"][section], profile.module_config)
|
||||
return profile
|
||||
|
||||
|
||||
def _read_profile(
|
||||
filename: str,
|
||||
fmt: str,
|
||||
seed_fn: Optional[Callable[[str, bool], Optional[Any]]] = None,
|
||||
) -> clientonly_pb2.DeviceProfile:
|
||||
"""Read a config file and return a DeviceProfile, autodetecting format by content."""
|
||||
with open(filename, "rb") as f:
|
||||
raw = f.read()
|
||||
|
||||
if fmt in ("binary", "protobuf"):
|
||||
try:
|
||||
return _parse_profile_bytes(raw)
|
||||
except Exception as e:
|
||||
meshtastic.util.our_exit(
|
||||
f"ERROR: {filename} is not a valid DeviceProfile (.cfg) file: {e}"
|
||||
)
|
||||
|
||||
if fmt == "yaml":
|
||||
try:
|
||||
configuration = yaml.safe_load(raw.decode("utf8"))
|
||||
except (UnicodeDecodeError, yaml.YAMLError) as e:
|
||||
meshtastic.util.our_exit(
|
||||
f"ERROR: {filename} is not a valid YAML config (expected UTF-8 YAML): {e}"
|
||||
)
|
||||
if not isinstance(configuration, dict):
|
||||
meshtastic.util.our_exit(
|
||||
f"ERROR: {filename} is not a valid YAML config (expected a mapping)."
|
||||
)
|
||||
return _profile_from_yaml(configuration, seed_fn=seed_fn)
|
||||
|
||||
# Auto: try YAML first (text files), fall back to protobuf (binary files).
|
||||
try:
|
||||
configuration = yaml.safe_load(raw.decode("utf8"))
|
||||
if isinstance(configuration, dict):
|
||||
return _profile_from_yaml(configuration, seed_fn=seed_fn)
|
||||
except (UnicodeDecodeError, yaml.YAMLError):
|
||||
pass
|
||||
|
||||
try:
|
||||
return _parse_profile_bytes(raw)
|
||||
except Exception as e:
|
||||
meshtastic.util.our_exit(
|
||||
f"ERROR: {filename} is not a valid YAML config or DeviceProfile (.cfg) file: {e}"
|
||||
)
|
||||
|
||||
|
||||
def _parse_profile_bytes(raw: bytes) -> clientonly_pb2.DeviceProfile:
|
||||
"""Parse raw bytes as a DeviceProfile protobuf, raising on failure."""
|
||||
profile = clientonly_pb2.DeviceProfile()
|
||||
profile.ParseFromString(raw)
|
||||
return profile
|
||||
|
||||
|
||||
def export_profile(interface) -> bytes:
|
||||
"""used in --export-config for binary .cfg files"""
|
||||
profile = clientonly_pb2.DeviceProfile()
|
||||
|
||||
owner = interface.getLongName()
|
||||
owner_short = interface.getShortName()
|
||||
channel_url = interface.localNode.getURL()
|
||||
myinfo = interface.getMyNodeInfo()
|
||||
canned_messages = interface.getCannedMessage()
|
||||
ringtone = interface.getRingtone()
|
||||
|
||||
if owner:
|
||||
profile.long_name = owner
|
||||
if owner_short:
|
||||
profile.short_name = owner_short
|
||||
if channel_url:
|
||||
profile.channel_url = channel_url
|
||||
if canned_messages:
|
||||
profile.canned_messages = canned_messages
|
||||
if ringtone:
|
||||
profile.ringtone = ringtone
|
||||
|
||||
profile.config.CopyFrom(interface.localNode.localConfig)
|
||||
profile.module_config.CopyFrom(interface.localNode.moduleConfig)
|
||||
|
||||
if interface.localNode.localConfig.position.fixed_position:
|
||||
pos = myinfo.get("position")
|
||||
if pos:
|
||||
lat = pos.get("latitude")
|
||||
lon = pos.get("longitude")
|
||||
alt = pos.get("altitude")
|
||||
|
||||
if lat or lon or alt:
|
||||
if lat:
|
||||
profile.fixed_position.latitude_i = int(Decimal(str(lat)) * Decimal("1e7"))
|
||||
if lon:
|
||||
profile.fixed_position.longitude_i = int(Decimal(str(lon)) * Decimal("1e7"))
|
||||
if alt:
|
||||
profile.fixed_position.altitude = int(alt)
|
||||
|
||||
return profile.SerializeToString()
|
||||
|
||||
|
||||
def create_power_meter():
|
||||
"""Setup the power meter."""
|
||||
@@ -1411,6 +1590,12 @@ def common():
|
||||
if not os.path.isfile(args.ota_update):
|
||||
meshtastic.util.our_exit(f"Error: OTA firmware file not found: {args.ota_update}", 1)
|
||||
|
||||
# OTA (WiFi/BLE) only needs the local node to send the admin request and then
|
||||
# streams the firmware directly; it never reads the node DB. Skip fetching it so a
|
||||
# large node DB dump can't stall/close the connection before the OTA request lands.
|
||||
if getattr(args, "ota_update", None) or getattr(args, "reboot_ota", False):
|
||||
args.no_nodes = True
|
||||
|
||||
if have_powermon:
|
||||
create_power_meter()
|
||||
|
||||
@@ -1702,7 +1887,9 @@ def addImportExportArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar
|
||||
|
||||
group.add_argument(
|
||||
"--configure",
|
||||
help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.",
|
||||
"--import-config",
|
||||
dest="configure",
|
||||
help="Specify a path to a configuration file to import. Autodetects format (yaml or binary protobuf).",
|
||||
action="append",
|
||||
)
|
||||
group.add_argument(
|
||||
@@ -1710,7 +1897,13 @@ def addImportExportArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar
|
||||
nargs="?",
|
||||
const="-", # default to "-" if no value provided
|
||||
metavar="FILE",
|
||||
help="Export device config as YAML (to stdout if no file given)"
|
||||
help="Export device config (to stdout if no file given). Autodetects format by extension if possible."
|
||||
)
|
||||
group.add_argument(
|
||||
"--export-format",
|
||||
choices=["auto", "yaml", "binary", "protobuf"],
|
||||
default="auto",
|
||||
help="Format for export or import. 'auto' uses file extension or contents. 'binary' or 'protobuf' forces binary format. 'yaml' forces yaml."
|
||||
)
|
||||
return parser
|
||||
|
||||
@@ -1782,43 +1975,61 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
|
||||
group.add_argument(
|
||||
"--ch-vlongslow",
|
||||
help="Change to the very long-range and slow modem preset",
|
||||
help="Change to the VERY_LONG_SLOW modem preset. Deprecated since 2.5 firmware.",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-longslow",
|
||||
help="Change to the long-range and slow modem preset",
|
||||
help="Change to the LONG_SLOW modem preset. Deprecated since 2.7 firmware.",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-longmod", "--ch-longmoderate",
|
||||
help="Change to the LONG_MODERATE modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-longfast",
|
||||
help="Change to the long-range and fast modem preset",
|
||||
help="Change to the LONG_FAST modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-longturbo",
|
||||
help="Change to the LONG_TURBO preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-medslow",
|
||||
help="Change to the med-range and slow modem preset",
|
||||
help="Change to the MEDIUM_SLOW modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-medfast",
|
||||
help="Change to the med-range and fast modem preset",
|
||||
help="Change to the MEDIUM_FAST modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-shortslow",
|
||||
help="Change to the short-range and slow modem preset",
|
||||
help="Change to the SHORT_SLOW modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-shortfast",
|
||||
help="Change to the short-range and fast modem preset",
|
||||
help="Change to the SHORT_FAST modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-shortturbo",
|
||||
help="Change to the SHORT_TURBO modem preset",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
|
||||
+12
-2
@@ -287,12 +287,22 @@ class Node:
|
||||
# for sending admin channels will also change
|
||||
adminIndex = self.iface.localNode._getAdminChannelIndex()
|
||||
|
||||
# Snapshot serialized channel payloads from channelIndex onward so we
|
||||
# can avoid writing slots whose protobuf content did not change after
|
||||
# the shift. Use bytes (not message objects), because _fixupChannels()
|
||||
# mutates message fields in-place.
|
||||
old_channels = [
|
||||
self.channels[i].SerializeToString()
|
||||
for i in range(channelIndex, len(self.channels))
|
||||
]
|
||||
|
||||
self.channels.pop(channelIndex)
|
||||
self._fixupChannels() # expand back to 8 channels
|
||||
|
||||
index = channelIndex
|
||||
while index < 8:
|
||||
self.writeChannel(index, adminIndex=adminIndex)
|
||||
for old_ch in old_channels:
|
||||
if self.channels[index].SerializeToString() != old_ch:
|
||||
self.writeChannel(index, adminIndex=adminIndex)
|
||||
index += 1
|
||||
|
||||
# if we are updating the local node, we might end up
|
||||
|
||||
Generated
+37
-29
File diff suppressed because one or more lines are too long.
Generated
+236
-6
@@ -236,6 +236,10 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
TAK module config
|
||||
"""
|
||||
MESHBEACON_CONFIG: AdminMessage._ModuleConfigType.ValueType # 16
|
||||
"""
|
||||
Mesh Beacon module config
|
||||
"""
|
||||
|
||||
class ModuleConfigType(_ModuleConfigType, metaclass=_ModuleConfigTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -306,6 +310,10 @@ class AdminMessage(google.protobuf.message.Message):
|
||||
"""
|
||||
TAK module config
|
||||
"""
|
||||
MESHBEACON_CONFIG: AdminMessage.ModuleConfigType.ValueType # 16
|
||||
"""
|
||||
Mesh Beacon module config
|
||||
"""
|
||||
|
||||
class _BackupLocation:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -851,6 +859,8 @@ class LockdownAuth(google.protobuf.message.Message):
|
||||
BOOTS_REMAINING_FIELD_NUMBER: builtins.int
|
||||
VALID_UNTIL_EPOCH_FIELD_NUMBER: builtins.int
|
||||
LOCK_NOW_FIELD_NUMBER: builtins.int
|
||||
MAX_SESSION_SECONDS_FIELD_NUMBER: builtins.int
|
||||
DISABLE_FIELD_NUMBER: builtins.int
|
||||
passphrase: builtins.bytes
|
||||
"""
|
||||
Passphrase bytes (1-32). Empty when lock_now is true.
|
||||
@@ -876,6 +886,67 @@ class LockdownAuth(google.protobuf.message.Message):
|
||||
connection-level admin authorization, and reboot the device into
|
||||
the locked state. Always honoured regardless of current lock state.
|
||||
"""
|
||||
max_session_seconds: builtins.int
|
||||
"""
|
||||
Optional per-boot uptime cap on the unlocked session, in seconds.
|
||||
0 = unlimited (token-only enforcement, suitable for unattended
|
||||
tower / infrastructure nodes).
|
||||
|
||||
When non-zero, the firmware arms an uptime timer at unlock. On
|
||||
each expiry, while there is still boot-count budget, the firmware
|
||||
decrements the on-flash boot count in place, revokes per-
|
||||
connection admin auth (clients must re-authenticate to see
|
||||
content), re-engages the screen lock, and re-arms the timer
|
||||
without rebooting. Mesh routing keeps running across session
|
||||
boundaries; only when the boot-count budget reaches zero does
|
||||
the device hard-lock and reboot.
|
||||
|
||||
Total exposure ceiling = ((resolved boot count) + 1) * max_session_seconds.
|
||||
The +1 accounts for the initial passphrase-unlocked session
|
||||
itself, since boots_remaining is the number of subsequent
|
||||
session rolls (each consuming one boot from the rollback ledger).
|
||||
The resolved boot count is the value the firmware writes into the
|
||||
token at unlock time: the client-supplied boots_remaining when
|
||||
non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS).
|
||||
Note that boots_remaining == 0 in this message means "use firmware
|
||||
default", NOT "zero boots" — a client computing the ceiling for
|
||||
display should mirror that resolution rather than multiplying the
|
||||
raw request value.
|
||||
|
||||
The cap is persisted in the token, so it survives token-based
|
||||
auto-unlock across reboots. Explicit operator Lock Now still
|
||||
deletes the token and forces passphrase re-entry.
|
||||
|
||||
Uses millis() (CPU uptime), not wall-clock time, so the cap is
|
||||
immune to GPS spoofing, RTC backup-battery removal, and Faraday
|
||||
cage isolation — none of those move the uptime counter. The only
|
||||
way to reset the session clock is a reboot, which costs a boot
|
||||
from the on-flash, HMAC-bound counter.
|
||||
"""
|
||||
disable: builtins.bool
|
||||
"""
|
||||
Disable lockdown mode. Requires a valid passphrase in the same
|
||||
message (the device must prove the operator owns it before
|
||||
reverting at-rest encryption). On success the firmware decrypts
|
||||
every stored config / channel / nodedb file back to plaintext,
|
||||
removes the wrapped DEK, unlock token, monotonic-counter, and
|
||||
backoff files, and reboots out of lockdown.
|
||||
|
||||
This is the inverse of the provision/unlock path: it is how the
|
||||
client app's "lockdown mode" toggle returns a device to normal
|
||||
operation.
|
||||
|
||||
NOT reversed by this operation: APPROTECT. Once the debug port
|
||||
lockout has been burned (on silicon where it is effective) it is
|
||||
permanent — disabling lockdown decrypts your data and removes the
|
||||
access gates, but the SWD/JTAG port stays locked for the life of
|
||||
the device (recoverable only via a full chip erase over a debug
|
||||
probe, which destroys all data). Clients should make this
|
||||
irreversibility clear at the moment lockdown is first enabled.
|
||||
|
||||
When true the passphrase field is still required; boots_remaining,
|
||||
valid_until_epoch, max_session_seconds, and lock_now are ignored.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -883,8 +954,10 @@ class LockdownAuth(google.protobuf.message.Message):
|
||||
boots_remaining: builtins.int = ...,
|
||||
valid_until_epoch: builtins.int = ...,
|
||||
lock_now: builtins.bool = ...,
|
||||
max_session_seconds: builtins.int = ...,
|
||||
disable: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["boots_remaining", b"boots_remaining", "lock_now", b"lock_now", "passphrase", b"passphrase", "valid_until_epoch", b"valid_until_epoch"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["boots_remaining", b"boots_remaining", "disable", b"disable", "lock_now", b"lock_now", "max_session_seconds", b"max_session_seconds", "passphrase", b"passphrase", "valid_until_epoch", b"valid_until_epoch"]) -> None: ...
|
||||
|
||||
global___LockdownAuth = LockdownAuth
|
||||
|
||||
@@ -900,6 +973,7 @@ class HamParameters(google.protobuf.message.Message):
|
||||
TX_POWER_FIELD_NUMBER: builtins.int
|
||||
FREQUENCY_FIELD_NUMBER: builtins.int
|
||||
SHORT_NAME_FIELD_NUMBER: builtins.int
|
||||
LONG_NAME_FIELD_NUMBER: builtins.int
|
||||
call_sign: builtins.str
|
||||
"""
|
||||
Amateur radio call sign, eg. KD2ABC
|
||||
@@ -918,6 +992,11 @@ class HamParameters(google.protobuf.message.Message):
|
||||
"""
|
||||
Optional short name of user
|
||||
"""
|
||||
long_name: builtins.str
|
||||
"""
|
||||
Optional long name of user
|
||||
Appended to callsign
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -925,8 +1004,9 @@ class HamParameters(google.protobuf.message.Message):
|
||||
tx_power: builtins.int = ...,
|
||||
frequency: builtins.float = ...,
|
||||
short_name: builtins.str = ...,
|
||||
long_name: builtins.str = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["call_sign", b"call_sign", "frequency", b"frequency", "short_name", b"short_name", "tx_power", b"tx_power"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["call_sign", b"call_sign", "frequency", b"frequency", "long_name", b"long_name", "short_name", b"short_name", "tx_power", b"tx_power"]) -> None: ...
|
||||
|
||||
global___HamParameters = HamParameters
|
||||
|
||||
@@ -1087,6 +1167,9 @@ class SensorConfig(google.protobuf.message.Message):
|
||||
SEN5X_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SCD30_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SHTXX_CONFIG_FIELD_NUMBER: builtins.int
|
||||
DS248X_CONFIG_FIELD_NUMBER: builtins.int
|
||||
SEN6X_CONFIG_FIELD_NUMBER: builtins.int
|
||||
AS3935_CONFIG_FIELD_NUMBER: builtins.int
|
||||
@property
|
||||
def scd4x_config(self) -> global___SCD4X_config:
|
||||
"""
|
||||
@@ -1111,6 +1194,24 @@ class SensorConfig(google.protobuf.message.Message):
|
||||
SHTXX temperature and relative humidity sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def ds248x_config(self) -> global___DS248X_config:
|
||||
"""
|
||||
DS248X-800 temperature sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def sen6x_config(self) -> global___SEN6X_config:
|
||||
"""
|
||||
SEN6X PM/RHT/VOC/NOx/CO2/HCHO Sensor configuration
|
||||
"""
|
||||
|
||||
@property
|
||||
def as3935_config(self) -> global___AS3935_config:
|
||||
"""
|
||||
AS3935 lightning sensor configuration
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1118,9 +1219,12 @@ class SensorConfig(google.protobuf.message.Message):
|
||||
sen5x_config: global___SEN5X_config | None = ...,
|
||||
scd30_config: global___SCD30_config | None = ...,
|
||||
shtxx_config: global___SHTXX_config | None = ...,
|
||||
ds248x_config: global___DS248X_config | None = ...,
|
||||
sen6x_config: global___SEN6X_config | None = ...,
|
||||
as3935_config: global___AS3935_config | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["scd30_config", b"scd30_config", "scd4x_config", b"scd4x_config", "sen5x_config", b"sen5x_config", "shtxx_config", b"shtxx_config"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["scd30_config", b"scd30_config", "scd4x_config", b"scd4x_config", "sen5x_config", b"sen5x_config", "shtxx_config", b"shtxx_config"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["as3935_config", b"as3935_config", "ds248x_config", b"ds248x_config", "scd30_config", b"scd30_config", "scd4x_config", b"scd4x_config", "sen5x_config", b"sen5x_config", "sen6x_config", b"sen6x_config", "shtxx_config", b"shtxx_config"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["as3935_config", b"as3935_config", "ds248x_config", b"ds248x_config", "scd30_config", b"scd30_config", "scd4x_config", b"scd4x_config", "sen5x_config", b"sen5x_config", "sen6x_config", b"sen6x_config", "shtxx_config", b"shtxx_config"]) -> None: ...
|
||||
|
||||
global___SensorConfig = SensorConfig
|
||||
|
||||
@@ -1199,6 +1303,7 @@ class SEN5X_config(google.protobuf.message.Message):
|
||||
|
||||
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
SET_ONE_SHOT_MODE_FIELD_NUMBER: builtins.int
|
||||
START_FAN_CLEANING_FIELD_NUMBER: builtins.int
|
||||
set_temperature: builtins.float
|
||||
"""
|
||||
Reference temperature in degC
|
||||
@@ -1207,21 +1312,105 @@ class SEN5X_config(google.protobuf.message.Message):
|
||||
"""
|
||||
One-shot mode (true for low power - one-shot mode, false for normal - continuous mode)
|
||||
"""
|
||||
start_fan_cleaning: builtins.bool
|
||||
"""
|
||||
Trigger a fan cleaning cycle
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_temperature: builtins.float | None = ...,
|
||||
set_one_shot_mode: builtins.bool | None = ...,
|
||||
start_fan_cleaning: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode", "_set_temperature", b"_set_temperature", "set_one_shot_mode", b"set_one_shot_mode", "set_temperature", b"set_temperature"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode", "_set_temperature", b"_set_temperature", "set_one_shot_mode", b"set_one_shot_mode", "set_temperature", b"set_temperature"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode", "_set_temperature", b"_set_temperature", "_start_fan_cleaning", b"_start_fan_cleaning", "set_one_shot_mode", b"set_one_shot_mode", "set_temperature", b"set_temperature", "start_fan_cleaning", b"start_fan_cleaning"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode", "_set_temperature", b"_set_temperature", "_start_fan_cleaning", b"_start_fan_cleaning", "set_one_shot_mode", b"set_one_shot_mode", "set_temperature", b"set_temperature", "start_fan_cleaning", b"start_fan_cleaning"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode"]) -> typing.Literal["set_one_shot_mode"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_temperature", b"_set_temperature"]) -> typing.Literal["set_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_start_fan_cleaning", b"_start_fan_cleaning"]) -> typing.Literal["start_fan_cleaning"] | None: ...
|
||||
|
||||
global___SEN5X_config = SEN5X_config
|
||||
|
||||
@typing.final
|
||||
class SEN6X_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
SET_ONE_SHOT_MODE_FIELD_NUMBER: builtins.int
|
||||
START_FAN_CLEANING_FIELD_NUMBER: builtins.int
|
||||
SET_ASC_FIELD_NUMBER: builtins.int
|
||||
SET_TARGET_CO2_CONC_FIELD_NUMBER: builtins.int
|
||||
SET_ALTITUDE_FIELD_NUMBER: builtins.int
|
||||
SET_AMBIENT_PRESSURE_FIELD_NUMBER: builtins.int
|
||||
FACTORY_RESET_FIELD_NUMBER: builtins.int
|
||||
set_temperature: builtins.float
|
||||
"""
|
||||
Reference temperature in degC
|
||||
"""
|
||||
set_one_shot_mode: builtins.bool
|
||||
"""
|
||||
One-shot mode (true for low power - one-shot mode, false for normal - continuous mode)
|
||||
"""
|
||||
start_fan_cleaning: builtins.bool
|
||||
"""
|
||||
Trigger a fan cleaning cycle
|
||||
"""
|
||||
set_asc: builtins.bool
|
||||
"""
|
||||
Set Automatic self-calibration enabled (CO2-capable variants only: SEN63C, SEN66, SEN69C)
|
||||
"""
|
||||
set_target_co2_conc: builtins.int
|
||||
"""
|
||||
Recalibration target CO2 concentration in ppm (FRC only), CO2-capable variants only
|
||||
"""
|
||||
set_altitude: builtins.int
|
||||
"""
|
||||
Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure), CO2-capable variants only
|
||||
"""
|
||||
set_ambient_pressure: builtins.int
|
||||
"""
|
||||
Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude), CO2-capable variants only
|
||||
"""
|
||||
factory_reset: builtins.bool
|
||||
"""
|
||||
Perform a factory reset of the CO2 sensor's calibration, CO2-capable variants only
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_temperature: builtins.float | None = ...,
|
||||
set_one_shot_mode: builtins.bool | None = ...,
|
||||
start_fan_cleaning: builtins.bool | None = ...,
|
||||
set_asc: builtins.bool | None = ...,
|
||||
set_target_co2_conc: builtins.int | None = ...,
|
||||
set_altitude: builtins.int | None = ...,
|
||||
set_ambient_pressure: builtins.int | None = ...,
|
||||
factory_reset: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_factory_reset", b"_factory_reset", "_set_altitude", b"_set_altitude", "_set_ambient_pressure", b"_set_ambient_pressure", "_set_asc", b"_set_asc", "_set_one_shot_mode", b"_set_one_shot_mode", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "_start_fan_cleaning", b"_start_fan_cleaning", "factory_reset", b"factory_reset", "set_altitude", b"set_altitude", "set_ambient_pressure", b"set_ambient_pressure", "set_asc", b"set_asc", "set_one_shot_mode", b"set_one_shot_mode", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature", "start_fan_cleaning", b"start_fan_cleaning"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_factory_reset", b"_factory_reset", "_set_altitude", b"_set_altitude", "_set_ambient_pressure", b"_set_ambient_pressure", "_set_asc", b"_set_asc", "_set_one_shot_mode", b"_set_one_shot_mode", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "_start_fan_cleaning", b"_start_fan_cleaning", "factory_reset", b"factory_reset", "set_altitude", b"set_altitude", "set_ambient_pressure", b"set_ambient_pressure", "set_asc", b"set_asc", "set_one_shot_mode", b"set_one_shot_mode", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature", "start_fan_cleaning", b"start_fan_cleaning"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_factory_reset", b"_factory_reset"]) -> typing.Literal["factory_reset"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_altitude", b"_set_altitude"]) -> typing.Literal["set_altitude"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_ambient_pressure", b"_set_ambient_pressure"]) -> typing.Literal["set_ambient_pressure"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_asc", b"_set_asc"]) -> typing.Literal["set_asc"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_one_shot_mode", b"_set_one_shot_mode"]) -> typing.Literal["set_one_shot_mode"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_target_co2_conc", b"_set_target_co2_conc"]) -> typing.Literal["set_target_co2_conc"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_temperature", b"_set_temperature"]) -> typing.Literal["set_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_start_fan_cleaning", b"_start_fan_cleaning"]) -> typing.Literal["start_fan_cleaning"] | None: ...
|
||||
|
||||
global___SEN6X_config = SEN6X_config
|
||||
|
||||
@typing.final
|
||||
class SCD30_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
@@ -1302,3 +1491,44 @@ class SHTXX_config(google.protobuf.message.Message):
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_accuracy", b"_set_accuracy"]) -> typing.Literal["set_accuracy"] | None: ...
|
||||
|
||||
global___SHTXX_config = SHTXX_config
|
||||
|
||||
@typing.final
|
||||
class DS248X_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
MAIN_TEMPERATURE_CHANNEL_FIELD_NUMBER: builtins.int
|
||||
main_temperature_channel: builtins.int
|
||||
"""
|
||||
Main channel for temperature reporting (0-7)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
main_temperature_channel: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_main_temperature_channel", b"_main_temperature_channel", "main_temperature_channel", b"main_temperature_channel"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_main_temperature_channel", b"_main_temperature_channel", "main_temperature_channel", b"main_temperature_channel"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_main_temperature_channel", b"_main_temperature_channel"]) -> typing.Literal["main_temperature_channel"] | None: ...
|
||||
|
||||
global___DS248X_config = DS248X_config
|
||||
|
||||
@typing.final
|
||||
class AS3935_config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
SET_TUNING_CAP_PF_FIELD_NUMBER: builtins.int
|
||||
set_tuning_cap_pf: builtins.int
|
||||
"""
|
||||
Antenna tuning capacitance in pF, 0 to 120 in steps of 8. The antenna tank must
|
||||
resonate within 3.5% of 500kHz; the correct trim is specific to the sensor board.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
set_tuning_cap_pf: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_set_tuning_cap_pf", b"_set_tuning_cap_pf", "set_tuning_cap_pf", b"set_tuning_cap_pf"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_set_tuning_cap_pf", b"_set_tuning_cap_pf", "set_tuning_cap_pf", b"set_tuning_cap_pf"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_set_tuning_cap_pf", b"_set_tuning_cap_pf"]) -> typing.Literal["set_tuning_cap_pf"] | None: ...
|
||||
|
||||
global___AS3935_config = AS3935_config
|
||||
Generated
+3
-3
@@ -16,7 +16,7 @@ from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a meshtastic/protobuf/nanopb.proto\"\xe2\x03\n\rDeviceProfile\x12\x1d\n\tlong_name\x18\x01 \x01(\tB\x05\x92?\x02\x08(H\x00\x88\x01\x01\x12\x1e\n\nshort_name\x18\x02 \x01(\tB\x05\x92?\x02\x08\x05H\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x1d\n\x08ringtone\x18\x07 \x01(\tB\x06\x92?\x03\x08\xe7\x01H\x06\x88\x01\x01\x12$\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tB\x06\x92?\x03\x08\xc9\x01H\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBf\n\x14org.meshtastic.protoB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a meshtastic/protobuf/nanopb.proto\"\xbe\x04\n\rDeviceProfile\x12\x1d\n\tlong_name\x18\x01 \x01(\tB\x05\x92?\x02\x08\x19H\x00\x88\x01\x01\x12\x1e\n\nshort_name\x18\x02 \x01(\tB\x05\x92?\x02\x08\x05H\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x1d\n\x08ringtone\x18\x07 \x01(\tB\x06\x92?\x03\x08\xe7\x01H\x06\x88\x01\x01\x12$\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tB\x06\x92?\x03\x08\xc9\x01H\x07\x88\x01\x01\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x08\x88\x01\x01\x12\x18\n\x0bis_licensed\x18\n \x01(\x08H\t\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesB\x12\n\x10_is_unmessagableB\x0e\n\x0c_is_licensedBf\n\x14org.meshtastic.protoB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -25,7 +25,7 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_DEVICEPROFILE.fields_by_name['long_name']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['long_name']._serialized_options = b'\222?\002\010('
|
||||
_DEVICEPROFILE.fields_by_name['long_name']._serialized_options = b'\222?\002\010\031'
|
||||
_DEVICEPROFILE.fields_by_name['short_name']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['short_name']._serialized_options = b'\222?\002\010\005'
|
||||
_DEVICEPROFILE.fields_by_name['ringtone']._options = None
|
||||
@@ -33,5 +33,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
_DEVICEPROFILE.fields_by_name['canned_messages']._options = None
|
||||
_DEVICEPROFILE.fields_by_name['canned_messages']._serialized_options = b'\222?\003\010\311\001'
|
||||
_globals['_DEVICEPROFILE']._serialized_start=165
|
||||
_globals['_DEVICEPROFILE']._serialized_end=647
|
||||
_globals['_DEVICEPROFILE']._serialized_end=739
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Generated
+18
-2
@@ -29,6 +29,8 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
FIXED_POSITION_FIELD_NUMBER: builtins.int
|
||||
RINGTONE_FIELD_NUMBER: builtins.int
|
||||
CANNED_MESSAGES_FIELD_NUMBER: builtins.int
|
||||
IS_UNMESSAGABLE_FIELD_NUMBER: builtins.int
|
||||
IS_LICENSED_FIELD_NUMBER: builtins.int
|
||||
long_name: builtins.str
|
||||
"""
|
||||
Long name for the node
|
||||
@@ -49,6 +51,14 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
"""
|
||||
Predefined messages for CannedMessage
|
||||
"""
|
||||
is_unmessagable: builtins.bool
|
||||
"""
|
||||
Is the node unmessagable
|
||||
"""
|
||||
is_licensed: builtins.bool
|
||||
"""
|
||||
Is this node in licensed user mode
|
||||
"""
|
||||
@property
|
||||
def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
|
||||
"""
|
||||
@@ -78,9 +88,11 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
|
||||
ringtone: builtins.str | None = ...,
|
||||
canned_messages: builtins.str | None = ...,
|
||||
is_unmessagable: builtins.bool | None = ...,
|
||||
is_licensed: builtins.bool | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_is_licensed", b"_is_licensed", "_is_unmessagable", b"_is_unmessagable", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "is_licensed", b"is_licensed", "is_unmessagable", b"is_unmessagable", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_is_licensed", b"_is_licensed", "_is_unmessagable", b"_is_unmessagable", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "is_licensed", b"is_licensed", "is_unmessagable", b"is_unmessagable", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_canned_messages", b"_canned_messages"]) -> typing.Literal["canned_messages"] | None: ...
|
||||
@typing.overload
|
||||
@@ -90,6 +102,10 @@ class DeviceProfile(google.protobuf.message.Message):
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_fixed_position", b"_fixed_position"]) -> typing.Literal["fixed_position"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_is_licensed", b"_is_licensed"]) -> typing.Literal["is_licensed"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_is_unmessagable", b"_is_unmessagable"]) -> typing.Literal["is_unmessagable"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_long_name", b"_long_name"]) -> typing.Literal["long_name"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ...
|
||||
|
||||
Generated
+20
-16
File diff suppressed because one or more lines are too long.
Generated
+140
-1
@@ -1402,6 +1402,27 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
ITU Region 3 Amateur Radio 2m band (144-148 MHz)
|
||||
"""
|
||||
ITU1_70CM: Config.LoRaConfig._RegionCode.ValueType # 34
|
||||
"""
|
||||
ITU Region 1 Amateur Radio 70cm band (430-440 MHz)
|
||||
"""
|
||||
ITU2_70CM: Config.LoRaConfig._RegionCode.ValueType # 35
|
||||
"""
|
||||
ITU Region 2 Amateur Radio 70cm band (420-450 MHz)
|
||||
Note: Some countries do not allocate 420-430 MHz or 440-450 MHz.
|
||||
Check local law!
|
||||
"""
|
||||
ITU3_70CM: Config.LoRaConfig._RegionCode.ValueType # 36
|
||||
"""
|
||||
ITU Region 3 Amateur Radio 70cm band (430-450 MHz)
|
||||
Note: Some countries do not allocate 440-450 MHz. Check local law!
|
||||
"""
|
||||
ITU2_125CM: Config.LoRaConfig._RegionCode.ValueType # 37
|
||||
"""
|
||||
ITU Region 2 Amateur Radio 1.25m '125cm' band (220-225 MHz)
|
||||
Note: Some countries do not allocate 220-222 MHz (Ex: USA/Canada).
|
||||
Check local law!
|
||||
"""
|
||||
|
||||
class RegionCode(_RegionCode, metaclass=_RegionCodeEnumTypeWrapper): ...
|
||||
UNSET: Config.LoRaConfig.RegionCode.ValueType # 0
|
||||
@@ -1537,6 +1558,27 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
ITU Region 3 Amateur Radio 2m band (144-148 MHz)
|
||||
"""
|
||||
ITU1_70CM: Config.LoRaConfig.RegionCode.ValueType # 34
|
||||
"""
|
||||
ITU Region 1 Amateur Radio 70cm band (430-440 MHz)
|
||||
"""
|
||||
ITU2_70CM: Config.LoRaConfig.RegionCode.ValueType # 35
|
||||
"""
|
||||
ITU Region 2 Amateur Radio 70cm band (420-450 MHz)
|
||||
Note: Some countries do not allocate 420-430 MHz or 440-450 MHz.
|
||||
Check local law!
|
||||
"""
|
||||
ITU3_70CM: Config.LoRaConfig.RegionCode.ValueType # 36
|
||||
"""
|
||||
ITU Region 3 Amateur Radio 70cm band (430-450 MHz)
|
||||
Note: Some countries do not allocate 440-450 MHz. Check local law!
|
||||
"""
|
||||
ITU2_125CM: Config.LoRaConfig.RegionCode.ValueType # 37
|
||||
"""
|
||||
ITU Region 2 Amateur Radio 1.25m '125cm' band (220-225 MHz)
|
||||
Note: Some countries do not allocate 220-222 MHz (Ex: USA/Canada).
|
||||
Check local law!
|
||||
"""
|
||||
|
||||
class _ModemPreset:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -1614,6 +1656,30 @@ class Config(google.protobuf.message.Message):
|
||||
Moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
|
||||
Comparable link budget and data rate to LONG_FAST.
|
||||
"""
|
||||
TINY_FAST: Config.LoRaConfig._ModemPreset.ValueType # 14
|
||||
"""
|
||||
Tiny Fast
|
||||
Preset optimized for compliance with Amateur Radio restrictions with 20kHz bandwidth.
|
||||
Many regions limit data transmission bandwidth in lower amateur bands (2 Meter).
|
||||
Note: TCXO with tight tolerances (±5 ppm or better) is *absolutely required* at these narrow bandwidths.
|
||||
Only compatible with SX127x and SX126x chipsets.
|
||||
Comparable link budget and data rate to LONG_FAST.
|
||||
"""
|
||||
TINY_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 15
|
||||
"""
|
||||
Tiny Slow
|
||||
Preset optimized for compliance with Amateur Radio restrictions with 20kHz bandwidth.
|
||||
Many regions limit data transmission bandwidth in lower amateur bands (2 Meter).
|
||||
Note: TCXO with tight tolerances (±5 ppm or better) is *absolutely required* at these narrow bandwidths.
|
||||
Only compatible with SX127x and SX126x chipsets.
|
||||
Comparable link budget and data rate to LONG_MODERATE.
|
||||
"""
|
||||
MEDIUM_TURBO: Config.LoRaConfig._ModemPreset.ValueType # 16
|
||||
"""
|
||||
Medium Range - Turbo
|
||||
This preset performs similarly to MEDIUM_FAST, but with 500kHz bandwidth.
|
||||
It is not legal to use in all regions due to this wider bandwidth.
|
||||
"""
|
||||
|
||||
class ModemPreset(_ModemPreset, metaclass=_ModemPresetEnumTypeWrapper):
|
||||
"""
|
||||
@@ -1691,6 +1757,30 @@ class Config(google.protobuf.message.Message):
|
||||
Moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth.
|
||||
Comparable link budget and data rate to LONG_FAST.
|
||||
"""
|
||||
TINY_FAST: Config.LoRaConfig.ModemPreset.ValueType # 14
|
||||
"""
|
||||
Tiny Fast
|
||||
Preset optimized for compliance with Amateur Radio restrictions with 20kHz bandwidth.
|
||||
Many regions limit data transmission bandwidth in lower amateur bands (2 Meter).
|
||||
Note: TCXO with tight tolerances (±5 ppm or better) is *absolutely required* at these narrow bandwidths.
|
||||
Only compatible with SX127x and SX126x chipsets.
|
||||
Comparable link budget and data rate to LONG_FAST.
|
||||
"""
|
||||
TINY_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 15
|
||||
"""
|
||||
Tiny Slow
|
||||
Preset optimized for compliance with Amateur Radio restrictions with 20kHz bandwidth.
|
||||
Many regions limit data transmission bandwidth in lower amateur bands (2 Meter).
|
||||
Note: TCXO with tight tolerances (±5 ppm or better) is *absolutely required* at these narrow bandwidths.
|
||||
Only compatible with SX127x and SX126x chipsets.
|
||||
Comparable link budget and data rate to LONG_MODERATE.
|
||||
"""
|
||||
MEDIUM_TURBO: Config.LoRaConfig.ModemPreset.ValueType # 16
|
||||
"""
|
||||
Medium Range - Turbo
|
||||
This preset performs similarly to MEDIUM_FAST, but with 500kHz bandwidth.
|
||||
It is not legal to use in all regions due to this wider bandwidth.
|
||||
"""
|
||||
|
||||
class _FEM_LNA_Mode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -1949,6 +2039,49 @@ class Config(google.protobuf.message.Message):
|
||||
class SecurityConfig(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _PacketSignaturePolicy:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _PacketSignaturePolicyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.SecurityConfig._PacketSignaturePolicy.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
PACKET_SIGNATURE_POLICY_COMPATIBLE: Config.SecurityConfig._PacketSignaturePolicy.ValueType # 0
|
||||
"""
|
||||
Accept unsigned packets for maximum compatibility while still rejecting malformed or invalid signatures.
|
||||
This is the default to avoid legacy nodes dropping signed packets during rebroadcast.
|
||||
"""
|
||||
PACKET_SIGNATURE_POLICY_BALANCED: Config.SecurityConfig._PacketSignaturePolicy.ValueType # 1
|
||||
"""
|
||||
Prefer authenticated packets while retaining compatibility with unsigned packets from nodes not known to sign.
|
||||
Rejects unsigned, signable broadcasts from nodes that have previously signed.
|
||||
"""
|
||||
PACKET_SIGNATURE_POLICY_STRICT: Config.SecurityConfig._PacketSignaturePolicy.ValueType # 2
|
||||
"""
|
||||
Accept only packets authenticated by a verified XEdDSA signature or successful PKI decryption.
|
||||
Unsigned, malformed, invalid, or unverifiable packets are ignored.
|
||||
"""
|
||||
|
||||
class PacketSignaturePolicy(_PacketSignaturePolicy, metaclass=_PacketSignaturePolicyEnumTypeWrapper):
|
||||
"""
|
||||
Controls how the device authenticates remotely received mesh packets.
|
||||
"""
|
||||
|
||||
PACKET_SIGNATURE_POLICY_COMPATIBLE: Config.SecurityConfig.PacketSignaturePolicy.ValueType # 0
|
||||
"""
|
||||
Accept unsigned packets for maximum compatibility while still rejecting malformed or invalid signatures.
|
||||
This is the default to avoid legacy nodes dropping signed packets during rebroadcast.
|
||||
"""
|
||||
PACKET_SIGNATURE_POLICY_BALANCED: Config.SecurityConfig.PacketSignaturePolicy.ValueType # 1
|
||||
"""
|
||||
Prefer authenticated packets while retaining compatibility with unsigned packets from nodes not known to sign.
|
||||
Rejects unsigned, signable broadcasts from nodes that have previously signed.
|
||||
"""
|
||||
PACKET_SIGNATURE_POLICY_STRICT: Config.SecurityConfig.PacketSignaturePolicy.ValueType # 2
|
||||
"""
|
||||
Accept only packets authenticated by a verified XEdDSA signature or successful PKI decryption.
|
||||
Unsigned, malformed, invalid, or unverifiable packets are ignored.
|
||||
"""
|
||||
|
||||
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
||||
PRIVATE_KEY_FIELD_NUMBER: builtins.int
|
||||
ADMIN_KEY_FIELD_NUMBER: builtins.int
|
||||
@@ -1956,6 +2089,7 @@ class Config(google.protobuf.message.Message):
|
||||
SERIAL_ENABLED_FIELD_NUMBER: builtins.int
|
||||
DEBUG_LOG_API_ENABLED_FIELD_NUMBER: builtins.int
|
||||
ADMIN_CHANNEL_ENABLED_FIELD_NUMBER: builtins.int
|
||||
PACKET_SIGNATURE_POLICY_FIELD_NUMBER: builtins.int
|
||||
public_key: builtins.bytes
|
||||
"""
|
||||
The public key of the user's device.
|
||||
@@ -1984,6 +2118,10 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Allow incoming device control over the insecure legacy admin channel.
|
||||
"""
|
||||
packet_signature_policy: global___Config.SecurityConfig.PacketSignaturePolicy.ValueType
|
||||
"""
|
||||
Determines the packet signature policy applied to remotely received mesh packets.
|
||||
"""
|
||||
@property
|
||||
def admin_key(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]:
|
||||
"""
|
||||
@@ -2000,8 +2138,9 @@ class Config(google.protobuf.message.Message):
|
||||
serial_enabled: builtins.bool = ...,
|
||||
debug_log_api_enabled: builtins.bool = ...,
|
||||
admin_channel_enabled: builtins.bool = ...,
|
||||
packet_signature_policy: global___Config.SecurityConfig.PacketSignaturePolicy.ValueType = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["admin_channel_enabled", b"admin_channel_enabled", "admin_key", b"admin_key", "debug_log_api_enabled", b"debug_log_api_enabled", "is_managed", b"is_managed", "private_key", b"private_key", "public_key", b"public_key", "serial_enabled", b"serial_enabled"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["admin_channel_enabled", b"admin_channel_enabled", "admin_key", b"admin_key", "debug_log_api_enabled", b"debug_log_api_enabled", "is_managed", b"is_managed", "packet_signature_policy", b"packet_signature_policy", "private_key", b"private_key", "public_key", b"public_key", "serial_enabled", b"serial_enabled"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class SessionkeyConfig(google.protobuf.message.Message):
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/deviceonly_legacy.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import deviceonly_pb2 as meshtastic_dot_protobuf_dot_deviceonly__pb2
|
||||
from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/deviceonly_legacy.proto\x12\x13meshtastic.protobuf\x1a$meshtastic/protobuf/deviceonly.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.proto\"\x8c\x03\n\x13NodeInfoLite_Legacy\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x16\n\x07\x63hannel\x18\x07 \x01(\rB\x05\x92?\x02\x38\x08\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x1d\n\thops_away\x18\t \x01(\rB\x05\x92?\x02\x38\x08H\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x17\n\x08next_hop\x18\x0c \x01(\rB\x05\x92?\x02\x38\x08\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\x92\x01\n\x13NodeDatabase_Legacy\x12\x0f\n\x07version\x18\x01 \x01(\r\x12j\n\x05nodes\x18\x02 \x03(\x0b\x32(.meshtastic.protobuf.NodeInfoLite_LegacyB1\x92?.\x92\x01+std::vector<meshtastic_NodeInfoLite_Legacy>Bt\n\x14org.meshtastic.protoB\x10\x44\x65viceOnlyLegacyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceonly_legacy_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020DeviceOnlyLegacyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
|
||||
_NODEINFOLITE_LEGACY.fields_by_name['channel']._options = None
|
||||
_NODEINFOLITE_LEGACY.fields_by_name['channel']._serialized_options = b'\222?\0028\010'
|
||||
_NODEINFOLITE_LEGACY.fields_by_name['hops_away']._options = None
|
||||
_NODEINFOLITE_LEGACY.fields_by_name['hops_away']._serialized_options = b'\222?\0028\010'
|
||||
_NODEINFOLITE_LEGACY.fields_by_name['next_hop']._options = None
|
||||
_NODEINFOLITE_LEGACY.fields_by_name['next_hop']._serialized_options = b'\222?\0028\010'
|
||||
_NODEDATABASE_LEGACY.fields_by_name['nodes']._options = None
|
||||
_NODEDATABASE_LEGACY.fields_by_name['nodes']._serialized_options = b'\222?.\222\001+std::vector<meshtastic_NodeInfoLite_Legacy>'
|
||||
_globals['_NODEINFOLITE_LEGACY']._serialized_start=178
|
||||
_globals['_NODEINFOLITE_LEGACY']._serialized_end=574
|
||||
_globals['_NODEDATABASE_LEGACY']._serialized_start=577
|
||||
_globals['_NODEDATABASE_LEGACY']._serialized_end=723
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import collections.abc
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.containers
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.deviceonly_pb2
|
||||
import meshtastic.protobuf.telemetry_pb2
|
||||
import typing
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
@typing.final
|
||||
class NodeInfoLite_Legacy(google.protobuf.message.Message):
|
||||
"""
|
||||
Legacy NodeInfoLite descriptor used only to decode pre-split
|
||||
/prefs/nodes.proto saves during the v24 -> v25 migration boot.
|
||||
This preserves the original NodeInfoLite-compatible field numbers needed
|
||||
to parse old wire bytes cleanly, including user (2), position (3),
|
||||
device_metrics (6), and the legacy-only compatibility fields via_mqtt (8),
|
||||
is_favorite (10), and is_ignored (11). Steady-state code does not use
|
||||
this struct; it is dropped after migration completes. This file should be
|
||||
removed once DEVICESTATE_MIN_VER advances past 24.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NUM_FIELD_NUMBER: builtins.int
|
||||
USER_FIELD_NUMBER: builtins.int
|
||||
POSITION_FIELD_NUMBER: builtins.int
|
||||
SNR_FIELD_NUMBER: builtins.int
|
||||
LAST_HEARD_FIELD_NUMBER: builtins.int
|
||||
DEVICE_METRICS_FIELD_NUMBER: builtins.int
|
||||
CHANNEL_FIELD_NUMBER: builtins.int
|
||||
VIA_MQTT_FIELD_NUMBER: builtins.int
|
||||
HOPS_AWAY_FIELD_NUMBER: builtins.int
|
||||
IS_FAVORITE_FIELD_NUMBER: builtins.int
|
||||
IS_IGNORED_FIELD_NUMBER: builtins.int
|
||||
NEXT_HOP_FIELD_NUMBER: builtins.int
|
||||
BITFIELD_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
snr: builtins.float
|
||||
last_heard: builtins.int
|
||||
channel: builtins.int
|
||||
via_mqtt: builtins.bool
|
||||
hops_away: builtins.int
|
||||
is_favorite: builtins.bool
|
||||
is_ignored: builtins.bool
|
||||
next_hop: builtins.int
|
||||
bitfield: builtins.int
|
||||
@property
|
||||
def user(self) -> meshtastic.protobuf.deviceonly_pb2.UserLite: ...
|
||||
@property
|
||||
def position(self) -> meshtastic.protobuf.deviceonly_pb2.PositionLite: ...
|
||||
@property
|
||||
def device_metrics(self) -> meshtastic.protobuf.telemetry_pb2.DeviceMetrics: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
user: meshtastic.protobuf.deviceonly_pb2.UserLite | None = ...,
|
||||
position: meshtastic.protobuf.deviceonly_pb2.PositionLite | None = ...,
|
||||
snr: builtins.float = ...,
|
||||
last_heard: builtins.int = ...,
|
||||
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
|
||||
channel: builtins.int = ...,
|
||||
via_mqtt: builtins.bool = ...,
|
||||
hops_away: builtins.int | None = ...,
|
||||
is_favorite: builtins.bool = ...,
|
||||
is_ignored: builtins.bool = ...,
|
||||
next_hop: builtins.int = ...,
|
||||
bitfield: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "bitfield", b"bitfield", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "last_heard", b"last_heard", "next_hop", b"next_hop", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
|
||||
|
||||
global___NodeInfoLite_Legacy = NodeInfoLite_Legacy
|
||||
|
||||
@typing.final
|
||||
class NodeDatabase_Legacy(google.protobuf.message.Message):
|
||||
"""
|
||||
Legacy NodeDatabase shape: one repeated array of fat NodeInfoLite_Legacy
|
||||
with no satellite position/telemetry arrays.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
NODES_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
@property
|
||||
def nodes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodeInfoLite_Legacy]: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
version: builtins.int = ...,
|
||||
nodes: collections.abc.Iterable[global___NodeInfoLite_Legacy] | None = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["nodes", b"nodes", "version", b"version"]) -> None: ...
|
||||
|
||||
global___NodeDatabase_Legacy = NodeDatabase_Legacy
|
||||
Generated
+40
-14
File diff suppressed because one or more lines are too long.
Generated
+159
-52
@@ -30,6 +30,7 @@ class PositionLite(google.protobuf.message.Message):
|
||||
ALTITUDE_FIELD_NUMBER: builtins.int
|
||||
TIME_FIELD_NUMBER: builtins.int
|
||||
LOCATION_SOURCE_FIELD_NUMBER: builtins.int
|
||||
PRECISION_BITS_FIELD_NUMBER: builtins.int
|
||||
latitude_i: builtins.int
|
||||
"""
|
||||
The new preferred location encoding, multiply by 1e-7 to get degrees
|
||||
@@ -55,6 +56,10 @@ class PositionLite(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
"""
|
||||
precision_bits: builtins.int
|
||||
"""
|
||||
Indicates the bits of precision set by the sending node
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -63,8 +68,9 @@ class PositionLite(google.protobuf.message.Message):
|
||||
altitude: builtins.int = ...,
|
||||
time: builtins.int = ...,
|
||||
location_source: meshtastic.protobuf.mesh_pb2.Position.LocSource.ValueType = ...,
|
||||
precision_bits: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["altitude", b"altitude", "latitude_i", b"latitude_i", "location_source", b"location_source", "longitude_i", b"longitude_i", "time", b"time"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["altitude", b"altitude", "latitude_i", b"latitude_i", "location_source", b"location_source", "longitude_i", b"longitude_i", "precision_bits", b"precision_bits", "time", b"time"]) -> None: ...
|
||||
|
||||
global___PositionLite = PositionLite
|
||||
|
||||
@@ -142,26 +148,26 @@ class NodeInfoLite(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NUM_FIELD_NUMBER: builtins.int
|
||||
USER_FIELD_NUMBER: builtins.int
|
||||
POSITION_FIELD_NUMBER: builtins.int
|
||||
SNR_FIELD_NUMBER: builtins.int
|
||||
LAST_HEARD_FIELD_NUMBER: builtins.int
|
||||
DEVICE_METRICS_FIELD_NUMBER: builtins.int
|
||||
CHANNEL_FIELD_NUMBER: builtins.int
|
||||
VIA_MQTT_FIELD_NUMBER: builtins.int
|
||||
HOPS_AWAY_FIELD_NUMBER: builtins.int
|
||||
IS_FAVORITE_FIELD_NUMBER: builtins.int
|
||||
IS_IGNORED_FIELD_NUMBER: builtins.int
|
||||
NEXT_HOP_FIELD_NUMBER: builtins.int
|
||||
BITFIELD_FIELD_NUMBER: builtins.int
|
||||
LONG_NAME_FIELD_NUMBER: builtins.int
|
||||
SHORT_NAME_FIELD_NUMBER: builtins.int
|
||||
HW_MODEL_FIELD_NUMBER: builtins.int
|
||||
ROLE_FIELD_NUMBER: builtins.int
|
||||
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
||||
SNR_Q4_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
"""
|
||||
The node number
|
||||
"""
|
||||
snr: builtins.float
|
||||
"""
|
||||
Returns the Signal-to-noise ratio (SNR) of the last received message,
|
||||
as measured by the receiver. Return SNR of the last received message in dB
|
||||
In-memory SNR of the last received message in dB. Not serialised directly:
|
||||
always zeroed before encode; persisted as snr_q4 = 19 below.
|
||||
"""
|
||||
last_heard: builtins.int
|
||||
"""
|
||||
@@ -171,72 +177,69 @@ class NodeInfoLite(google.protobuf.message.Message):
|
||||
"""
|
||||
local channel index we heard that node on. Only populated if its not the default channel.
|
||||
"""
|
||||
via_mqtt: builtins.bool
|
||||
"""
|
||||
True if we witnessed the node over MQTT instead of LoRA transport
|
||||
"""
|
||||
hops_away: builtins.int
|
||||
"""
|
||||
Number of hops away from us this node is (0 if direct neighbor)
|
||||
"""
|
||||
is_favorite: builtins.bool
|
||||
"""
|
||||
True if node is in our favorites list
|
||||
Persists between NodeDB internal clean ups
|
||||
"""
|
||||
is_ignored: builtins.bool
|
||||
"""
|
||||
True if node is in our ignored list
|
||||
Persists between NodeDB internal clean ups
|
||||
"""
|
||||
next_hop: builtins.int
|
||||
"""
|
||||
Last byte of the node number of the node that should be used as the next hop to reach this node.
|
||||
"""
|
||||
bitfield: builtins.int
|
||||
"""
|
||||
Bitfield for storing booleans.
|
||||
LSB 0 is_key_manually_verified
|
||||
LSB 1 is_muted
|
||||
Bitfield for storing booleans. See NODEINFO_BITFIELD_* in src/mesh/NodeDB.h.
|
||||
"""
|
||||
@property
|
||||
def user(self) -> global___UserLite:
|
||||
"""
|
||||
The user info for this node
|
||||
"""
|
||||
long_name: builtins.str
|
||||
"""Flattened user fields (formerly UserLite). macaddr dropped (deprecated 1.2.11).
|
||||
|
||||
@property
|
||||
def position(self) -> global___PositionLite:
|
||||
"""
|
||||
This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
|
||||
Position.time now indicates the last time we received a POSITION from that node.
|
||||
"""
|
||||
|
||||
@property
|
||||
def device_metrics(self) -> meshtastic.protobuf.telemetry_pb2.DeviceMetrics:
|
||||
"""
|
||||
The latest device metrics for the node.
|
||||
"""
|
||||
|
||||
A full name for this user, i.e. "Kevin Hester".
|
||||
"""
|
||||
short_name: builtins.str
|
||||
"""
|
||||
A VERY short name, ideally two characters or an emoji.
|
||||
Suitable for a tiny OLED screen.
|
||||
"""
|
||||
hw_model: meshtastic.protobuf.mesh_pb2.HardwareModel.ValueType
|
||||
"""
|
||||
Hardware model the user's device is running.
|
||||
"""
|
||||
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType
|
||||
"""
|
||||
The user's role in the mesh.
|
||||
"""
|
||||
public_key: builtins.bytes
|
||||
"""
|
||||
The public key of the user's device, for PKI-based encrypted DMs.
|
||||
"""
|
||||
snr_q4: builtins.int
|
||||
"""
|
||||
Q4-encoded SNR: dB × 4, sint32 zigzag. Matches RouteDiscovery convention.
|
||||
Encode: snr_q4 = (int32_t)lroundf(snr * 4.0f). Decode: snr = snr_q4 / 4.0f.
|
||||
float snr is always zeroed on disk; this field carries all persisted SNR.
|
||||
A stored 0 does not by itself mean "unknown" here - see NODEINFO_BITFIELD_HAS_SNR in
|
||||
src/mesh/NodeDB.h for the presence bit that disambiguates a genuine 0 dB reading from
|
||||
"never measured".
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
user: global___UserLite | None = ...,
|
||||
position: global___PositionLite | None = ...,
|
||||
snr: builtins.float = ...,
|
||||
last_heard: builtins.int = ...,
|
||||
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
|
||||
channel: builtins.int = ...,
|
||||
via_mqtt: builtins.bool = ...,
|
||||
hops_away: builtins.int | None = ...,
|
||||
is_favorite: builtins.bool = ...,
|
||||
is_ignored: builtins.bool = ...,
|
||||
next_hop: builtins.int = ...,
|
||||
bitfield: builtins.int = ...,
|
||||
long_name: builtins.str = ...,
|
||||
short_name: builtins.str = ...,
|
||||
hw_model: meshtastic.protobuf.mesh_pb2.HardwareModel.ValueType = ...,
|
||||
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType = ...,
|
||||
public_key: builtins.bytes = ...,
|
||||
snr_q4: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "bitfield", b"bitfield", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "last_heard", b"last_heard", "next_hop", b"next_hop", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "hops_away", b"hops_away"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "bitfield", b"bitfield", "channel", b"channel", "hops_away", b"hops_away", "hw_model", b"hw_model", "last_heard", b"last_heard", "long_name", b"long_name", "next_hop", b"next_hop", "num", b"num", "public_key", b"public_key", "role", b"role", "short_name", b"short_name", "snr", b"snr", "snr_q4", b"snr_q4"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
|
||||
|
||||
global___NodeInfoLite = NodeInfoLite
|
||||
@@ -337,12 +340,100 @@ class DeviceState(google.protobuf.message.Message):
|
||||
|
||||
global___DeviceState = DeviceState
|
||||
|
||||
@typing.final
|
||||
class NodePositionEntry(google.protobuf.message.Message):
|
||||
"""Satellite per-node entries; stored alongside the slim NodeInfoLite so nodes
|
||||
that never report don't pay the embedded cost.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NUM_FIELD_NUMBER: builtins.int
|
||||
POSITION_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
@property
|
||||
def position(self) -> global___PositionLite: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
position: global___PositionLite | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["position", b"position"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["num", b"num", "position", b"position"]) -> None: ...
|
||||
|
||||
global___NodePositionEntry = NodePositionEntry
|
||||
|
||||
@typing.final
|
||||
class NodeTelemetryEntry(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NUM_FIELD_NUMBER: builtins.int
|
||||
DEVICE_METRICS_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
@property
|
||||
def device_metrics(self) -> meshtastic.protobuf.telemetry_pb2.DeviceMetrics: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "num", b"num"]) -> None: ...
|
||||
|
||||
global___NodeTelemetryEntry = NodeTelemetryEntry
|
||||
|
||||
@typing.final
|
||||
class NodeEnvironmentEntry(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NUM_FIELD_NUMBER: builtins.int
|
||||
ENVIRONMENT_METRICS_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
@property
|
||||
def environment_metrics(self) -> meshtastic.protobuf.telemetry_pb2.EnvironmentMetrics: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
environment_metrics: meshtastic.protobuf.telemetry_pb2.EnvironmentMetrics | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["environment_metrics", b"environment_metrics"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["environment_metrics", b"environment_metrics", "num", b"num"]) -> None: ...
|
||||
|
||||
global___NodeEnvironmentEntry = NodeEnvironmentEntry
|
||||
|
||||
@typing.final
|
||||
class NodeStatusEntry(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NUM_FIELD_NUMBER: builtins.int
|
||||
STATUS_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
@property
|
||||
def status(self) -> meshtastic.protobuf.mesh_pb2.StatusMessage: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
num: builtins.int = ...,
|
||||
status: meshtastic.protobuf.mesh_pb2.StatusMessage | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["status", b"status"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["num", b"num", "status", b"status"]) -> None: ...
|
||||
|
||||
global___NodeStatusEntry = NodeStatusEntry
|
||||
|
||||
@typing.final
|
||||
class NodeDatabase(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
NODES_FIELD_NUMBER: builtins.int
|
||||
POSITIONS_FIELD_NUMBER: builtins.int
|
||||
TELEMETRY_FIELD_NUMBER: builtins.int
|
||||
STATUS_FIELD_NUMBER: builtins.int
|
||||
ENVIRONMENT_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
A version integer used to invalidate old save files when we make
|
||||
@@ -355,13 +446,29 @@ class NodeDatabase(google.protobuf.message.Message):
|
||||
New lite version of NodeDB to decrease memory footprint
|
||||
"""
|
||||
|
||||
@property
|
||||
def positions(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodePositionEntry]:
|
||||
"""Per-NodeNum satellite arrays. Constrained platforms (e.g. STM32WL) omit
|
||||
these via MESHTASTIC_EXCLUDE_*DB build flags.
|
||||
"""
|
||||
|
||||
@property
|
||||
def telemetry(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodeTelemetryEntry]: ...
|
||||
@property
|
||||
def status(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodeStatusEntry]: ...
|
||||
@property
|
||||
def environment(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___NodeEnvironmentEntry]: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
version: builtins.int = ...,
|
||||
nodes: collections.abc.Iterable[global___NodeInfoLite] | None = ...,
|
||||
positions: collections.abc.Iterable[global___NodePositionEntry] | None = ...,
|
||||
telemetry: collections.abc.Iterable[global___NodeTelemetryEntry] | None = ...,
|
||||
status: collections.abc.Iterable[global___NodeStatusEntry] | None = ...,
|
||||
environment: collections.abc.Iterable[global___NodeEnvironmentEntry] | None = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["nodes", b"nodes", "version", b"version"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["environment", b"environment", "nodes", b"nodes", "positions", b"positions", "status", b"status", "telemetry", b"telemetry", "version", b"version"]) -> None: ...
|
||||
|
||||
global___NodeDatabase = NodeDatabase
|
||||
|
||||
|
||||
Generated
+47
-7
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"s\n\nSensorData\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .meshtastic.protobuf.MessageType\x12\x15\n\x0b\x66loat_value\x18\x02 \x01(\x02H\x00\x12\x16\n\x0cuint32_value\x18\x03 \x01(\rH\x00\x42\x06\n\x04\x64\x61ta\"g\n\x12InterdeviceMessage\x12\x16\n\x04nmea\x18\x01 \x01(\tB\x06\x92?\x03\x08\x80\x08H\x00\x12\x31\n\x06sensor\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.SensorDataH\x00\x42\x06\n\x04\x64\x61ta*\xd5\x01\n\x0bMessageType\x12\x07\n\x03\x41\x43K\x10\x00\x12\x15\n\x10\x43OLLECT_INTERVAL\x10\xa0\x01\x12\x0c\n\x07\x42\x45\x45P_ON\x10\xa1\x01\x12\r\n\x08\x42\x45\x45P_OFF\x10\xa2\x01\x12\r\n\x08SHUTDOWN\x10\xa3\x01\x12\r\n\x08POWER_ON\x10\xa4\x01\x12\x0f\n\nSCD41_TEMP\x10\xb0\x01\x12\x13\n\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n\tSCD41_CO2\x10\xb2\x01\x12\x0f\n\nAHT20_TEMP\x10\xb3\x01\x12\x13\n\x0e\x41HT20_HUMIDITY\x10\xb4\x01\x12\x0f\n\nTVOC_INDEX\x10\xb5\x01\x42g\n\x14org.meshtastic.protoB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/nanopb.proto\"\xf6\x01\n\x0c\x46ileTransfer\x12\x35\n\toperation\x18\x01 \x01(\x0e\x32\".meshtastic.protobuf.FileOperation\x12\x18\n\x08\x66ilepath\x18\x02 \x01(\tB\x06\x92?\x03\x08\x80\x02\x12\x18\n\x08\x66iledata\x18\x03 \x01(\x0c\x42\x06\x92?\x03\x08\x80 \x12/\n\x06status\x18\x04 \x01(\x0e\x32\x1f.meshtastic.protobuf.FileStatus\x12\x17\n\x07message\x18\x05 \x01(\tB\x06\x92?\x03\x08\xff\x01\x12\x0e\n\x06offset\x18\x06 \x01(\x04\x12\x0e\n\x06length\x18\x07 \x01(\r\x12\x11\n\tfile_size\x18\x08 \x01(\x04\"\xb9\x01\n\x10\x44irectoryListing\x12\x19\n\tdirectory\x18\x01 \x01(\tB\x06\x92?\x03\x08\x80\x02\x12\x1b\n\tfilenames\x18\x02 \x03(\tB\x08\x92?\x05\x10\x10p\xff\x01\x12/\n\x06status\x18\x03 \x01(\x0e\x32\x1f.meshtastic.protobuf.FileStatus\x12\x17\n\x07message\x18\x04 \x01(\tB\x06\x92?\x03\x08\xff\x01\x12\x0e\n\x06offset\x18\x05 \x01(\r\x12\x13\n\x0btotal_count\x18\x06 \x01(\r\"O\n\x0eI2CTransaction\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x1a\n\nwrite_data\x18\x02 \x01(\x0c\x42\x06\x92?\x03\x08\x80\x02\x12\x10\n\x08read_len\x18\x03 \x01(\r\"\x92\x03\n\nSdCardInfo\x12\x0f\n\x07present\x18\x01 \x01(\x08\x12;\n\tcard_type\x18\x02 \x01(\x0e\x32(.meshtastic.protobuf.SdCardInfo.CardType\x12\x39\n\x08\x66\x61t_type\x18\x03 \x01(\x0e\x32\'.meshtastic.protobuf.SdCardInfo.FatType\x12\x11\n\tcard_size\x18\x04 \x01(\x04\x12\x12\n\nused_bytes\x18\x05 \x01(\x04\x12\x12\n\nfree_bytes\x18\x06 \x01(\x04\x12\x13\n\x0bstats_valid\x18\x07 \x01(\x08\x12\x0c\n\x04\x62usy\x18\x08 \x01(\x08\x12\x13\n\x0bunformatted\x18\t \x01(\x08\"K\n\x08\x43\x61rdType\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03MMC\x10\x01\x12\x06\n\x02SD\x10\x02\x12\x08\n\x04SDHC\x10\x03\x12\x08\n\x04SDXC\x10\x04\x12\x10\n\x0cUNKNOWN_CARD\x10\x05\";\n\x07\x46\x61tType\x12\x0f\n\x0bUNKNOWN_FAT\x10\x00\x12\t\n\x05\x46\x41T16\x10\x01\x12\t\n\x05\x46\x41T32\x10\x02\x12\t\n\x05\x45XFAT\x10\x03\"\xac\x01\n\tI2CResult\x12\x35\n\x06status\x18\x01 \x01(\x0e\x32%.meshtastic.protobuf.I2CResult.Status\x12\x19\n\tread_data\x18\x02 \x01(\x0c\x42\x06\x92?\x03\x08\x80\x02\"M\n\x06Status\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x06\n\x02OK\x10\x01\x12\x10\n\x0cNACK_ADDRESS\x10\x02\x12\r\n\tNACK_DATA\x10\x03\x12\t\n\x05\x45RROR\x10\x04\"\x87\x05\n\x12InterdeviceMessage\x12\n\n\x02id\x18\x0f \x01(\r\x12\x16\n\x04nmea\x18\x01 \x01(\tB\x06\x92?\x03\x08\x80\x08H\x00\x12\x15\n\x04\x62\x65\x65p\x18\x02 \x01(\rB\x05\x92?\x02\x38\x10H\x00\x12>\n\x0fi2c_transaction\x18\x03 \x01(\x0b\x32#.meshtastic.protobuf.I2CTransactionH\x00\x12\x34\n\ni2c_result\x18\x04 \x01(\x0b\x32\x1e.meshtastic.protobuf.I2CResultH\x00\x12\x12\n\x08i2c_scan\x18\x05 \x01(\x08H\x00\x12!\n\x0fi2c_scan_result\x18\x06 \x01(\x0c\x42\x06\x92?\x03\x08\x80\x01H\x00\x12:\n\rfile_transfer\x18\x07 \x01(\x0b\x32!.meshtastic.protobuf.FileTransferH\x00\x12\x42\n\x11\x64irectory_listing\x18\x08 \x01(\x0b\x32%.meshtastic.protobuf.DirectoryListingH\x00\x12\x15\n\x0bget_sd_info\x18\t \x01(\x08H\x00\x12\x32\n\x07sd_info\x18\n \x01(\x0b\x32\x1f.meshtastic.protobuf.SdCardInfoH\x00\x12\x37\n\x04ping\x18\x0b \x01(\x0e\x32\'.meshtastic.protobuf.InterdeviceVersionH\x00\x12\x37\n\x04pong\x18\x0c \x01(\x0e\x32\'.meshtastic.protobuf.InterdeviceVersionH\x00\x12\x0e\n\x04nack\x18\r \x01(\x08H\x00\x12\x34\n\nsd_command\x18\x0e \x01(\x0e\x32\x1e.meshtastic.protobuf.SdCommandH\x00\x42\x06\n\x04\x64\x61ta*Z\n\x12InterdeviceVersion\x12#\n\x1fINTERDEVICE_VERSION_UNSPECIFIED\x10\x00\x12\x1f\n\x1bINTERDEVICE_VERSION_CURRENT\x10\x02*7\n\rFileOperation\x12\x07\n\x03GET\x10\x00\x12\x08\n\x04POST\x10\x01\x12\x07\n\x03PUT\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03*\xa6\x01\n\nFileStatus\x12\x14\n\x10\x46ILE_UNSPECIFIED\x10\x00\x12\x0b\n\x07\x46ILE_OK\x10\x01\x12\r\n\tFILE_BUSY\x10\x02\x12\x10\n\x0c\x46ILE_NO_CARD\x10\x03\x12\x12\n\x0e\x46ILE_NOT_FOUND\x10\x04\x12\x18\n\x14\x46ILE_OFFSET_CONFLICT\x10\x05\x12\x11\n\rFILE_IO_ERROR\x10\x06\x12\x13\n\x0f\x46ILE_NOT_A_FILE\x10\x07*R\n\tSdCommand\x12\x1a\n\x16SD_COMMAND_UNSPECIFIED\x10\x00\x12\x0c\n\x08SD_MOUNT\x10\x01\x12\x0c\n\x08SD_EJECT\x10\x02\x12\r\n\tSD_FORMAT\x10\x03\x42g\n\x14org.meshtastic.protoB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -22,12 +22,52 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.interde
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021InterdeviceProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_FILETRANSFER.fields_by_name['filepath']._options = None
|
||||
_FILETRANSFER.fields_by_name['filepath']._serialized_options = b'\222?\003\010\200\002'
|
||||
_FILETRANSFER.fields_by_name['filedata']._options = None
|
||||
_FILETRANSFER.fields_by_name['filedata']._serialized_options = b'\222?\003\010\200 '
|
||||
_FILETRANSFER.fields_by_name['message']._options = None
|
||||
_FILETRANSFER.fields_by_name['message']._serialized_options = b'\222?\003\010\377\001'
|
||||
_DIRECTORYLISTING.fields_by_name['directory']._options = None
|
||||
_DIRECTORYLISTING.fields_by_name['directory']._serialized_options = b'\222?\003\010\200\002'
|
||||
_DIRECTORYLISTING.fields_by_name['filenames']._options = None
|
||||
_DIRECTORYLISTING.fields_by_name['filenames']._serialized_options = b'\222?\005\020\020p\377\001'
|
||||
_DIRECTORYLISTING.fields_by_name['message']._options = None
|
||||
_DIRECTORYLISTING.fields_by_name['message']._serialized_options = b'\222?\003\010\377\001'
|
||||
_I2CTRANSACTION.fields_by_name['write_data']._options = None
|
||||
_I2CTRANSACTION.fields_by_name['write_data']._serialized_options = b'\222?\003\010\200\002'
|
||||
_I2CRESULT.fields_by_name['read_data']._options = None
|
||||
_I2CRESULT.fields_by_name['read_data']._serialized_options = b'\222?\003\010\200\002'
|
||||
_INTERDEVICEMESSAGE.fields_by_name['nmea']._options = None
|
||||
_INTERDEVICEMESSAGE.fields_by_name['nmea']._serialized_options = b'\222?\003\010\200\010'
|
||||
_globals['_MESSAGETYPE']._serialized_start=319
|
||||
_globals['_MESSAGETYPE']._serialized_end=532
|
||||
_globals['_SENSORDATA']._serialized_start=96
|
||||
_globals['_SENSORDATA']._serialized_end=211
|
||||
_globals['_INTERDEVICEMESSAGE']._serialized_start=213
|
||||
_globals['_INTERDEVICEMESSAGE']._serialized_end=316
|
||||
_INTERDEVICEMESSAGE.fields_by_name['beep']._options = None
|
||||
_INTERDEVICEMESSAGE.fields_by_name['beep']._serialized_options = b'\222?\0028\020'
|
||||
_INTERDEVICEMESSAGE.fields_by_name['i2c_scan_result']._options = None
|
||||
_INTERDEVICEMESSAGE.fields_by_name['i2c_scan_result']._serialized_options = b'\222?\003\010\200\001'
|
||||
_globals['_INTERDEVICEVERSION']._serialized_start=1844
|
||||
_globals['_INTERDEVICEVERSION']._serialized_end=1934
|
||||
_globals['_FILEOPERATION']._serialized_start=1936
|
||||
_globals['_FILEOPERATION']._serialized_end=1991
|
||||
_globals['_FILESTATUS']._serialized_start=1994
|
||||
_globals['_FILESTATUS']._serialized_end=2160
|
||||
_globals['_SDCOMMAND']._serialized_start=2162
|
||||
_globals['_SDCOMMAND']._serialized_end=2244
|
||||
_globals['_FILETRANSFER']._serialized_start=97
|
||||
_globals['_FILETRANSFER']._serialized_end=343
|
||||
_globals['_DIRECTORYLISTING']._serialized_start=346
|
||||
_globals['_DIRECTORYLISTING']._serialized_end=531
|
||||
_globals['_I2CTRANSACTION']._serialized_start=533
|
||||
_globals['_I2CTRANSACTION']._serialized_end=612
|
||||
_globals['_SDCARDINFO']._serialized_start=615
|
||||
_globals['_SDCARDINFO']._serialized_end=1017
|
||||
_globals['_SDCARDINFO_CARDTYPE']._serialized_start=881
|
||||
_globals['_SDCARDINFO_CARDTYPE']._serialized_end=956
|
||||
_globals['_SDCARDINFO_FATTYPE']._serialized_start=958
|
||||
_globals['_SDCARDINFO_FATTYPE']._serialized_end=1017
|
||||
_globals['_I2CRESULT']._serialized_start=1020
|
||||
_globals['_I2CRESULT']._serialized_end=1192
|
||||
_globals['_I2CRESULT_STATUS']._serialized_start=1115
|
||||
_globals['_I2CRESULT_STATUS']._serialized_end=1192
|
||||
_globals['_INTERDEVICEMESSAGE']._serialized_start=1195
|
||||
_globals['_INTERDEVICEMESSAGE']._serialized_end=1842
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Generated
+445
-56
@@ -4,7 +4,9 @@ isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import collections.abc
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.internal.containers
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import sys
|
||||
@@ -17,89 +19,476 @@ else:
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _MessageType:
|
||||
class _InterdeviceVersion:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _MessageTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_MessageType.ValueType], builtins.type):
|
||||
class _InterdeviceVersionEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_InterdeviceVersion.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
ACK: _MessageType.ValueType # 0
|
||||
COLLECT_INTERVAL: _MessageType.ValueType # 160
|
||||
"""in ms"""
|
||||
BEEP_ON: _MessageType.ValueType # 161
|
||||
"""duration ms"""
|
||||
BEEP_OFF: _MessageType.ValueType # 162
|
||||
"""cancel prematurely"""
|
||||
SHUTDOWN: _MessageType.ValueType # 163
|
||||
POWER_ON: _MessageType.ValueType # 164
|
||||
SCD41_TEMP: _MessageType.ValueType # 176
|
||||
SCD41_HUMIDITY: _MessageType.ValueType # 177
|
||||
SCD41_CO2: _MessageType.ValueType # 178
|
||||
AHT20_TEMP: _MessageType.ValueType # 179
|
||||
AHT20_HUMIDITY: _MessageType.ValueType # 180
|
||||
TVOC_INDEX: _MessageType.ValueType # 181
|
||||
INTERDEVICE_VERSION_UNSPECIFIED: _InterdeviceVersion.ValueType # 0
|
||||
INTERDEVICE_VERSION_CURRENT: _InterdeviceVersion.ValueType # 2
|
||||
"""Never use 1: ping/pong were bools before the handshake existed, and a
|
||||
bool true is the same varint on the wire as the number 1, so firmware
|
||||
predating the handshake would pass it.
|
||||
"""
|
||||
|
||||
class MessageType(_MessageType, metaclass=_MessageTypeEnumTypeWrapper):
|
||||
"""encapsulate up to 1k of NMEA string data"""
|
||||
class InterdeviceVersion(_InterdeviceVersion, metaclass=_InterdeviceVersionEnumTypeWrapper):
|
||||
"""Version of the interdevice protocol spoken on the link. Both sides send
|
||||
theirs in the ping/pong handshake; a peer reporting a different one runs
|
||||
firmware that does not match and is not talked to.
|
||||
|
||||
ACK: MessageType.ValueType # 0
|
||||
COLLECT_INTERVAL: MessageType.ValueType # 160
|
||||
"""in ms"""
|
||||
BEEP_ON: MessageType.ValueType # 161
|
||||
"""duration ms"""
|
||||
BEEP_OFF: MessageType.ValueType # 162
|
||||
"""cancel prematurely"""
|
||||
SHUTDOWN: MessageType.ValueType # 163
|
||||
POWER_ON: MessageType.ValueType # 164
|
||||
SCD41_TEMP: MessageType.ValueType # 176
|
||||
SCD41_HUMIDITY: MessageType.ValueType # 177
|
||||
SCD41_CO2: MessageType.ValueType # 178
|
||||
AHT20_TEMP: MessageType.ValueType # 179
|
||||
AHT20_HUMIDITY: MessageType.ValueType # 180
|
||||
TVOC_INDEX: MessageType.ValueType # 181
|
||||
global___MessageType = MessageType
|
||||
On a change that breaks the other side (renumbered fields, changed
|
||||
semantics, removed messages), raise the value of CURRENT. Do not add
|
||||
another entry: this enum carries a single constant, not a history.
|
||||
"""
|
||||
|
||||
INTERDEVICE_VERSION_UNSPECIFIED: InterdeviceVersion.ValueType # 0
|
||||
INTERDEVICE_VERSION_CURRENT: InterdeviceVersion.ValueType # 2
|
||||
"""Never use 1: ping/pong were bools before the handshake existed, and a
|
||||
bool true is the same varint on the wire as the number 1, so firmware
|
||||
predating the handshake would pass it.
|
||||
"""
|
||||
global___InterdeviceVersion = InterdeviceVersion
|
||||
|
||||
class _FileOperation:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FileOperationEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_FileOperation.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
GET: _FileOperation.ValueType # 0
|
||||
POST: _FileOperation.ValueType # 1
|
||||
PUT: _FileOperation.ValueType # 2
|
||||
DELETE: _FileOperation.ValueType # 3
|
||||
|
||||
class FileOperation(_FileOperation, metaclass=_FileOperationEnumTypeWrapper):
|
||||
"""Defines the supported file operations"""
|
||||
|
||||
GET: FileOperation.ValueType # 0
|
||||
POST: FileOperation.ValueType # 1
|
||||
PUT: FileOperation.ValueType # 2
|
||||
DELETE: FileOperation.ValueType # 3
|
||||
global___FileOperation = FileOperation
|
||||
|
||||
class _FileStatus:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FileStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_FileStatus.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
FILE_UNSPECIFIED: _FileStatus.ValueType # 0
|
||||
FILE_OK: _FileStatus.ValueType # 1
|
||||
FILE_BUSY: _FileStatus.ValueType # 2
|
||||
"""Retry later: the co-processor is doing card maintenance (mount,
|
||||
free space scan) and cannot serve the request right now
|
||||
"""
|
||||
FILE_NO_CARD: _FileStatus.ValueType # 3
|
||||
FILE_NOT_FOUND: _FileStatus.ValueType # 4
|
||||
FILE_OFFSET_CONFLICT: _FileStatus.ValueType # 5
|
||||
"""PUT only: offset did not match the current end of the file. file_size
|
||||
carries the size the file actually has, so the writer can resync (or
|
||||
recognize its own chunk as already written after a lost response).
|
||||
"""
|
||||
FILE_IO_ERROR: _FileStatus.ValueType # 6
|
||||
FILE_NOT_A_FILE: _FileStatus.ValueType # 7
|
||||
"""path is a directory (GET) or not one (listing)"""
|
||||
|
||||
class FileStatus(_FileStatus, metaclass=_FileStatusEnumTypeWrapper):
|
||||
"""Outcome of a file or directory operation. The requester must be able to
|
||||
tell a transient condition from a definitive one: BUSY is worth another
|
||||
try, NOT_FOUND is not.
|
||||
"""
|
||||
|
||||
FILE_UNSPECIFIED: FileStatus.ValueType # 0
|
||||
FILE_OK: FileStatus.ValueType # 1
|
||||
FILE_BUSY: FileStatus.ValueType # 2
|
||||
"""Retry later: the co-processor is doing card maintenance (mount,
|
||||
free space scan) and cannot serve the request right now
|
||||
"""
|
||||
FILE_NO_CARD: FileStatus.ValueType # 3
|
||||
FILE_NOT_FOUND: FileStatus.ValueType # 4
|
||||
FILE_OFFSET_CONFLICT: FileStatus.ValueType # 5
|
||||
"""PUT only: offset did not match the current end of the file. file_size
|
||||
carries the size the file actually has, so the writer can resync (or
|
||||
recognize its own chunk as already written after a lost response).
|
||||
"""
|
||||
FILE_IO_ERROR: FileStatus.ValueType # 6
|
||||
FILE_NOT_A_FILE: FileStatus.ValueType # 7
|
||||
"""path is a directory (GET) or not one (listing)"""
|
||||
global___FileStatus = FileStatus
|
||||
|
||||
class _SdCommand:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _SdCommandEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_SdCommand.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
SD_COMMAND_UNSPECIFIED: _SdCommand.ValueType # 0
|
||||
SD_MOUNT: _SdCommand.ValueType # 1
|
||||
"""mount a card that is in the slot, also after an eject"""
|
||||
SD_EJECT: _SdCommand.ValueType # 2
|
||||
"""flush and release the card so it can be pulled safely"""
|
||||
SD_FORMAT: _SdCommand.ValueType # 3
|
||||
"""wipe the card and put a fresh FAT on it, then mount it"""
|
||||
|
||||
class SdCommand(_SdCommand, metaclass=_SdCommandEnumTypeWrapper):
|
||||
"""What to do with the SD card of the co-processor"""
|
||||
|
||||
SD_COMMAND_UNSPECIFIED: SdCommand.ValueType # 0
|
||||
SD_MOUNT: SdCommand.ValueType # 1
|
||||
"""mount a card that is in the slot, also after an eject"""
|
||||
SD_EJECT: SdCommand.ValueType # 2
|
||||
"""flush and release the card so it can be pulled safely"""
|
||||
SD_FORMAT: SdCommand.ValueType # 3
|
||||
"""wipe the card and put a fresh FAT on it, then mount it"""
|
||||
global___SdCommand = SdCommand
|
||||
|
||||
@typing.final
|
||||
class SensorData(google.protobuf.message.Message):
|
||||
class FileTransfer(google.protobuf.message.Message):
|
||||
"""Message for file operations"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
TYPE_FIELD_NUMBER: builtins.int
|
||||
FLOAT_VALUE_FIELD_NUMBER: builtins.int
|
||||
UINT32_VALUE_FIELD_NUMBER: builtins.int
|
||||
type: global___MessageType.ValueType
|
||||
"""The message type"""
|
||||
float_value: builtins.float
|
||||
uint32_value: builtins.int
|
||||
OPERATION_FIELD_NUMBER: builtins.int
|
||||
FILEPATH_FIELD_NUMBER: builtins.int
|
||||
FILEDATA_FIELD_NUMBER: builtins.int
|
||||
STATUS_FIELD_NUMBER: builtins.int
|
||||
MESSAGE_FIELD_NUMBER: builtins.int
|
||||
OFFSET_FIELD_NUMBER: builtins.int
|
||||
LENGTH_FIELD_NUMBER: builtins.int
|
||||
FILE_SIZE_FIELD_NUMBER: builtins.int
|
||||
operation: global___FileOperation.ValueType
|
||||
"""File operation (GET, POST, PUT, DELETE)"""
|
||||
filepath: builtins.str
|
||||
filedata: builtins.bytes
|
||||
status: global___FileStatus.ValueType
|
||||
"""Response: outcome of the operation"""
|
||||
message: builtins.str
|
||||
offset: builtins.int
|
||||
"""Byte offset of this chunk within the file (ranged GET/PUT)"""
|
||||
length: builtins.int
|
||||
"""GET request: number of bytes to read, 0 = max chunk size. A response
|
||||
carries at most the filedata max_size (see interdevice.options) per
|
||||
chunk; larger requests are truncated, visible in the filedata length.
|
||||
"""
|
||||
file_size: builtins.int
|
||||
"""GET response: total size of the file"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
type: global___MessageType.ValueType = ...,
|
||||
float_value: builtins.float = ...,
|
||||
uint32_value: builtins.int = ...,
|
||||
operation: global___FileOperation.ValueType = ...,
|
||||
filepath: builtins.str = ...,
|
||||
filedata: builtins.bytes = ...,
|
||||
status: global___FileStatus.ValueType = ...,
|
||||
message: builtins.str = ...,
|
||||
offset: builtins.int = ...,
|
||||
length: builtins.int = ...,
|
||||
file_size: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["data", b"data", "float_value", b"float_value", "uint32_value", b"uint32_value"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["data", b"data", "float_value", b"float_value", "type", b"type", "uint32_value", b"uint32_value"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["float_value", "uint32_value"] | None: ...
|
||||
def ClearField(self, field_name: typing.Literal["file_size", b"file_size", "filedata", b"filedata", "filepath", b"filepath", "length", b"length", "message", b"message", "offset", b"offset", "operation", b"operation", "status", b"status"]) -> None: ...
|
||||
|
||||
global___SensorData = SensorData
|
||||
global___FileTransfer = FileTransfer
|
||||
|
||||
@typing.final
|
||||
class DirectoryListing(google.protobuf.message.Message):
|
||||
"""Message for structured directory listing"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
DIRECTORY_FIELD_NUMBER: builtins.int
|
||||
FILENAMES_FIELD_NUMBER: builtins.int
|
||||
STATUS_FIELD_NUMBER: builtins.int
|
||||
MESSAGE_FIELD_NUMBER: builtins.int
|
||||
OFFSET_FIELD_NUMBER: builtins.int
|
||||
TOTAL_COUNT_FIELD_NUMBER: builtins.int
|
||||
directory: builtins.str
|
||||
status: global___FileStatus.ValueType
|
||||
"""Response: outcome of the operation"""
|
||||
message: builtins.str
|
||||
offset: builtins.int
|
||||
"""Request: skip this many entries (paging)"""
|
||||
total_count: builtins.int
|
||||
"""Response: total number of entries in the directory"""
|
||||
@property
|
||||
def filenames(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]:
|
||||
"""One page of entry names, full FAT LFN length. Subdirectories carry a
|
||||
trailing slash. Note that a name whose directory prefix pushes the
|
||||
combined path past the FileTransfer.filepath limit cannot round-trip.
|
||||
Page size is the max_count in interdevice.options; page through with
|
||||
offset and total_count.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
directory: builtins.str = ...,
|
||||
filenames: collections.abc.Iterable[builtins.str] | None = ...,
|
||||
status: global___FileStatus.ValueType = ...,
|
||||
message: builtins.str = ...,
|
||||
offset: builtins.int = ...,
|
||||
total_count: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["directory", b"directory", "filenames", b"filenames", "message", b"message", "offset", b"offset", "status", b"status", "total_count", b"total_count"]) -> None: ...
|
||||
|
||||
global___DirectoryListing = DirectoryListing
|
||||
|
||||
@typing.final
|
||||
class I2CTransaction(google.protobuf.message.Message):
|
||||
"""A single I2C transaction: an optional write followed by an optional
|
||||
read with repeated start, matching the TwoWire usage of sensor drivers
|
||||
(beginTransmission/write.../endTransmission(false)/requestFrom)
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
ADDRESS_FIELD_NUMBER: builtins.int
|
||||
WRITE_DATA_FIELD_NUMBER: builtins.int
|
||||
READ_LEN_FIELD_NUMBER: builtins.int
|
||||
address: builtins.int
|
||||
"""7-bit device address"""
|
||||
write_data: builtins.bytes
|
||||
read_len: builtins.int
|
||||
"""Number of bytes to read after the write, 0 = write-only. Bounded by
|
||||
the read_data max_size of I2CResult (see interdevice.options); larger
|
||||
requests are truncated, visible in the returned byte count.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
address: builtins.int = ...,
|
||||
write_data: builtins.bytes = ...,
|
||||
read_len: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["address", b"address", "read_len", b"read_len", "write_data", b"write_data"]) -> None: ...
|
||||
|
||||
global___I2CTransaction = I2CTransaction
|
||||
|
||||
@typing.final
|
||||
class SdCardInfo(google.protobuf.message.Message):
|
||||
"""SD card statistics"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _CardType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _CardTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SdCardInfo._CardType.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
NONE: SdCardInfo._CardType.ValueType # 0
|
||||
MMC: SdCardInfo._CardType.ValueType # 1
|
||||
SD: SdCardInfo._CardType.ValueType # 2
|
||||
SDHC: SdCardInfo._CardType.ValueType # 3
|
||||
SDXC: SdCardInfo._CardType.ValueType # 4
|
||||
UNKNOWN_CARD: SdCardInfo._CardType.ValueType # 5
|
||||
|
||||
class CardType(_CardType, metaclass=_CardTypeEnumTypeWrapper): ...
|
||||
NONE: SdCardInfo.CardType.ValueType # 0
|
||||
MMC: SdCardInfo.CardType.ValueType # 1
|
||||
SD: SdCardInfo.CardType.ValueType # 2
|
||||
SDHC: SdCardInfo.CardType.ValueType # 3
|
||||
SDXC: SdCardInfo.CardType.ValueType # 4
|
||||
UNKNOWN_CARD: SdCardInfo.CardType.ValueType # 5
|
||||
|
||||
class _FatType:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FatTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SdCardInfo._FatType.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
UNKNOWN_FAT: SdCardInfo._FatType.ValueType # 0
|
||||
FAT16: SdCardInfo._FatType.ValueType # 1
|
||||
FAT32: SdCardInfo._FatType.ValueType # 2
|
||||
EXFAT: SdCardInfo._FatType.ValueType # 3
|
||||
|
||||
class FatType(_FatType, metaclass=_FatTypeEnumTypeWrapper): ...
|
||||
UNKNOWN_FAT: SdCardInfo.FatType.ValueType # 0
|
||||
FAT16: SdCardInfo.FatType.ValueType # 1
|
||||
FAT32: SdCardInfo.FatType.ValueType # 2
|
||||
EXFAT: SdCardInfo.FatType.ValueType # 3
|
||||
|
||||
PRESENT_FIELD_NUMBER: builtins.int
|
||||
CARD_TYPE_FIELD_NUMBER: builtins.int
|
||||
FAT_TYPE_FIELD_NUMBER: builtins.int
|
||||
CARD_SIZE_FIELD_NUMBER: builtins.int
|
||||
USED_BYTES_FIELD_NUMBER: builtins.int
|
||||
FREE_BYTES_FIELD_NUMBER: builtins.int
|
||||
STATS_VALID_FIELD_NUMBER: builtins.int
|
||||
BUSY_FIELD_NUMBER: builtins.int
|
||||
UNFORMATTED_FIELD_NUMBER: builtins.int
|
||||
present: builtins.bool
|
||||
"""Card initialized and usable. False while `busy` is set does not mean
|
||||
there is no card: the co-processor does not know yet.
|
||||
"""
|
||||
card_type: global___SdCardInfo.CardType.ValueType
|
||||
fat_type: global___SdCardInfo.FatType.ValueType
|
||||
card_size: builtins.int
|
||||
"""Filesystem size in bytes"""
|
||||
used_bytes: builtins.int
|
||||
"""Used bytes (may be expensive to compute on FAT32)"""
|
||||
free_bytes: builtins.int
|
||||
"""Free bytes"""
|
||||
stats_valid: builtins.bool
|
||||
"""used_bytes/free_bytes are only meaningful when true: the scan behind
|
||||
them runs in the background after mount and can take a while, and a
|
||||
full card is otherwise indistinguishable from a scan in progress
|
||||
"""
|
||||
busy: builtins.bool
|
||||
"""The co-processor is mounting a card right now, so whether one is
|
||||
present is not decided yet. Ask again rather than concluding the slot
|
||||
is empty.
|
||||
"""
|
||||
unformatted: builtins.bool
|
||||
"""A card answers in the slot but carries no filesystem that could be
|
||||
mounted (present is false then). Formatting it makes it usable.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
present: builtins.bool = ...,
|
||||
card_type: global___SdCardInfo.CardType.ValueType = ...,
|
||||
fat_type: global___SdCardInfo.FatType.ValueType = ...,
|
||||
card_size: builtins.int = ...,
|
||||
used_bytes: builtins.int = ...,
|
||||
free_bytes: builtins.int = ...,
|
||||
stats_valid: builtins.bool = ...,
|
||||
busy: builtins.bool = ...,
|
||||
unformatted: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["busy", b"busy", "card_size", b"card_size", "card_type", b"card_type", "fat_type", b"fat_type", "free_bytes", b"free_bytes", "present", b"present", "stats_valid", b"stats_valid", "unformatted", b"unformatted", "used_bytes", b"used_bytes"]) -> None: ...
|
||||
|
||||
global___SdCardInfo = SdCardInfo
|
||||
|
||||
@typing.final
|
||||
class I2CResult(google.protobuf.message.Message):
|
||||
"""Result of an I2CTransaction"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _Status:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _StatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[I2CResult._Status.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
UNSPECIFIED: I2CResult._Status.ValueType # 0
|
||||
"""Never sent: an all-defaults (e.g. accidentally empty) message must
|
||||
not decode as a successful transaction
|
||||
"""
|
||||
OK: I2CResult._Status.ValueType # 1
|
||||
NACK_ADDRESS: I2CResult._Status.ValueType # 2
|
||||
NACK_DATA: I2CResult._Status.ValueType # 3
|
||||
ERROR: I2CResult._Status.ValueType # 4
|
||||
|
||||
class Status(_Status, metaclass=_StatusEnumTypeWrapper): ...
|
||||
UNSPECIFIED: I2CResult.Status.ValueType # 0
|
||||
"""Never sent: an all-defaults (e.g. accidentally empty) message must
|
||||
not decode as a successful transaction
|
||||
"""
|
||||
OK: I2CResult.Status.ValueType # 1
|
||||
NACK_ADDRESS: I2CResult.Status.ValueType # 2
|
||||
NACK_DATA: I2CResult.Status.ValueType # 3
|
||||
ERROR: I2CResult.Status.ValueType # 4
|
||||
|
||||
STATUS_FIELD_NUMBER: builtins.int
|
||||
READ_DATA_FIELD_NUMBER: builtins.int
|
||||
status: global___I2CResult.Status.ValueType
|
||||
read_data: builtins.bytes
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
status: global___I2CResult.Status.ValueType = ...,
|
||||
read_data: builtins.bytes = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["read_data", b"read_data", "status", b"status"]) -> None: ...
|
||||
|
||||
global___I2CResult = I2CResult
|
||||
|
||||
@typing.final
|
||||
class InterdeviceMessage(google.protobuf.message.Message):
|
||||
"""Main message for interdevice communication"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
ID_FIELD_NUMBER: builtins.int
|
||||
NMEA_FIELD_NUMBER: builtins.int
|
||||
SENSOR_FIELD_NUMBER: builtins.int
|
||||
BEEP_FIELD_NUMBER: builtins.int
|
||||
I2C_TRANSACTION_FIELD_NUMBER: builtins.int
|
||||
I2C_RESULT_FIELD_NUMBER: builtins.int
|
||||
I2C_SCAN_FIELD_NUMBER: builtins.int
|
||||
I2C_SCAN_RESULT_FIELD_NUMBER: builtins.int
|
||||
FILE_TRANSFER_FIELD_NUMBER: builtins.int
|
||||
DIRECTORY_LISTING_FIELD_NUMBER: builtins.int
|
||||
GET_SD_INFO_FIELD_NUMBER: builtins.int
|
||||
SD_INFO_FIELD_NUMBER: builtins.int
|
||||
PING_FIELD_NUMBER: builtins.int
|
||||
PONG_FIELD_NUMBER: builtins.int
|
||||
NACK_FIELD_NUMBER: builtins.int
|
||||
SD_COMMAND_FIELD_NUMBER: builtins.int
|
||||
id: builtins.int
|
||||
"""Correlates a response with its request: responses echo the id of the
|
||||
request they answer. 0 for unsolicited messages (e.g. the nmea stream).
|
||||
"""
|
||||
nmea: builtins.str
|
||||
beep: builtins.int
|
||||
i2c_scan: builtins.bool
|
||||
"""Request: scan the secondary I2C bus"""
|
||||
i2c_scan_result: builtins.bytes
|
||||
get_sd_info: builtins.bool
|
||||
"""Request: SD card statistics"""
|
||||
ping: global___InterdeviceVersion.ValueType
|
||||
"""Link liveness probe and version handshake. The receiver answers ping
|
||||
with pong, echoing the id. Touches no peripherals, so it works with
|
||||
nothing attached. Both carry the version the sender speaks; a peer
|
||||
that answers with a different one speaks another protocol and must
|
||||
not be used.
|
||||
"""
|
||||
pong: global___InterdeviceVersion.ValueType
|
||||
nack: builtins.bool
|
||||
"""Response: the request could not be decoded or is of an unhandled
|
||||
type, so the requester fails fast instead of burning its timeout.
|
||||
Echoes the id when known, 0 when the frame was undecodable. Never
|
||||
sent in reaction to a nack.
|
||||
"""
|
||||
sd_command: global___SdCommand.ValueType
|
||||
"""Request: mount the card, or release it so it can be pulled safely. The
|
||||
co-processor answers with sd_info. Without an eject the card is mounted
|
||||
on its own and kept mounted; after one it stays released until a mount
|
||||
is asked for.
|
||||
"""
|
||||
@property
|
||||
def sensor(self) -> global___SensorData: ...
|
||||
def i2c_transaction(self) -> global___I2CTransaction: ...
|
||||
@property
|
||||
def i2c_result(self) -> global___I2CResult: ...
|
||||
@property
|
||||
def file_transfer(self) -> global___FileTransfer: ...
|
||||
@property
|
||||
def directory_listing(self) -> global___DirectoryListing: ...
|
||||
@property
|
||||
def sd_info(self) -> global___SdCardInfo:
|
||||
"""Response"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: builtins.int = ...,
|
||||
nmea: builtins.str = ...,
|
||||
sensor: global___SensorData | None = ...,
|
||||
beep: builtins.int = ...,
|
||||
i2c_transaction: global___I2CTransaction | None = ...,
|
||||
i2c_result: global___I2CResult | None = ...,
|
||||
i2c_scan: builtins.bool = ...,
|
||||
i2c_scan_result: builtins.bytes = ...,
|
||||
file_transfer: global___FileTransfer | None = ...,
|
||||
directory_listing: global___DirectoryListing | None = ...,
|
||||
get_sd_info: builtins.bool = ...,
|
||||
sd_info: global___SdCardInfo | None = ...,
|
||||
ping: global___InterdeviceVersion.ValueType = ...,
|
||||
pong: global___InterdeviceVersion.ValueType = ...,
|
||||
nack: builtins.bool = ...,
|
||||
sd_command: global___SdCommand.ValueType = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["data", b"data", "nmea", b"nmea", "sensor", b"sensor"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["data", b"data", "nmea", b"nmea", "sensor", b"sensor"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["nmea", "sensor"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["beep", b"beep", "data", b"data", "directory_listing", b"directory_listing", "file_transfer", b"file_transfer", "get_sd_info", b"get_sd_info", "i2c_result", b"i2c_result", "i2c_scan", b"i2c_scan", "i2c_scan_result", b"i2c_scan_result", "i2c_transaction", b"i2c_transaction", "nack", b"nack", "nmea", b"nmea", "ping", b"ping", "pong", b"pong", "sd_command", b"sd_command", "sd_info", b"sd_info"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["beep", b"beep", "data", b"data", "directory_listing", b"directory_listing", "file_transfer", b"file_transfer", "get_sd_info", b"get_sd_info", "i2c_result", b"i2c_result", "i2c_scan", b"i2c_scan", "i2c_scan_result", b"i2c_scan_result", "i2c_transaction", b"i2c_transaction", "id", b"id", "nack", b"nack", "nmea", b"nmea", "ping", b"ping", "pong", b"pong", "sd_command", b"sd_command", "sd_info", b"sd_info"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["nmea", "beep", "i2c_transaction", "i2c_result", "i2c_scan", "i2c_scan_result", "file_transfer", "directory_listing", "get_sd_info", "sd_info", "ping", "pong", "nack", "sd_command"] | None: ...
|
||||
|
||||
global___InterdeviceMessage = InterdeviceMessage
|
||||
Generated
+2
-2
@@ -15,7 +15,7 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
|
||||
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xcf\t\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12L\n\rstatusmessage\x18\x0f \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.StatusMessageConfig\x12U\n\x12traffic_management\x18\x10 \x01(\x0b\x32\x39.meshtastic.protobuf.ModuleConfig.TrafficManagementConfig\x12\x38\n\x03tak\x18\x11 \x01(\x0b\x32+.meshtastic.protobuf.ModuleConfig.TAKConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBe\n\x14org.meshtastic.protoB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\x98\n\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12L\n\rstatusmessage\x18\x0f \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.StatusMessageConfig\x12U\n\x12traffic_management\x18\x10 \x01(\x0b\x32\x39.meshtastic.protobuf.ModuleConfig.TrafficManagementConfig\x12\x38\n\x03tak\x18\x11 \x01(\x0b\x32+.meshtastic.protobuf.ModuleConfig.TAKConfig\x12G\n\x0bmesh_beacon\x18\x12 \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.MeshBeaconConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBe\n\x14org.meshtastic.protoB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -26,5 +26,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
_globals['_LOCALCONFIG']._serialized_start=136
|
||||
_globals['_LOCALCONFIG']._serialized_end=642
|
||||
_globals['_LOCALMODULECONFIG']._serialized_start=645
|
||||
_globals['_LOCALMODULECONFIG']._serialized_end=1876
|
||||
_globals['_LOCALMODULECONFIG']._serialized_end=1949
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Generated
+10
-2
@@ -122,6 +122,7 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
||||
STATUSMESSAGE_FIELD_NUMBER: builtins.int
|
||||
TRAFFIC_MANAGEMENT_FIELD_NUMBER: builtins.int
|
||||
TAK_FIELD_NUMBER: builtins.int
|
||||
MESH_BEACON_FIELD_NUMBER: builtins.int
|
||||
VERSION_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
@@ -225,6 +226,12 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
||||
TAK Config
|
||||
"""
|
||||
|
||||
@property
|
||||
def mesh_beacon(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.MeshBeaconConfig:
|
||||
"""
|
||||
MeshBeacon Config
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -244,9 +251,10 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
||||
statusmessage: meshtastic.protobuf.module_config_pb2.ModuleConfig.StatusMessageConfig | None = ...,
|
||||
traffic_management: meshtastic.protobuf.module_config_pb2.ModuleConfig.TrafficManagementConfig | None = ...,
|
||||
tak: meshtastic.protobuf.module_config_pb2.ModuleConfig.TAKConfig | None = ...,
|
||||
mesh_beacon: meshtastic.protobuf.module_config_pb2.ModuleConfig.MeshBeaconConfig | None = ...,
|
||||
version: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management", "version", b"version"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mesh_beacon", b"mesh_beacon", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mesh_beacon", b"mesh_beacon", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management", "version", b"version"]) -> None: ...
|
||||
|
||||
global___LocalModuleConfig = LocalModuleConfig
|
||||
Generated
+31
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: meshtastic/protobuf/mesh_beacon.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__pb2
|
||||
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/mesh_beacon.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a meshtastic/protobuf/nanopb.proto\"\x8a\x02\n\nMeshBeacon\x12\x16\n\x07message\x18\x01 \x01(\tB\x05\x92?\x02\x08\x65\x12;\n\roffer_channel\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12G\n\x0coffer_region\x18\x03 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12M\n\x0coffer_preset\x18\x04 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPresetH\x00\x88\x01\x01\x42\x0f\n\r_offer_presetBf\n\x14org.meshtastic.protoB\x10MeshBeaconProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mesh_beacon_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020MeshBeaconProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_MESHBEACON.fields_by_name['message']._options = None
|
||||
_MESHBEACON.fields_by_name['message']._serialized_options = b'\222?\002\010e'
|
||||
_globals['_MESHBEACON']._serialized_start=166
|
||||
_globals['_MESHBEACON']._serialized_end=432
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Generated
+62
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
@generated by mypy-protobuf. Do not edit manually!
|
||||
isort:skip_file
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import google.protobuf.descriptor
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.channel_pb2
|
||||
import meshtastic.protobuf.config_pb2
|
||||
import typing
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
@typing.final
|
||||
class MeshBeacon(google.protobuf.message.Message):
|
||||
"""
|
||||
Payload for MESH_BEACON_APP packets.
|
||||
Periodically broadcast by nodes in beacon mode.
|
||||
Listeners deliver the text message to the local inbox and cache any offered
|
||||
channel/preset for the client app to act on — the firmware never auto-applies them.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
MESSAGE_FIELD_NUMBER: builtins.int
|
||||
OFFER_CHANNEL_FIELD_NUMBER: builtins.int
|
||||
OFFER_REGION_FIELD_NUMBER: builtins.int
|
||||
OFFER_PRESET_FIELD_NUMBER: builtins.int
|
||||
message: builtins.str
|
||||
"""
|
||||
Human-readable beacon message. Max 100 bytes enforced by firmware on send.
|
||||
"""
|
||||
offer_region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType
|
||||
"""
|
||||
Optional region being advertised alongside offer_preset.
|
||||
"""
|
||||
offer_preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType
|
||||
"""
|
||||
Optional modem preset being advertised.
|
||||
Combined with offer_region, tells a client "there is a mesh on this preset/region".
|
||||
"""
|
||||
@property
|
||||
def offer_channel(self) -> meshtastic.protobuf.channel_pb2.ChannelSettings:
|
||||
"""
|
||||
Optional channel (name + PSK) being advertised to listening clients.
|
||||
A client app may offer to switch the user to this channel; firmware never applies it automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
message: builtins.str = ...,
|
||||
offer_channel: meshtastic.protobuf.channel_pb2.ChannelSettings | None = ...,
|
||||
offer_region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType = ...,
|
||||
offer_preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_offer_preset", b"_offer_preset", "offer_channel", b"offer_channel", "offer_preset", b"offer_preset"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_offer_preset", b"_offer_preset", "message", b"message", "offer_channel", b"offer_channel", "offer_preset", b"offer_preset", "offer_region", b"offer_region"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_offer_preset", b"_offer_preset"]) -> typing.Literal["offer_preset"] | None: ...
|
||||
|
||||
global___MeshBeacon = MeshBeacon
|
||||
Generated
+104
-86
File diff suppressed because one or more lines are too long.
Generated
+384
-25
@@ -142,13 +142,13 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
"""
|
||||
RAK11310 (RP2040 + SX1262)
|
||||
"""
|
||||
SENSELORA_RP2040: _HardwareModel.ValueType # 27
|
||||
MAKERFABS_TRACKER: _HardwareModel.ValueType # 27
|
||||
"""
|
||||
Makerfabs SenseLoRA Receiver (RP2040 + RFM96)
|
||||
Makerfabs Tracker Reserved
|
||||
"""
|
||||
SENSELORA_S3: _HardwareModel.ValueType # 28
|
||||
MAKERFABS_RESERVED: _HardwareModel.ValueType # 28
|
||||
"""
|
||||
Makerfabs SenseLoRA Industrial Monitor (ESP32-S3 + RFM96)
|
||||
Makerfabs Reserved
|
||||
"""
|
||||
CANARYONE: _HardwareModel.ValueType # 29
|
||||
"""
|
||||
@@ -563,9 +563,9 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
"""
|
||||
Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen.
|
||||
"""
|
||||
TRACKER_T1000_E_PRO: _HardwareModel.ValueType # 128
|
||||
MESH_TRACKER_X1: _HardwareModel.ValueType # 128
|
||||
"""
|
||||
Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio,
|
||||
Seeed studio Mesh Tracker X1card. NRF52840 w/ LR2021 radio,
|
||||
GPS, button, buzzer, and sensors.
|
||||
"""
|
||||
THINKNODE_M7: _HardwareModel.ValueType # 129
|
||||
@@ -610,6 +610,30 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
"""
|
||||
Meshnology W10
|
||||
"""
|
||||
HELTEC_RC32: _HardwareModel.ValueType # 141
|
||||
"""
|
||||
Heltec ESP32S3 + SX1262
|
||||
"""
|
||||
HELTEC_RC52: _HardwareModel.ValueType # 142
|
||||
"""
|
||||
Heltec NRF52840 + SX1262
|
||||
"""
|
||||
HELTEC_RCC6: _HardwareModel.ValueType # 143
|
||||
"""
|
||||
Heltec ESP32C6 + SX1262
|
||||
"""
|
||||
SEEED_WIO_TRACKER_L1_PRO_1W: _HardwareModel.ValueType # 144
|
||||
"""
|
||||
Seeed Wio Tracker L1 Pro 1W, nRF52840 + SX1262 with 1 W external PA
|
||||
"""
|
||||
MESHNOLOGY_W12: _HardwareModel.ValueType # 145
|
||||
"""
|
||||
Meshnology W12
|
||||
"""
|
||||
MESHPAGER_X2: _HardwareModel.ValueType # 146
|
||||
"""
|
||||
Seeed Studio MeshPager X2
|
||||
"""
|
||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -735,13 +759,13 @@ RAK11310: HardwareModel.ValueType # 26
|
||||
"""
|
||||
RAK11310 (RP2040 + SX1262)
|
||||
"""
|
||||
SENSELORA_RP2040: HardwareModel.ValueType # 27
|
||||
MAKERFABS_TRACKER: HardwareModel.ValueType # 27
|
||||
"""
|
||||
Makerfabs SenseLoRA Receiver (RP2040 + RFM96)
|
||||
Makerfabs Tracker Reserved
|
||||
"""
|
||||
SENSELORA_S3: HardwareModel.ValueType # 28
|
||||
MAKERFABS_RESERVED: HardwareModel.ValueType # 28
|
||||
"""
|
||||
Makerfabs SenseLoRA Industrial Monitor (ESP32-S3 + RFM96)
|
||||
Makerfabs Reserved
|
||||
"""
|
||||
CANARYONE: HardwareModel.ValueType # 29
|
||||
"""
|
||||
@@ -1156,9 +1180,9 @@ HELTEC_MESH_NODE_T096: HardwareModel.ValueType # 127
|
||||
"""
|
||||
Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen.
|
||||
"""
|
||||
TRACKER_T1000_E_PRO: HardwareModel.ValueType # 128
|
||||
MESH_TRACKER_X1: HardwareModel.ValueType # 128
|
||||
"""
|
||||
Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio,
|
||||
Seeed studio Mesh Tracker X1card. NRF52840 w/ LR2021 radio,
|
||||
GPS, button, buzzer, and sensors.
|
||||
"""
|
||||
THINKNODE_M7: HardwareModel.ValueType # 129
|
||||
@@ -1203,6 +1227,30 @@ MESHNOLOGY_W10: HardwareModel.ValueType # 140
|
||||
"""
|
||||
Meshnology W10
|
||||
"""
|
||||
HELTEC_RC32: HardwareModel.ValueType # 141
|
||||
"""
|
||||
Heltec ESP32S3 + SX1262
|
||||
"""
|
||||
HELTEC_RC52: HardwareModel.ValueType # 142
|
||||
"""
|
||||
Heltec NRF52840 + SX1262
|
||||
"""
|
||||
HELTEC_RCC6: HardwareModel.ValueType # 143
|
||||
"""
|
||||
Heltec ESP32C6 + SX1262
|
||||
"""
|
||||
SEEED_WIO_TRACKER_L1_PRO_1W: HardwareModel.ValueType # 144
|
||||
"""
|
||||
Seeed Wio Tracker L1 Pro 1W, nRF52840 + SX1262 with 1 W external PA
|
||||
"""
|
||||
MESHNOLOGY_W12: HardwareModel.ValueType # 145
|
||||
"""
|
||||
Meshnology W12
|
||||
"""
|
||||
MESHPAGER_X2: HardwareModel.ValueType # 146
|
||||
"""
|
||||
Seeed Studio MeshPager X2
|
||||
"""
|
||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1414,6 +1462,18 @@ class _FirmwareEditionEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper
|
||||
"""
|
||||
Hamvention, the Dayton amateur radio convention
|
||||
"""
|
||||
FAB: _FirmwareEdition.ValueType # 20
|
||||
"""
|
||||
FAB, the international Fab Lab digital fabrication conference
|
||||
"""
|
||||
DRAGON_CON: _FirmwareEdition.ValueType # 21
|
||||
"""
|
||||
Dragon Con, the yearly pop culture convention in Atlanta, GA
|
||||
"""
|
||||
CCC: _FirmwareEdition.ValueType # 22
|
||||
"""
|
||||
Chaos Communication Congress, the hacker conference held yearly in Germany
|
||||
"""
|
||||
DIY_EDITION: _FirmwareEdition.ValueType # 127
|
||||
"""
|
||||
Placeholder for DIY and unofficial events
|
||||
@@ -1449,6 +1509,18 @@ HAMVENTION: FirmwareEdition.ValueType # 19
|
||||
"""
|
||||
Hamvention, the Dayton amateur radio convention
|
||||
"""
|
||||
FAB: FirmwareEdition.ValueType # 20
|
||||
"""
|
||||
FAB, the international Fab Lab digital fabrication conference
|
||||
"""
|
||||
DRAGON_CON: FirmwareEdition.ValueType # 21
|
||||
"""
|
||||
Dragon Con, the yearly pop culture convention in Atlanta, GA
|
||||
"""
|
||||
CCC: FirmwareEdition.ValueType # 22
|
||||
"""
|
||||
Chaos Communication Congress, the hacker conference held yearly in Germany
|
||||
"""
|
||||
DIY_EDITION: FirmwareEdition.ValueType # 127
|
||||
"""
|
||||
Placeholder for DIY and unofficial events
|
||||
@@ -1797,7 +1869,7 @@ class Position(google.protobuf.message.Message):
|
||||
"""
|
||||
ground_speed: builtins.int
|
||||
"""
|
||||
Ground speed in m/s and True North TRACK in 1/100 degrees
|
||||
Ground speed in km/h and True North TRACK in 1/100 degrees
|
||||
Clarification of terms:
|
||||
- "track" is the direction of motion (measured in horizontal plane)
|
||||
- "heading" is where the fuselage points (measured in horizontal plane)
|
||||
@@ -1932,6 +2004,9 @@ class User(google.protobuf.message.Message):
|
||||
long_name: builtins.str
|
||||
"""
|
||||
A full name for this user, i.e. "Kevin Hester"
|
||||
Limited to 24 bytes of UTF-8: longer names are accepted from senders
|
||||
built against the older 39-byte limit, but devices truncate them before
|
||||
storing or rebroadcasting. Clients should enforce 24 bytes in their UI.
|
||||
"""
|
||||
short_name: builtins.str
|
||||
"""
|
||||
@@ -2263,6 +2338,7 @@ class Data(google.protobuf.message.Message):
|
||||
REPLY_ID_FIELD_NUMBER: builtins.int
|
||||
EMOJI_FIELD_NUMBER: builtins.int
|
||||
BITFIELD_FIELD_NUMBER: builtins.int
|
||||
XEDDSA_SIGNATURE_FIELD_NUMBER: builtins.int
|
||||
portnum: meshtastic.protobuf.portnums_pb2.PortNum.ValueType
|
||||
"""
|
||||
Formerly named typ and of type Type
|
||||
@@ -2309,6 +2385,10 @@ class Data(google.protobuf.message.Message):
|
||||
"""
|
||||
Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT.
|
||||
"""
|
||||
xeddsa_signature: builtins.bytes
|
||||
"""
|
||||
XEdDSA signature for the payload
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -2321,9 +2401,10 @@ class Data(google.protobuf.message.Message):
|
||||
reply_id: builtins.int = ...,
|
||||
emoji: builtins.int = ...,
|
||||
bitfield: builtins.int | None = ...,
|
||||
xeddsa_signature: builtins.bytes = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_bitfield", b"_bitfield", "bitfield", b"bitfield"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_bitfield", b"_bitfield", "bitfield", b"bitfield", "dest", b"dest", "emoji", b"emoji", "payload", b"payload", "portnum", b"portnum", "reply_id", b"reply_id", "request_id", b"request_id", "source", b"source", "want_response", b"want_response"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["_bitfield", b"_bitfield", "bitfield", b"bitfield", "dest", b"dest", "emoji", b"emoji", "payload", b"payload", "portnum", b"portnum", "reply_id", b"reply_id", "request_id", b"request_id", "source", b"source", "want_response", b"want_response", "xeddsa_signature", b"xeddsa_signature"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_bitfield", b"_bitfield"]) -> typing.Literal["bitfield"] | None: ...
|
||||
|
||||
global___Data = Data
|
||||
@@ -2620,6 +2701,51 @@ class RemoteShell(google.protobuf.message.Message):
|
||||
|
||||
global___RemoteShell = RemoteShell
|
||||
|
||||
@typing.final
|
||||
class BoundingBox(google.protobuf.message.Message):
|
||||
"""
|
||||
A rectangular, axis-aligned geographic bounding box.
|
||||
Used to define a rectangular geofence region for a Waypoint.
|
||||
Fields are ordered west, south, east, north to match the standard bounding box
|
||||
convention used by GeoJSON and PMTiles (min longitude, min latitude, max longitude, max latitude),
|
||||
so the box can drive an offline map extract directly.
|
||||
All coordinates are in degrees scaled by 1e-7 (same convention as Position and Waypoint).
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
LONGITUDE_WEST_I_FIELD_NUMBER: builtins.int
|
||||
LATITUDE_SOUTH_I_FIELD_NUMBER: builtins.int
|
||||
LONGITUDE_EAST_I_FIELD_NUMBER: builtins.int
|
||||
LATITUDE_NORTH_I_FIELD_NUMBER: builtins.int
|
||||
longitude_west_i: builtins.int
|
||||
"""
|
||||
Western edge of the box - minimum longitude (south-west corner)
|
||||
"""
|
||||
latitude_south_i: builtins.int
|
||||
"""
|
||||
Southern edge of the box - minimum latitude (south-west corner)
|
||||
"""
|
||||
longitude_east_i: builtins.int
|
||||
"""
|
||||
Eastern edge of the box - maximum longitude (north-east corner)
|
||||
"""
|
||||
latitude_north_i: builtins.int
|
||||
"""
|
||||
Northern edge of the box - maximum latitude (north-east corner)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
longitude_west_i: builtins.int = ...,
|
||||
latitude_south_i: builtins.int = ...,
|
||||
longitude_east_i: builtins.int = ...,
|
||||
latitude_north_i: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["latitude_north_i", b"latitude_north_i", "latitude_south_i", b"latitude_south_i", "longitude_east_i", b"longitude_east_i", "longitude_west_i", b"longitude_west_i"]) -> None: ...
|
||||
|
||||
global___BoundingBox = BoundingBox
|
||||
|
||||
@typing.final
|
||||
class Waypoint(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -2636,6 +2762,11 @@ class Waypoint(google.protobuf.message.Message):
|
||||
NAME_FIELD_NUMBER: builtins.int
|
||||
DESCRIPTION_FIELD_NUMBER: builtins.int
|
||||
ICON_FIELD_NUMBER: builtins.int
|
||||
GEOFENCE_RADIUS_FIELD_NUMBER: builtins.int
|
||||
BOUNDING_BOX_FIELD_NUMBER: builtins.int
|
||||
NOTIFY_ON_ENTER_FIELD_NUMBER: builtins.int
|
||||
NOTIFY_ON_EXIT_FIELD_NUMBER: builtins.int
|
||||
NOTIFY_FAVORITES_ONLY_FIELD_NUMBER: builtins.int
|
||||
id: builtins.int
|
||||
"""
|
||||
Id of the waypoint
|
||||
@@ -2669,6 +2800,36 @@ class Waypoint(google.protobuf.message.Message):
|
||||
"""
|
||||
Designator icon for the waypoint in the form of a unicode emoji
|
||||
"""
|
||||
geofence_radius: builtins.int
|
||||
"""
|
||||
If greater than zero, defines a circular geofence centred on this waypoint's
|
||||
location (latitude_i / longitude_i) with this radius in meters.
|
||||
Zero means the waypoint has no circular geofence.
|
||||
"""
|
||||
notify_on_enter: builtins.bool
|
||||
"""
|
||||
If true, a notification should be raised when a tracked node enters this
|
||||
waypoint's geofence (the circular radius and/or the bounding box).
|
||||
"""
|
||||
notify_on_exit: builtins.bool
|
||||
"""
|
||||
If true, a notification should be raised when a tracked node exits this
|
||||
waypoint's geofence (the circular radius and/or the bounding box).
|
||||
"""
|
||||
notify_favorites_only: builtins.bool
|
||||
"""
|
||||
If true, only raise geofence enter/exit notifications for nodes that are
|
||||
marked as favorites on the receiving device. Applies to both notify_on_enter
|
||||
and notify_on_exit. Favorite status is resolved locally per receiver, so the
|
||||
same waypoint alerts each node only for its own favorites.
|
||||
"""
|
||||
@property
|
||||
def bounding_box(self) -> global___BoundingBox:
|
||||
"""
|
||||
Optional rectangular geofence region for this waypoint.
|
||||
May be used instead of, or in addition to, geofence_radius.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -2680,9 +2841,16 @@ class Waypoint(google.protobuf.message.Message):
|
||||
name: builtins.str = ...,
|
||||
description: builtins.str = ...,
|
||||
icon: builtins.int = ...,
|
||||
geofence_radius: builtins.int = ...,
|
||||
bounding_box: global___BoundingBox | None = ...,
|
||||
notify_on_enter: builtins.bool = ...,
|
||||
notify_on_exit: builtins.bool = ...,
|
||||
notify_favorites_only: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "latitude_i", b"latitude_i", "longitude_i", b"longitude_i"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "description", b"description", "expire", b"expire", "icon", b"icon", "id", b"id", "latitude_i", b"latitude_i", "locked_to", b"locked_to", "longitude_i", b"longitude_i", "name", b"name"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_bounding_box", b"_bounding_box", "_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "bounding_box", b"bounding_box", "latitude_i", b"latitude_i", "longitude_i", b"longitude_i"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_bounding_box", b"_bounding_box", "_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "bounding_box", b"bounding_box", "description", b"description", "expire", b"expire", "geofence_radius", b"geofence_radius", "icon", b"icon", "id", b"id", "latitude_i", b"latitude_i", "locked_to", b"locked_to", "longitude_i", b"longitude_i", "name", b"name", "notify_favorites_only", b"notify_favorites_only", "notify_on_enter", b"notify_on_enter", "notify_on_exit", b"notify_on_exit"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_bounding_box", b"_bounding_box"]) -> typing.Literal["bounding_box"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_latitude_i", b"_latitude_i"]) -> typing.Literal["latitude_i"] | None: ...
|
||||
@typing.overload
|
||||
@@ -2954,6 +3122,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
"""
|
||||
Arrived via API connection
|
||||
"""
|
||||
TRANSPORT_UNICAST_UDP: MeshPacket._TransportMechanism.ValueType # 8
|
||||
"""
|
||||
Arrived via Unicast UDP
|
||||
"""
|
||||
|
||||
class TransportMechanism(_TransportMechanism, metaclass=_TransportMechanismEnumTypeWrapper):
|
||||
"""
|
||||
@@ -2992,6 +3164,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
"""
|
||||
Arrived via API connection
|
||||
"""
|
||||
TRANSPORT_UNICAST_UDP: MeshPacket.TransportMechanism.ValueType # 8
|
||||
"""
|
||||
Arrived via Unicast UDP
|
||||
"""
|
||||
|
||||
FROM_FIELD_NUMBER: builtins.int
|
||||
TO_FIELD_NUMBER: builtins.int
|
||||
@@ -3014,6 +3190,7 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
RELAY_NODE_FIELD_NUMBER: builtins.int
|
||||
TX_AFTER_FIELD_NUMBER: builtins.int
|
||||
TRANSPORT_MECHANISM_FIELD_NUMBER: builtins.int
|
||||
XEDDSA_SIGNED_FIELD_NUMBER: builtins.int
|
||||
to: builtins.int
|
||||
"""
|
||||
The (immediate) destination for this packet
|
||||
@@ -3053,6 +3230,12 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
Note: this field is _never_ sent on the radio link itself (to save space) Times
|
||||
are typically not sent over the mesh, but they will be added to any Packet
|
||||
(chain of SubPacket) sent to the phone (so the phone can know exact time of reception)
|
||||
Explicit presence: firmware cannot always attach a trustworthy wall-clock timestamp at the
|
||||
moment of reception - a node with no GPS and no phone connected yet has no time source at
|
||||
all. has_rx_time disambiguates that state from a genuine (if coincidental) 1970-01-01
|
||||
reading. A packet delivered with this field absent may still be re-timestamped once a valid
|
||||
clock becomes available, before the phone ever sees it - "absent" is not guaranteed
|
||||
permanent, only "not yet known at last observation".
|
||||
"""
|
||||
rx_snr: builtins.float
|
||||
"""
|
||||
@@ -3087,6 +3270,9 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
rx_rssi: builtins.int
|
||||
"""
|
||||
rssi of received packet. Only sent to phone for dispay purposes.
|
||||
Explicit presence: rssi 0 is a legitimate reading on some radios (SX126x can report exactly
|
||||
0 dBm; SX127x's formula can even go positive). has_rx_rssi disambiguates; a replayed packet
|
||||
built from history should leave this field absent rather than emitting 0.
|
||||
"""
|
||||
delayed: global___MeshPacket.Delayed.ValueType
|
||||
"""
|
||||
@@ -3100,6 +3286,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
"""
|
||||
Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
|
||||
When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled.
|
||||
hop_start == 0 does not necessarily mean a direct (0-hop) neighbor: firmware prior to 2.3.0
|
||||
never populated this field, so a receiver can only trust hop_start == 0 as genuine once it has
|
||||
decoded the packet and confirmed the sender's bitfield is present (added in 2.5.0). Until then,
|
||||
or for a sender that never sets that bitfield, treat hop_start == 0 as unknown, not direct.
|
||||
"""
|
||||
public_key: builtins.bytes
|
||||
"""
|
||||
@@ -3129,6 +3319,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
"""
|
||||
Indicates which transport mechanism this packet arrived over
|
||||
"""
|
||||
xeddsa_signed: builtins.bool
|
||||
"""
|
||||
Indicates whether the packet has a valid signature
|
||||
"""
|
||||
@property
|
||||
def decoded(self) -> global___Data:
|
||||
"""
|
||||
@@ -3143,12 +3337,12 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
decoded: global___Data | None = ...,
|
||||
encrypted: builtins.bytes = ...,
|
||||
id: builtins.int = ...,
|
||||
rx_time: builtins.int = ...,
|
||||
rx_time: builtins.int | None = ...,
|
||||
rx_snr: builtins.float = ...,
|
||||
hop_limit: builtins.int = ...,
|
||||
want_ack: builtins.bool = ...,
|
||||
priority: global___MeshPacket.Priority.ValueType = ...,
|
||||
rx_rssi: builtins.int = ...,
|
||||
rx_rssi: builtins.int | None = ...,
|
||||
delayed: global___MeshPacket.Delayed.ValueType = ...,
|
||||
via_mqtt: builtins.bool = ...,
|
||||
hop_start: builtins.int = ...,
|
||||
@@ -3158,9 +3352,15 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
relay_node: builtins.int = ...,
|
||||
tx_after: builtins.int = ...,
|
||||
transport_mechanism: global___MeshPacket.TransportMechanism.ValueType = ...,
|
||||
xeddsa_signed: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "next_hop", b"next_hop", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "relay_node", b"relay_node", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "transport_mechanism", b"transport_mechanism", "tx_after", b"tx_after", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_rx_rssi", b"_rx_rssi", "_rx_time", b"_rx_time", "decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant", "rx_rssi", b"rx_rssi", "rx_time", b"rx_time"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_rx_rssi", b"_rx_rssi", "_rx_time", b"_rx_time", "channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "next_hop", b"next_hop", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "relay_node", b"relay_node", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "transport_mechanism", b"transport_mechanism", "tx_after", b"tx_after", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack", "xeddsa_signed", b"xeddsa_signed"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_rx_rssi", b"_rx_rssi"]) -> typing.Literal["rx_rssi"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_rx_time", b"_rx_time"]) -> typing.Literal["rx_time"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["decoded", "encrypted"] | None: ...
|
||||
|
||||
global___MeshPacket = MeshPacket
|
||||
@@ -3201,6 +3401,7 @@ class NodeInfo(google.protobuf.message.Message):
|
||||
IS_IGNORED_FIELD_NUMBER: builtins.int
|
||||
IS_KEY_MANUALLY_VERIFIED_FIELD_NUMBER: builtins.int
|
||||
IS_MUTED_FIELD_NUMBER: builtins.int
|
||||
HAS_XEDDSA_SIGNED_FIELD_NUMBER: builtins.int
|
||||
num: builtins.int
|
||||
"""
|
||||
The node number
|
||||
@@ -3253,6 +3454,12 @@ class NodeInfo(google.protobuf.message.Message):
|
||||
True if node has been muted
|
||||
Persistes between NodeDB internal clean ups
|
||||
"""
|
||||
has_xeddsa_signed: builtins.bool
|
||||
"""
|
||||
True if node is signing its packets via XEdDSA
|
||||
Persists between NodeDB internal clean ups
|
||||
LSB 1 of the bitfield
|
||||
"""
|
||||
@property
|
||||
def user(self) -> global___User:
|
||||
"""
|
||||
@@ -3288,9 +3495,10 @@ class NodeInfo(google.protobuf.message.Message):
|
||||
is_ignored: builtins.bool = ...,
|
||||
is_key_manually_verified: builtins.bool = ...,
|
||||
is_muted: builtins.bool = ...,
|
||||
has_xeddsa_signed: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "is_key_manually_verified", b"is_key_manually_verified", "is_muted", b"is_muted", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "has_xeddsa_signed", b"has_xeddsa_signed", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "is_key_manually_verified", b"is_key_manually_verified", "is_muted", b"is_muted", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
|
||||
|
||||
global___NodeInfo = NodeInfo
|
||||
@@ -3529,6 +3737,7 @@ class FromRadio(google.protobuf.message.Message):
|
||||
CLIENTNOTIFICATION_FIELD_NUMBER: builtins.int
|
||||
DEVICEUICONFIG_FIELD_NUMBER: builtins.int
|
||||
LOCKDOWN_STATUS_FIELD_NUMBER: builtins.int
|
||||
REGION_PRESETS_FIELD_NUMBER: builtins.int
|
||||
id: builtins.int
|
||||
"""
|
||||
The packet id, used to allow the phone to request missing read packets from the FIFO,
|
||||
@@ -3644,6 +3853,16 @@ class FromRadio(google.protobuf.message.Message):
|
||||
encoding state as magic-string prefixes inside ClientNotification.
|
||||
"""
|
||||
|
||||
@property
|
||||
def region_presets(self) -> global___LoRaRegionPresetMap:
|
||||
"""
|
||||
Map of which modem presets are legal in each LoRa region. Sent once
|
||||
during the want_config handshake (right after `metadata`, before the
|
||||
first `channel`) so client UIs can prevent the user from selecting an
|
||||
illegal region+preset combination. A region that does not appear in
|
||||
any group carries no constraint info and should not be restricted.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -3665,10 +3884,11 @@ class FromRadio(google.protobuf.message.Message):
|
||||
clientNotification: global___ClientNotification | None = ...,
|
||||
deviceuiConfig: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
|
||||
lockdown_status: global___LockdownStatus | None = ...,
|
||||
region_presets: global___LoRaRegionPresetMap | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "fileInfo", b"fileInfo", "lockdown_status", b"lockdown_status", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "fileInfo", b"fileInfo", "id", b"id", "lockdown_status", b"lockdown_status", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["packet", "my_info", "node_info", "config", "log_record", "config_complete_id", "rebooted", "moduleConfig", "channel", "queueStatus", "xmodemPacket", "metadata", "mqttClientProxyMessage", "fileInfo", "clientNotification", "deviceuiConfig", "lockdown_status"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "fileInfo", b"fileInfo", "lockdown_status", b"lockdown_status", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "region_presets", b"region_presets", "xmodemPacket", b"xmodemPacket"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "fileInfo", b"fileInfo", "id", b"id", "lockdown_status", b"lockdown_status", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "region_presets", b"region_presets", "xmodemPacket", b"xmodemPacket"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["packet", "my_info", "node_info", "config", "log_record", "config_complete_id", "rebooted", "moduleConfig", "channel", "queueStatus", "xmodemPacket", "metadata", "mqttClientProxyMessage", "fileInfo", "clientNotification", "deviceuiConfig", "lockdown_status", "region_presets"] | None: ...
|
||||
|
||||
global___FromRadio = FromRadio
|
||||
|
||||
@@ -3713,6 +3933,15 @@ class LockdownStatus(google.protobuf.message.Message):
|
||||
"""
|
||||
Passphrase rejected. backoff_seconds is non-zero when rate-limited.
|
||||
"""
|
||||
DISABLED: LockdownStatus._State.ValueType # 5
|
||||
"""
|
||||
Lockdown is supported by this firmware but not currently active
|
||||
(no passphrase has been provisioned, or it was disabled via
|
||||
AdminMessage.lockdown_auth.disable). The device is operating in
|
||||
normal, non-encrypted mode. Clients render the lockdown-mode
|
||||
toggle as OFF on receiving this. Distinct from NEEDS_PROVISION,
|
||||
which is only used during an in-progress enable flow.
|
||||
"""
|
||||
|
||||
class State(_State, metaclass=_StateEnumTypeWrapper): ...
|
||||
STATE_UNSPECIFIED: LockdownStatus.State.ValueType # 0
|
||||
@@ -3739,6 +3968,15 @@ class LockdownStatus(google.protobuf.message.Message):
|
||||
"""
|
||||
Passphrase rejected. backoff_seconds is non-zero when rate-limited.
|
||||
"""
|
||||
DISABLED: LockdownStatus.State.ValueType # 5
|
||||
"""
|
||||
Lockdown is supported by this firmware but not currently active
|
||||
(no passphrase has been provisioned, or it was disabled via
|
||||
AdminMessage.lockdown_auth.disable). The device is operating in
|
||||
normal, non-encrypted mode. Clients render the lockdown-mode
|
||||
toggle as OFF on receiving this. Distinct from NEEDS_PROVISION,
|
||||
which is only used during an in-progress enable flow.
|
||||
"""
|
||||
|
||||
STATE_FIELD_NUMBER: builtins.int
|
||||
LOCK_REASON_FIELD_NUMBER: builtins.int
|
||||
@@ -4174,6 +4412,7 @@ class DeviceMetadata(google.protobuf.message.Message):
|
||||
HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int
|
||||
HASPKC_FIELD_NUMBER: builtins.int
|
||||
EXCLUDED_MODULES_FIELD_NUMBER: builtins.int
|
||||
HAS_XEDDSA_FIELD_NUMBER: builtins.int
|
||||
firmware_version: builtins.str
|
||||
"""
|
||||
Device firmware version string
|
||||
@@ -4223,6 +4462,11 @@ class DeviceMetadata(google.protobuf.message.Message):
|
||||
Bit field of boolean for excluded modules
|
||||
(bitwise OR of ExcludedModules)
|
||||
"""
|
||||
has_xeddsa: builtins.bool
|
||||
"""
|
||||
Indicates whether this firmware build includes XEdDSA packet signature verification.
|
||||
This is a read-only capability and must be false when XEdDSA is not compiled in.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -4238,11 +4482,126 @@ class DeviceMetadata(google.protobuf.message.Message):
|
||||
hasRemoteHardware: builtins.bool = ...,
|
||||
hasPKC: builtins.bool = ...,
|
||||
excluded_modules: builtins.int = ...,
|
||||
has_xeddsa: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "excluded_modules", b"excluded_modules", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasPKC", b"hasPKC", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "excluded_modules", b"excluded_modules", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasPKC", b"hasPKC", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "has_xeddsa", b"has_xeddsa", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ...
|
||||
|
||||
global___DeviceMetadata = DeviceMetadata
|
||||
|
||||
@typing.final
|
||||
class LoRaPresetGroup(google.protobuf.message.Message):
|
||||
"""
|
||||
A distinct set of legal modem presets shared by one or more LoRa regions.
|
||||
Regions that have an identical preset list / default / licensing reference
|
||||
the same group (by index) via LoRaRegionPresetMap.region_groups. This keeps
|
||||
the whole map small enough to fit in a single FromRadio packet, since most
|
||||
regions share the one standard preset list.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
PRESETS_FIELD_NUMBER: builtins.int
|
||||
DEFAULT_PRESET_FIELD_NUMBER: builtins.int
|
||||
LICENSED_ONLY_FIELD_NUMBER: builtins.int
|
||||
default_preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType
|
||||
"""
|
||||
The firmware's default modem preset for regions in this group.
|
||||
Always one of `presets`. Clients should select this when switching to one
|
||||
of these regions, or when the current preset is not legal in the new region.
|
||||
"""
|
||||
licensed_only: builtins.bool
|
||||
"""
|
||||
True if regions referencing this group are for licensed operators only
|
||||
(e.g. amateur / ham radio bands). Clients should warn or gate accordingly.
|
||||
"""
|
||||
@property
|
||||
def presets(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType]:
|
||||
"""
|
||||
The modem presets that are legal for every region referencing this group.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
presets: collections.abc.Iterable[meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType] | None = ...,
|
||||
default_preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType = ...,
|
||||
licensed_only: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["default_preset", b"default_preset", "licensed_only", b"licensed_only", "presets", b"presets"]) -> None: ...
|
||||
|
||||
global___LoRaPresetGroup = LoRaPresetGroup
|
||||
|
||||
@typing.final
|
||||
class LoRaRegionPresets(google.protobuf.message.Message):
|
||||
"""
|
||||
Associates a single LoRa region with its preset group.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
REGION_FIELD_NUMBER: builtins.int
|
||||
GROUP_INDEX_FIELD_NUMBER: builtins.int
|
||||
region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType
|
||||
"""
|
||||
The LoRa region this entry describes.
|
||||
"""
|
||||
group_index: builtins.int
|
||||
"""
|
||||
Index into LoRaRegionPresetMap.groups for the preset list that is legal
|
||||
in `region`.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType = ...,
|
||||
group_index: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["group_index", b"group_index", "region", b"region"]) -> None: ...
|
||||
|
||||
global___LoRaRegionPresets = LoRaRegionPresets
|
||||
|
||||
@typing.final
|
||||
class LoRaRegionPresetMap(google.protobuf.message.Message):
|
||||
"""
|
||||
Map describing which modem presets are valid for each LoRa region. Sent by
|
||||
the firmware during the want_config handshake (as FromRadio.region_presets)
|
||||
so that client UIs can prevent illegal region+preset selections.
|
||||
|
||||
Delivery is grouped to save space: `groups` holds each distinct preset list,
|
||||
and `region_groups` maps every known region to one of those groups by index.
|
||||
A region that does NOT appear in `region_groups` carries no constraint
|
||||
information and should not be restricted by the client (e.g. firmware that
|
||||
predates this message, or a region with no firmware table entry). Clients
|
||||
must also tolerate this whole message being absent.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
GROUPS_FIELD_NUMBER: builtins.int
|
||||
REGION_GROUPS_FIELD_NUMBER: builtins.int
|
||||
@property
|
||||
def groups(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___LoRaPresetGroup]:
|
||||
"""
|
||||
One entry per distinct (preset-list, default, licensing) combination.
|
||||
Referenced by index from `region_groups`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def region_groups(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___LoRaRegionPresets]:
|
||||
"""
|
||||
One entry per known LoRa region, pointing at its preset group.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
groups: collections.abc.Iterable[global___LoRaPresetGroup] | None = ...,
|
||||
region_groups: collections.abc.Iterable[global___LoRaRegionPresets] | None = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["groups", b"groups", "region_groups", b"region_groups"]) -> None: ...
|
||||
|
||||
global___LoRaRegionPresetMap = LoRaRegionPresetMap
|
||||
|
||||
@typing.final
|
||||
class Heartbeat(google.protobuf.message.Message):
|
||||
"""
|
||||
|
||||
Generated
+63
-51
File diff suppressed because one or more lines are too long.
+193
-63
@@ -10,6 +10,8 @@ import google.protobuf.internal.containers
|
||||
import google.protobuf.internal.enum_type_wrapper
|
||||
import google.protobuf.message
|
||||
import meshtastic.protobuf.atak_pb2
|
||||
import meshtastic.protobuf.channel_pb2
|
||||
import meshtastic.protobuf.config_pb2
|
||||
import sys
|
||||
import typing
|
||||
|
||||
@@ -502,95 +504,48 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
POSITION_DEDUP_ENABLED_FIELD_NUMBER: builtins.int
|
||||
POSITION_PRECISION_BITS_FIELD_NUMBER: builtins.int
|
||||
POSITION_MIN_INTERVAL_SECS_FIELD_NUMBER: builtins.int
|
||||
NODEINFO_DIRECT_RESPONSE_FIELD_NUMBER: builtins.int
|
||||
NODEINFO_DIRECT_RESPONSE_MAX_HOPS_FIELD_NUMBER: builtins.int
|
||||
RATE_LIMIT_ENABLED_FIELD_NUMBER: builtins.int
|
||||
RATE_LIMIT_WINDOW_SECS_FIELD_NUMBER: builtins.int
|
||||
RATE_LIMIT_MAX_PACKETS_FIELD_NUMBER: builtins.int
|
||||
DROP_UNKNOWN_ENABLED_FIELD_NUMBER: builtins.int
|
||||
UNKNOWN_PACKET_THRESHOLD_FIELD_NUMBER: builtins.int
|
||||
EXHAUST_HOP_TELEMETRY_FIELD_NUMBER: builtins.int
|
||||
EXHAUST_HOP_POSITION_FIELD_NUMBER: builtins.int
|
||||
ROUTER_PRESERVE_HOPS_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Master enable for traffic management module
|
||||
"""
|
||||
position_dedup_enabled: builtins.bool
|
||||
"""
|
||||
Enable position deduplication to drop redundant position broadcasts
|
||||
"""
|
||||
position_precision_bits: builtins.int
|
||||
"""
|
||||
Number of bits of precision for position deduplication (0-32)
|
||||
"""
|
||||
position_min_interval_secs: builtins.int
|
||||
"""
|
||||
Minimum interval in seconds between position updates from the same node
|
||||
"""
|
||||
nodeinfo_direct_response: builtins.bool
|
||||
"""
|
||||
Enable direct response to NodeInfo requests from local cache
|
||||
Minimum interval in seconds between position updates from the same node.
|
||||
A non-zero value implicitly enables the suppression window; 0 disables it.
|
||||
"""
|
||||
nodeinfo_direct_response_max_hops: builtins.int
|
||||
"""
|
||||
Minimum hop distance from requestor before responding to NodeInfo requests
|
||||
"""
|
||||
rate_limit_enabled: builtins.bool
|
||||
"""
|
||||
Enable per-node rate limiting to throttle chatty nodes
|
||||
Maximum hop distance from the requestor at which direct NodeInfo responses
|
||||
are served from the local cache. A non-zero value implicitly enables direct
|
||||
response; 0 disables it.
|
||||
"""
|
||||
rate_limit_window_secs: builtins.int
|
||||
"""
|
||||
Time window in seconds for rate limiting calculations
|
||||
Time window in seconds for per-node rate limiting.
|
||||
A non-zero value implicitly enables rate limiting; 0 disables it.
|
||||
"""
|
||||
rate_limit_max_packets: builtins.int
|
||||
"""
|
||||
Maximum packets allowed per node within the rate limit window
|
||||
"""
|
||||
drop_unknown_enabled: builtins.bool
|
||||
"""
|
||||
Enable dropping of unknown/undecryptable packets per rate_limit_window_secs
|
||||
Maximum packets allowed per node within the rate limit window.
|
||||
A non-zero value implicitly enables rate limiting; 0 disables it.
|
||||
"""
|
||||
unknown_packet_threshold: builtins.int
|
||||
"""
|
||||
Number of unknown packets before dropping from a node
|
||||
"""
|
||||
exhaust_hop_telemetry: builtins.bool
|
||||
"""
|
||||
Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected)
|
||||
"""
|
||||
exhaust_hop_position: builtins.bool
|
||||
"""
|
||||
Set hop_limit to 0 for relayed position broadcasts (own packets unaffected)
|
||||
"""
|
||||
router_preserve_hops: builtins.bool
|
||||
"""
|
||||
Preserve hop_limit for router-to-router traffic
|
||||
Maximum unknown/undecryptable packets per rate window before the source
|
||||
is dropped. A non-zero value implicitly enables unknown-packet filtering;
|
||||
0 disables it.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
position_dedup_enabled: builtins.bool = ...,
|
||||
position_precision_bits: builtins.int = ...,
|
||||
position_min_interval_secs: builtins.int = ...,
|
||||
nodeinfo_direct_response: builtins.bool = ...,
|
||||
nodeinfo_direct_response_max_hops: builtins.int = ...,
|
||||
rate_limit_enabled: builtins.bool = ...,
|
||||
rate_limit_window_secs: builtins.int = ...,
|
||||
rate_limit_max_packets: builtins.int = ...,
|
||||
drop_unknown_enabled: builtins.bool = ...,
|
||||
unknown_packet_threshold: builtins.int = ...,
|
||||
exhaust_hop_telemetry: builtins.bool = ...,
|
||||
exhaust_hop_position: builtins.bool = ...,
|
||||
router_preserve_hops: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["drop_unknown_enabled", b"drop_unknown_enabled", "enabled", b"enabled", "exhaust_hop_position", b"exhaust_hop_position", "exhaust_hop_telemetry", b"exhaust_hop_telemetry", "nodeinfo_direct_response", b"nodeinfo_direct_response", "nodeinfo_direct_response_max_hops", b"nodeinfo_direct_response_max_hops", "position_dedup_enabled", b"position_dedup_enabled", "position_min_interval_secs", b"position_min_interval_secs", "position_precision_bits", b"position_precision_bits", "rate_limit_enabled", b"rate_limit_enabled", "rate_limit_max_packets", b"rate_limit_max_packets", "rate_limit_window_secs", b"rate_limit_window_secs", "router_preserve_hops", b"router_preserve_hops", "unknown_packet_threshold", b"unknown_packet_threshold"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["nodeinfo_direct_response_max_hops", b"nodeinfo_direct_response_max_hops", "position_min_interval_secs", b"position_min_interval_secs", "rate_limit_max_packets", b"rate_limit_max_packets", "rate_limit_window_secs", b"rate_limit_window_secs", "unknown_packet_threshold", b"unknown_packet_threshold"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class SerialConfig(google.protobuf.message.Message):
|
||||
@@ -1302,6 +1257,173 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["node_status", b"node_status"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class MeshBeaconConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
MeshBeacon module config
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
class _Flags:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FlagsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ModuleConfig.MeshBeaconConfig._Flags.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
FLAG_NONE: ModuleConfig.MeshBeaconConfig._Flags.ValueType # 0
|
||||
"""
|
||||
No options enabled.
|
||||
"""
|
||||
FLAG_LISTEN_ENABLED: ModuleConfig.MeshBeaconConfig._Flags.ValueType # 1
|
||||
"""
|
||||
Enable receiving MESH_BEACON_APP packets from other nodes.
|
||||
The text portion is delivered to the local message inbox.
|
||||
Offered channel/preset are stored for the client app to act on.
|
||||
"""
|
||||
FLAG_BROADCAST_ENABLED: ModuleConfig.MeshBeaconConfig._Flags.ValueType # 2
|
||||
"""
|
||||
Enable periodically broadcasting MESH_BEACON_APP packets from this node.
|
||||
"""
|
||||
FLAG_LEGACY_SPLIT: ModuleConfig.MeshBeaconConfig._Flags.ValueType # 4
|
||||
"""
|
||||
When both text and offer content are present, split the beacon into a separate
|
||||
MESH_BEACON_APP (offer only) and TEXT_MESSAGE_APP (text only) packet, so firmware
|
||||
that only decodes TEXT_MESSAGE_APP still receives the human-readable text.
|
||||
"""
|
||||
|
||||
class Flags(_Flags, metaclass=_FlagsEnumTypeWrapper):
|
||||
"""
|
||||
Boolean options for the beacon module, packed into the `flags` bitfield below.
|
||||
OR the FLAG_* values together; a flag is on when its bit is set.
|
||||
"""
|
||||
|
||||
FLAG_NONE: ModuleConfig.MeshBeaconConfig.Flags.ValueType # 0
|
||||
"""
|
||||
No options enabled.
|
||||
"""
|
||||
FLAG_LISTEN_ENABLED: ModuleConfig.MeshBeaconConfig.Flags.ValueType # 1
|
||||
"""
|
||||
Enable receiving MESH_BEACON_APP packets from other nodes.
|
||||
The text portion is delivered to the local message inbox.
|
||||
Offered channel/preset are stored for the client app to act on.
|
||||
"""
|
||||
FLAG_BROADCAST_ENABLED: ModuleConfig.MeshBeaconConfig.Flags.ValueType # 2
|
||||
"""
|
||||
Enable periodically broadcasting MESH_BEACON_APP packets from this node.
|
||||
"""
|
||||
FLAG_LEGACY_SPLIT: ModuleConfig.MeshBeaconConfig.Flags.ValueType # 4
|
||||
"""
|
||||
When both text and offer content are present, split the beacon into a separate
|
||||
MESH_BEACON_APP (offer only) and TEXT_MESSAGE_APP (text only) packet, so firmware
|
||||
that only decodes TEXT_MESSAGE_APP still receives the human-readable text.
|
||||
"""
|
||||
|
||||
@typing.final
|
||||
class BroadcastTarget(google.protobuf.message.Message):
|
||||
"""
|
||||
One entry in the broadcast destination list.
|
||||
Each entry names one set of radio settings to send a beacon copy on.
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
PRESET_FIELD_NUMBER: builtins.int
|
||||
REGION_FIELD_NUMBER: builtins.int
|
||||
CHANNEL_INDEX_FIELD_NUMBER: builtins.int
|
||||
preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType
|
||||
"""
|
||||
Modem preset to use for this target.
|
||||
Falls back to the running config preset if unset.
|
||||
"""
|
||||
region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType
|
||||
"""
|
||||
Region to use for this target. UNSET means use the running config region.
|
||||
"""
|
||||
channel_index: builtins.int
|
||||
"""Tag 3 was an embedded ChannelSettings; replaced by channel_index (tag 4) to keep
|
||||
ModuleConfig within the BLE FromRadio size budget. Branch unreleased, so tag 3 is a gap.
|
||||
|
||||
|
||||
Index into the device's channel table (0..MAX_NUM_CHANNELS-1) of the channel to
|
||||
transmit this target's beacon on. The referenced channel must already be configured
|
||||
on the node (its key is needed to encrypt). If unset, the default channel for the
|
||||
preset is used.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType | None = ...,
|
||||
region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType = ...,
|
||||
channel_index: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_channel_index", b"_channel_index", "_preset", b"_preset", "channel_index", b"channel_index", "preset", b"preset"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_channel_index", b"_channel_index", "_preset", b"_preset", "channel_index", b"channel_index", "preset", b"preset", "region", b"region"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_channel_index", b"_channel_index"]) -> typing.Literal["channel_index"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_preset", b"_preset"]) -> typing.Literal["preset"] | None: ...
|
||||
|
||||
FLAGS_FIELD_NUMBER: builtins.int
|
||||
BROADCAST_MESSAGE_FIELD_NUMBER: builtins.int
|
||||
BROADCAST_OFFER_CHANNEL_FIELD_NUMBER: builtins.int
|
||||
BROADCAST_OFFER_REGION_FIELD_NUMBER: builtins.int
|
||||
BROADCAST_OFFER_PRESET_FIELD_NUMBER: builtins.int
|
||||
BROADCAST_INTERVAL_SECS_FIELD_NUMBER: builtins.int
|
||||
BROADCAST_TARGETS_FIELD_NUMBER: builtins.int
|
||||
flags: builtins.int
|
||||
"""
|
||||
Bitwise-OR of Flags values (listen / broadcast / legacy-split toggles).
|
||||
"""
|
||||
broadcast_message: builtins.str
|
||||
"""
|
||||
Message to include in each beacon broadcast. Max 100 bytes enforced by firmware.
|
||||
"""
|
||||
broadcast_offer_region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType
|
||||
"""
|
||||
Optional region to advertise in the MeshBeacon offer_region field.
|
||||
"""
|
||||
broadcast_offer_preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType
|
||||
"""
|
||||
Optional modem preset to advertise in the MeshBeacon offer_preset field.
|
||||
"""
|
||||
broadcast_interval_secs: builtins.int
|
||||
"""
|
||||
How often to broadcast, in seconds. Min 3600 (1 h), default 3600.
|
||||
"""
|
||||
@property
|
||||
def broadcast_offer_channel(self) -> meshtastic.protobuf.channel_pb2.ChannelSettings:
|
||||
"""
|
||||
Optional channel (name + PSK) to advertise in the MeshBeacon offer_channel field.
|
||||
"""
|
||||
|
||||
@property
|
||||
def broadcast_targets(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ModuleConfig.MeshBeaconConfig.BroadcastTarget]:
|
||||
"""
|
||||
Broadcast destination list.
|
||||
The broadcaster sends one beacon copy per distinct destination, in sequence, temporarily
|
||||
switching the radio to that entry's preset/region/channel for each.
|
||||
When empty, a single beacon is sent on the node's running preset and region over the
|
||||
primary channel.
|
||||
Entries that resolve to the same effective preset, region and channel are deduplicated, so
|
||||
a duplicate entry does not produce a second transmission.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
flags: builtins.int = ...,
|
||||
broadcast_message: builtins.str = ...,
|
||||
broadcast_offer_channel: meshtastic.protobuf.channel_pb2.ChannelSettings | None = ...,
|
||||
broadcast_offer_region: meshtastic.protobuf.config_pb2.Config.LoRaConfig.RegionCode.ValueType = ...,
|
||||
broadcast_offer_preset: meshtastic.protobuf.config_pb2.Config.LoRaConfig.ModemPreset.ValueType | None = ...,
|
||||
broadcast_interval_secs: builtins.int = ...,
|
||||
broadcast_targets: collections.abc.Iterable[global___ModuleConfig.MeshBeaconConfig.BroadcastTarget] | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_broadcast_offer_preset", b"_broadcast_offer_preset", "broadcast_offer_channel", b"broadcast_offer_channel", "broadcast_offer_preset", b"broadcast_offer_preset"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_broadcast_offer_preset", b"_broadcast_offer_preset", "broadcast_interval_secs", b"broadcast_interval_secs", "broadcast_message", b"broadcast_message", "broadcast_offer_channel", b"broadcast_offer_channel", "broadcast_offer_preset", b"broadcast_offer_preset", "broadcast_offer_region", b"broadcast_offer_region", "broadcast_targets", b"broadcast_targets", "flags", b"flags"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_broadcast_offer_preset", b"_broadcast_offer_preset"]) -> typing.Literal["broadcast_offer_preset"] | None: ...
|
||||
|
||||
@typing.final
|
||||
class TAKConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
@@ -1346,6 +1468,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
STATUSMESSAGE_FIELD_NUMBER: builtins.int
|
||||
TRAFFIC_MANAGEMENT_FIELD_NUMBER: builtins.int
|
||||
TAK_FIELD_NUMBER: builtins.int
|
||||
MESH_BEACON_FIELD_NUMBER: builtins.int
|
||||
@property
|
||||
def mqtt(self) -> global___ModuleConfig.MQTTConfig:
|
||||
"""
|
||||
@@ -1442,6 +1565,12 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
TAK team/role configuration for TAK_TRACKER
|
||||
"""
|
||||
|
||||
@property
|
||||
def mesh_beacon(self) -> global___ModuleConfig.MeshBeaconConfig:
|
||||
"""
|
||||
MeshBeacon module config
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1461,10 +1590,11 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
statusmessage: global___ModuleConfig.StatusMessageConfig | None = ...,
|
||||
traffic_management: global___ModuleConfig.TrafficManagementConfig | None = ...,
|
||||
tak: global___ModuleConfig.TAKConfig | None = ...,
|
||||
mesh_beacon: global___ModuleConfig.MeshBeaconConfig | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["mqtt", "serial", "external_notification", "store_forward", "range_test", "telemetry", "canned_message", "audio", "remote_hardware", "neighbor_info", "ambient_lighting", "detection_sensor", "paxcounter", "statusmessage", "traffic_management", "tak"] | None: ...
|
||||
def HasField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mesh_beacon", b"mesh_beacon", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["ambient_lighting", b"ambient_lighting", "audio", b"audio", "canned_message", b"canned_message", "detection_sensor", b"detection_sensor", "external_notification", b"external_notification", "mesh_beacon", b"mesh_beacon", "mqtt", b"mqtt", "neighbor_info", b"neighbor_info", "paxcounter", b"paxcounter", "payload_variant", b"payload_variant", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "statusmessage", b"statusmessage", "store_forward", b"store_forward", "tak", b"tak", "telemetry", b"telemetry", "traffic_management", b"traffic_management"]) -> None: ...
|
||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["mqtt", "serial", "external_notification", "store_forward", "range_test", "telemetry", "canned_message", "audio", "remote_hardware", "neighbor_info", "ambient_lighting", "detection_sensor", "paxcounter", "statusmessage", "traffic_management", "tak", "mesh_beacon"] | None: ...
|
||||
|
||||
global___ModuleConfig = ModuleConfig
|
||||
|
||||
|
||||
Generated
+11
-5
@@ -16,7 +16,7 @@ from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a meshtastic/protobuf/nanopb.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\x9f\x04\n\tMapReport\x12\x18\n\tlong_name\x18\x01 \x01(\tB\x05\x92?\x02\x08(\x12\x19\n\nshort_name\x18\x02 \x01(\tB\x05\x92?\x02\x08\x05\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x1f\n\x10\x66irmware_version\x18\x05 \x01(\tB\x05\x92?\x02\x08\x12\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12%\n\x16num_online_local_nodes\x18\r \x01(\rB\x05\x92?\x02\x38\x10\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42`\n\x14org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a meshtastic/protobuf/nanopb.proto\"\x7f\n\x0fServiceEnvelope\x12\x36\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacketB\x05\x92?\x02\x18\x04\x12\x19\n\nchannel_id\x18\x02 \x01(\tB\x05\x92?\x02\x18\x04\x12\x19\n\ngateway_id\x18\x03 \x01(\tB\x05\x92?\x02\x18\x04\"\x9f\x04\n\tMapReport\x12\x18\n\tlong_name\x18\x01 \x01(\tB\x05\x92?\x02\x08\x19\x12\x19\n\nshort_name\x18\x02 \x01(\tB\x05\x92?\x02\x08\x05\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x1f\n\x10\x66irmware_version\x18\x05 \x01(\tB\x05\x92?\x02\x08\x12\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12%\n\x16num_online_local_nodes\x18\r \x01(\rB\x05\x92?\x02\x38\x10\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42`\n\x14org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -24,8 +24,14 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mqtt_pb
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_SERVICEENVELOPE.fields_by_name['packet']._options = None
|
||||
_SERVICEENVELOPE.fields_by_name['packet']._serialized_options = b'\222?\002\030\004'
|
||||
_SERVICEENVELOPE.fields_by_name['channel_id']._options = None
|
||||
_SERVICEENVELOPE.fields_by_name['channel_id']._serialized_options = b'\222?\002\030\004'
|
||||
_SERVICEENVELOPE.fields_by_name['gateway_id']._options = None
|
||||
_SERVICEENVELOPE.fields_by_name['gateway_id']._serialized_options = b'\222?\002\030\004'
|
||||
_MAPREPORT.fields_by_name['long_name']._options = None
|
||||
_MAPREPORT.fields_by_name['long_name']._serialized_options = b'\222?\002\010('
|
||||
_MAPREPORT.fields_by_name['long_name']._serialized_options = b'\222?\002\010\031'
|
||||
_MAPREPORT.fields_by_name['short_name']._options = None
|
||||
_MAPREPORT.fields_by_name['short_name']._serialized_options = b'\222?\002\010\005'
|
||||
_MAPREPORT.fields_by_name['firmware_version']._options = None
|
||||
@@ -33,7 +39,7 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
_MAPREPORT.fields_by_name['num_online_local_nodes']._options = None
|
||||
_MAPREPORT.fields_by_name['num_online_local_nodes']._serialized_options = b'\222?\0028\020'
|
||||
_globals['_SERVICEENVELOPE']._serialized_start=155
|
||||
_globals['_SERVICEENVELOPE']._serialized_end=261
|
||||
_globals['_MAPREPORT']._serialized_start=264
|
||||
_globals['_MAPREPORT']._serialized_end=807
|
||||
_globals['_SERVICEENVELOPE']._serialized_end=282
|
||||
_globals['_MAPREPORT']._serialized_start=285
|
||||
_globals['_MAPREPORT']._serialized_end=828
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Generated
+2
-2
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xfd\x05\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\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\x14\n\x10REMOTE_SHELL_APP\x10\r\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x1e\n\x1aSTORE_FORWARD_PLUSPLUS_APP\x10#\x12\x13\n\x0fNODE_STATUS_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\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x12\n\x0eLORAWAN_BRIDGE\x10K\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x12\n\x0e\x41TAK_PLUGIN_V2\x10N\x12\x12\n\x0eGROUPALARM_APP\x10p\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^\n\x14org.meshtastic.protoB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xa4\x06\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\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\x14\n\x10REMOTE_SHELL_APP\x10\r\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x1e\n\x1aSTORE_FORWARD_PLUSPLUS_APP\x10#\x12\x13\n\x0fNODE_STATUS_APP\x10$\x12\x13\n\x0fMESH_BEACON_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\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x12\n\x0eLORAWAN_BRIDGE\x10K\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x12\n\x0e\x41TAK_PLUGIN_V2\x10N\x12\x10\n\x0cLORA_OTA_APP\x10O\x12\x12\n\x0eGROUPALARM_APP\x10p\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^\n\x14org.meshtastic.protoB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -22,5 +22,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_PORTNUM']._serialized_start=60
|
||||
_globals['_PORTNUM']._serialized_end=825
|
||||
_globals['_PORTNUM']._serialized_end=864
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
Generated
+24
@@ -135,6 +135,13 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
This module allows setting an extra string of status for a node.
|
||||
Broadcasts on change and on a timer, possibly once a day.
|
||||
"""
|
||||
MESH_BEACON_APP: _PortNum.ValueType # 37
|
||||
"""
|
||||
Beacon module broadcast packets.
|
||||
ENCODING: protobuf
|
||||
Periodically broadcast by nodes in beacon mode; received by nodes with MeshBeaconConfig.FLAG_LISTEN_ENABLED.
|
||||
Carries a text message plus optional channel/preset offers for client apps.
|
||||
"""
|
||||
SERIAL_APP: _PortNum.ValueType # 64
|
||||
"""
|
||||
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
@@ -223,6 +230,11 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
Portnum for payloads from the official Meshtastic ATAK plugin using
|
||||
TAKPacketV2 with zstd dictionary compression.
|
||||
"""
|
||||
LORA_OTA_APP: _PortNum.ValueType # 79
|
||||
"""signed firmware updates over lora.
|
||||
|
||||
ENCODING: binary (ota-common transport frames)
|
||||
"""
|
||||
GROUPALARM_APP: _PortNum.ValueType # 112
|
||||
"""
|
||||
GroupAlarm integration
|
||||
@@ -374,6 +386,13 @@ ENCODING: protobuf
|
||||
This module allows setting an extra string of status for a node.
|
||||
Broadcasts on change and on a timer, possibly once a day.
|
||||
"""
|
||||
MESH_BEACON_APP: PortNum.ValueType # 37
|
||||
"""
|
||||
Beacon module broadcast packets.
|
||||
ENCODING: protobuf
|
||||
Periodically broadcast by nodes in beacon mode; received by nodes with MeshBeaconConfig.FLAG_LISTEN_ENABLED.
|
||||
Carries a text message plus optional channel/preset offers for client apps.
|
||||
"""
|
||||
SERIAL_APP: PortNum.ValueType # 64
|
||||
"""
|
||||
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
@@ -462,6 +481,11 @@ ATAK Plugin V2
|
||||
Portnum for payloads from the official Meshtastic ATAK plugin using
|
||||
TAKPacketV2 with zstd dictionary compression.
|
||||
"""
|
||||
LORA_OTA_APP: PortNum.ValueType # 79
|
||||
"""signed firmware updates over lora.
|
||||
|
||||
ENCODING: binary (ota-common transport frames)
|
||||
"""
|
||||
GROUPALARM_APP: PortNum.ValueType # 112
|
||||
"""
|
||||
GroupAlarm integration
|
||||
|
||||
Generated
+55
-23
File diff suppressed because one or more lines are too long.
Generated
+285
-16
@@ -241,6 +241,22 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
||||
"""
|
||||
ICM-42607-P 6‑Axis IMU
|
||||
"""
|
||||
SPA06: _TelemetrySensorType.ValueType # 54
|
||||
"""
|
||||
SPA06 pressure and temperature
|
||||
"""
|
||||
HM330X: _TelemetrySensorType.ValueType # 55
|
||||
"""
|
||||
HM330X PM SENSOR
|
||||
"""
|
||||
SEN6X: _TelemetrySensorType.ValueType # 56
|
||||
"""
|
||||
Sensirion SEN6X PM/RHT/VOC/NOx/CO2/HCHO sensor family (SEN62, SEN63C, SEN65, SEN66, SEN68, SEN69C)
|
||||
"""
|
||||
AS3935: _TelemetrySensorType.ValueType # 57
|
||||
"""
|
||||
AS3935 Franklin lightning sensor
|
||||
"""
|
||||
|
||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -463,6 +479,22 @@ ICM42607P: TelemetrySensorType.ValueType # 53
|
||||
"""
|
||||
ICM-42607-P 6‑Axis IMU
|
||||
"""
|
||||
SPA06: TelemetrySensorType.ValueType # 54
|
||||
"""
|
||||
SPA06 pressure and temperature
|
||||
"""
|
||||
HM330X: TelemetrySensorType.ValueType # 55
|
||||
"""
|
||||
HM330X PM SENSOR
|
||||
"""
|
||||
SEN6X: TelemetrySensorType.ValueType # 56
|
||||
"""
|
||||
Sensirion SEN6X PM/RHT/VOC/NOx/CO2/HCHO sensor family (SEN62, SEN63C, SEN65, SEN66, SEN68, SEN69C)
|
||||
"""
|
||||
AS3935: TelemetrySensorType.ValueType # 57
|
||||
"""
|
||||
AS3935 Franklin lightning sensor
|
||||
"""
|
||||
global___TelemetrySensorType = TelemetrySensorType
|
||||
|
||||
@typing.final
|
||||
@@ -553,6 +585,24 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
SOIL_MOISTURE_FIELD_NUMBER: builtins.int
|
||||
SOIL_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH0_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH1_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH2_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH3_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH4_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH5_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH6_FIELD_NUMBER: builtins.int
|
||||
ADC_VOLTAGE_CH7_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH0_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH1_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH2_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH3_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH4_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH5_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH6_FIELD_NUMBER: builtins.int
|
||||
ONE_WIRE_TEMPERATURE_CH7_FIELD_NUMBER: builtins.int
|
||||
LIGHTNING_STRIKE_COUNT_1H_FIELD_NUMBER: builtins.int
|
||||
LIGHTNING_DISTANCE_KM_FIELD_NUMBER: builtins.int
|
||||
temperature: builtins.float
|
||||
"""
|
||||
Temperature measured
|
||||
@@ -643,10 +693,82 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
"""
|
||||
Soil temperature measured (*C)
|
||||
"""
|
||||
adc_voltage_ch0: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 0 (V)
|
||||
"""
|
||||
adc_voltage_ch1: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 1 (V)
|
||||
"""
|
||||
adc_voltage_ch2: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 2 (V)
|
||||
"""
|
||||
adc_voltage_ch3: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 3 (V)
|
||||
"""
|
||||
adc_voltage_ch4: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 4 (V)
|
||||
"""
|
||||
adc_voltage_ch5: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 5 (V)
|
||||
"""
|
||||
adc_voltage_ch6: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 6 (V)
|
||||
"""
|
||||
adc_voltage_ch7: builtins.float
|
||||
"""
|
||||
Multi-channel ADC Voltage Channel 7 (V)
|
||||
"""
|
||||
one_wire_temperature_ch0: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 0 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch1: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 1 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch2: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 2 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch3: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 3 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch4: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 4 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch5: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 5 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch6: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 6 (*C)
|
||||
"""
|
||||
one_wire_temperature_ch7: builtins.float
|
||||
"""
|
||||
Multi-channel One-Wire Temperature Channel 7 (*C)
|
||||
"""
|
||||
lightning_strike_count_1h: builtins.int
|
||||
"""
|
||||
Lightning strikes detected in the last hour
|
||||
"""
|
||||
lightning_distance_km: builtins.float
|
||||
"""
|
||||
Estimated distance to the leading edge of the storm, in km
|
||||
"""
|
||||
@property
|
||||
def one_wire_temperature(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]:
|
||||
"""
|
||||
One-wire temperature (*C)
|
||||
Never implemented, but Voltage may be mis-interpreted by old clients as temperature
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -675,9 +797,43 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
soil_moisture: builtins.int | None = ...,
|
||||
soil_temperature: builtins.float | None = ...,
|
||||
one_wire_temperature: collections.abc.Iterable[builtins.float] | None = ...,
|
||||
adc_voltage_ch0: builtins.float | None = ...,
|
||||
adc_voltage_ch1: builtins.float | None = ...,
|
||||
adc_voltage_ch2: builtins.float | None = ...,
|
||||
adc_voltage_ch3: builtins.float | None = ...,
|
||||
adc_voltage_ch4: builtins.float | None = ...,
|
||||
adc_voltage_ch5: builtins.float | None = ...,
|
||||
adc_voltage_ch6: builtins.float | None = ...,
|
||||
adc_voltage_ch7: builtins.float | None = ...,
|
||||
one_wire_temperature_ch0: builtins.float | None = ...,
|
||||
one_wire_temperature_ch1: builtins.float | None = ...,
|
||||
one_wire_temperature_ch2: builtins.float | None = ...,
|
||||
one_wire_temperature_ch3: builtins.float | None = ...,
|
||||
one_wire_temperature_ch4: builtins.float | None = ...,
|
||||
one_wire_temperature_ch5: builtins.float | None = ...,
|
||||
one_wire_temperature_ch6: builtins.float | None = ...,
|
||||
one_wire_temperature_ch7: builtins.float | None = ...,
|
||||
lightning_strike_count_1h: builtins.int | None = ...,
|
||||
lightning_distance_km: builtins.float | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_rainfall_1h", b"_rainfall_1h", "_rainfall_24h", b"_rainfall_24h", "_relative_humidity", b"_relative_humidity", "_soil_moisture", b"_soil_moisture", "_soil_temperature", b"_soil_temperature", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "rainfall_1h", b"rainfall_1h", "rainfall_24h", b"rainfall_24h", "relative_humidity", b"relative_humidity", "soil_moisture", b"soil_moisture", "soil_temperature", b"soil_temperature", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_rainfall_1h", b"_rainfall_1h", "_rainfall_24h", b"_rainfall_24h", "_relative_humidity", b"_relative_humidity", "_soil_moisture", b"_soil_moisture", "_soil_temperature", b"_soil_temperature", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "one_wire_temperature", b"one_wire_temperature", "radiation", b"radiation", "rainfall_1h", b"rainfall_1h", "rainfall_24h", b"rainfall_24h", "relative_humidity", b"relative_humidity", "soil_moisture", b"soil_moisture", "soil_temperature", b"soil_temperature", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_adc_voltage_ch0", b"_adc_voltage_ch0", "_adc_voltage_ch1", b"_adc_voltage_ch1", "_adc_voltage_ch2", b"_adc_voltage_ch2", "_adc_voltage_ch3", b"_adc_voltage_ch3", "_adc_voltage_ch4", b"_adc_voltage_ch4", "_adc_voltage_ch5", b"_adc_voltage_ch5", "_adc_voltage_ch6", b"_adc_voltage_ch6", "_adc_voltage_ch7", b"_adc_voltage_ch7", "_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lightning_distance_km", b"_lightning_distance_km", "_lightning_strike_count_1h", b"_lightning_strike_count_1h", "_lux", b"_lux", "_one_wire_temperature_ch0", b"_one_wire_temperature_ch0", "_one_wire_temperature_ch1", b"_one_wire_temperature_ch1", "_one_wire_temperature_ch2", b"_one_wire_temperature_ch2", "_one_wire_temperature_ch3", b"_one_wire_temperature_ch3", "_one_wire_temperature_ch4", b"_one_wire_temperature_ch4", "_one_wire_temperature_ch5", b"_one_wire_temperature_ch5", "_one_wire_temperature_ch6", b"_one_wire_temperature_ch6", "_one_wire_temperature_ch7", b"_one_wire_temperature_ch7", "_radiation", b"_radiation", "_rainfall_1h", b"_rainfall_1h", "_rainfall_24h", b"_rainfall_24h", "_relative_humidity", b"_relative_humidity", "_soil_moisture", b"_soil_moisture", "_soil_temperature", b"_soil_temperature", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "adc_voltage_ch0", b"adc_voltage_ch0", "adc_voltage_ch1", b"adc_voltage_ch1", "adc_voltage_ch2", b"adc_voltage_ch2", "adc_voltage_ch3", b"adc_voltage_ch3", "adc_voltage_ch4", b"adc_voltage_ch4", "adc_voltage_ch5", b"adc_voltage_ch5", "adc_voltage_ch6", b"adc_voltage_ch6", "adc_voltage_ch7", b"adc_voltage_ch7", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lightning_distance_km", b"lightning_distance_km", "lightning_strike_count_1h", b"lightning_strike_count_1h", "lux", b"lux", "one_wire_temperature_ch0", b"one_wire_temperature_ch0", "one_wire_temperature_ch1", b"one_wire_temperature_ch1", "one_wire_temperature_ch2", b"one_wire_temperature_ch2", "one_wire_temperature_ch3", b"one_wire_temperature_ch3", "one_wire_temperature_ch4", b"one_wire_temperature_ch4", "one_wire_temperature_ch5", b"one_wire_temperature_ch5", "one_wire_temperature_ch6", b"one_wire_temperature_ch6", "one_wire_temperature_ch7", b"one_wire_temperature_ch7", "radiation", b"radiation", "rainfall_1h", b"rainfall_1h", "rainfall_24h", b"rainfall_24h", "relative_humidity", b"relative_humidity", "soil_moisture", b"soil_moisture", "soil_temperature", b"soil_temperature", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_adc_voltage_ch0", b"_adc_voltage_ch0", "_adc_voltage_ch1", b"_adc_voltage_ch1", "_adc_voltage_ch2", b"_adc_voltage_ch2", "_adc_voltage_ch3", b"_adc_voltage_ch3", "_adc_voltage_ch4", b"_adc_voltage_ch4", "_adc_voltage_ch5", b"_adc_voltage_ch5", "_adc_voltage_ch6", b"_adc_voltage_ch6", "_adc_voltage_ch7", b"_adc_voltage_ch7", "_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lightning_distance_km", b"_lightning_distance_km", "_lightning_strike_count_1h", b"_lightning_strike_count_1h", "_lux", b"_lux", "_one_wire_temperature_ch0", b"_one_wire_temperature_ch0", "_one_wire_temperature_ch1", b"_one_wire_temperature_ch1", "_one_wire_temperature_ch2", b"_one_wire_temperature_ch2", "_one_wire_temperature_ch3", b"_one_wire_temperature_ch3", "_one_wire_temperature_ch4", b"_one_wire_temperature_ch4", "_one_wire_temperature_ch5", b"_one_wire_temperature_ch5", "_one_wire_temperature_ch6", b"_one_wire_temperature_ch6", "_one_wire_temperature_ch7", b"_one_wire_temperature_ch7", "_radiation", b"_radiation", "_rainfall_1h", b"_rainfall_1h", "_rainfall_24h", b"_rainfall_24h", "_relative_humidity", b"_relative_humidity", "_soil_moisture", b"_soil_moisture", "_soil_temperature", b"_soil_temperature", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "adc_voltage_ch0", b"adc_voltage_ch0", "adc_voltage_ch1", b"adc_voltage_ch1", "adc_voltage_ch2", b"adc_voltage_ch2", "adc_voltage_ch3", b"adc_voltage_ch3", "adc_voltage_ch4", b"adc_voltage_ch4", "adc_voltage_ch5", b"adc_voltage_ch5", "adc_voltage_ch6", b"adc_voltage_ch6", "adc_voltage_ch7", b"adc_voltage_ch7", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lightning_distance_km", b"lightning_distance_km", "lightning_strike_count_1h", b"lightning_strike_count_1h", "lux", b"lux", "one_wire_temperature", b"one_wire_temperature", "one_wire_temperature_ch0", b"one_wire_temperature_ch0", "one_wire_temperature_ch1", b"one_wire_temperature_ch1", "one_wire_temperature_ch2", b"one_wire_temperature_ch2", "one_wire_temperature_ch3", b"one_wire_temperature_ch3", "one_wire_temperature_ch4", b"one_wire_temperature_ch4", "one_wire_temperature_ch5", b"one_wire_temperature_ch5", "one_wire_temperature_ch6", b"one_wire_temperature_ch6", "one_wire_temperature_ch7", b"one_wire_temperature_ch7", "radiation", b"radiation", "rainfall_1h", b"rainfall_1h", "rainfall_24h", b"rainfall_24h", "relative_humidity", b"relative_humidity", "soil_moisture", b"soil_moisture", "soil_temperature", b"soil_temperature", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch0", b"_adc_voltage_ch0"]) -> typing.Literal["adc_voltage_ch0"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch1", b"_adc_voltage_ch1"]) -> typing.Literal["adc_voltage_ch1"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch2", b"_adc_voltage_ch2"]) -> typing.Literal["adc_voltage_ch2"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch3", b"_adc_voltage_ch3"]) -> typing.Literal["adc_voltage_ch3"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch4", b"_adc_voltage_ch4"]) -> typing.Literal["adc_voltage_ch4"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch5", b"_adc_voltage_ch5"]) -> typing.Literal["adc_voltage_ch5"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch6", b"_adc_voltage_ch6"]) -> typing.Literal["adc_voltage_ch6"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_adc_voltage_ch7", b"_adc_voltage_ch7"]) -> typing.Literal["adc_voltage_ch7"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_barometric_pressure", b"_barometric_pressure"]) -> typing.Literal["barometric_pressure"] | None: ...
|
||||
@typing.overload
|
||||
@@ -691,8 +847,28 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ir_lux", b"_ir_lux"]) -> typing.Literal["ir_lux"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_lightning_distance_km", b"_lightning_distance_km"]) -> typing.Literal["lightning_distance_km"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_lightning_strike_count_1h", b"_lightning_strike_count_1h"]) -> typing.Literal["lightning_strike_count_1h"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_lux", b"_lux"]) -> typing.Literal["lux"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch0", b"_one_wire_temperature_ch0"]) -> typing.Literal["one_wire_temperature_ch0"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch1", b"_one_wire_temperature_ch1"]) -> typing.Literal["one_wire_temperature_ch1"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch2", b"_one_wire_temperature_ch2"]) -> typing.Literal["one_wire_temperature_ch2"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch3", b"_one_wire_temperature_ch3"]) -> typing.Literal["one_wire_temperature_ch3"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch4", b"_one_wire_temperature_ch4"]) -> typing.Literal["one_wire_temperature_ch4"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch5", b"_one_wire_temperature_ch5"]) -> typing.Literal["one_wire_temperature_ch5"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch6", b"_one_wire_temperature_ch6"]) -> typing.Literal["one_wire_temperature_ch6"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_one_wire_temperature_ch7", b"_one_wire_temperature_ch7"]) -> typing.Literal["one_wire_temperature_ch7"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_radiation", b"_radiation"]) -> typing.Literal["radiation"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_rainfall_1h", b"_rainfall_1h"]) -> typing.Literal["rainfall_1h"] | None: ...
|
||||
@@ -775,43 +951,43 @@ class PowerMetrics(google.protobuf.message.Message):
|
||||
"""
|
||||
ch4_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch4)
|
||||
Voltage (Ch4) - TODO Remove
|
||||
"""
|
||||
ch4_current: builtins.float
|
||||
"""
|
||||
Current (Ch4)
|
||||
Current (Ch4) - TODO Remove
|
||||
"""
|
||||
ch5_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch5)
|
||||
Voltage (Ch5) - TODO Remove
|
||||
"""
|
||||
ch5_current: builtins.float
|
||||
"""
|
||||
Current (Ch5)
|
||||
Current (Ch5) - TODO Remove
|
||||
"""
|
||||
ch6_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch6)
|
||||
Voltage (Ch6) - TODO Remove
|
||||
"""
|
||||
ch6_current: builtins.float
|
||||
"""
|
||||
Current (Ch6)
|
||||
Current (Ch6) - TODO Remove
|
||||
"""
|
||||
ch7_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch7)
|
||||
Voltage (Ch7) - TODO Remove
|
||||
"""
|
||||
ch7_current: builtins.float
|
||||
"""
|
||||
Current (Ch7)
|
||||
Current (Ch7) - TODO Remove
|
||||
"""
|
||||
ch8_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch8)
|
||||
Voltage (Ch8) - TODO Remove
|
||||
"""
|
||||
ch8_current: builtins.float
|
||||
"""
|
||||
Current (Ch8)
|
||||
Current (Ch8) - TODO Remove
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
@@ -903,6 +1079,7 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
PM_VOC_IDX_FIELD_NUMBER: builtins.int
|
||||
PM_NOX_IDX_FIELD_NUMBER: builtins.int
|
||||
PARTICLES_TPS_FIELD_NUMBER: builtins.int
|
||||
PM_STATUS_FLAGS_FIELD_NUMBER: builtins.int
|
||||
pm10_standard: builtins.int
|
||||
"""
|
||||
Concentration Units Standard PM1.0 in ug/m3
|
||||
@@ -1003,6 +1180,13 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
"""
|
||||
Typical Particle Size in um
|
||||
"""
|
||||
pm_status_flags: builtins.int
|
||||
"""
|
||||
Raw PM sensor device status/error register bitmask, as defined by the sensor's own datasheet
|
||||
(currently populated by the SEN6X family: bit 4 fan error, bit 6 RH&T error, bit 7 gas/VOC-NOx
|
||||
error, bit 9 CO2 error (SEN66), bit 10 HCHO error, bit 11 PM error, bit 12 CO2 error (SEN63C/SEN69C),
|
||||
bit 21 fan speed warning)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1031,9 +1215,10 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
pm_voc_idx: builtins.float | None = ...,
|
||||
pm_nox_idx: builtins.float | None = ...,
|
||||
particles_tps: builtins.float | None = ...,
|
||||
pm_status_flags: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_co2", b"_co2", "_co2_humidity", b"_co2_humidity", "_co2_temperature", b"_co2_temperature", "_form_formaldehyde", b"_form_formaldehyde", "_form_humidity", b"_form_humidity", "_form_temperature", b"_form_temperature", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_40um", b"_particles_40um", "_particles_50um", b"_particles_50um", "_particles_tps", b"_particles_tps", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "_pm40_standard", b"_pm40_standard", "_pm_humidity", b"_pm_humidity", "_pm_nox_idx", b"_pm_nox_idx", "_pm_temperature", b"_pm_temperature", "_pm_voc_idx", b"_pm_voc_idx", "co2", b"co2", "co2_humidity", b"co2_humidity", "co2_temperature", b"co2_temperature", "form_formaldehyde", b"form_formaldehyde", "form_humidity", b"form_humidity", "form_temperature", b"form_temperature", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_40um", b"particles_40um", "particles_50um", b"particles_50um", "particles_tps", b"particles_tps", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard", "pm40_standard", b"pm40_standard", "pm_humidity", b"pm_humidity", "pm_nox_idx", b"pm_nox_idx", "pm_temperature", b"pm_temperature", "pm_voc_idx", b"pm_voc_idx"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_co2", b"_co2", "_co2_humidity", b"_co2_humidity", "_co2_temperature", b"_co2_temperature", "_form_formaldehyde", b"_form_formaldehyde", "_form_humidity", b"_form_humidity", "_form_temperature", b"_form_temperature", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_40um", b"_particles_40um", "_particles_50um", b"_particles_50um", "_particles_tps", b"_particles_tps", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "_pm40_standard", b"_pm40_standard", "_pm_humidity", b"_pm_humidity", "_pm_nox_idx", b"_pm_nox_idx", "_pm_temperature", b"_pm_temperature", "_pm_voc_idx", b"_pm_voc_idx", "co2", b"co2", "co2_humidity", b"co2_humidity", "co2_temperature", b"co2_temperature", "form_formaldehyde", b"form_formaldehyde", "form_humidity", b"form_humidity", "form_temperature", b"form_temperature", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_40um", b"particles_40um", "particles_50um", b"particles_50um", "particles_tps", b"particles_tps", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard", "pm40_standard", b"pm40_standard", "pm_humidity", b"pm_humidity", "pm_nox_idx", b"pm_nox_idx", "pm_temperature", b"pm_temperature", "pm_voc_idx", b"pm_voc_idx"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_co2", b"_co2", "_co2_humidity", b"_co2_humidity", "_co2_temperature", b"_co2_temperature", "_form_formaldehyde", b"_form_formaldehyde", "_form_humidity", b"_form_humidity", "_form_temperature", b"_form_temperature", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_40um", b"_particles_40um", "_particles_50um", b"_particles_50um", "_particles_tps", b"_particles_tps", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "_pm40_standard", b"_pm40_standard", "_pm_humidity", b"_pm_humidity", "_pm_nox_idx", b"_pm_nox_idx", "_pm_status_flags", b"_pm_status_flags", "_pm_temperature", b"_pm_temperature", "_pm_voc_idx", b"_pm_voc_idx", "co2", b"co2", "co2_humidity", b"co2_humidity", "co2_temperature", b"co2_temperature", "form_formaldehyde", b"form_formaldehyde", "form_humidity", b"form_humidity", "form_temperature", b"form_temperature", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_40um", b"particles_40um", "particles_50um", b"particles_50um", "particles_tps", b"particles_tps", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard", "pm40_standard", b"pm40_standard", "pm_humidity", b"pm_humidity", "pm_nox_idx", b"pm_nox_idx", "pm_status_flags", b"pm_status_flags", "pm_temperature", b"pm_temperature", "pm_voc_idx", b"pm_voc_idx"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_co2", b"_co2", "_co2_humidity", b"_co2_humidity", "_co2_temperature", b"_co2_temperature", "_form_formaldehyde", b"_form_formaldehyde", "_form_humidity", b"_form_humidity", "_form_temperature", b"_form_temperature", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_40um", b"_particles_40um", "_particles_50um", b"_particles_50um", "_particles_tps", b"_particles_tps", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "_pm40_standard", b"_pm40_standard", "_pm_humidity", b"_pm_humidity", "_pm_nox_idx", b"_pm_nox_idx", "_pm_status_flags", b"_pm_status_flags", "_pm_temperature", b"_pm_temperature", "_pm_voc_idx", b"_pm_voc_idx", "co2", b"co2", "co2_humidity", b"co2_humidity", "co2_temperature", b"co2_temperature", "form_formaldehyde", b"form_formaldehyde", "form_humidity", b"form_humidity", "form_temperature", b"form_temperature", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_40um", b"particles_40um", "particles_50um", b"particles_50um", "particles_tps", b"particles_tps", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard", "pm40_standard", b"pm40_standard", "pm_humidity", b"pm_humidity", "pm_nox_idx", b"pm_nox_idx", "pm_status_flags", b"pm_status_flags", "pm_temperature", b"pm_temperature", "pm_voc_idx", b"pm_voc_idx"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_co2", b"_co2"]) -> typing.Literal["co2"] | None: ...
|
||||
@typing.overload
|
||||
@@ -1081,6 +1266,8 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_nox_idx", b"_pm_nox_idx"]) -> typing.Literal["pm_nox_idx"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_status_flags", b"_pm_status_flags"]) -> typing.Literal["pm_status_flags"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_temperature", b"_pm_temperature"]) -> typing.Literal["pm_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_voc_idx", b"_pm_voc_idx"]) -> typing.Literal["pm_voc_idx"] | None: ...
|
||||
@@ -1488,10 +1675,33 @@ class Nau7802Config(google.protobuf.message.Message):
|
||||
|
||||
global___Nau7802Config = Nau7802Config
|
||||
|
||||
@typing.final
|
||||
class AS3935Config(google.protobuf.message.Message):
|
||||
"""
|
||||
AS3935 lightning sensor configuration, for saving to flash
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
TUNING_CAP_PF_FIELD_NUMBER: builtins.int
|
||||
tuning_cap_pf: builtins.int
|
||||
"""
|
||||
Antenna tuning capacitance in pF, 0 to 120 in steps of 8. The chip does not retain
|
||||
this across power loss, so it is stored here and re-applied on every boot.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tuning_cap_pf: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["tuning_cap_pf", b"tuning_cap_pf"]) -> None: ...
|
||||
|
||||
global___AS3935Config = AS3935Config
|
||||
|
||||
@typing.final
|
||||
class SEN5XState(google.protobuf.message.Message):
|
||||
"""
|
||||
SEN5X State, for saving to flash
|
||||
SEN5X State, for saving to flash (to be merged with SEN6XState)
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
@@ -1546,3 +1756,62 @@ class SEN5XState(google.protobuf.message.Message):
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_voc_state_valid", b"_voc_state_valid"]) -> typing.Literal["voc_state_valid"] | None: ...
|
||||
|
||||
global___SEN5XState = SEN5XState
|
||||
|
||||
@typing.final
|
||||
class SEN6XState(google.protobuf.message.Message):
|
||||
"""
|
||||
SEN6X State, for saving to flash
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
LAST_CLEANING_TIME_FIELD_NUMBER: builtins.int
|
||||
LAST_CLEANING_VALID_FIELD_NUMBER: builtins.int
|
||||
ONE_SHOT_MODE_FIELD_NUMBER: builtins.int
|
||||
VOC_STATE_TIME_FIELD_NUMBER: builtins.int
|
||||
VOC_STATE_VALID_FIELD_NUMBER: builtins.int
|
||||
VOC_STATE_ARRAY_FIELD_NUMBER: builtins.int
|
||||
last_cleaning_time: builtins.int
|
||||
"""
|
||||
Last cleaning time for SEN6X
|
||||
"""
|
||||
last_cleaning_valid: builtins.bool
|
||||
"""
|
||||
Last cleaning time for SEN6X - valid flag
|
||||
"""
|
||||
one_shot_mode: builtins.bool
|
||||
"""
|
||||
Config flag for one-shot mode (see admin.proto)
|
||||
"""
|
||||
voc_state_time: builtins.int
|
||||
"""
|
||||
Last VOC state time, for models with a VOC sensor (SEN65, SEN66, SEN68, SEN69C)
|
||||
"""
|
||||
voc_state_valid: builtins.bool
|
||||
"""
|
||||
Last VOC state validity flag, for models with a VOC sensor (SEN65, SEN66, SEN68, SEN69C)
|
||||
"""
|
||||
voc_state_array: builtins.int
|
||||
"""
|
||||
VOC state array (8x uint8t), for models with a VOC sensor (SEN65, SEN66, SEN68, SEN69C)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
last_cleaning_time: builtins.int = ...,
|
||||
last_cleaning_valid: builtins.bool = ...,
|
||||
one_shot_mode: builtins.bool = ...,
|
||||
voc_state_time: builtins.int | None = ...,
|
||||
voc_state_valid: builtins.bool | None = ...,
|
||||
voc_state_array: builtins.int | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_voc_state_array", b"_voc_state_array", "_voc_state_time", b"_voc_state_time", "_voc_state_valid", b"_voc_state_valid", "voc_state_array", b"voc_state_array", "voc_state_time", b"voc_state_time", "voc_state_valid", b"voc_state_valid"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_voc_state_array", b"_voc_state_array", "_voc_state_time", b"_voc_state_time", "_voc_state_valid", b"_voc_state_valid", "last_cleaning_time", b"last_cleaning_time", "last_cleaning_valid", b"last_cleaning_valid", "one_shot_mode", b"one_shot_mode", "voc_state_array", b"voc_state_array", "voc_state_time", b"voc_state_time", "voc_state_valid", b"voc_state_valid"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_voc_state_array", b"_voc_state_array"]) -> typing.Literal["voc_state_array"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_voc_state_time", b"_voc_state_time"]) -> typing.Literal["voc_state_time"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_voc_state_valid", b"_voc_state_valid"]) -> typing.Literal["voc_state_valid"] | None: ...
|
||||
|
||||
global___SEN6XState = SEN6XState
|
||||
@@ -11,6 +11,11 @@ from typing import Optional
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
DEFAULT_TCP_PORT = 4403
|
||||
|
||||
# How long close() gives the device to consume what we last wrote before forcing
|
||||
# the connection down. See close() for why this exists.
|
||||
GRACEFUL_CLOSE_TIMEOUT = 0.25
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -80,6 +85,18 @@ class TCPInterface(StreamInterface):
|
||||
server_address = (self.hostname, self.portNumber)
|
||||
self.socket = socket.create_connection(server_address)
|
||||
|
||||
def _wait_for_reader_exit(self, timeout: float) -> None:
|
||||
"""Wait briefly for the reader thread to drain and exit after a half-close.
|
||||
|
||||
Returns as soon as it exits, or after timeout: the device is not obliged
|
||||
to close just because we did.
|
||||
"""
|
||||
rx = getattr(self, "_rxThread", None)
|
||||
if rx is None or rx is threading.current_thread():
|
||||
return
|
||||
with contextlib.suppress(Exception):
|
||||
rx.join(timeout)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close a connection to the device."""
|
||||
logger.debug("Closing TCP stream")
|
||||
@@ -87,6 +104,20 @@ class TCPInterface(StreamInterface):
|
||||
# Therefore force a shutdown first to unblock reader thread reads.
|
||||
self._wantExit = True
|
||||
if self.socket is not None:
|
||||
# Half-close first. shutdown(SHUT_WR) sends FIN, which tells the
|
||||
# device we are done writing and lets it consume what we last wrote
|
||||
# before the connection goes away -- typically the admin message a
|
||||
# one-shot command such as `--set` just sent.
|
||||
#
|
||||
# Going straight to shutdown(SHUT_RDWR) + close() while either side
|
||||
# still has unread data makes the stack send RST instead. Winsock
|
||||
# then discards data the peer had already received but not yet read,
|
||||
# so the write is lost; Linux delivers it before reporting
|
||||
# ECONNRESET, which is why this only bites on Windows.
|
||||
with contextlib.suppress(Exception):
|
||||
self.socket.shutdown(socket.SHUT_WR)
|
||||
self._wait_for_reader_exit(GRACEFUL_CLOSE_TIMEOUT)
|
||||
|
||||
with contextlib.suppress(
|
||||
Exception
|
||||
): # Ignore errors in shutdown, because we might have a race with the server
|
||||
|
||||
@@ -8,6 +8,87 @@ import pytest
|
||||
from meshtastic import mt_config
|
||||
|
||||
from ..mesh_interface import MeshInterface
|
||||
from .firmware_harness import (
|
||||
CHAIN_TOPOLOGY,
|
||||
DEFAULT_BASE_PORT,
|
||||
SimMesh,
|
||||
find_meshtasticd,
|
||||
is_compatible_host,
|
||||
)
|
||||
from .fw_helpers import set_region
|
||||
|
||||
# Use a different base port for the single-node fixture so it doesn't
|
||||
# conflict with the multi-node mesh fixture.
|
||||
SINGLE_NODE_BASE_PORT = DEFAULT_BASE_PORT + 100
|
||||
|
||||
|
||||
def _skip_firmware_if_unavailable() -> None:
|
||||
"""Skip the test when meshtasticd can't run on this host."""
|
||||
if not is_compatible_host():
|
||||
pytest.skip("meshtasticd firmware tests require Linux")
|
||||
if find_meshtasticd() is None:
|
||||
pytest.skip(
|
||||
"meshtasticd not found — set MESHTASTICD_BIN or install it on PATH"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def firmware_node():
|
||||
"""A single meshtasticd sim node for smokevirt tests.
|
||||
|
||||
Function-scoped so every test gets a freshly-erased node with no
|
||||
state leaking from previous tests. This makes destructive commands
|
||||
(``--reboot``, ``--set factory_reset true``) safe to run and lets
|
||||
tests be order-independent.
|
||||
|
||||
Yields the SimNode instance. The node is booted with a fresh erased
|
||||
config and listens on localhost at its TCP port. Region is set to US
|
||||
so modem-preset tests work against firmware >= 2.8, which clamps
|
||||
presets to the legal set for the current region.
|
||||
"""
|
||||
_skip_firmware_if_unavailable()
|
||||
mesh = SimMesh(n_nodes=1, base_port=SINGLE_NODE_BASE_PORT)
|
||||
mesh.start()
|
||||
node = mesh.get_node(0)
|
||||
set_region(node.port, "US")
|
||||
# The region commit restarts the TCP listener, so reconnect the harness
|
||||
# interface in case a test wants to use it directly.
|
||||
if node.iface is not None:
|
||||
try:
|
||||
node.iface.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
node.connect()
|
||||
yield node
|
||||
mesh.stop()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def firmware_mesh():
|
||||
"""A 3-node chain (A-B-C) meshtasticd sim mesh for smokemesh tests.
|
||||
|
||||
Yields the SimMesh instance. Nodes are connected and the SIMULATOR_APP
|
||||
packet bridge is running. Region is set to US for firmware >= 2.8
|
||||
compatibility, interfaces are reconnected after the region change, and
|
||||
node DB convergence is awaited.
|
||||
"""
|
||||
_skip_firmware_if_unavailable()
|
||||
mesh = SimMesh(n_nodes=3, topology=CHAIN_TOPOLOGY)
|
||||
mesh.start()
|
||||
for node in mesh.nodes:
|
||||
set_region(node.port, "US")
|
||||
# The region commit restarts each node's TCP listener, so reconnect the
|
||||
# harness interfaces before waiting for convergence.
|
||||
for node in mesh.nodes:
|
||||
if node.iface is not None:
|
||||
try:
|
||||
node.iface.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
node.connect()
|
||||
mesh.wait_for_convergence(timeout=30)
|
||||
yield mesh
|
||||
mesh.stop()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
"""Test harness for running real meshtasticd firmware instances.
|
||||
|
||||
Launches one or more meshtasticd processes in simulator mode (-s) and bridges
|
||||
their "over-the-air" packets via the SIMULATOR_APP protocol so that multiple
|
||||
instances can communicate as if via LoRa, with a configurable topology.
|
||||
|
||||
The harness expects meshtasticd to be available on PATH or via the
|
||||
MESHTASTICD_BIN environment variable. It does not download or build the
|
||||
binary itself.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Dict, List, Optional, Set
|
||||
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic import BROADCAST_NUM, mesh_pb2, portnums_pb2
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HW_ID_OFFSET = 16
|
||||
DEFAULT_BASE_PORT = 4404
|
||||
DEFAULT_RSSI = -50
|
||||
DEFAULT_SNR = 10.0
|
||||
BOOT_TIMEOUT = 30
|
||||
CONNECT_TIMEOUT = 30
|
||||
|
||||
CHAIN_TOPOLOGY: Dict[int, Set[int]] = {
|
||||
0: {1},
|
||||
1: {0, 2},
|
||||
2: {1},
|
||||
}
|
||||
|
||||
|
||||
def find_meshtasticd() -> Optional[str]:
|
||||
"""Return the path to the meshtasticd binary, or None if not found."""
|
||||
env_path = os.environ.get("MESHTASTICD_BIN")
|
||||
if env_path and os.path.isfile(env_path) and os.access(env_path, os.X_OK):
|
||||
return env_path
|
||||
return shutil.which("meshtasticd")
|
||||
|
||||
|
||||
def is_compatible_host() -> bool:
|
||||
"""True when the host can run meshtasticd natively (Linux only)."""
|
||||
return platform.system() == "Linux"
|
||||
|
||||
|
||||
def _wait_for_port(port: int, timeout: int = BOOT_TIMEOUT) -> bool:
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
s = socket.create_connection(("localhost", port), timeout=0.5)
|
||||
s.close()
|
||||
return True
|
||||
except OSError:
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
|
||||
class SimNode:
|
||||
"""A single meshtasticd simulator instance."""
|
||||
|
||||
def __init__(self, node_id: int, base_port: int = DEFAULT_BASE_PORT):
|
||||
self.node_id = node_id
|
||||
self.hw_id = node_id + HW_ID_OFFSET
|
||||
self.port = base_port + node_id
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
self.workdir: Optional[str] = None
|
||||
self.iface: Optional[TCPInterface] = None
|
||||
self._log_files: list = []
|
||||
|
||||
@property
|
||||
def node_num(self) -> int:
|
||||
"""The firmware-assigned node number (== hw_id)."""
|
||||
if self.iface and self.iface.myInfo:
|
||||
return self.iface.myInfo.my_node_num
|
||||
return self.hw_id
|
||||
|
||||
def start(self, binary: str) -> None:
|
||||
"""Launch the meshtasticd process in simulator mode."""
|
||||
self.workdir = tempfile.mkdtemp(prefix=f"mtd_node{self.node_id}_")
|
||||
vfs_dir = os.path.join(self.workdir, "vfs")
|
||||
os.mkdir(vfs_dir)
|
||||
# Files are closed in _kill(); keep them open for the process lifetime.
|
||||
log_stdout = open( # pylint: disable=consider-using-with
|
||||
os.path.join(self.workdir, "meshtasticd.log"), "wb", buffering=0
|
||||
)
|
||||
log_stderr = open( # pylint: disable=consider-using-with
|
||||
os.path.join(self.workdir, "meshtasticd.err"), "wb", buffering=0
|
||||
)
|
||||
self._log_files = [log_stdout, log_stderr]
|
||||
self.process = subprocess.Popen( # pylint: disable=consider-using-with
|
||||
[
|
||||
binary,
|
||||
"-s",
|
||||
"-h", str(self.hw_id),
|
||||
"-p", str(self.port),
|
||||
"-d", vfs_dir,
|
||||
"-e",
|
||||
],
|
||||
stdout=log_stdout,
|
||||
stderr=log_stderr,
|
||||
start_new_session=True,
|
||||
)
|
||||
if not _wait_for_port(self.port):
|
||||
self._kill()
|
||||
raise RuntimeError(
|
||||
f"meshtasticd node {self.node_id} did not start listening on port {self.port}"
|
||||
)
|
||||
def connect(self) -> None:
|
||||
"""Open a TCPInterface connection to this node."""
|
||||
self.iface = TCPInterface(
|
||||
hostname="localhost",
|
||||
portNumber=self.port,
|
||||
connectNow=False,
|
||||
)
|
||||
self.iface.myConnect()
|
||||
self.iface.connect()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the interface and kill the process."""
|
||||
if self.iface is not None:
|
||||
try:
|
||||
self.iface.localNode.exitSimulator()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.iface.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.iface = None
|
||||
self._kill()
|
||||
if self.workdir:
|
||||
shutil.rmtree(self.workdir, ignore_errors=True)
|
||||
self.workdir = None
|
||||
|
||||
def _kill(self) -> None:
|
||||
for f in self._log_files:
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._log_files = []
|
||||
if self.process is not None:
|
||||
try:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
except (ProcessLookupError, OSError):
|
||||
pass
|
||||
try:
|
||||
self.process.wait(timeout=5)
|
||||
except Exception:
|
||||
try:
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
# Give OS time to release TCP port (avoid TIME_WAIT preventing
|
||||
# next instance from binding the same port)
|
||||
time.sleep(1.0)
|
||||
self.process = None
|
||||
|
||||
|
||||
class SimMesh:
|
||||
"""Manages N meshtasticd sim instances and bridges their SIMULATOR_APP packets.
|
||||
|
||||
When *topology* is None every node hears every other node (full mesh).
|
||||
Otherwise *topology* maps a transmitter's node index to the set of receiver
|
||||
node indices that can hear it.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
n_nodes: int = 1,
|
||||
topology: Optional[Dict[int, Set[int]]] = None,
|
||||
base_port: int = DEFAULT_BASE_PORT,
|
||||
):
|
||||
self.n_nodes = n_nodes
|
||||
self.topology = topology
|
||||
self.base_port = base_port
|
||||
self.nodes: List[SimNode] = [
|
||||
SimNode(i, base_port) for i in range(n_nodes)
|
||||
]
|
||||
self._port_to_idx: Dict[int, int] = {}
|
||||
self._started = False
|
||||
|
||||
def start(self) -> None:
|
||||
"""Launch all nodes, connect, and start the packet bridge."""
|
||||
binary = find_meshtasticd()
|
||||
if binary is None:
|
||||
raise RuntimeError(
|
||||
"meshtasticd not found. Set MESHTASTICD_BIN or install it on PATH."
|
||||
)
|
||||
|
||||
for node in self.nodes:
|
||||
node.start(binary)
|
||||
|
||||
for node in self.nodes:
|
||||
node.connect()
|
||||
self._port_to_idx[node.port] = node.node_id
|
||||
|
||||
pub.subscribe(self._on_sim_packet, "meshtastic.receive.simulator")
|
||||
self._started = True
|
||||
|
||||
if self.n_nodes > 1:
|
||||
self._trigger_convergence()
|
||||
|
||||
def _trigger_convergence(self) -> None:
|
||||
"""Actively trigger NodeInfo exchange instead of waiting passively.
|
||||
|
||||
Sends a NODEINFO_APP packet with wantResponse from each node so the
|
||||
firmware's NodeInfoModule responds with its own user info, populating
|
||||
all node DBs deterministically.
|
||||
"""
|
||||
for node in self.nodes:
|
||||
iface = node.iface
|
||||
if iface is None:
|
||||
continue
|
||||
user = mesh_pb2.User()
|
||||
user.id = f"!{node.node_num:08x}"
|
||||
user.long_name = f"Node {node.node_id}"
|
||||
user.short_name = f"{node.node_id:04d}"[:4]
|
||||
user.hw_model = mesh_pb2.HardwareModel.PORTDUINO
|
||||
try:
|
||||
iface.sendData(
|
||||
user,
|
||||
destinationId=BROADCAST_NUM,
|
||||
portNum=portnums_pb2.PortNum.NODEINFO_APP,
|
||||
wantAck=False,
|
||||
wantResponse=True,
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.debug("NodeInfo trigger for node %d failed: %s", node.node_id, ex)
|
||||
time.sleep(5)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Shut down all nodes and clean up."""
|
||||
if not self._started:
|
||||
return
|
||||
try:
|
||||
pub.unsubscribe(self._on_sim_packet, "meshtastic.receive.simulator")
|
||||
except Exception:
|
||||
pass
|
||||
for node in self.nodes:
|
||||
node.close()
|
||||
self._started = False
|
||||
|
||||
def get_node(self, idx: int) -> SimNode:
|
||||
"""Return the SimNode at the given index."""
|
||||
return self.nodes[idx]
|
||||
|
||||
def get_iface(self, idx: int) -> TCPInterface:
|
||||
"""Return the TCPInterface for the node at the given index."""
|
||||
iface = self.nodes[idx].iface
|
||||
assert iface is not None, f"node {idx} has no interface"
|
||||
return iface
|
||||
|
||||
def wait_for_convergence(self, timeout: int = 30) -> bool:
|
||||
"""Wait until every node sees all others in its node DB.
|
||||
|
||||
Returns True if converged, False if timed out (non-fatal — the packet
|
||||
bridge forwards regardless of node DB state).
|
||||
"""
|
||||
if self.n_nodes <= 1:
|
||||
return True
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if all(
|
||||
node.iface is not None
|
||||
and node.iface.nodes is not None
|
||||
and len(node.iface.nodes) >= self.n_nodes
|
||||
for node in self.nodes
|
||||
):
|
||||
return True
|
||||
time.sleep(2)
|
||||
logger.warning("Mesh did not fully converge within %ds", timeout)
|
||||
return False
|
||||
|
||||
def _get_receivers(self, tx_idx: int) -> List[int]:
|
||||
"""Return node indices that can hear a transmission from *tx_idx*."""
|
||||
if self.topology is not None:
|
||||
return sorted(self.topology.get(tx_idx, set()))
|
||||
return [i for i in range(self.n_nodes) if i != tx_idx]
|
||||
|
||||
def _on_sim_packet(self, interface, packet) -> None:
|
||||
"""Bridge callback: forward a SIMULATOR_APP packet to receiving nodes."""
|
||||
tx_port = getattr(interface, "portNumber", None)
|
||||
tx_idx = self._port_to_idx.get(tx_port) if tx_port else None
|
||||
if tx_idx is None:
|
||||
return
|
||||
|
||||
rx_indices = self._get_receivers(tx_idx)
|
||||
if not rx_indices:
|
||||
return
|
||||
|
||||
data = packet["decoded"]["payload"]
|
||||
if hasattr(data, "SerializeToString"):
|
||||
data = data.SerializeToString()
|
||||
|
||||
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
|
||||
logger.warning("Simulator payload too big (%d bytes), dropping", len(data))
|
||||
return
|
||||
|
||||
mesh_packet = _build_mesh_packet(packet, data)
|
||||
|
||||
for rx_idx in rx_indices:
|
||||
rx_iface = self.nodes[rx_idx].iface
|
||||
if rx_iface is None:
|
||||
continue
|
||||
mesh_packet.rx_rssi = DEFAULT_RSSI
|
||||
mesh_packet.rx_snr = DEFAULT_SNR
|
||||
to_radio = mesh_pb2.ToRadio()
|
||||
to_radio.packet.CopyFrom(mesh_packet)
|
||||
try:
|
||||
rx_iface._sendToRadio(to_radio)
|
||||
except Exception as ex:
|
||||
logger.error("Error forwarding packet to node %d: %s", rx_idx, ex)
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc):
|
||||
self.stop()
|
||||
|
||||
|
||||
def _build_mesh_packet(packet: dict, data: bytes) -> mesh_pb2.MeshPacket:
|
||||
"""Reconstruct a MeshPacket for SIMULATOR_APP injection."""
|
||||
mp = mesh_pb2.MeshPacket()
|
||||
mp.decoded.payload = data
|
||||
mp.decoded.portnum = portnums_pb2.PortNum.SIMULATOR_APP
|
||||
mp.to = packet.get("to", BROADCAST_NUM)
|
||||
setattr(mp, "from", packet.get("from", 0))
|
||||
mp.id = packet.get("id", 0)
|
||||
mp.want_ack = packet.get("wantAck", False)
|
||||
mp.hop_limit = packet.get("hopLimit", 0)
|
||||
mp.hop_start = packet.get("hopStart", 0)
|
||||
mp.via_mqtt = packet.get("viaMQTT", False)
|
||||
mp.relay_node = packet.get("relayNode", 0)
|
||||
mp.next_hop = packet.get("nextHop", 0)
|
||||
mp.channel = int(packet.get("channel", 0))
|
||||
|
||||
decoded = packet.get("decoded", {})
|
||||
if "requestId" in decoded:
|
||||
mp.decoded.request_id = decoded["requestId"]
|
||||
if "wantResponse" in decoded:
|
||||
mp.decoded.want_response = decoded["wantResponse"]
|
||||
if "bitfield" in decoded:
|
||||
mp.decoded.bitfield = decoded["bitfield"]
|
||||
|
||||
return mp
|
||||
@@ -0,0 +1,374 @@
|
||||
"""Shared helpers for meshtasticd-backed smoke tests.
|
||||
|
||||
Both ``test_smokevirt`` (single node) and ``test_smokemesh`` (multi-node
|
||||
chain) use these helpers to drive the ``meshtastic`` CLI against real
|
||||
``meshtasticd`` simulator instances and then verify the resulting firmware
|
||||
state through the Python library's ``TCPInterface``.
|
||||
|
||||
Verifying through the library (rather than regex on CLI stdout) is the
|
||||
core design choice: it makes tests robust against CLI wording changes
|
||||
while still exercising both the CLI argparse path and the firmware I/O
|
||||
path of every feature.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shlex
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic.tcp_interface import TCPInterface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Pause between a CLI command finishing and a verification interface
|
||||
# opening, to let the firmware flush its TCP bookkeeping. Keeps the
|
||||
# simulator happy when many short-lived connections are happening.
|
||||
PAUSE_AFTER_CLI = 0.2
|
||||
|
||||
# Pause after changing the LoRa region. The firmware may restart the TCP
|
||||
# listener while committing the new radio config, so give it time to settle
|
||||
# before the next CLI call or harness reconnect.
|
||||
PAUSE_AFTER_REGION_CHANGE = 2.0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI invocation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def resolve_cli() -> str:
|
||||
"""Return a shell-invokable ``meshtastic`` command.
|
||||
|
||||
Prefers the in-tree module run through the current interpreter so
|
||||
tests exercise the source we are editing, regardless of any
|
||||
separately-installed ``meshtastic`` entry point on PATH.
|
||||
"""
|
||||
# The PATH binary may live outside the nono sandbox's allowed paths;
|
||||
# ``python -m meshtastic`` is more portable and always available.
|
||||
return f"{sys.executable} -m meshtastic"
|
||||
|
||||
|
||||
def run_cli(
|
||||
port: int,
|
||||
*args: str,
|
||||
timeout: int = 60,
|
||||
retries: int = 2,
|
||||
retry_delay: float = 1.0,
|
||||
) -> Tuple[int, str]:
|
||||
"""Run the ``meshtastic`` CLI against the sim node on *port*.
|
||||
|
||||
Returns ``(return_code, merged_stdout_stderr)``. ``--host
|
||||
localhost:PORT`` is prefixed automatically. stderr is merged into
|
||||
stdout so callers can match warning text such as "Warning: Need to
|
||||
specify ..." regardless of which stream it lands on.
|
||||
|
||||
If the CLI fails to connect on the first attempt (which happens
|
||||
transiently when a freshly-booted sim node needs a moment to settle
|
||||
after the harness interface connects), retry up to *retries*
|
||||
additional times after *retry_delay* seconds.
|
||||
"""
|
||||
cli = resolve_cli()
|
||||
argv = [*_shlex_split(cli)]
|
||||
argv.extend(["--host", f"localhost:{port}"])
|
||||
argv.extend(args)
|
||||
logger.debug("run_cli: %s", argv)
|
||||
|
||||
last_out = ""
|
||||
for attempt in range(retries + 1):
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
argv,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired as ex:
|
||||
last_out = ex.stdout.decode("utf-8", errors="replace") if ex.stdout else ""
|
||||
if attempt < retries:
|
||||
time.sleep(retry_delay)
|
||||
continue
|
||||
return 124, last_out
|
||||
|
||||
out = proc.stdout.decode("utf-8", errors="replace")
|
||||
rc = proc.returncode
|
||||
|
||||
# Retry on transient connection-refused / timed-out errors that
|
||||
# are common right after a sim node spins up.
|
||||
transient = (
|
||||
"Error connecting" in out
|
||||
or "Timed out waiting for connection" in out
|
||||
or "Connection reset by peer" in out
|
||||
)
|
||||
if rc != 0 and transient and attempt < retries:
|
||||
logger.debug("run_cli: transient failure, retry %d/%d", attempt + 1, retries)
|
||||
time.sleep(retry_delay)
|
||||
last_out = out
|
||||
continue
|
||||
return rc, out
|
||||
return rc, last_out
|
||||
|
||||
|
||||
def _shlex_split(cmd: str) -> List[str]:
|
||||
"""Split a shell string into argv, honoring quotes."""
|
||||
return shlex.split(cmd)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fresh-connection state verification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _wait_for_port(port: int, timeout: float = 30.0) -> None:
|
||||
"""Wait until *port* accepts a TCP connection (firmware sim comes up
|
||||
or comes back after a config-commit reboot)."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
s = socket.create_connection(("localhost", port), timeout=0.5)
|
||||
s.close()
|
||||
return
|
||||
except OSError:
|
||||
time.sleep(0.2)
|
||||
raise TimeoutError(
|
||||
f"port {port} did not accept connections within {timeout}s"
|
||||
)
|
||||
|
||||
|
||||
def connect_iface(
|
||||
port: int,
|
||||
no_nodes: bool = False,
|
||||
retries: int = 4,
|
||||
wait_timeout: float = 30.0,
|
||||
) -> TCPInterface:
|
||||
"""Open a fresh ``TCPInterface`` to *port* and block on the config exchange.
|
||||
|
||||
Firmware config writes (``writeChannel``, ``--seturl``, ``--factory_reset``)
|
||||
can briefly restart the sim's TCP listener. We wait for the port to
|
||||
come up first, then retry the connect+config-exchange a few times.
|
||||
"""
|
||||
last_exc: Optional[Exception] = None
|
||||
for attempt in range(retries + 1):
|
||||
try:
|
||||
_wait_for_port(port, timeout=wait_timeout)
|
||||
return TCPInterface(
|
||||
hostname="localhost",
|
||||
portNumber=port,
|
||||
connectNow=True,
|
||||
noNodes=no_nodes,
|
||||
)
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
last_exc = ex
|
||||
if attempt < retries:
|
||||
logger.debug(
|
||||
"connect_iface attempt %d/%d failed: %s",
|
||||
attempt + 1, retries, ex,
|
||||
)
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
raise
|
||||
assert last_exc is not None # type guard; unreachable
|
||||
raise last_exc # pragma: no cover
|
||||
|
||||
|
||||
def verify_state(
|
||||
port: int,
|
||||
verifier: Callable[[TCPInterface], None],
|
||||
*,
|
||||
no_nodes: bool = False,
|
||||
) -> None:
|
||||
"""Open a fresh interface and run *verifier(iface)*, then close.
|
||||
|
||||
Used after a CLI mutation to verify firmware state through the
|
||||
library. Always closes the interface so the next test starts clean.
|
||||
"""
|
||||
iface = connect_iface(port, no_nodes=no_nodes)
|
||||
try:
|
||||
verifier(iface)
|
||||
finally:
|
||||
try:
|
||||
iface.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
time.sleep(PAUSE_AFTER_CLI)
|
||||
|
||||
|
||||
def cli_then_verify(
|
||||
port: int,
|
||||
cli_args: List[str],
|
||||
verifier: Optional[Callable[[TCPInterface], None]],
|
||||
*,
|
||||
expect_rc: Optional[int] = 0,
|
||||
no_nodes: bool = False,
|
||||
cli_timeout: int = 60,
|
||||
) -> str:
|
||||
"""Run *cli_args* against *port*, optionally asserting *expect_rc*,
|
||||
then (if *verifier* is not None) open a fresh interface and run
|
||||
*verifier(iface)* against the just-mutated firmware state.
|
||||
|
||||
Returns the CLI stdout.
|
||||
"""
|
||||
rc, out = run_cli(port, *cli_args, timeout=cli_timeout)
|
||||
if expect_rc is not None:
|
||||
assert rc == expect_rc, f"CLI rc={rc} (expected {expect_rc}): {out}"
|
||||
time.sleep(PAUSE_AFTER_CLI)
|
||||
if verifier is not None:
|
||||
verify_state(port, verifier, no_nodes=no_nodes)
|
||||
return out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Packet collectors (used by smokemesh receive-verification tests)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
RECEIVE_TIMEOUT = 15.0
|
||||
|
||||
|
||||
class PacketCollector:
|
||||
"""Collect packets received on a specific interface via pubsub.
|
||||
|
||||
``Listener`` (pubsub 4.x) wraps handlers with a weak reference, so
|
||||
we keep a strong reference to ``handler`` on the instance to prevent
|
||||
garbage collection before the publishing thread gets to call it.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.packets: List[dict] = []
|
||||
self._handler: Optional[Callable] = None
|
||||
|
||||
def on_receive(self, packet, interface): # pylint: disable=unused-argument
|
||||
"""Append a received packet to the internal list."""
|
||||
self.packets.append(packet)
|
||||
|
||||
def wait_for(self, count: int, timeout: float = RECEIVE_TIMEOUT) -> bool:
|
||||
"""Wait until *count* packets have been collected or *timeout* expires."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if len(self.packets) >= count:
|
||||
return True
|
||||
time.sleep(0.2)
|
||||
return len(self.packets) >= count
|
||||
|
||||
@property
|
||||
def texts(self) -> List[str]:
|
||||
"""Return text payloads from TEXT_MESSAGE_APP packets."""
|
||||
return [
|
||||
p.get("decoded", {}).get("text", "")
|
||||
for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "TEXT_MESSAGE_APP"
|
||||
]
|
||||
|
||||
@property
|
||||
def traceroutes(self) -> List[dict]:
|
||||
"""Return TRACEROUTE_APP packets."""
|
||||
return [
|
||||
p for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "TRACEROUTE_APP"
|
||||
]
|
||||
|
||||
@property
|
||||
def telemetries(self) -> List[dict]:
|
||||
"""Return TELEMETRY_APP packets."""
|
||||
return [
|
||||
p for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "TELEMETRY_APP"
|
||||
]
|
||||
|
||||
@property
|
||||
def positions(self) -> List[dict]:
|
||||
"""Return POSITION_APP packets."""
|
||||
return [
|
||||
p for p in self.packets
|
||||
if p.get("decoded", {}).get("portnum") == "POSITION_APP"
|
||||
]
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Clear all collected packets."""
|
||||
self.packets.clear()
|
||||
|
||||
|
||||
def _subscribe_topic(
|
||||
iface: TCPInterface, topic: str
|
||||
) -> PacketCollector:
|
||||
"""Internal: subscribe a fully-qualified *topic* and return a collector.
|
||||
|
||||
Filters by *interface* so multi-node tests can subscribe several
|
||||
collectors concurrently without cross-talk.
|
||||
"""
|
||||
collector = PacketCollector()
|
||||
|
||||
def handler(packet, interface):
|
||||
if interface is iface:
|
||||
collector.on_receive(packet, interface)
|
||||
|
||||
pub.subscribe(handler, topic)
|
||||
collector._handler = handler # strong ref; see PacketCollector docstring
|
||||
return collector
|
||||
|
||||
|
||||
def subscribe_texts(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.text`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.text")
|
||||
|
||||
|
||||
def subscribe_traceroutes(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.traceroute`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.traceroute")
|
||||
|
||||
|
||||
def subscribe_telemetries(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.telemetry`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.telemetry")
|
||||
|
||||
|
||||
def subscribe_positions(iface: TCPInterface) -> PacketCollector:
|
||||
"""Subscribe to ``meshtastic.receive.position`` filtered to *iface*."""
|
||||
return _subscribe_topic(iface, "meshtastic.receive.position")
|
||||
|
||||
|
||||
def set_region(port: int, region: str = "US") -> None:
|
||||
"""Set the LoRa region on a sim node via the CLI.
|
||||
|
||||
The node briefly restarts its TCP listener while committing the radio
|
||||
config; ``run_cli()`` handles the reconnect retry, and we sleep long
|
||||
enough for the new region to take effect before callers continue.
|
||||
"""
|
||||
rc, out = run_cli(port, "--set", "lora.region", region)
|
||||
if rc != 0:
|
||||
raise RuntimeError(f"Failed to set lora.region={region}: {out}")
|
||||
time.sleep(PAUSE_AFTER_REGION_CHANGE)
|
||||
|
||||
|
||||
def unsubscribe_all(topic: str) -> None:
|
||||
"""Drop every handler currently registered on *topic*.
|
||||
|
||||
Tests use this in a ``finally`` block to keep pubsub clean across
|
||||
the function-scoped mesh fixtures.
|
||||
"""
|
||||
try:
|
||||
pub.unsubAll(topic)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PAUSE_AFTER_CLI",
|
||||
"PAUSE_AFTER_REGION_CHANGE",
|
||||
"PacketCollector",
|
||||
"RECEIVE_TIMEOUT",
|
||||
"cli_then_verify",
|
||||
"connect_iface",
|
||||
"resolve_cli",
|
||||
"run_cli",
|
||||
"set_region",
|
||||
"subscribe_positions",
|
||||
"subscribe_telemetries",
|
||||
"subscribe_texts",
|
||||
"subscribe_traceroutes",
|
||||
"unsubscribe_all",
|
||||
"verify_state",
|
||||
]
|
||||
@@ -21,6 +21,7 @@ from meshtastic.protobuf import (
|
||||
atak_pb2,
|
||||
config_pb2,
|
||||
mesh_pb2,
|
||||
mqtt_pb2,
|
||||
nanopb_pb2,
|
||||
telemetry_pb2,
|
||||
)
|
||||
@@ -640,7 +641,15 @@ def test_descriptor_multilevel_nested_route_link_uid():
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_descriptor_telemetry_environment_one_wire_temperature():
|
||||
"""EnvironmentMetrics.one_wire_temperature has max_count = 8 from telemetry.options."""
|
||||
"""EnvironmentMetrics.one_wire_temperature has type = FT_IGNORE from telemetry.options."""
|
||||
env = telemetry_pb2.DESCRIPTOR.message_types_by_name["EnvironmentMetrics"]
|
||||
opts = _field_opts(env, "one_wire_temperature")
|
||||
assert opts.max_count == 8
|
||||
assert opts.type == nanopb_pb2.FT_IGNORE
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_descriptor_mqtt_service_envelope_pointer_fields():
|
||||
"""ServiceEnvelope fields carry type = FT_POINTER from mqtt.options."""
|
||||
envelope = mqtt_pb2.DESCRIPTOR.message_types_by_name["ServiceEnvelope"]
|
||||
for name in ("packet", "channel_id", "gateway_id"):
|
||||
assert _field_opts(envelope, name).type == nanopb_pb2.FT_POINTER
|
||||
@@ -10,11 +10,13 @@ import tempfile
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import mock_open, MagicMock, patch
|
||||
|
||||
import yaml
|
||||
import pytest
|
||||
import meshtastic.__main__ as mt_main
|
||||
|
||||
from meshtastic.__main__ import (
|
||||
export_config,
|
||||
export_profile,
|
||||
initParser,
|
||||
main,
|
||||
onConnection,
|
||||
@@ -23,11 +25,14 @@ from meshtastic.__main__ import (
|
||||
setPref,
|
||||
tunnelMain,
|
||||
set_missing_flags_false,
|
||||
_profile_from_yaml,
|
||||
)
|
||||
from meshtastic import mt_config
|
||||
|
||||
from ..protobuf.channel_pb2 import Channel # pylint: disable=E0611
|
||||
from ..protobuf.config_pb2 import Config # pylint: disable=E0611
|
||||
from ..protobuf.clientonly_pb2 import DeviceProfile # pylint: disable=E0611
|
||||
from ..protobuf.localonly_pb2 import LocalConfig, LocalModuleConfig # pylint: disable=E0611
|
||||
|
||||
# from ..ble_interface import BLEInterface
|
||||
from ..mesh_interface import MeshInterface
|
||||
@@ -871,7 +876,7 @@ def test_main_sendtext_with_invalid_channel_nine(caplog, capsys):
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_hupcl, capsys, caplog, iface_with_nodes):
|
||||
@@ -1069,7 +1074,7 @@ def test_main_seturl(capsys):
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_valid(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -1093,7 +1098,7 @@ def test_main_set_valid(mocked_findports, mocked_serial, mocked_open, mocked_hup
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_valid_wifi_psk(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -1117,7 +1122,7 @@ def test_main_set_valid_wifi_psk(mocked_findports, mocked_serial, mocked_open, m
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_invalid_wifi_psk(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -1144,7 +1149,7 @@ def test_main_set_invalid_wifi_psk(mocked_findports, mocked_serial, mocked_open,
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_valid_camel_case(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -1169,7 +1174,7 @@ def test_main_set_valid_camel_case(mocked_findports, mocked_serial, mocked_open,
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_with_invalid(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -1194,7 +1199,7 @@ def test_main_set_with_invalid(mocked_findports, mocked_serial, mocked_open, moc
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_configure_with_snake_case(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -1226,7 +1231,7 @@ def test_main_configure_with_snake_case(mocked_findports, mocked_serial, mocked_
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_configure_with_camel_case_keys(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
@@ -2012,6 +2017,471 @@ position_flags: 35"""
|
||||
assert err == ""
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_export_profile_serializes_deviceprofile():
|
||||
"""export_profile() returns a parseable DeviceProfile protobuf"""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = "foo"
|
||||
iface.getShortName.return_value = "oof"
|
||||
iface.localNode.getURL.return_value = "https://meshtastic.org/e/#test"
|
||||
iface.getCannedMessage.return_value = "Hi|Bye"
|
||||
iface.getRingtone.return_value = "24:d=32,o=5"
|
||||
iface.getMyNodeInfo.return_value = {
|
||||
"position": {"latitude": 35.88888, "longitude": -93.88888, "altitude": 304}
|
||||
}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.localConfig.position.fixed_position = True
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
raw = export_profile(iface)
|
||||
|
||||
profile = DeviceProfile()
|
||||
profile.ParseFromString(raw) # must not raise
|
||||
assert profile.long_name == "foo"
|
||||
assert profile.short_name == "oof"
|
||||
assert profile.channel_url == "https://meshtastic.org/e/#test"
|
||||
assert profile.canned_messages == "Hi|Bye"
|
||||
assert profile.ringtone == "24:d=32,o=5"
|
||||
assert profile.HasField("fixed_position")
|
||||
assert profile.fixed_position.latitude_i == 358888800
|
||||
assert profile.fixed_position.longitude_i == -938888800
|
||||
assert profile.fixed_position.altitude == 304
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_export_profile_omits_fixed_position_when_not_enabled():
|
||||
"""export_profile() omits fixed_position when position.fixed_position is False"""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = "foo"
|
||||
iface.getShortName.return_value = "oof"
|
||||
iface.localNode.getURL.return_value = ""
|
||||
iface.getCannedMessage.return_value = None
|
||||
iface.getRingtone.return_value = None
|
||||
iface.getMyNodeInfo.return_value = {
|
||||
"position": {"latitude": 35.88888, "longitude": -93.88888, "altitude": 304}
|
||||
}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
raw = export_profile(iface)
|
||||
|
||||
profile = DeviceProfile()
|
||||
profile.ParseFromString(raw)
|
||||
assert not profile.HasField("fixed_position"), \
|
||||
"fixed_position should be omitted when position.fixed_position is not enabled"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_export_profile_omits_empty_fields():
|
||||
"""export_profile() should not populate protobuf oneofs for missing values"""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = None
|
||||
iface.getShortName.return_value = None
|
||||
iface.localNode.getURL.return_value = ""
|
||||
iface.getCannedMessage.return_value = None
|
||||
iface.getRingtone.return_value = None
|
||||
iface.getMyNodeInfo.return_value = {}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
raw = export_profile(iface)
|
||||
|
||||
profile = DeviceProfile()
|
||||
profile.ParseFromString(raw)
|
||||
assert not profile.HasField("long_name")
|
||||
assert not profile.HasField("short_name")
|
||||
assert not profile.HasField("channel_url")
|
||||
assert not profile.HasField("canned_messages")
|
||||
assert not profile.HasField("ringtone")
|
||||
assert not profile.HasField("fixed_position")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_export_profile_converts_precisely():
|
||||
"""export_profile() uses exact arithmetic for lat/lon conversion"""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = None
|
||||
iface.getShortName.return_value = None
|
||||
iface.localNode.getURL.return_value = ""
|
||||
iface.getCannedMessage.return_value = None
|
||||
iface.getRingtone.return_value = None
|
||||
iface.getMyNodeInfo.return_value = {
|
||||
"position": {"latitude": -49.82207, "longitude": 0, "altitude": 0},
|
||||
}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.localConfig.position.fixed_position = True
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
raw = export_profile(iface)
|
||||
|
||||
profile = DeviceProfile()
|
||||
profile.ParseFromString(raw)
|
||||
assert profile.fixed_position.latitude_i == -498220700
|
||||
assert profile.fixed_position.longitude_i == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_profile_from_yaml_maps_all_fields():
|
||||
"""_profile_from_yaml() converts a YAML dict to an equivalent DeviceProfile"""
|
||||
configuration = {
|
||||
"owner": "YAML Owner",
|
||||
"owner_short": "YO",
|
||||
"channel_url": "https://meshtastic.org/e/#test",
|
||||
"canned_messages": "Hi|Bye",
|
||||
"ringtone": "24:d=32,o=5",
|
||||
"location": {"lat": 35.88888, "lon": -93.88888, "alt": 304},
|
||||
"config": {"bluetooth": {"enabled": True, "fixedPin": 123456}},
|
||||
"module_config": {"telemetry": {"deviceUpdateInterval": 900}},
|
||||
}
|
||||
profile = _profile_from_yaml(configuration)
|
||||
|
||||
assert profile.long_name == "YAML Owner"
|
||||
assert profile.short_name == "YO"
|
||||
assert profile.channel_url == "https://meshtastic.org/e/#test"
|
||||
assert profile.canned_messages == "Hi|Bye"
|
||||
assert profile.ringtone == "24:d=32,o=5"
|
||||
assert profile.HasField("fixed_position")
|
||||
assert profile.fixed_position.latitude_i == 358888800
|
||||
assert profile.fixed_position.longitude_i == -938888800
|
||||
assert profile.fixed_position.altitude == 304
|
||||
assert profile.HasField("config")
|
||||
assert profile.config.bluetooth.enabled is True
|
||||
assert profile.config.bluetooth.fixed_pin == 123456
|
||||
assert profile.HasField("module_config")
|
||||
assert profile.module_config.telemetry.device_update_interval == 900
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_profile_from_yaml_camelcase_keys():
|
||||
"""_profile_from_yaml() handles camelCase YAML keys"""
|
||||
configuration = {
|
||||
"ownerShort": "CB",
|
||||
"channelUrl": "https://meshtastic.org/e/#camel",
|
||||
}
|
||||
profile = _profile_from_yaml(configuration)
|
||||
|
||||
assert profile.short_name == "CB"
|
||||
assert profile.channel_url == "https://meshtastic.org/e/#camel"
|
||||
assert not profile.HasField("long_name")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_profile_from_yaml_converts_precisely():
|
||||
"""_profile_from_yaml() uses exact arithmetic for lat/lon conversion"""
|
||||
# -49.82207 is a value where int(float * 1e7) truncates to -498220699
|
||||
# instead of the correct -498220700
|
||||
configuration = {
|
||||
"location": {"lat": -49.82207, "lon": 0, "alt": 0},
|
||||
}
|
||||
profile = _profile_from_yaml(configuration)
|
||||
|
||||
assert profile.fixed_position.latitude_i == -498220700
|
||||
assert profile.fixed_position.longitude_i == 0
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_combines_setowner_into_single_call(tmp_path, capsys):
|
||||
"""Both long_name and short_name are set in a single setOwner call"""
|
||||
profile = DeviceProfile()
|
||||
profile.long_name = "Single Call"
|
||||
profile.short_name = "SC"
|
||||
cfg_path = tmp_path / "combined.cfg"
|
||||
cfg_path.write_bytes(profile.SerializeToString())
|
||||
|
||||
sys.argv = ["", "--configure", str(cfg_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
# Single combined message, not two separate ones
|
||||
assert re.search(r"Setting device owner to Single Call and short name to SC", out, re.MULTILINE)
|
||||
assert not re.search(r"Setting device owner short to SC$", out, re.MULTILINE)
|
||||
# setOwner should be called once, not twice
|
||||
iface.getNode.return_value.setOwner.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_with_binary_cfg(tmp_path, capsys):
|
||||
"""--configure with a .cfg binary file parses and applies a DeviceProfile"""
|
||||
profile = DeviceProfile()
|
||||
profile.long_name = "Binary Bob"
|
||||
profile.short_name = "BB"
|
||||
cfg_path = tmp_path / "backup.cfg"
|
||||
cfg_path.write_bytes(profile.SerializeToString())
|
||||
|
||||
sys.argv = ["", "--configure", str(cfg_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Setting device owner to Binary Bob and short name to BB", out, re.MULTILINE)
|
||||
assert re.search(r"Writing modified configuration to device", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_rejects_invalid_yaml_and_extension(tmp_path, capsys):
|
||||
"""A non-dict YAML file with a non-.cfg extension should error cleanly."""
|
||||
bad = tmp_path / "bad.yaml"
|
||||
bad.write_text("just a scalar string")
|
||||
sys.argv = ["", "--configure", str(bad)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"is not a valid YAML config or DeviceProfile", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_rejects_corrupt_cfg(tmp_path, capsys):
|
||||
"""A .cfg file that is not a valid DeviceProfile should error cleanly."""
|
||||
bad = tmp_path / "corrupt.cfg"
|
||||
bad.write_bytes(b"\x00\x01\x02 not really protobuf \xff\xfe")
|
||||
sys.argv = ["", "--configure", str(bad)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
with pytest.raises(SystemExit):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"is not a valid YAML config or DeviceProfile", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_binary_with_explicit_format(tmp_path, capsys):
|
||||
"""--configure --export-format binary forces binary parsing regardless of extension"""
|
||||
profile = DeviceProfile()
|
||||
profile.long_name = "Forced Binary"
|
||||
cfg_path = tmp_path / "notcfg.dat" # non-.cfg extension, but format forced
|
||||
cfg_path.write_bytes(profile.SerializeToString())
|
||||
|
||||
sys.argv = ["", "--configure", str(cfg_path), "--export-format", "binary"]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Setting device owner to Forced Binary", out, re.MULTILINE)
|
||||
assert re.search(r"Writing modified configuration to device", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_autodetects_binary_by_content(tmp_path, capsys):
|
||||
"""Binary DeviceProfile is detected by content even without .cfg extension"""
|
||||
profile = DeviceProfile()
|
||||
profile.long_name = "Content Detected"
|
||||
# Use .bin extension — autodetection must rely on content, not extension
|
||||
cfg_path = tmp_path / "backup.bin"
|
||||
cfg_path.write_bytes(profile.SerializeToString())
|
||||
|
||||
sys.argv = ["", "--configure", str(cfg_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Setting device owner to Content Detected", out, re.MULTILINE)
|
||||
assert re.search(r"Writing modified configuration to device", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_autodetects_yaml_by_content(tmp_path, capsys):
|
||||
"""YAML config is detected by content even with a non-standard extension"""
|
||||
yaml_path = tmp_path / "config.txt"
|
||||
yaml_path.write_text("owner: YAML Detected\n")
|
||||
|
||||
sys.argv = ["", "--configure", str(yaml_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Setting device owner to YAML Detected", out, re.MULTILINE)
|
||||
assert re.search(r"Writing modified configuration to device", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_export_config_binary_round_trip(tmp_path, capsys):
|
||||
"""Round-trip: export a .cfg via main(), then --configure it back via main()."""
|
||||
cfg_path = tmp_path / "roundtrip.cfg"
|
||||
|
||||
# Export
|
||||
sys.argv = ["", "--export-config", str(cfg_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = "Round Trip"
|
||||
iface.getShortName.return_value = "RT"
|
||||
iface.localNode.getURL.return_value = "https://meshtastic.org/e/#rt"
|
||||
iface.getCannedMessage.return_value = "Yes|No"
|
||||
iface.getRingtone.return_value = None
|
||||
iface.getMyNodeInfo.return_value = {
|
||||
"position": {"latitude": 1.0, "longitude": 2.0, "altitude": 3}
|
||||
}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.localConfig.position.fixed_position = True
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Exported profile to", out, re.MULTILINE)
|
||||
assert cfg_path.exists()
|
||||
|
||||
# Verify the exported file is a valid DeviceProfile
|
||||
profile = DeviceProfile()
|
||||
profile.ParseFromString(cfg_path.read_bytes())
|
||||
assert profile.long_name == "Round Trip"
|
||||
assert profile.short_name == "RT"
|
||||
assert profile.HasField("fixed_position")
|
||||
assert profile.fixed_position.latitude_i == int(1.0 * 1e7)
|
||||
assert profile.fixed_position.longitude_i == int(2.0 * 1e7)
|
||||
assert profile.fixed_position.altitude == 3
|
||||
|
||||
# Re-import the file we just wrote
|
||||
sys.argv = ["", "--configure", str(cfg_path)]
|
||||
mt_config.args = sys.argv
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Setting device owner to Round Trip and short name to RT", out, re.MULTILINE)
|
||||
assert re.search(r"Setting device position", out, re.MULTILINE)
|
||||
assert re.search(r"Writing modified configuration to device", out, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_does_not_set_fixed_position_for_legacy_yaml(tmp_path, capsys):
|
||||
"""Legacy YAML with location and no position config does NOT call setFixedPosition"""
|
||||
yaml_path = tmp_path / "legacy.yaml"
|
||||
yaml_path.write_text("location:\n lat: 35.0\n lon: -93.0\n alt: 100\n")
|
||||
|
||||
sys.argv = ["", "--configure", str(yaml_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert not re.search(r"Setting device position", out, re.MULTILINE)
|
||||
iface.getNode.return_value.setFixedPosition.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_skips_setfixedposition_when_config_disables_it(tmp_path, capsys):
|
||||
"""YAML with location but position.fixed_position=false skips setFixedPosition"""
|
||||
yaml_path = tmp_path / "no_fixed.yaml"
|
||||
yaml_path.write_text(
|
||||
"location:\n lat: 35.0\n lon: -93.0\n alt: 100\n"
|
||||
"config:\n position:\n fixed_position: false\n"
|
||||
)
|
||||
|
||||
sys.argv = ["", "--configure", str(yaml_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert not re.search(r"Setting device position", out, re.MULTILINE)
|
||||
iface.getNode.return_value.setFixedPosition.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_calls_setfixedposition_when_config_opt_in(tmp_path, capsys):
|
||||
"""YAML with location and position.fixed_position=true calls setFixedPosition"""
|
||||
yaml_path = tmp_path / "fixed_on.yaml"
|
||||
yaml_path.write_text(
|
||||
"location:\n lat: 35.0\n lon: -93.0\n alt: 100\n"
|
||||
"config:\n position:\n fixed_position: true\n"
|
||||
)
|
||||
|
||||
sys.argv = ["", "--configure", str(yaml_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Setting device position", out, re.MULTILINE)
|
||||
iface.getNode.return_value.setFixedPosition.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_yaml_preserves_unmentioned_fields(tmp_path, capsys):
|
||||
"""Partial YAML config does not reset unspecified fields in the same section."""
|
||||
yaml_path = tmp_path / "partial.yaml"
|
||||
yaml_path.write_text("config:\n bluetooth:\n enabled: false\n")
|
||||
|
||||
sys.argv = ["", "--configure", str(yaml_path)]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
local_config = LocalConfig()
|
||||
local_config.bluetooth.enabled = True
|
||||
local_config.bluetooth.fixed_pin = 654321
|
||||
iface.localNode.localConfig = local_config
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
# Make getNode return the same node object so apply writes back to our real config.
|
||||
iface.getNode.return_value = iface.localNode
|
||||
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Writing modified configuration to device", out, re.MULTILINE)
|
||||
assert iface.localNode.localConfig.bluetooth.enabled is False
|
||||
assert iface.localNode.localConfig.bluetooth.fixed_pin == 654321
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_configure_rejects_remote_node(tmp_path, capsys):
|
||||
"""--configure with --dest pointing to a remote node is rejected."""
|
||||
yaml_path = tmp_path / "remote.yaml"
|
||||
yaml_path.write_text("owner: Remote Owner\n")
|
||||
|
||||
sys.argv = ["", "--configure", str(yaml_path), "--dest", "!12345678"]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
|
||||
main()
|
||||
out, _ = capsys.readouterr()
|
||||
assert re.search(r"Configuring remote nodes is not supported", out, re.MULTILINE)
|
||||
|
||||
|
||||
# TODO
|
||||
# recursion depth exceeded error
|
||||
#@pytest.mark.unit
|
||||
@@ -2077,6 +2547,124 @@ position_flags: 35"""
|
||||
# mo.assert_called()
|
||||
|
||||
|
||||
_TRUE_DEFAULTS = [
|
||||
"bluetooth.enabled",
|
||||
"lora.sx126x_rx_boosted_gain",
|
||||
"lora.tx_enabled",
|
||||
"lora.use_preset",
|
||||
"position.position_broadcast_smart_enabled",
|
||||
"security.serial_enabled",
|
||||
]
|
||||
|
||||
_MOD_TRUE_DEFAULTS = [
|
||||
"mqtt.encryption_enabled",
|
||||
]
|
||||
|
||||
|
||||
def _set_config_bool(lc, path, value):
|
||||
section, field = path.split(".")
|
||||
setattr(getattr(lc, section), field, value)
|
||||
|
||||
|
||||
def _assert_config_bool(profile, path, expected):
|
||||
section, field = path.split(".")
|
||||
assert getattr(getattr(profile.config, section), field) is expected
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@pytest.mark.parametrize("fmt", ["binary", "yaml"])
|
||||
@pytest.mark.parametrize("value", [True, False])
|
||||
def test_round_trip_preserves_config_true_defaults(fmt, value):
|
||||
"""Export-->import preserves config_true_defaults fields for both formats"""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = None
|
||||
iface.getShortName.return_value = None
|
||||
iface.localNode.getURL.return_value = ""
|
||||
iface.getCannedMessage.return_value = None
|
||||
iface.getRingtone.return_value = None
|
||||
iface.getMyNodeInfo.return_value = {}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
for path in _TRUE_DEFAULTS:
|
||||
_set_config_bool(iface.localNode.localConfig, path, value)
|
||||
for path in _MOD_TRUE_DEFAULTS:
|
||||
_set_config_bool(iface.localNode.moduleConfig, path, value)
|
||||
|
||||
if fmt == "binary":
|
||||
raw = export_profile(iface)
|
||||
profile = DeviceProfile()
|
||||
profile.ParseFromString(raw)
|
||||
else:
|
||||
yaml_str = export_config(iface)
|
||||
configuration = yaml.safe_load(yaml_str)
|
||||
profile = _profile_from_yaml(configuration)
|
||||
|
||||
assert profile.HasField("config")
|
||||
assert profile.HasField("module_config")
|
||||
for path in _TRUE_DEFAULTS:
|
||||
_assert_config_bool(profile, path, value)
|
||||
for path in _MOD_TRUE_DEFAULTS:
|
||||
section, field = path.split(".")
|
||||
assert getattr(getattr(profile.module_config, section), field) is value
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_binary_and_yaml_export_consistent():
|
||||
"""Binary and YAML export paths produce equivalent DeviceProfile protos"""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getLongName.return_value = "Consistency"
|
||||
iface.getShortName.return_value = "CON"
|
||||
iface.localNode.getURL.return_value = ""
|
||||
iface.getCannedMessage.return_value = None
|
||||
iface.getRingtone.return_value = None
|
||||
iface.getMyNodeInfo.return_value = {}
|
||||
iface.localNode.localConfig = LocalConfig()
|
||||
iface.localNode.moduleConfig = LocalModuleConfig()
|
||||
|
||||
lc = iface.localNode.localConfig
|
||||
lc.bluetooth.enabled = True
|
||||
lc.bluetooth.fixed_pin = 123456
|
||||
lc.lora.sx126x_rx_boosted_gain = False
|
||||
lc.lora.tx_enabled = True
|
||||
lc.lora.use_preset = True
|
||||
lc.position.position_broadcast_smart_enabled = False
|
||||
lc.security.serial_enabled = True
|
||||
|
||||
lc_mod = iface.localNode.moduleConfig
|
||||
lc_mod.mqtt.encryption_enabled = True
|
||||
|
||||
# Binary path
|
||||
raw = export_profile(iface)
|
||||
binary_profile = DeviceProfile()
|
||||
binary_profile.ParseFromString(raw)
|
||||
|
||||
# YAML path
|
||||
yaml_str = export_config(iface)
|
||||
configuration = yaml.safe_load(yaml_str)
|
||||
yaml_profile = _profile_from_yaml(configuration)
|
||||
|
||||
# Both should have config
|
||||
assert binary_profile.HasField("config") == yaml_profile.HasField("config")
|
||||
assert binary_profile.HasField("module_config") == yaml_profile.HasField("module_config")
|
||||
|
||||
# Compare individual field values
|
||||
assert binary_profile.config.bluetooth.enabled == yaml_profile.config.bluetooth.enabled
|
||||
assert binary_profile.config.bluetooth.fixed_pin == yaml_profile.config.bluetooth.fixed_pin
|
||||
assert binary_profile.config.lora.sx126x_rx_boosted_gain == yaml_profile.config.lora.sx126x_rx_boosted_gain
|
||||
assert binary_profile.config.lora.tx_enabled == yaml_profile.config.lora.tx_enabled
|
||||
assert binary_profile.config.lora.use_preset == yaml_profile.config.lora.use_preset
|
||||
assert binary_profile.config.position.position_broadcast_smart_enabled == yaml_profile.config.position.position_broadcast_smart_enabled
|
||||
assert binary_profile.config.security.serial_enabled == yaml_profile.config.security.serial_enabled
|
||||
assert binary_profile.module_config.mqtt.encryption_enabled == yaml_profile.module_config.mqtt.encryption_enabled
|
||||
|
||||
# Owner fields also match
|
||||
assert binary_profile.long_name == yaml_profile.long_name
|
||||
assert binary_profile.short_name == yaml_profile.short_name
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_set_missing_flags_false():
|
||||
"""Test set_missing_flags_false() function"""
|
||||
@@ -2906,7 +3494,7 @@ def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, caplog, capsys)
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("platform.system")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=b"{}")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_tunnel_tunnel_arg(
|
||||
|
||||
@@ -12,11 +12,11 @@ def test_handleFromRadio_with_traffic_management_module_config():
|
||||
"""Test _handleFromRadio with moduleConfig.traffic_management."""
|
||||
iface = MeshInterface(noProto=True)
|
||||
from_radio = mesh_pb2.FromRadio()
|
||||
from_radio.moduleConfig.traffic_management.enabled = True
|
||||
from_radio.moduleConfig.traffic_management.rate_limit_enabled = True
|
||||
from_radio.moduleConfig.traffic_management.position_min_interval_secs = 30
|
||||
from_radio.moduleConfig.traffic_management.rate_limit_window_secs = 60
|
||||
|
||||
iface._handleFromRadio(from_radio.SerializeToString())
|
||||
|
||||
assert iface.localNode.moduleConfig.traffic_management.enabled is True
|
||||
assert iface.localNode.moduleConfig.traffic_management.rate_limit_enabled is True
|
||||
assert iface.localNode.moduleConfig.traffic_management.position_min_interval_secs == 30
|
||||
assert iface.localNode.moduleConfig.traffic_management.rate_limit_window_secs == 60
|
||||
iface.close()
|
||||
@@ -669,6 +669,78 @@ def test_getChannelByChannelIndex():
|
||||
assert anode.getChannelByChannelIndex(9) is None
|
||||
|
||||
|
||||
def _build_channels(highest_secondary_index: int):
|
||||
"""Build an 8-slot channel table with contiguous active channels.
|
||||
|
||||
Slot 0 is PRIMARY. Slots 1..highest_secondary_index are SECONDARY.
|
||||
Remaining slots are DISABLED.
|
||||
"""
|
||||
channels = []
|
||||
for idx in range(8):
|
||||
ch = Channel()
|
||||
ch.index = idx
|
||||
if idx == 0:
|
||||
ch.role = Channel.Role.PRIMARY
|
||||
ch.settings.name = "primary"
|
||||
elif idx <= highest_secondary_index:
|
||||
ch.role = Channel.Role.SECONDARY
|
||||
ch.settings.name = f"ch{idx}"
|
||||
else:
|
||||
ch.role = Channel.Role.DISABLED
|
||||
channels.append(ch)
|
||||
return channels
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.parametrize(
|
||||
"highest_secondary_index,delete_index,expected_writes",
|
||||
[
|
||||
pytest.param(1, 1, [1], id="active-0-1-del-1"),
|
||||
pytest.param(2, 1, [1, 2], id="active-0-2-del-1"),
|
||||
pytest.param(3, 1, [1, 2, 3], id="active-0-3-del-1"),
|
||||
pytest.param(3, 2, [2, 3], id="active-0-3-del-2"),
|
||||
],
|
||||
)
|
||||
def test_delete_channel_writes_only_changed_suffix(
|
||||
highest_secondary_index, delete_index, expected_writes
|
||||
):
|
||||
"""deleteChannel should only write slots whose payload changed."""
|
||||
iface = MagicMock()
|
||||
anode = Node(iface, "bar", noProto=True)
|
||||
iface.localNode = anode
|
||||
anode.channels = _build_channels(highest_secondary_index)
|
||||
|
||||
writes = []
|
||||
|
||||
def fake_write(channel_index, adminIndex=0):
|
||||
writes.append((channel_index, adminIndex))
|
||||
|
||||
anode.writeChannel = fake_write
|
||||
|
||||
anode.deleteChannel(delete_index)
|
||||
|
||||
written_indices = [idx for idx, _ in writes]
|
||||
assert written_indices == expected_writes
|
||||
assert all(admin_idx == 0 for _, admin_idx in writes)
|
||||
assert 0 not in written_indices
|
||||
assert all(idx < 4 for idx in written_indices)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_delete_channel_rejects_primary():
|
||||
"""deleteChannel should refuse deleting PRIMARY slot 0."""
|
||||
iface = MagicMock()
|
||||
anode = Node(iface, "bar", noProto=True)
|
||||
iface.localNode = anode
|
||||
anode.channels = _build_channels(3)
|
||||
|
||||
with pytest.raises(SystemExit) as pytest_wrapped_e:
|
||||
anode.deleteChannel(0)
|
||||
|
||||
assert pytest_wrapped_e.type is SystemExit
|
||||
assert pytest_wrapped_e.value.code == 1
|
||||
|
||||
|
||||
# TODO
|
||||
# @pytest.mark.unit
|
||||
# def test_deleteChannel_try_to_delete_primary_channel(capsys):
|
||||
@@ -1081,8 +1153,8 @@ def test_writeConfig_traffic_management():
|
||||
"""Test writeConfig with traffic_management module config."""
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
anode = Node(iface, 123, noProto=True)
|
||||
anode.moduleConfig.traffic_management.enabled = True
|
||||
anode.moduleConfig.traffic_management.rate_limit_enabled = True
|
||||
anode.moduleConfig.traffic_management.position_min_interval_secs = 30
|
||||
anode.moduleConfig.traffic_management.rate_limit_window_secs = 60
|
||||
|
||||
sent_admin = []
|
||||
|
||||
@@ -1095,8 +1167,8 @@ def test_writeConfig_traffic_management():
|
||||
assert len(sent_admin) == 1
|
||||
assert sent_admin[0].HasField("set_module_config")
|
||||
assert sent_admin[0].set_module_config.HasField("traffic_management")
|
||||
assert sent_admin[0].set_module_config.traffic_management.enabled is True
|
||||
assert sent_admin[0].set_module_config.traffic_management.rate_limit_enabled is True
|
||||
assert sent_admin[0].set_module_config.traffic_management.position_min_interval_secs == 30
|
||||
assert sent_admin[0].set_module_config.traffic_management.rate_limit_window_secs == 60
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Meshtastic smoke tests with multiple meshtasticd sim instances.
|
||||
|
||||
Uses the ``firmware_mesh`` session fixture which provides a 3-node chain
|
||||
topology (A-B-C) where A hears B, B hears A and C, and C hears B.
|
||||
The SIMULATOR_APP packet bridge forwards transmissions between nodes
|
||||
according to this topology.
|
||||
"""
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from .fw_helpers import (
|
||||
RECEIVE_TIMEOUT,
|
||||
subscribe_texts,
|
||||
subscribe_traceroutes,
|
||||
unsubscribe_all,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_node_db_convergence(firmware_mesh):
|
||||
"""Each node should see all 3 nodes in its node DB after convergence."""
|
||||
counts = [len(n.iface.nodes) for n in firmware_mesh.nodes if n.iface]
|
||||
if any(c < 3 for c in counts):
|
||||
pytest.skip(f"Mesh did not converge (counts={counts})")
|
||||
for i, node in enumerate(firmware_mesh.nodes):
|
||||
iface = node.iface
|
||||
assert iface is not None
|
||||
assert len(iface.nodes) >= 3, f"node {i} only sees {len(iface.nodes)} nodes"
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_broadcast_text(firmware_mesh):
|
||||
"""A broadcast from node A should arrive on node B."""
|
||||
collector = subscribe_texts(firmware_mesh.get_iface(1))
|
||||
try:
|
||||
firmware_mesh.get_iface(0).sendText("hello mesh", wantAck=False)
|
||||
assert collector.wait_for(1)
|
||||
assert "hello mesh" in collector.texts
|
||||
finally:
|
||||
unsubscribe_all("meshtastic.receive.text")
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_dm(firmware_mesh):
|
||||
"""A DM from node A to node B should arrive on B."""
|
||||
dest = firmware_mesh.get_node(1).node_num
|
||||
collector = subscribe_texts(firmware_mesh.get_iface(1))
|
||||
try:
|
||||
firmware_mesh.get_iface(0).sendText(
|
||||
"hey B", destinationId=dest, wantAck=False
|
||||
)
|
||||
assert collector.wait_for(1)
|
||||
assert "hey B" in collector.texts
|
||||
finally:
|
||||
unsubscribe_all("meshtastic.receive.text")
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_dm_across_relay(firmware_mesh):
|
||||
"""A DM from node A to node C must relay through B (chain topology)."""
|
||||
dest = firmware_mesh.get_node(2).node_num
|
||||
collector = subscribe_texts(firmware_mesh.get_iface(2))
|
||||
try:
|
||||
firmware_mesh.get_iface(0).sendText(
|
||||
"relay test", destinationId=dest, wantAck=False
|
||||
)
|
||||
assert collector.wait_for(1), "node C did not receive the DM within timeout"
|
||||
assert "relay test" in collector.texts
|
||||
finally:
|
||||
unsubscribe_all("meshtastic.receive.text")
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_hop_limit_prevents_relay(firmware_mesh):
|
||||
"""A broadcast with hopLimit=0 from A reaches B but B does not relay to C."""
|
||||
col_b = subscribe_texts(firmware_mesh.get_iface(1))
|
||||
col_c = subscribe_texts(firmware_mesh.get_iface(2))
|
||||
try:
|
||||
firmware_mesh.get_iface(0).sendText(
|
||||
"hop0", wantAck=False, hopLimit=0
|
||||
)
|
||||
assert col_b.wait_for(1), "B should receive A's broadcast"
|
||||
assert "hop0" in col_b.texts, "B got wrong text"
|
||||
|
||||
time.sleep(RECEIVE_TIMEOUT)
|
||||
assert "hop0" not in col_c.texts, (
|
||||
"C should NOT receive — B must not relay hopLimit=0"
|
||||
)
|
||||
finally:
|
||||
unsubscribe_all("meshtastic.receive.text")
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_show_nodes(firmware_mesh):
|
||||
"""showNodes should report the other nodes in the mesh."""
|
||||
for i in range(3):
|
||||
iface = firmware_mesh.get_iface(i)
|
||||
iface.showNodes()
|
||||
|
||||
|
||||
@pytest.mark.smokemesh
|
||||
def test_smokemesh_traceroute_across_relay(firmware_mesh):
|
||||
"""Traceroute from A to C should show route via B in both directions."""
|
||||
col_a = subscribe_traceroutes(firmware_mesh.get_iface(0))
|
||||
col_c = subscribe_traceroutes(firmware_mesh.get_iface(2))
|
||||
try:
|
||||
src_a = firmware_mesh.get_node(0).node_num
|
||||
dest_c = firmware_mesh.get_node(2).node_num
|
||||
node_b = firmware_mesh.get_node(1).node_num
|
||||
|
||||
firmware_mesh.get_iface(0).sendTraceRoute(dest=dest_c, hopLimit=3)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
assert len(col_a.traceroutes) >= 1, "A did not receive traceroute response"
|
||||
a_resp = col_a.traceroutes[0]
|
||||
assert a_resp["from"] == dest_c, "response source should be C"
|
||||
|
||||
route = a_resp["decoded"]["traceroute"]
|
||||
assert route.get("route") == [node_b], "forward route should be A→B→C"
|
||||
assert route.get("routeBack") == [node_b], "return route should be C→B→A"
|
||||
|
||||
assert len(col_c.traceroutes) >= 1, "C did not receive traceroute request"
|
||||
c_req = col_c.traceroutes[0]
|
||||
assert c_req["from"] == src_a, "request source should be A"
|
||||
finally:
|
||||
unsubscribe_all("meshtastic.receive.traceroute")
|
||||
+508
-701
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,7 @@
|
||||
"""Meshtastic unit tests for tcp_interface.py"""
|
||||
|
||||
import re
|
||||
import socket
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -78,6 +79,56 @@ def test_TCPInterface_close_shutdowns_socket_before_super_close():
|
||||
assert iface.socket is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_TCPInterface_close_half_closes_before_shutdown():
|
||||
"""Close should send FIN and let the device drain before forcing the link down.
|
||||
|
||||
Going straight to shutdown(SHUT_RDWR)/close() with data still unread makes the
|
||||
stack send RST, and Winsock then discards data the device had received but not
|
||||
yet read, silently losing writes such as `--set`.
|
||||
"""
|
||||
iface = TCPInterface(hostname="localhost", noProto=True, connectNow=False)
|
||||
sock = MagicMock()
|
||||
iface.socket = sock
|
||||
call_order = []
|
||||
|
||||
with patch.object(TCPInterface, "_socket_shutdown", autospec=True) as mock_shutdown:
|
||||
with patch.object(
|
||||
TCPInterface, "_wait_for_reader_exit", autospec=True
|
||||
) as mock_wait:
|
||||
with patch(
|
||||
"meshtastic.stream_interface.StreamInterface.close", autospec=True
|
||||
) as mock_super_close:
|
||||
sock.shutdown.side_effect = lambda how: call_order.append(f"shutdown_{how}")
|
||||
mock_wait.side_effect = lambda _self, _t: call_order.append("wait")
|
||||
mock_shutdown.side_effect = lambda _self: call_order.append("full_shutdown")
|
||||
mock_super_close.side_effect = lambda _self: call_order.append("super_close")
|
||||
|
||||
iface.close()
|
||||
|
||||
assert call_order == [
|
||||
f"shutdown_{socket.SHUT_WR}",
|
||||
"wait",
|
||||
"full_shutdown",
|
||||
"super_close",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_TCPInterface_close_survives_half_close_failure():
|
||||
"""A peer that already vanished must not break close()."""
|
||||
iface = TCPInterface(hostname="localhost", noProto=True, connectNow=False)
|
||||
sock = MagicMock()
|
||||
sock.shutdown.side_effect = OSError("already gone")
|
||||
iface.socket = sock
|
||||
|
||||
with patch("meshtastic.stream_interface.StreamInterface.close", autospec=True):
|
||||
iface.close() # must not raise
|
||||
|
||||
sock.close.assert_called_once()
|
||||
assert iface.socket is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_TCPInterface_reconnect():
|
||||
"""Test that _reconnect correctly reconnects"""
|
||||
|
||||
Generated
+115
-47
@@ -608,7 +608,7 @@ files = [
|
||||
{file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"},
|
||||
{file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"},
|
||||
]
|
||||
markers = {main = "extra == \"analysis\" and python_version >= \"3.11\"", powermon = "python_version >= \"3.11\""}
|
||||
markers = {main = "python_version >= \"3.11\" and extra == \"analysis\"", powermon = "python_version >= \"3.11\""}
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
@@ -3284,7 +3284,7 @@ version = "2.0.2"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "analysis", "powermon"]
|
||||
groups = ["main", "analysis"]
|
||||
files = [
|
||||
{file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"},
|
||||
{file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"},
|
||||
@@ -3332,7 +3332,7 @@ files = [
|
||||
{file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"},
|
||||
{file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"},
|
||||
]
|
||||
markers = {main = "python_version < \"3.11\" and extra == \"analysis\"", analysis = "python_version < \"3.11\"", powermon = "python_version < \"3.11\""}
|
||||
markers = {main = "python_version < \"3.11\" and extra == \"analysis\"", analysis = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
@@ -3340,7 +3340,7 @@ version = "2.3.4"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.11"
|
||||
groups = ["main", "analysis", "powermon"]
|
||||
groups = ["main", "analysis"]
|
||||
files = [
|
||||
{file = "numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f"},
|
||||
@@ -3417,7 +3417,7 @@ files = [
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f"},
|
||||
{file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"},
|
||||
]
|
||||
markers = {main = "extra == \"analysis\" and python_version >= \"3.11\"", analysis = "python_version >= \"3.11\"", powermon = "python_version >= \"3.11\""}
|
||||
markers = {main = "python_version >= \"3.11\" and extra == \"analysis\"", analysis = "python_version >= \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "overrides"
|
||||
@@ -3569,7 +3569,7 @@ description = "Type annotations for pandas"
|
||||
optional = true
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"analysis\" and python_version >= \"3.11\""
|
||||
markers = "python_version >= \"3.11\" and extra == \"analysis\""
|
||||
files = [
|
||||
{file = "pandas_stubs-2.3.2.250926-py3-none-any.whl", hash = "sha256:81121818453dcfe00f45c852f4dceee043640b813830f6e7bd084a4ef7ff7270"},
|
||||
{file = "pandas_stubs-2.3.2.250926.tar.gz", hash = "sha256:c64b9932760ceefb96a3222b953e6a251321a9832a28548be6506df473a66406"},
|
||||
@@ -4150,52 +4150,120 @@ tests = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "pyarrow"
|
||||
version = "16.1.0"
|
||||
version = "18.1.0"
|
||||
description = "Python library for Apache Arrow"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["powermon"]
|
||||
markers = "python_version < \"3.14\""
|
||||
files = [
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:17e23b9a65a70cc733d8b738baa6ad3722298fa0c81d88f63ff94bf25eaa77b9"},
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4740cc41e2ba5d641071d0ab5e9ef9b5e6e8c7611351a5cb7c1d175eaf43674a"},
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98100e0268d04e0eec47b73f20b39c45b4006f3c4233719c3848aa27a03c1aef"},
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68f409e7b283c085f2da014f9ef81e885d90dcd733bd648cfba3ef265961848"},
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a8914cd176f448e09746037b0c6b3a9d7688cef451ec5735094055116857580c"},
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:48be160782c0556156d91adbdd5a4a7e719f8d407cb46ae3bb4eaee09b3111bd"},
|
||||
{file = "pyarrow-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cf389d444b0f41d9fe1444b70650fea31e9d52cfcb5f818b7888b91b586efff"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d0ebea336b535b37eee9eee31761813086d33ed06de9ab6fc6aaa0bace7b250c"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e73cfc4a99e796727919c5541c65bb88b973377501e39b9842ea71401ca6c1c"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9251264247ecfe93e5f5a0cd43b8ae834f1e61d1abca22da55b20c788417f6"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf5aace92d520d3d2a20031d8b0ec27b4395cab9f74e07cc95edf42a5cc0147"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:25233642583bf658f629eb230b9bb79d9af4d9f9229890b3c878699c82f7d11e"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a33a64576fddfbec0a44112eaf844c20853647ca833e9a647bfae0582b2ff94b"},
|
||||
{file = "pyarrow-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:185d121b50836379fe012753cf15c4ba9638bda9645183ab36246923875f8d1b"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:2e51ca1d6ed7f2e9d5c3c83decf27b0d17bb207a7dea986e8dc3e24f80ff7d6f"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06ebccb6f8cb7357de85f60d5da50e83507954af617d7b05f48af1621d331c9a"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04707f1979815f5e49824ce52d1dceb46e2f12909a48a6a753fe7cafbc44a0c"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d32000693deff8dc5df444b032b5985a48592c0697cb6e3071a5d59888714e2"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8785bb10d5d6fd5e15d718ee1d1f914fe768bf8b4d1e5e9bf253de8a26cb1628"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e1369af39587b794873b8a307cc6623a3b1194e69399af0efd05bb202195a5a7"},
|
||||
{file = "pyarrow-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:febde33305f1498f6df85e8020bca496d0e9ebf2093bab9e0f65e2b4ae2b3444"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b5f5705ab977947a43ac83b52ade3b881eb6e95fcc02d76f501d549a210ba77f"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d27bf89dfc2576f6206e9cd6cf7a107c9c06dc13d53bbc25b0bd4556f19cf5f"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d07de3ee730647a600037bc1d7b7994067ed64d0eba797ac74b2bc77384f4c2"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbef391b63f708e103df99fbaa3acf9f671d77a183a07546ba2f2c297b361e83"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19741c4dbbbc986d38856ee7ddfdd6a00fc3b0fc2d928795b95410d38bb97d15"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f2c5fb249caa17b94e2b9278b36a05ce03d3180e6da0c4c3b3ce5b2788f30eed"},
|
||||
{file = "pyarrow-16.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:e6b6d3cd35fbb93b70ade1336022cc1147b95ec6af7d36906ca7fe432eb09710"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:18da9b76a36a954665ccca8aa6bd9f46c1145f79c0bb8f4f244f5f8e799bca55"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99f7549779b6e434467d2aa43ab2b7224dd9e41bdde486020bae198978c9e05e"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f07fdffe4fd5b15f5ec15c8b64584868d063bc22b86b46c9695624ca3505b7b4"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfe389a08ea374972bd4065d5f25d14e36b43ebc22fc75f7b951f24378bf0b5"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b20bd67c94b3a2ea0a749d2a5712fc845a69cb5d52e78e6449bbd295611f3aa"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ba8ac20693c0bb0bf4b238751d4409e62852004a8cf031c73b0e0962b03e45e3"},
|
||||
{file = "pyarrow-16.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:31a1851751433d89a986616015841977e0a188662fcffd1a5677453f1df2de0a"},
|
||||
{file = "pyarrow-16.1.0.tar.gz", hash = "sha256:15fbb22ea96d11f0b5768504a3f961edab25eaf4197c341720c4a387f6c60315"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56"},
|
||||
{file = "pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0"},
|
||||
{file = "pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30"},
|
||||
{file = "pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9"},
|
||||
{file = "pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b331e477e40f07238adc7ba7469c36b908f07c89b95dd4bd3a0ec84a3d1e21e"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:2c4dd0c9010a25ba03e198fe743b1cc03cd33c08190afff371749c52ccbbaf76"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f97b31b4c4e21ff58c6f330235ff893cc81e23da081b1a4b1c982075e0ed4e9"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a4813cb8ecf1809871fd2d64a8eff740a1bd3691bbe55f01a3cf6c5ec869754"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:05a5636ec3eb5cc2a36c6edb534a38ef57b2ab127292a716d00eabb887835f1e"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:73eeed32e724ea3568bb06161cad5fa7751e45bc2228e33dcb10c614044165c7"},
|
||||
{file = "pyarrow-18.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:a1880dd6772b685e803011a6b43a230c23b566859a6e0c9a276c1e0faf4f4052"},
|
||||
{file = "pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.16.6"
|
||||
[package.extras]
|
||||
test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"]
|
||||
|
||||
[[package]]
|
||||
name = "pyarrow"
|
||||
version = "22.0.0"
|
||||
description = "Python library for Apache Arrow"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["powermon"]
|
||||
markers = "python_version == \"3.14\""
|
||||
files = [
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88"},
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44d2d26cda26d18f7af7db71453b7b783788322d756e81730acb98f24eb90ace"},
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b9d71701ce97c95480fecb0039ec5bb889e75f110da72005743451339262f4ce"},
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:710624ab925dc2b05a6229d47f6f0dac1c1155e6ed559be7109f684eba048a48"},
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f963ba8c3b0199f9d6b794c90ec77545e05eadc83973897a4523c9e8d84e9340"},
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd0d42297ace400d8febe55f13fdf46e86754842b860c978dfec16f081e5c653"},
|
||||
{file = "pyarrow-22.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:00626d9dc0f5ef3a75fe63fd68b9c7c8302d2b5bbc7f74ecaedba83447a24f84"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:3e294c5eadfb93d78b0763e859a0c16d4051fc1c5231ae8956d61cb0b5666f5a"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:69763ab2445f632d90b504a815a2a033f74332997052b721002298ed6de40f2e"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b41f37cabfe2463232684de44bad753d6be08a7a072f6a83447eeaf0e4d2a215"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:35ad0f0378c9359b3f297299c3309778bb03b8612f987399a0333a560b43862d"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8382ad21458075c2e66a82a29d650f963ce51c7708c7c0ff313a8c206c4fd5e8"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a812a5b727bc09c3d7ea072c4eebf657c2f7066155506ba31ebf4792f88f016"},
|
||||
{file = "pyarrow-22.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ec5d40dd494882704fb876c16fa7261a69791e784ae34e6b5992e977bd2e238c"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9"},
|
||||
{file = "pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95"},
|
||||
{file = "pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9bddc2cade6561f6820d4cd73f99a0243532ad506bc510a75a5a65a522b2d74d"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e70ff90c64419709d38c8932ea9fe1cc98415c4f87ea8da81719e43f02534bc9"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:92843c305330aa94a36e706c16209cd4df274693e777ca47112617db7d0ef3d7"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6dda1ddac033d27421c20d7a7943eec60be44e0db4e079f33cc5af3b8280ccde"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:84378110dd9a6c06323b41b56e129c504d157d1a983ce8f5443761eb5256bafc"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:854794239111d2b88b40b6ef92aa478024d1e5074f364033e73e21e3f76b25e0"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:b883fe6fd85adad7932b3271c38ac289c65b7337c2c132e9569f9d3940620730"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a820d8ae11facf32585507c11f04e3f38343c1e784c9b5a8b1da5c930547fe2"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:c6ec3675d98915bf1ec8b3c7986422682f7232ea76cad276f4c8abd5b7319b70"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3e739edd001b04f654b166204fc7a9de896cf6007eaff33409ee9e50ceaff754"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7388ac685cab5b279a41dfe0a6ccd99e4dbf322edfb63e02fc0443bf24134e91"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f633074f36dbc33d5c05b5dc75371e5660f1dbf9c8b1d95669def05e5425989c"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4c19236ae2402a8663a2c8f21f1870a03cc57f0bef7e4b6eb3238cc82944de80"},
|
||||
{file = "pyarrow-22.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0c34fe18094686194f204a3b1787a27456897d8a2d62caf84b61e8dfbc0252ae"},
|
||||
{file = "pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyarrow-stubs"
|
||||
@@ -6117,4 +6185,4 @@ tunnel = ["pytap2"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.9,<3.15"
|
||||
content-hash = "674308d6eb7c3730031cc3e73c98b2413c7f59002a9317bfad387bc34a17c64d"
|
||||
content-hash = "7dd0dc28e13f7569f1a8cc28453d5983a6f742790d9328bafe86d1a2914d88ad"
|
||||
+1
-1
Submodule protobufs updated: da60cee584...7b2464c9b8.
+5
-2
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "meshtastic"
|
||||
version = "2.7.10"
|
||||
version = "2.7.11"
|
||||
description = "Python API & client shell for talking to Meshtastic devices"
|
||||
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -51,7 +51,10 @@ optional = true
|
||||
riden = { git = "https://github.com/geeksville/riden.git#1.2.1" }
|
||||
ppk2-api = "^0.9.2"
|
||||
parse = "^1.20.2"
|
||||
pyarrow = "^16.1.0"
|
||||
pyarrow = [
|
||||
{ version = "^18.0.0", python = "<3.14" },
|
||||
{ version = "^22.0.0", python = ">=3.14" }
|
||||
]
|
||||
platformdirs = "^4.2.2"
|
||||
|
||||
# If you are doing power analysis you might want these extra devtools
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
[pytest]
|
||||
|
||||
addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples and not smokevirt"
|
||||
addopts = -m "not int and not smoke1 and not smoke2 and not smokewifi and not examples and not smokevirt and not smokemesh"
|
||||
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
@@ -13,4 +13,5 @@ markers =
|
||||
smoke1: runs smoke tests on a single device connected via USB
|
||||
smoke2: runs smoke tests on a two devices connected via USB
|
||||
smokewifi: runs smoke test on an esp32 device setup with wifi
|
||||
smokemesh: runs smoke tests against multiple meshtasticd sim instances
|
||||
examples: runs the examples tests which validates the library
|
||||
Reference in new issue
Block a user