mirror of
https://github.com/meshtastic/python.git
synced 2026-07-31 17:16:45 -04:00
Compare commits
48 Commits
2.7.6
...
fix-factor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5fc51ea37 | ||
|
|
cec79a7c1f | ||
|
|
6805fee667 | ||
|
|
3cb9ce2a56 | ||
|
|
eb964d78bb | ||
|
|
80c40ef893 | ||
|
|
2494bb4c63 | ||
|
|
1e4822fe87 | ||
|
|
c7ee644ad2 | ||
|
|
9af5f22837 | ||
|
|
1d3bdf143a | ||
|
|
7129a9f963 | ||
|
|
d8e438b225 | ||
|
|
6511d06e2f | ||
|
|
94c531e7ee | ||
|
|
ad4f3f557e | ||
|
|
1c9cf37974 | ||
|
|
cd9199bc00 | ||
|
|
04a23ae882 | ||
|
|
b003214d1b | ||
|
|
414a621091 | ||
|
|
a87065a081 | ||
|
|
942ce115f3 | ||
|
|
d5eaecea07 | ||
|
|
545c3ab192 | ||
|
|
fee2b6ccbd | ||
|
|
d0ccb1a597 | ||
|
|
5721859314 | ||
|
|
4de19f50d9 | ||
|
|
4d8430d360 | ||
|
|
bf580c36ae | ||
|
|
1eb13c9953 | ||
|
|
cdf893e618 | ||
|
|
9b9df9e585 | ||
|
|
b26d80f186 | ||
|
|
da559f0e37 | ||
|
|
a73f432f42 | ||
|
|
c3c5ce64dd | ||
|
|
683dd23d63 | ||
|
|
57d43b84e4 | ||
|
|
4f6d183ed1 | ||
|
|
d9057c0aaf | ||
|
|
38a13f300f | ||
|
|
eef8a37703 | ||
|
|
63b940defb | ||
|
|
e5efa94264 | ||
|
|
efa841b08c | ||
|
|
6ff13eb95c |
206
.github/copilot-instructions.md
vendored
Normal file
206
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# Copilot Instructions for Meshtastic Python
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is the Meshtastic Python library and CLI - a Python API for interacting with Meshtastic mesh radio devices. It supports communication via Serial, TCP, and BLE interfaces.
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
- **Language**: Python 3.9 - 3.14
|
||||||
|
- **Package Manager**: Poetry
|
||||||
|
- **Testing**: pytest with hypothesis for property-based testing
|
||||||
|
- **Linting**: pylint
|
||||||
|
- **Type Checking**: mypy (working toward strict mode)
|
||||||
|
- **Documentation**: pdoc3
|
||||||
|
- **License**: GPL-3.0
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
meshtastic/ # Main library package
|
||||||
|
├── __init__.py # Core interface classes and pub/sub topics
|
||||||
|
├── __main__.py # CLI entry point
|
||||||
|
├── mesh_interface.py # Base interface class for all connection types
|
||||||
|
├── serial_interface.py
|
||||||
|
├── tcp_interface.py
|
||||||
|
├── ble_interface.py
|
||||||
|
├── node.py # Node representation and configuration
|
||||||
|
├── protobuf/ # Generated Protocol Buffer files (*_pb2.py, *_pb2.pyi)
|
||||||
|
├── tests/ # Unit and integration tests
|
||||||
|
├── powermon/ # Power monitoring tools
|
||||||
|
└── analysis/ # Data analysis tools
|
||||||
|
examples/ # Usage examples
|
||||||
|
protobufs/ # Protocol Buffer source definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
### Style Guidelines
|
||||||
|
|
||||||
|
- Follow PEP 8 style conventions
|
||||||
|
- Use type hints for function parameters and return values
|
||||||
|
- Document public functions and classes with docstrings
|
||||||
|
- Prefer explicit imports over wildcard imports
|
||||||
|
- Use `logging` module instead of print statements for debug output
|
||||||
|
|
||||||
|
### Type Annotations
|
||||||
|
|
||||||
|
- Add type hints to all new code
|
||||||
|
- Use `Optional[T]` for nullable types
|
||||||
|
- Use `Dict`, `List`, `Tuple` from `typing` module for Python 3.9 compatibility
|
||||||
|
- Protobuf types are in `meshtastic.protobuf.*_pb2` modules
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
- Classes: `PascalCase` (e.g., `MeshInterface`, `SerialInterface`)
|
||||||
|
- Functions/methods: `camelCase` for public API (e.g., `sendText`, `sendData`)
|
||||||
|
- Internal functions: `snake_case` with leading underscore (e.g., `_send_packet`)
|
||||||
|
- Constants: `UPPER_SNAKE_CASE` (e.g., `BROADCAST_ADDR`, `LOCAL_ADDR`)
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- Use custom exception classes when appropriate (e.g., `MeshInterface.MeshInterfaceError`)
|
||||||
|
- Provide meaningful error messages
|
||||||
|
- Use `our_exit()` from `meshtastic.util` for CLI exits with error codes
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Organization
|
||||||
|
|
||||||
|
Tests are in `meshtastic/tests/` and use pytest markers:
|
||||||
|
|
||||||
|
- `@pytest.mark.unit` - Fast unit tests (default)
|
||||||
|
- `@pytest.mark.unitslow` - Slower unit tests
|
||||||
|
- `@pytest.mark.int` - Integration tests
|
||||||
|
- `@pytest.mark.smoke1` - Single device smoke tests
|
||||||
|
- `@pytest.mark.smoke2` - Two device smoke tests
|
||||||
|
- `@pytest.mark.smokevirt` - Virtual device smoke tests
|
||||||
|
- `@pytest.mark.examples` - Example validation tests
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run unit tests only (default)
|
||||||
|
make test
|
||||||
|
# or
|
||||||
|
pytest -m unit
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
make cov
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing Tests
|
||||||
|
|
||||||
|
- Use `pytest` fixtures from `conftest.py`
|
||||||
|
- Use `hypothesis` for property-based testing where appropriate
|
||||||
|
- Mock external dependencies (serial ports, network connections)
|
||||||
|
- Test file naming: `test_<module_name>.py`
|
||||||
|
|
||||||
|
## Pub/Sub Events
|
||||||
|
|
||||||
|
The library uses pypubsub for event handling. Key topics:
|
||||||
|
|
||||||
|
- `meshtastic.connection.established` - Connection successful
|
||||||
|
- `meshtastic.connection.lost` - Connection lost
|
||||||
|
- `meshtastic.receive.text(packet)` - Text message received
|
||||||
|
- `meshtastic.receive.position(packet)` - Position update received
|
||||||
|
- `meshtastic.receive.data.portnum(packet)` - Data packet by port number
|
||||||
|
- `meshtastic.node.updated(node)` - Node database changed
|
||||||
|
- `meshtastic.log.line(line)` - Raw log line from device
|
||||||
|
|
||||||
|
## Protocol Buffers
|
||||||
|
|
||||||
|
- Protobuf definitions are in `protobufs/meshtastic/`
|
||||||
|
- Generated Python files are in `meshtastic/protobuf/`
|
||||||
|
- Never edit `*_pb2.py` or `*_pb2.pyi` files directly
|
||||||
|
- Regenerate with: `make protobufs` or `./bin/regen-protobufs.sh`
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Creating an Interface
|
||||||
|
|
||||||
|
```python
|
||||||
|
import meshtastic.serial_interface
|
||||||
|
|
||||||
|
# Auto-detect device
|
||||||
|
iface = meshtastic.serial_interface.SerialInterface()
|
||||||
|
|
||||||
|
# Specific device
|
||||||
|
iface = meshtastic.serial_interface.SerialInterface(devPath="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Always close when done
|
||||||
|
iface.close()
|
||||||
|
|
||||||
|
# Or use context manager
|
||||||
|
with meshtastic.serial_interface.SerialInterface() as iface:
|
||||||
|
iface.sendText("Hello mesh")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending Messages
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Text message (broadcast)
|
||||||
|
iface.sendText("Hello")
|
||||||
|
|
||||||
|
# Text message to specific node
|
||||||
|
iface.sendText("Hello", destinationId="!abcd1234")
|
||||||
|
|
||||||
|
# Binary data
|
||||||
|
iface.sendData(data, portNum=portnums_pb2.PRIVATE_APP)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscribing to Events
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pubsub import pub
|
||||||
|
|
||||||
|
def on_receive(packet, interface):
|
||||||
|
print(f"Received: {packet}")
|
||||||
|
|
||||||
|
pub.subscribe(on_receive, "meshtastic.receive")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. Install dependencies: `poetry install --all-extras --with dev`
|
||||||
|
2. Make changes
|
||||||
|
3. Run linting: `poetry run pylint meshtastic examples/`
|
||||||
|
4. Run type checking: `poetry run mypy meshtastic/`
|
||||||
|
5. Run tests: `poetry run pytest -m unit`
|
||||||
|
6. Update documentation if needed
|
||||||
|
|
||||||
|
## CLI Development
|
||||||
|
|
||||||
|
The CLI is in `meshtastic/__main__.py`. When adding new CLI commands:
|
||||||
|
|
||||||
|
- Use argparse for argument parsing
|
||||||
|
- Support `--dest` for specifying target node
|
||||||
|
- Provide `--help` documentation
|
||||||
|
- Handle errors gracefully with meaningful messages
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Required
|
||||||
|
- `pyserial` - Serial port communication
|
||||||
|
- `protobuf` - Protocol Buffers
|
||||||
|
- `pypubsub` - Pub/sub messaging
|
||||||
|
- `bleak` - BLE communication
|
||||||
|
- `tabulate` - Table formatting
|
||||||
|
- `pyyaml` - YAML config support
|
||||||
|
- `requests` - HTTP requests
|
||||||
|
|
||||||
|
### Optional (extras)
|
||||||
|
- `cli` extra: `pyqrcode`, `print-color`, `dotmap`, `argcomplete`
|
||||||
|
- `tunnel` extra: `pytap2`
|
||||||
|
- `analysis` extra: `dash`, `pandas`
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- Always test with actual Meshtastic hardware when possible
|
||||||
|
- Be mindful of radio regulations in your region
|
||||||
|
- The nodedb (`interface.nodes`) is read-only
|
||||||
|
- Packet IDs are random 32-bit integers
|
||||||
|
- Default timeout is 300 seconds for operations
|
||||||
33
.github/workflows/update_protobufs.yml
vendored
33
.github/workflows/update_protobufs.yml
vendored
@@ -1,6 +1,9 @@
|
|||||||
name: "Update protobufs"
|
name: "Update protobufs"
|
||||||
on: workflow_dispatch
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-protobufs:
|
update-protobufs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -9,23 +12,34 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Update Submodule
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
run: |
|
run: |
|
||||||
git pull --recurse-submodules
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install poetry
|
||||||
|
|
||||||
|
- name: Update protobuf submodule
|
||||||
|
run: |
|
||||||
|
git submodule sync --recursive
|
||||||
|
git submodule update --init --recursive
|
||||||
git submodule update --remote --recursive
|
git submodule update --remote --recursive
|
||||||
|
|
||||||
- name: Download nanopb
|
- name: Download nanopb
|
||||||
run: |
|
run: |
|
||||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
|
curl -L -o nanopb-0.4.8-linux-x86.tar.gz https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
|
||||||
tar xvzf nanopb-0.4.8-linux-x86.tar.gz
|
tar xvzf nanopb-0.4.8-linux-x86.tar.gz
|
||||||
mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
|
mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
|
||||||
|
|
||||||
- name: Install poetry (needed by regen-protobufs.sh)
|
- name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
poetry install --with dev
|
||||||
pip3 install poetry
|
|
||||||
|
|
||||||
- name: Re-generate protocol buffers
|
- name: Re-generate protocol buffers
|
||||||
run: |
|
run: |
|
||||||
@@ -38,4 +52,9 @@ jobs:
|
|||||||
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||||
git add protobufs
|
git add protobufs
|
||||||
git add meshtastic/protobuf
|
git add meshtastic/protobuf
|
||||||
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
|
git commit -m "Update protobufs"
|
||||||
|
git push
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ try:
|
|||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
have_test = False
|
have_test = False
|
||||||
|
|
||||||
|
import meshtastic.ota
|
||||||
import meshtastic.util
|
import meshtastic.util
|
||||||
import meshtastic.serial_interface
|
import meshtastic.serial_interface
|
||||||
import meshtastic.tcp_interface
|
import meshtastic.tcp_interface
|
||||||
@@ -60,7 +61,7 @@ except ImportError as e:
|
|||||||
have_powermon = False
|
have_powermon = False
|
||||||
powermon_exception = e
|
powermon_exception = e
|
||||||
meter = None
|
meter = None
|
||||||
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2, mesh_pb2
|
from meshtastic.protobuf import admin_pb2, channel_pb2, config_pb2, portnums_pb2, mesh_pb2
|
||||||
from meshtastic.version import get_active_version
|
from meshtastic.version import get_active_version
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -158,11 +159,11 @@ def getPref(node, comp_name) -> bool:
|
|||||||
config_values = getattr(config, config_type.name)
|
config_values = getattr(config, config_type.name)
|
||||||
if not wholeField:
|
if not wholeField:
|
||||||
pref_value = getattr(config_values, pref.name)
|
pref_value = getattr(config_values, pref.name)
|
||||||
repeated = pref.label == pref.LABEL_REPEATED
|
repeated = _is_repeated_field(pref)
|
||||||
_printSetting(config_type, uni_name, pref_value, repeated)
|
_printSetting(config_type, uni_name, pref_value, repeated)
|
||||||
else:
|
else:
|
||||||
for field in config_values.ListFields():
|
for field in config_values.ListFields():
|
||||||
repeated = field[0].label == field[0].LABEL_REPEATED
|
repeated = _is_repeated_field(field[0])
|
||||||
_printSetting(config_type, field[0].name, field[1], repeated)
|
_printSetting(config_type, field[0].name, field[1], repeated)
|
||||||
else:
|
else:
|
||||||
# Always show whole field for remote node
|
# Always show whole field for remote node
|
||||||
@@ -253,7 +254,7 @@ def setPref(config, comp_name, raw_val) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# repeating fields need to be handled with append, not setattr
|
# repeating fields need to be handled with append, not setattr
|
||||||
if pref.label != pref.LABEL_REPEATED:
|
if not _is_repeated_field(pref):
|
||||||
try:
|
try:
|
||||||
if config_type.message_type is not None:
|
if config_type.message_type is not None:
|
||||||
config_values = getattr(config_part, config_type.name)
|
config_values = getattr(config_part, config_type.name)
|
||||||
@@ -452,6 +453,41 @@ def onConnected(interface):
|
|||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
interface.getNode(args.dest, False, **getNode_kwargs).rebootOTA()
|
interface.getNode(args.dest, False, **getNode_kwargs).rebootOTA()
|
||||||
|
|
||||||
|
if args.ota_update:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
|
||||||
|
if not isinstance(interface, meshtastic.tcp_interface.TCPInterface):
|
||||||
|
meshtastic.util.our_exit(
|
||||||
|
"Error: OTA update currently requires a TCP connection to the node (use --host)."
|
||||||
|
)
|
||||||
|
|
||||||
|
ota = meshtastic.ota.ESP32WiFiOTA(args.ota_update, interface.hostname)
|
||||||
|
|
||||||
|
print(f"Triggering OTA update on {interface.hostname}...")
|
||||||
|
interface.getNode(args.dest, False, **getNode_kwargs).startOTA(
|
||||||
|
ota_mode=admin_pb2.OTAMode.OTA_WIFI,
|
||||||
|
ota_file_hash=ota.hash_bytes()
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Waiting for device to reboot into OTA mode...")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
retries = 5
|
||||||
|
while retries > 0:
|
||||||
|
try:
|
||||||
|
ota.update()
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
retries -= 1
|
||||||
|
if retries == 0:
|
||||||
|
meshtastic.util.our_exit(f"\nOTA update failed: {e}")
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
print("\nOTA update completed successfully!")
|
||||||
|
|
||||||
if args.enter_dfu:
|
if args.enter_dfu:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
@@ -1131,6 +1167,14 @@ def subscribe() -> None:
|
|||||||
|
|
||||||
# pub.subscribe(onNode, "meshtastic.node")
|
# pub.subscribe(onNode, "meshtastic.node")
|
||||||
|
|
||||||
|
def _is_repeated_field(field_desc) -> bool:
|
||||||
|
"""Return True if the protobuf field is repeated.
|
||||||
|
Protobuf 6.31.0 and later use an is_repeated property, while older versions compare against the label field.
|
||||||
|
"""
|
||||||
|
if hasattr(field_desc, "is_repeated"):
|
||||||
|
return bool(field_desc.is_repeated)
|
||||||
|
return field_desc.label == field_desc.LABEL_REPEATED
|
||||||
|
|
||||||
def set_missing_flags_false(config_dict: dict, true_defaults: set[tuple[str, str]]) -> None:
|
def set_missing_flags_false(config_dict: dict, true_defaults: set[tuple[str, str]]) -> None:
|
||||||
"""Ensure that missing default=True keys are present in the config_dict and set to False."""
|
"""Ensure that missing default=True keys are present in the config_dict and set to False."""
|
||||||
for path in true_defaults:
|
for path in true_defaults:
|
||||||
@@ -1904,10 +1948,18 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
|
|||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--reboot-ota",
|
"--reboot-ota",
|
||||||
help="Tell the destination node to reboot into factory firmware (ESP32)",
|
help="Tell the destination node to reboot into factory firmware (ESP32, firmware version <2.7.18)",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--ota-update",
|
||||||
|
help="Perform an OTA update on the local node (ESP32, firmware version >=2.7.18, WiFi/TCP only for now). "
|
||||||
|
"Specify the path to the firmware file.",
|
||||||
|
metavar="FIRMWARE_FILE",
|
||||||
|
action="store",
|
||||||
|
)
|
||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--enter-dfu",
|
"--enter-dfu",
|
||||||
help="Tell the destination node to enter DFU mode (NRF52)",
|
help="Tell the destination node to enter DFU mode (NRF52)",
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import asyncio
|
|||||||
import atexit
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import io
|
import io
|
||||||
from threading import Thread
|
from threading import Thread, Event
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import google.protobuf
|
import google.protobuf
|
||||||
@@ -258,11 +259,13 @@ class BLEClient:
|
|||||||
"""Client for managing connection to a BLE device"""
|
"""Client for managing connection to a BLE device"""
|
||||||
|
|
||||||
def __init__(self, address=None, **kwargs) -> None:
|
def __init__(self, address=None, **kwargs) -> None:
|
||||||
|
self._loop_ready = Event()
|
||||||
self._eventLoop = asyncio.new_event_loop()
|
self._eventLoop = asyncio.new_event_loop()
|
||||||
self._eventThread = Thread(
|
self._eventThread = Thread(
|
||||||
target=self._run_event_loop, name="BLEClient", daemon=True
|
target=self._run_event_loop, name="BLEClient", daemon=True
|
||||||
)
|
)
|
||||||
self._eventThread.start()
|
self._eventThread.start()
|
||||||
|
self._loop_ready.wait() # Wait for event loop to be running
|
||||||
|
|
||||||
if not address:
|
if not address:
|
||||||
logger.debug("No address provided - only discover method will work.")
|
logger.debug("No address provided - only discover method will work.")
|
||||||
@@ -306,13 +309,28 @@ class BLEClient:
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def async_await(self, coro, timeout=None): # pylint: disable=C0116
|
def async_await(self, coro, timeout=None): # pylint: disable=C0116
|
||||||
return self.async_run(coro).result(timeout)
|
"""Wait for async operation to complete.
|
||||||
|
|
||||||
|
On macOS, CoreBluetooth requires occasional I/O operations for
|
||||||
|
callbacks to be properly delivered. The debug logging provides this
|
||||||
|
I/O when enabled, allowing the system to process pending callbacks.
|
||||||
|
"""
|
||||||
|
logger.debug(f"async_await: waiting for {coro}")
|
||||||
|
future = self.async_run(coro)
|
||||||
|
# On macOS without debug logging, callbacks may not be delivered
|
||||||
|
# unless we trigger some I/O. This is a known quirk of CoreBluetooth.
|
||||||
|
sys.stdout.flush()
|
||||||
|
result = future.result(timeout)
|
||||||
|
logger.debug("async_await: complete")
|
||||||
|
return result
|
||||||
|
|
||||||
def async_run(self, coro): # pylint: disable=C0116
|
def async_run(self, coro): # pylint: disable=C0116
|
||||||
return asyncio.run_coroutine_threadsafe(coro, self._eventLoop)
|
return asyncio.run_coroutine_threadsafe(coro, self._eventLoop)
|
||||||
|
|
||||||
def _run_event_loop(self):
|
def _run_event_loop(self):
|
||||||
try:
|
try:
|
||||||
|
# Signal ready from WITHIN the loop to guarantee it's actually running
|
||||||
|
self._eventLoop.call_soon(self._loop_ready.set)
|
||||||
self._eventLoop.run_forever()
|
self._eventLoop.run_forever()
|
||||||
finally:
|
finally:
|
||||||
self._eventLoop.close()
|
self._eventLoop.close()
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
"channel": "Channel",
|
"channel": "Channel",
|
||||||
"lastHeard": "LastHeard",
|
"lastHeard": "LastHeard",
|
||||||
"since": "Since",
|
"since": "Since",
|
||||||
|
"isFavorite": "Fav",
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +301,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
showFields = ["N", "user.longName", "user.id", "user.shortName", "user.hwModel", "user.publicKey",
|
showFields = ["N", "user.longName", "user.id", "user.shortName", "user.hwModel", "user.publicKey",
|
||||||
"user.role", "position.latitude", "position.longitude", "position.altitude",
|
"user.role", "position.latitude", "position.longitude", "position.altitude",
|
||||||
"deviceMetrics.batteryLevel", "deviceMetrics.channelUtilization",
|
"deviceMetrics.batteryLevel", "deviceMetrics.channelUtilization",
|
||||||
"deviceMetrics.airUtilTx", "snr", "hopsAway", "channel", "lastHeard", "since"]
|
"deviceMetrics.airUtilTx", "snr", "hopsAway", "channel", "isFavorite", "lastHeard", "since"]
|
||||||
else:
|
else:
|
||||||
# Always at least include the row number.
|
# Always at least include the row number.
|
||||||
showFields.insert(0, "N")
|
showFields.insert(0, "N")
|
||||||
@@ -342,6 +343,8 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
formatted_value = "Powered"
|
formatted_value = "Powered"
|
||||||
else:
|
else:
|
||||||
formatted_value = formatFloat(raw_value, 0, "%")
|
formatted_value = formatFloat(raw_value, 0, "%")
|
||||||
|
elif field == "isFavorite":
|
||||||
|
formatted_value = "*" if raw_value else ""
|
||||||
elif field == "lastHeard":
|
elif field == "lastHeard":
|
||||||
formatted_value = getLH(raw_value)
|
formatted_value = getLH(raw_value)
|
||||||
elif field == "position.latitude":
|
elif field == "position.latitude":
|
||||||
@@ -416,6 +419,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
channelIndex: int = 0,
|
channelIndex: int = 0,
|
||||||
portNum: portnums_pb2.PortNum.ValueType = portnums_pb2.PortNum.TEXT_MESSAGE_APP,
|
portNum: portnums_pb2.PortNum.ValueType = portnums_pb2.PortNum.TEXT_MESSAGE_APP,
|
||||||
replyId: Optional[int]=None,
|
replyId: Optional[int]=None,
|
||||||
|
hopLimit: Optional[int]=None,
|
||||||
):
|
):
|
||||||
"""Send a utf8 string to some other node, if the node has a display it
|
"""Send a utf8 string to some other node, if the node has a display it
|
||||||
will also be shown on the device.
|
will also be shown on the device.
|
||||||
@@ -433,6 +437,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
portNum -- the application portnum (similar to IP port numbers)
|
portNum -- the application portnum (similar to IP port numbers)
|
||||||
of the destination, see portnums.proto for a list
|
of the destination, see portnums.proto for a list
|
||||||
replyId -- the ID of the message that this packet is a response to
|
replyId -- the ID of the message that this packet is a response to
|
||||||
|
hopLimit {int} -- hop limit to use
|
||||||
|
|
||||||
Returns the sent packet. The id field will be populated in this packet
|
Returns the sent packet. The id field will be populated in this packet
|
||||||
and can be used to track future message acks/naks.
|
and can be used to track future message acks/naks.
|
||||||
@@ -446,7 +451,8 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=channelIndex,
|
channelIndex=channelIndex,
|
||||||
replyId=replyId
|
replyId=replyId,
|
||||||
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -456,6 +462,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||||
onResponse: Optional[Callable[[dict], Any]] = None,
|
onResponse: Optional[Callable[[dict], Any]] = None,
|
||||||
channelIndex: int = 0,
|
channelIndex: int = 0,
|
||||||
|
hopLimit: Optional[int]=None,
|
||||||
):
|
):
|
||||||
"""Send an alert text to some other node. This is similar to a text message,
|
"""Send an alert text to some other node. This is similar to a text message,
|
||||||
but carries a higher priority and is capable of generating special notifications
|
but carries a higher priority and is capable of generating special notifications
|
||||||
@@ -467,6 +474,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
Keyword Arguments:
|
Keyword Arguments:
|
||||||
destinationId {nodeId or nodeNum} -- where to send this
|
destinationId {nodeId or nodeNum} -- where to send this
|
||||||
message (default: {BROADCAST_ADDR})
|
message (default: {BROADCAST_ADDR})
|
||||||
|
hopLimit {int} -- hop limit to use
|
||||||
|
|
||||||
Returns the sent packet. The id field will be populated in this packet
|
Returns the sent packet. The id field will be populated in this packet
|
||||||
and can be used to track future message acks/naks.
|
and can be used to track future message acks/naks.
|
||||||
@@ -480,7 +488,8 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantResponse=False,
|
wantResponse=False,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=channelIndex,
|
channelIndex=channelIndex,
|
||||||
priority=mesh_pb2.MeshPacket.Priority.ALERT
|
priority=mesh_pb2.MeshPacket.Priority.ALERT,
|
||||||
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
def sendMqttClientProxyMessage(self, topic: str, data: bytes):
|
def sendMqttClientProxyMessage(self, topic: str, data: bytes):
|
||||||
@@ -582,6 +591,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantAck: bool = False,
|
wantAck: bool = False,
|
||||||
wantResponse: bool = False,
|
wantResponse: bool = False,
|
||||||
channelIndex: int = 0,
|
channelIndex: int = 0,
|
||||||
|
hopLimit: Optional[int]=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Send a position packet to some other node (normally a broadcast)
|
Send a position packet to some other node (normally a broadcast)
|
||||||
@@ -618,6 +628,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=channelIndex,
|
channelIndex=channelIndex,
|
||||||
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
self.waitForPosition()
|
self.waitForPosition()
|
||||||
@@ -670,7 +681,8 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
hopLimit=hopLimit,
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
# extend timeout based on number of nodes, limit by configured hopLimit
|
# extend timeout based on number of nodes, limit by configured hopLimit
|
||||||
waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
|
nodes_based_factor = (len(self.nodes) - 1) if self.nodes else (hopLimit + 1)
|
||||||
|
waitFactor = max(1, min(nodes_based_factor, hopLimit + 1))
|
||||||
self.waitForTraceRoute(waitFactor)
|
self.waitForTraceRoute(waitFactor)
|
||||||
|
|
||||||
def onResponseTraceRoute(self, p: dict):
|
def onResponseTraceRoute(self, p: dict):
|
||||||
@@ -723,7 +735,8 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||||
wantResponse: bool = False,
|
wantResponse: bool = False,
|
||||||
channelIndex: int = 0,
|
channelIndex: int = 0,
|
||||||
telemetryType: str = "device_metrics"
|
telemetryType: str = "device_metrics",
|
||||||
|
hopLimit: Optional[int]=None,
|
||||||
):
|
):
|
||||||
"""Send telemetry and optionally ask for a response"""
|
"""Send telemetry and optionally ask for a response"""
|
||||||
r = telemetry_pb2.Telemetry()
|
r = telemetry_pb2.Telemetry()
|
||||||
@@ -770,6 +783,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=channelIndex,
|
channelIndex=channelIndex,
|
||||||
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
self.waitForTelemetry()
|
self.waitForTelemetry()
|
||||||
@@ -839,6 +853,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantAck: bool = True,
|
wantAck: bool = True,
|
||||||
wantResponse: bool = False,
|
wantResponse: bool = False,
|
||||||
channelIndex: int = 0,
|
channelIndex: int = 0,
|
||||||
|
hopLimit: Optional[int]=None,
|
||||||
): # pylint: disable=R0913
|
): # pylint: disable=R0913
|
||||||
"""
|
"""
|
||||||
Send a waypoint packet to some other node (normally a broadcast)
|
Send a waypoint packet to some other node (normally a broadcast)
|
||||||
@@ -879,6 +894,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=channelIndex,
|
channelIndex=channelIndex,
|
||||||
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
self.waitForWaypoint()
|
self.waitForWaypoint()
|
||||||
@@ -891,6 +907,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantAck: bool = True,
|
wantAck: bool = True,
|
||||||
wantResponse: bool = False,
|
wantResponse: bool = False,
|
||||||
channelIndex: int = 0,
|
channelIndex: int = 0,
|
||||||
|
hopLimit: Optional[int]=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Send a waypoint deletion packet to some other node (normally a broadcast)
|
Send a waypoint deletion packet to some other node (normally a broadcast)
|
||||||
@@ -917,6 +934,7 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
channelIndex=channelIndex,
|
channelIndex=channelIndex,
|
||||||
|
hopLimit=hopLimit,
|
||||||
)
|
)
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
self.waitForWaypoint()
|
self.waitForWaypoint()
|
||||||
@@ -1452,6 +1470,10 @@ class MeshInterface: # pylint: disable=R0902
|
|||||||
self.localNode.moduleConfig.paxcounter.CopyFrom(
|
self.localNode.moduleConfig.paxcounter.CopyFrom(
|
||||||
fromRadio.moduleConfig.paxcounter
|
fromRadio.moduleConfig.paxcounter
|
||||||
)
|
)
|
||||||
|
elif fromRadio.moduleConfig.HasField("traffic_management"):
|
||||||
|
self.localNode.moduleConfig.traffic_management.CopyFrom(
|
||||||
|
fromRadio.moduleConfig.traffic_management
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug("Unexpected FromRadio payload")
|
logger.debug("Unexpected FromRadio payload")
|
||||||
|
|||||||
@@ -170,11 +170,10 @@ class Node:
|
|||||||
p.get_config_request = configType
|
p.get_config_request = configType
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msgIndex = configType.index
|
|
||||||
if configType.containing_type.name == "LocalConfig":
|
if configType.containing_type.name == "LocalConfig":
|
||||||
p.get_config_request = msgIndex
|
p.get_config_request = admin_pb2.AdminMessage.ConfigType.Value(configType.name.upper() + "_CONFIG")
|
||||||
else:
|
else:
|
||||||
p.get_module_config_request = msgIndex
|
p.get_module_config_request = configType.index
|
||||||
|
|
||||||
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
|
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
|
||||||
if onResponse:
|
if onResponse:
|
||||||
@@ -245,6 +244,8 @@ class Node:
|
|||||||
p.set_module_config.ambient_lighting.CopyFrom(self.moduleConfig.ambient_lighting)
|
p.set_module_config.ambient_lighting.CopyFrom(self.moduleConfig.ambient_lighting)
|
||||||
elif config_name == "paxcounter":
|
elif config_name == "paxcounter":
|
||||||
p.set_module_config.paxcounter.CopyFrom(self.moduleConfig.paxcounter)
|
p.set_module_config.paxcounter.CopyFrom(self.moduleConfig.paxcounter)
|
||||||
|
elif config_name == "traffic_management":
|
||||||
|
p.set_module_config.traffic_management.CopyFrom(self.moduleConfig.traffic_management)
|
||||||
else:
|
else:
|
||||||
our_exit(f"Error: No valid config with name {config_name}")
|
our_exit(f"Error: No valid config with name {config_name}")
|
||||||
|
|
||||||
@@ -654,7 +655,7 @@ class Node:
|
|||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
def rebootOTA(self, secs: int = 10):
|
def rebootOTA(self, secs: int = 10):
|
||||||
"""Tell the node to reboot into factory firmware."""
|
"""Tell the node to reboot into factory firmware (firmware < 2.7.18)."""
|
||||||
self.ensureSessionKey()
|
self.ensureSessionKey()
|
||||||
p = admin_pb2.AdminMessage()
|
p = admin_pb2.AdminMessage()
|
||||||
p.reboot_ota_seconds = secs
|
p.reboot_ota_seconds = secs
|
||||||
@@ -667,6 +668,22 @@ class Node:
|
|||||||
onResponse = self.onAckNak
|
onResponse = self.onAckNak
|
||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
|
def startOTA(
|
||||||
|
self,
|
||||||
|
ota_mode: admin_pb2.OTAMode.ValueType,
|
||||||
|
ota_file_hash: bytes,
|
||||||
|
):
|
||||||
|
"""Tell the node to start OTA mode (firmware >= 2.7.18)."""
|
||||||
|
if self != self.iface.localNode:
|
||||||
|
raise ValueError("startOTA only possible in local node")
|
||||||
|
|
||||||
|
self.ensureSessionKey()
|
||||||
|
p = admin_pb2.AdminMessage()
|
||||||
|
p.ota_request.reboot_ota_mode = ota_mode
|
||||||
|
p.ota_request.ota_hash = ota_file_hash
|
||||||
|
|
||||||
|
return self._sendAdmin(p)
|
||||||
|
|
||||||
def enterDFUMode(self):
|
def enterDFUMode(self):
|
||||||
"""Tell the node to enter DFU mode (NRF52)."""
|
"""Tell the node to enter DFU mode (NRF52)."""
|
||||||
self.ensureSessionKey()
|
self.ensureSessionKey()
|
||||||
@@ -714,7 +731,7 @@ class Node:
|
|||||||
p.factory_reset_device = True
|
p.factory_reset_device = True
|
||||||
logger.info(f"Telling node to factory reset (full device reset)")
|
logger.info(f"Telling node to factory reset (full device reset)")
|
||||||
else:
|
else:
|
||||||
p.factory_reset_config = True
|
p.factory_reset_config = 1
|
||||||
logger.info(f"Telling node to factory reset (config reset)")
|
logger.info(f"Telling node to factory reset (config reset)")
|
||||||
|
|
||||||
# If sending to a remote node, wait for ACK/NAK
|
# If sending to a remote node, wait for ACK/NAK
|
||||||
|
|||||||
128
meshtastic/ota.py
Normal file
128
meshtastic/ota.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"""Meshtastic ESP32 Unified OTA
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import socket
|
||||||
|
import logging
|
||||||
|
from typing import Optional, Callable
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _file_sha256(filename: str):
|
||||||
|
"""Calculate SHA256 hash of a file."""
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
for byte_block in iter(lambda: f.read(4096), b""):
|
||||||
|
sha256_hash.update(byte_block)
|
||||||
|
|
||||||
|
return sha256_hash
|
||||||
|
|
||||||
|
|
||||||
|
class OTAError(Exception):
|
||||||
|
"""Exception for OTA errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class ESP32WiFiOTA:
|
||||||
|
"""ESP32 WiFi Unified OTA updates."""
|
||||||
|
|
||||||
|
def __init__(self, filename: str, hostname: str, port: int = 3232):
|
||||||
|
self._filename = filename
|
||||||
|
self._hostname = hostname
|
||||||
|
self._port = port
|
||||||
|
self._socket: Optional[socket.socket] = None
|
||||||
|
|
||||||
|
if not os.path.exists(self._filename):
|
||||||
|
raise FileNotFoundError(f"File {self._filename} does not exist")
|
||||||
|
|
||||||
|
self._file_hash = _file_sha256(self._filename)
|
||||||
|
|
||||||
|
def _read_line(self) -> str:
|
||||||
|
"""Read a line from the socket."""
|
||||||
|
if not self._socket:
|
||||||
|
raise ConnectionError("Socket not connected")
|
||||||
|
|
||||||
|
line = b""
|
||||||
|
while not line.endswith(b"\n"):
|
||||||
|
char = self._socket.recv(1)
|
||||||
|
|
||||||
|
if not char:
|
||||||
|
raise ConnectionError("Connection closed while waiting for response")
|
||||||
|
|
||||||
|
line += char
|
||||||
|
|
||||||
|
return line.decode("utf-8").strip()
|
||||||
|
|
||||||
|
def hash_bytes(self) -> bytes:
|
||||||
|
"""Return the hash as bytes."""
|
||||||
|
return self._file_hash.digest()
|
||||||
|
|
||||||
|
def hash_hex(self) -> str:
|
||||||
|
"""Return the hash as a hex string."""
|
||||||
|
return self._file_hash.hexdigest()
|
||||||
|
|
||||||
|
def update(self, progress_callback: Optional[Callable[[int, int], None]] = None):
|
||||||
|
"""Perform the OTA update."""
|
||||||
|
with open(self._filename, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
size = len(data)
|
||||||
|
|
||||||
|
logger.info(f"Starting OTA update with {self._filename} ({size} bytes, hash {self.hash_hex()})")
|
||||||
|
|
||||||
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self._socket.settimeout(15)
|
||||||
|
try:
|
||||||
|
self._socket.connect((self._hostname, self._port))
|
||||||
|
logger.debug(f"Connected to {self._hostname}:{self._port}")
|
||||||
|
|
||||||
|
# Send start command
|
||||||
|
self._socket.sendall(f"OTA {size} {self.hash_hex()}\n".encode("utf-8"))
|
||||||
|
|
||||||
|
# Wait for OK from the device
|
||||||
|
while True:
|
||||||
|
response = self._read_line()
|
||||||
|
if response == "OK":
|
||||||
|
break
|
||||||
|
|
||||||
|
if response == "ERASING":
|
||||||
|
logger.info("Device is erasing flash...")
|
||||||
|
elif response.startswith("ERR "):
|
||||||
|
raise OTAError(f"Device reported error: {response}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Unexpected response: {response}")
|
||||||
|
|
||||||
|
# Stream firmware
|
||||||
|
sent_bytes = 0
|
||||||
|
chunk_size = 1024
|
||||||
|
while sent_bytes < size:
|
||||||
|
chunk = data[sent_bytes : sent_bytes + chunk_size]
|
||||||
|
self._socket.sendall(chunk)
|
||||||
|
sent_bytes += len(chunk)
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback(sent_bytes, size)
|
||||||
|
else:
|
||||||
|
print(f"[{sent_bytes / size * 100:5.1f}%] Sent {sent_bytes} of {size} bytes...", end="\r")
|
||||||
|
|
||||||
|
if not progress_callback:
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Wait for OK from device
|
||||||
|
logger.info("Firmware sent, waiting for verification...")
|
||||||
|
while True:
|
||||||
|
response = self._read_line()
|
||||||
|
if response == "OK":
|
||||||
|
logger.info("OTA update completed successfully!")
|
||||||
|
break
|
||||||
|
|
||||||
|
if response.startswith("ERR "):
|
||||||
|
raise OTAError(f"OTA update failed: {response}")
|
||||||
|
elif response != "ACK":
|
||||||
|
logger.warning(f"Unexpected final response: {response}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if self._socket:
|
||||||
|
self._socket.close()
|
||||||
|
self._socket = None
|
||||||
56
meshtastic/protobuf/admin_pb2.py
generated
56
meshtastic/protobuf/admin_pb2.py
generated
File diff suppressed because one or more lines are too long
310
meshtastic/protobuf/admin_pb2.pyi
generated
310
meshtastic/protobuf/admin_pb2.pyi
generated
@@ -224,6 +224,18 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
TODO: REPLACE
|
TODO: REPLACE
|
||||||
"""
|
"""
|
||||||
|
STATUSMESSAGE_CONFIG: AdminMessage._ModuleConfigType.ValueType # 13
|
||||||
|
"""
|
||||||
|
TODO: REPLACE
|
||||||
|
"""
|
||||||
|
TRAFFICMANAGEMENT_CONFIG: AdminMessage._ModuleConfigType.ValueType # 14
|
||||||
|
"""
|
||||||
|
Traffic management module config
|
||||||
|
"""
|
||||||
|
TAK_CONFIG: AdminMessage._ModuleConfigType.ValueType # 15
|
||||||
|
"""
|
||||||
|
TAK module config
|
||||||
|
"""
|
||||||
|
|
||||||
class ModuleConfigType(_ModuleConfigType, metaclass=_ModuleConfigTypeEnumTypeWrapper):
|
class ModuleConfigType(_ModuleConfigType, metaclass=_ModuleConfigTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -282,6 +294,18 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
TODO: REPLACE
|
TODO: REPLACE
|
||||||
"""
|
"""
|
||||||
|
STATUSMESSAGE_CONFIG: AdminMessage.ModuleConfigType.ValueType # 13
|
||||||
|
"""
|
||||||
|
TODO: REPLACE
|
||||||
|
"""
|
||||||
|
TRAFFICMANAGEMENT_CONFIG: AdminMessage.ModuleConfigType.ValueType # 14
|
||||||
|
"""
|
||||||
|
Traffic management module config
|
||||||
|
"""
|
||||||
|
TAK_CONFIG: AdminMessage.ModuleConfigType.ValueType # 15
|
||||||
|
"""
|
||||||
|
TAK module config
|
||||||
|
"""
|
||||||
|
|
||||||
class _BackupLocation:
|
class _BackupLocation:
|
||||||
ValueType = typing.NewType("ValueType", builtins.int)
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
@@ -346,6 +370,34 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["event_code", b"event_code", "kb_char", b"kb_char", "touch_x", b"touch_x", "touch_y", b"touch_y"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["event_code", b"event_code", "kb_char", b"kb_char", "touch_x", b"touch_x", "touch_y", b"touch_y"]) -> None: ...
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class OTAEvent(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
User is requesting an over the air update.
|
||||||
|
Node will reboot into the OTA loader
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
REBOOT_OTA_MODE_FIELD_NUMBER: builtins.int
|
||||||
|
OTA_HASH_FIELD_NUMBER: builtins.int
|
||||||
|
reboot_ota_mode: global___OTAMode.ValueType
|
||||||
|
"""
|
||||||
|
Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now)
|
||||||
|
"""
|
||||||
|
ota_hash: builtins.bytes
|
||||||
|
"""
|
||||||
|
A 32 byte hash of the OTA firmware.
|
||||||
|
Used to verify the integrity of the firmware before applying an update.
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
reboot_ota_mode: global___OTAMode.ValueType = ...,
|
||||||
|
ota_hash: builtins.bytes = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["ota_hash", b"ota_hash", "reboot_ota_mode", b"reboot_ota_mode"]) -> None: ...
|
||||||
|
|
||||||
SESSION_PASSKEY_FIELD_NUMBER: builtins.int
|
SESSION_PASSKEY_FIELD_NUMBER: builtins.int
|
||||||
GET_CHANNEL_REQUEST_FIELD_NUMBER: builtins.int
|
GET_CHANNEL_REQUEST_FIELD_NUMBER: builtins.int
|
||||||
GET_CHANNEL_RESPONSE_FIELD_NUMBER: builtins.int
|
GET_CHANNEL_RESPONSE_FIELD_NUMBER: builtins.int
|
||||||
@@ -390,11 +442,11 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
STORE_UI_CONFIG_FIELD_NUMBER: builtins.int
|
STORE_UI_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
SET_IGNORED_NODE_FIELD_NUMBER: builtins.int
|
SET_IGNORED_NODE_FIELD_NUMBER: builtins.int
|
||||||
REMOVE_IGNORED_NODE_FIELD_NUMBER: builtins.int
|
REMOVE_IGNORED_NODE_FIELD_NUMBER: builtins.int
|
||||||
|
TOGGLE_MUTED_NODE_FIELD_NUMBER: builtins.int
|
||||||
BEGIN_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
|
BEGIN_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
|
||||||
COMMIT_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
|
COMMIT_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
|
||||||
ADD_CONTACT_FIELD_NUMBER: builtins.int
|
ADD_CONTACT_FIELD_NUMBER: builtins.int
|
||||||
KEY_VERIFICATION_FIELD_NUMBER: builtins.int
|
KEY_VERIFICATION_FIELD_NUMBER: builtins.int
|
||||||
REBOOT_OTA_MODE_FIELD_NUMBER: builtins.int
|
|
||||||
FACTORY_RESET_DEVICE_FIELD_NUMBER: builtins.int
|
FACTORY_RESET_DEVICE_FIELD_NUMBER: builtins.int
|
||||||
REBOOT_OTA_SECONDS_FIELD_NUMBER: builtins.int
|
REBOOT_OTA_SECONDS_FIELD_NUMBER: builtins.int
|
||||||
EXIT_SIMULATOR_FIELD_NUMBER: builtins.int
|
EXIT_SIMULATOR_FIELD_NUMBER: builtins.int
|
||||||
@@ -402,6 +454,8 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
SHUTDOWN_SECONDS_FIELD_NUMBER: builtins.int
|
SHUTDOWN_SECONDS_FIELD_NUMBER: builtins.int
|
||||||
FACTORY_RESET_CONFIG_FIELD_NUMBER: builtins.int
|
FACTORY_RESET_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
NODEDB_RESET_FIELD_NUMBER: builtins.int
|
NODEDB_RESET_FIELD_NUMBER: builtins.int
|
||||||
|
OTA_REQUEST_FIELD_NUMBER: builtins.int
|
||||||
|
SENSOR_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
session_passkey: builtins.bytes
|
session_passkey: builtins.bytes
|
||||||
"""
|
"""
|
||||||
The node generates this key and sends it with any get_x_response packets.
|
The node generates this key and sends it with any get_x_response packets.
|
||||||
@@ -519,6 +573,10 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Set specified node-num to be un-ignored on the NodeDB on the device
|
Set specified node-num to be un-ignored on the NodeDB on the device
|
||||||
"""
|
"""
|
||||||
|
toggle_muted_node: builtins.int
|
||||||
|
"""
|
||||||
|
Set specified node-num to be muted
|
||||||
|
"""
|
||||||
begin_edit_settings: builtins.bool
|
begin_edit_settings: builtins.bool
|
||||||
"""
|
"""
|
||||||
Begins an edit transaction for config, module config, owner, and channel settings changes
|
Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||||
@@ -528,10 +586,6 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Commits an open transaction for any edits made to config, module config, owner, and channel settings
|
Commits an open transaction for any edits made to config, module config, owner, and channel settings
|
||||||
"""
|
"""
|
||||||
reboot_ota_mode: global___OTAMode.ValueType
|
|
||||||
"""
|
|
||||||
Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now)
|
|
||||||
"""
|
|
||||||
factory_reset_device: builtins.int
|
factory_reset_device: builtins.int
|
||||||
"""
|
"""
|
||||||
Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
|
Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
|
||||||
@@ -677,6 +731,18 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
Initiate or respond to a key verification request
|
Initiate or respond to a key verification request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ota_request(self) -> global___AdminMessage.OTAEvent:
|
||||||
|
"""
|
||||||
|
Tell the node to reset into the OTA Loader
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sensor_config(self) -> global___SensorConfig:
|
||||||
|
"""
|
||||||
|
Parameters and sensor configuration
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -724,11 +790,11 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
store_ui_config: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
|
store_ui_config: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
|
||||||
set_ignored_node: builtins.int = ...,
|
set_ignored_node: builtins.int = ...,
|
||||||
remove_ignored_node: builtins.int = ...,
|
remove_ignored_node: builtins.int = ...,
|
||||||
|
toggle_muted_node: builtins.int = ...,
|
||||||
begin_edit_settings: builtins.bool = ...,
|
begin_edit_settings: builtins.bool = ...,
|
||||||
commit_edit_settings: builtins.bool = ...,
|
commit_edit_settings: builtins.bool = ...,
|
||||||
add_contact: global___SharedContact | None = ...,
|
add_contact: global___SharedContact | None = ...,
|
||||||
key_verification: global___KeyVerificationAdmin | None = ...,
|
key_verification: global___KeyVerificationAdmin | None = ...,
|
||||||
reboot_ota_mode: global___OTAMode.ValueType = ...,
|
|
||||||
factory_reset_device: builtins.int = ...,
|
factory_reset_device: builtins.int = ...,
|
||||||
reboot_ota_seconds: builtins.int = ...,
|
reboot_ota_seconds: builtins.int = ...,
|
||||||
exit_simulator: builtins.bool = ...,
|
exit_simulator: builtins.bool = ...,
|
||||||
@@ -736,10 +802,12 @@ class AdminMessage(google.protobuf.message.Message):
|
|||||||
shutdown_seconds: builtins.int = ...,
|
shutdown_seconds: builtins.int = ...,
|
||||||
factory_reset_config: builtins.int = ...,
|
factory_reset_config: builtins.int = ...,
|
||||||
nodedb_reset: builtins.bool = ...,
|
nodedb_reset: builtins.bool = ...,
|
||||||
|
ota_request: global___AdminMessage.OTAEvent | None = ...,
|
||||||
|
sensor_config: global___SensorConfig | None = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def HasField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_mode", b"reboot_ota_mode", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> builtins.bool: ...
|
def HasField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "ota_request", b"ota_request", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "sensor_config", b"sensor_config", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config", "toggle_muted_node", b"toggle_muted_node"]) -> builtins.bool: ...
|
||||||
def ClearField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_mode", b"reboot_ota_mode", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "session_passkey", b"session_passkey", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["add_contact", b"add_contact", "backup_preferences", b"backup_preferences", "begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "key_verification", b"key_verification", "nodedb_reset", b"nodedb_reset", "ota_request", b"ota_request", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_backup_preferences", b"remove_backup_preferences", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "restore_preferences", b"restore_preferences", "send_input_event", b"send_input_event", "sensor_config", b"sensor_config", "session_passkey", b"session_passkey", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config", "toggle_muted_node", b"toggle_muted_node"]) -> None: ...
|
||||||
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "backup_preferences", "restore_preferences", "remove_backup_preferences", "send_input_event", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "set_time_only", "get_ui_config_request", "get_ui_config_response", "store_ui_config", "set_ignored_node", "remove_ignored_node", "begin_edit_settings", "commit_edit_settings", "add_contact", "key_verification", "reboot_ota_mode", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset"] | None: ...
|
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "backup_preferences", "restore_preferences", "remove_backup_preferences", "send_input_event", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "set_time_only", "get_ui_config_request", "get_ui_config_response", "store_ui_config", "set_ignored_node", "remove_ignored_node", "toggle_muted_node", "begin_edit_settings", "commit_edit_settings", "add_contact", "key_verification", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset", "ota_request", "sensor_config"] | None: ...
|
||||||
|
|
||||||
global___AdminMessage = AdminMessage
|
global___AdminMessage = AdminMessage
|
||||||
|
|
||||||
@@ -933,3 +1001,227 @@ class KeyVerificationAdmin(google.protobuf.message.Message):
|
|||||||
def WhichOneof(self, oneof_group: typing.Literal["_security_number", b"_security_number"]) -> typing.Literal["security_number"] | None: ...
|
def WhichOneof(self, oneof_group: typing.Literal["_security_number", b"_security_number"]) -> typing.Literal["security_number"] | None: ...
|
||||||
|
|
||||||
global___KeyVerificationAdmin = KeyVerificationAdmin
|
global___KeyVerificationAdmin = KeyVerificationAdmin
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class SensorConfig(google.protobuf.message.Message):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
SCD4X_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
|
SEN5X_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
|
SCD30_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
|
SHTXX_CONFIG_FIELD_NUMBER: builtins.int
|
||||||
|
@property
|
||||||
|
def scd4x_config(self) -> global___SCD4X_config:
|
||||||
|
"""
|
||||||
|
SCD4X CO2 Sensor configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sen5x_config(self) -> global___SEN5X_config:
|
||||||
|
"""
|
||||||
|
SEN5X PM Sensor configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scd30_config(self) -> global___SCD30_config:
|
||||||
|
"""
|
||||||
|
SCD30 CO2 Sensor configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shtxx_config(self) -> global___SHTXX_config:
|
||||||
|
"""
|
||||||
|
SHTXX temperature and relative humidity sensor configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
scd4x_config: global___SCD4X_config | None = ...,
|
||||||
|
sen5x_config: global___SEN5X_config | None = ...,
|
||||||
|
scd30_config: global___SCD30_config | None = ...,
|
||||||
|
shtxx_config: global___SHTXX_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: ...
|
||||||
|
|
||||||
|
global___SensorConfig = SensorConfig
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class SCD4X_config(google.protobuf.message.Message):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
SET_ASC_FIELD_NUMBER: builtins.int
|
||||||
|
SET_TARGET_CO2_CONC_FIELD_NUMBER: builtins.int
|
||||||
|
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||||
|
SET_ALTITUDE_FIELD_NUMBER: builtins.int
|
||||||
|
SET_AMBIENT_PRESSURE_FIELD_NUMBER: builtins.int
|
||||||
|
FACTORY_RESET_FIELD_NUMBER: builtins.int
|
||||||
|
SET_POWER_MODE_FIELD_NUMBER: builtins.int
|
||||||
|
set_asc: builtins.bool
|
||||||
|
"""
|
||||||
|
Set Automatic self-calibration enabled
|
||||||
|
"""
|
||||||
|
set_target_co2_conc: builtins.int
|
||||||
|
"""
|
||||||
|
Recalibration target CO2 concentration in ppm (FRC or ASC)
|
||||||
|
"""
|
||||||
|
set_temperature: builtins.float
|
||||||
|
"""
|
||||||
|
Reference temperature in degC
|
||||||
|
"""
|
||||||
|
set_altitude: builtins.int
|
||||||
|
"""
|
||||||
|
Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure)
|
||||||
|
"""
|
||||||
|
set_ambient_pressure: builtins.int
|
||||||
|
"""
|
||||||
|
Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude)
|
||||||
|
"""
|
||||||
|
factory_reset: builtins.bool
|
||||||
|
"""
|
||||||
|
Perform a factory reset of the sensor
|
||||||
|
"""
|
||||||
|
set_power_mode: builtins.bool
|
||||||
|
"""
|
||||||
|
Power mode for sensor (true for low power, false for normal)
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
set_asc: builtins.bool | None = ...,
|
||||||
|
set_target_co2_conc: builtins.int | None = ...,
|
||||||
|
set_temperature: builtins.float | None = ...,
|
||||||
|
set_altitude: builtins.int | None = ...,
|
||||||
|
set_ambient_pressure: builtins.int | None = ...,
|
||||||
|
factory_reset: builtins.bool | None = ...,
|
||||||
|
set_power_mode: 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_power_mode", b"_set_power_mode", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "factory_reset", b"factory_reset", "set_altitude", b"set_altitude", "set_ambient_pressure", b"set_ambient_pressure", "set_asc", b"set_asc", "set_power_mode", b"set_power_mode", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature"]) -> 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_power_mode", b"_set_power_mode", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "factory_reset", b"factory_reset", "set_altitude", b"set_altitude", "set_ambient_pressure", b"set_ambient_pressure", "set_asc", b"set_asc", "set_power_mode", b"set_power_mode", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature"]) -> 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_power_mode", b"_set_power_mode"]) -> typing.Literal["set_power_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: ...
|
||||||
|
|
||||||
|
global___SCD4X_config = SCD4X_config
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class SEN5X_config(google.protobuf.message.Message):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||||
|
SET_ONE_SHOT_MODE_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)
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
set_temperature: builtins.float | None = ...,
|
||||||
|
set_one_shot_mode: 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: ...
|
||||||
|
@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: ...
|
||||||
|
|
||||||
|
global___SEN5X_config = SEN5X_config
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class SCD30_config(google.protobuf.message.Message):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
SET_ASC_FIELD_NUMBER: builtins.int
|
||||||
|
SET_TARGET_CO2_CONC_FIELD_NUMBER: builtins.int
|
||||||
|
SET_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||||
|
SET_ALTITUDE_FIELD_NUMBER: builtins.int
|
||||||
|
SET_MEASUREMENT_INTERVAL_FIELD_NUMBER: builtins.int
|
||||||
|
SOFT_RESET_FIELD_NUMBER: builtins.int
|
||||||
|
set_asc: builtins.bool
|
||||||
|
"""
|
||||||
|
Set Automatic self-calibration enabled
|
||||||
|
"""
|
||||||
|
set_target_co2_conc: builtins.int
|
||||||
|
"""
|
||||||
|
Recalibration target CO2 concentration in ppm (FRC or ASC)
|
||||||
|
"""
|
||||||
|
set_temperature: builtins.float
|
||||||
|
"""
|
||||||
|
Reference temperature in degC
|
||||||
|
"""
|
||||||
|
set_altitude: builtins.int
|
||||||
|
"""
|
||||||
|
Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure)
|
||||||
|
"""
|
||||||
|
set_measurement_interval: builtins.int
|
||||||
|
"""
|
||||||
|
Power mode for sensor (true for low power, false for normal)
|
||||||
|
"""
|
||||||
|
soft_reset: builtins.bool
|
||||||
|
"""
|
||||||
|
Perform a factory reset of the sensor
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
set_asc: builtins.bool | None = ...,
|
||||||
|
set_target_co2_conc: builtins.int | None = ...,
|
||||||
|
set_temperature: builtins.float | None = ...,
|
||||||
|
set_altitude: builtins.int | None = ...,
|
||||||
|
set_measurement_interval: builtins.int | None = ...,
|
||||||
|
soft_reset: builtins.bool | None = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def HasField(self, field_name: typing.Literal["_set_altitude", b"_set_altitude", "_set_asc", b"_set_asc", "_set_measurement_interval", b"_set_measurement_interval", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "_soft_reset", b"_soft_reset", "set_altitude", b"set_altitude", "set_asc", b"set_asc", "set_measurement_interval", b"set_measurement_interval", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature", "soft_reset", b"soft_reset"]) -> builtins.bool: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["_set_altitude", b"_set_altitude", "_set_asc", b"_set_asc", "_set_measurement_interval", b"_set_measurement_interval", "_set_target_co2_conc", b"_set_target_co2_conc", "_set_temperature", b"_set_temperature", "_soft_reset", b"_soft_reset", "set_altitude", b"set_altitude", "set_asc", b"set_asc", "set_measurement_interval", b"set_measurement_interval", "set_target_co2_conc", b"set_target_co2_conc", "set_temperature", b"set_temperature", "soft_reset", b"soft_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_asc", b"_set_asc"]) -> typing.Literal["set_asc"] | None: ...
|
||||||
|
@typing.overload
|
||||||
|
def WhichOneof(self, oneof_group: typing.Literal["_set_measurement_interval", b"_set_measurement_interval"]) -> typing.Literal["set_measurement_interval"] | 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["_soft_reset", b"_soft_reset"]) -> typing.Literal["soft_reset"] | None: ...
|
||||||
|
|
||||||
|
global___SCD30_config = SCD30_config
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class SHTXX_config(google.protobuf.message.Message):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
SET_ACCURACY_FIELD_NUMBER: builtins.int
|
||||||
|
set_accuracy: builtins.int
|
||||||
|
"""
|
||||||
|
Accuracy mode (0 = low, 1 = medium, 2 = high)
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
set_accuracy: builtins.int | None = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def HasField(self, field_name: typing.Literal["_set_accuracy", b"_set_accuracy", "set_accuracy", b"set_accuracy"]) -> builtins.bool: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["_set_accuracy", b"_set_accuracy", "set_accuracy", b"set_accuracy"]) -> None: ...
|
||||||
|
def WhichOneof(self, oneof_group: typing.Literal["_set_accuracy", b"_set_accuracy"]) -> typing.Literal["set_accuracy"] | None: ...
|
||||||
|
|
||||||
|
global___SHTXX_config = SHTXX_config
|
||||||
|
|||||||
56
meshtastic/protobuf/config_pb2.py
generated
56
meshtastic/protobuf/config_pb2.py
generated
File diff suppressed because one or more lines are too long
53
meshtastic/protobuf/config_pb2.pyi
generated
53
meshtastic/protobuf/config_pb2.pyi
generated
@@ -119,7 +119,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
CLIENT_BASE: Config.DeviceConfig._Role.ValueType # 12
|
CLIENT_BASE: Config.DeviceConfig._Role.ValueType # 12
|
||||||
"""
|
"""
|
||||||
Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
|
Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
|
||||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
||||||
@@ -211,7 +211,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
CLIENT_BASE: Config.DeviceConfig.Role.ValueType # 12
|
CLIENT_BASE: Config.DeviceConfig.Role.ValueType # 12
|
||||||
"""
|
"""
|
||||||
Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
|
Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
|
||||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
||||||
@@ -1164,6 +1164,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
|
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
|
||||||
USE_12H_CLOCK_FIELD_NUMBER: builtins.int
|
USE_12H_CLOCK_FIELD_NUMBER: builtins.int
|
||||||
USE_LONG_NODE_NAME_FIELD_NUMBER: builtins.int
|
USE_LONG_NODE_NAME_FIELD_NUMBER: builtins.int
|
||||||
|
ENABLE_MESSAGE_BUBBLES_FIELD_NUMBER: builtins.int
|
||||||
screen_on_secs: builtins.int
|
screen_on_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
Number of seconds the screen stays on after pressing the user button or receiving a message
|
Number of seconds the screen stays on after pressing the user button or receiving a message
|
||||||
@@ -1222,6 +1223,10 @@ class Config(google.protobuf.message.Message):
|
|||||||
If false (default), the device will use short names for various display screens.
|
If false (default), the device will use short names for various display screens.
|
||||||
If true, node names will show in long format
|
If true, node names will show in long format
|
||||||
"""
|
"""
|
||||||
|
enable_message_bubbles: builtins.bool
|
||||||
|
"""
|
||||||
|
If true, the device will display message bubbles on screen.
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1238,8 +1243,9 @@ class Config(google.protobuf.message.Message):
|
|||||||
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
|
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
|
||||||
use_12h_clock: builtins.bool = ...,
|
use_12h_clock: builtins.bool = ...,
|
||||||
use_long_node_name: builtins.bool = ...,
|
use_long_node_name: builtins.bool = ...,
|
||||||
|
enable_message_bubbles: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "use_long_node_name", b"use_long_node_name", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "enable_message_bubbles", b"enable_message_bubbles", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "use_long_node_name", b"use_long_node_name", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class LoRaConfig(google.protobuf.message.Message):
|
class LoRaConfig(google.protobuf.message.Message):
|
||||||
@@ -1578,6 +1584,39 @@ class Config(google.protobuf.message.Message):
|
|||||||
This preset performs similarly to LongFast, but with 500Khz bandwidth.
|
This preset performs similarly to LongFast, but with 500Khz bandwidth.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class _FEM_LNA_Mode:
|
||||||
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
|
V: typing_extensions.TypeAlias = ValueType
|
||||||
|
|
||||||
|
class _FEM_LNA_ModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.LoRaConfig._FEM_LNA_Mode.ValueType], builtins.type):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||||
|
DISABLED: Config.LoRaConfig._FEM_LNA_Mode.ValueType # 0
|
||||||
|
"""
|
||||||
|
FEM_LNA is present but disabled
|
||||||
|
"""
|
||||||
|
ENABLED: Config.LoRaConfig._FEM_LNA_Mode.ValueType # 1
|
||||||
|
"""
|
||||||
|
FEM_LNA is present and enabled
|
||||||
|
"""
|
||||||
|
NOT_PRESENT: Config.LoRaConfig._FEM_LNA_Mode.ValueType # 2
|
||||||
|
"""
|
||||||
|
FEM_LNA is not present on the device
|
||||||
|
"""
|
||||||
|
|
||||||
|
class FEM_LNA_Mode(_FEM_LNA_Mode, metaclass=_FEM_LNA_ModeEnumTypeWrapper): ...
|
||||||
|
DISABLED: Config.LoRaConfig.FEM_LNA_Mode.ValueType # 0
|
||||||
|
"""
|
||||||
|
FEM_LNA is present but disabled
|
||||||
|
"""
|
||||||
|
ENABLED: Config.LoRaConfig.FEM_LNA_Mode.ValueType # 1
|
||||||
|
"""
|
||||||
|
FEM_LNA is present and enabled
|
||||||
|
"""
|
||||||
|
NOT_PRESENT: Config.LoRaConfig.FEM_LNA_Mode.ValueType # 2
|
||||||
|
"""
|
||||||
|
FEM_LNA is not present on the device
|
||||||
|
"""
|
||||||
|
|
||||||
USE_PRESET_FIELD_NUMBER: builtins.int
|
USE_PRESET_FIELD_NUMBER: builtins.int
|
||||||
MODEM_PRESET_FIELD_NUMBER: builtins.int
|
MODEM_PRESET_FIELD_NUMBER: builtins.int
|
||||||
BANDWIDTH_FIELD_NUMBER: builtins.int
|
BANDWIDTH_FIELD_NUMBER: builtins.int
|
||||||
@@ -1596,6 +1635,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
IGNORE_INCOMING_FIELD_NUMBER: builtins.int
|
IGNORE_INCOMING_FIELD_NUMBER: builtins.int
|
||||||
IGNORE_MQTT_FIELD_NUMBER: builtins.int
|
IGNORE_MQTT_FIELD_NUMBER: builtins.int
|
||||||
CONFIG_OK_TO_MQTT_FIELD_NUMBER: builtins.int
|
CONFIG_OK_TO_MQTT_FIELD_NUMBER: builtins.int
|
||||||
|
FEM_LNA_MODE_FIELD_NUMBER: builtins.int
|
||||||
use_preset: builtins.bool
|
use_preset: builtins.bool
|
||||||
"""
|
"""
|
||||||
When enabled, the `modem_preset` fields will be adhered to, else the `bandwidth`/`spread_factor`/`coding_rate`
|
When enabled, the `modem_preset` fields will be adhered to, else the `bandwidth`/`spread_factor`/`coding_rate`
|
||||||
@@ -1693,6 +1733,10 @@ class Config(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Sets the ok_to_mqtt bit on outgoing packets
|
Sets the ok_to_mqtt bit on outgoing packets
|
||||||
"""
|
"""
|
||||||
|
fem_lna_mode: global___Config.LoRaConfig.FEM_LNA_Mode.ValueType
|
||||||
|
"""
|
||||||
|
Set where LORA FEM is enabled, disabled, or not present
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def ignore_incoming(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
|
def ignore_incoming(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
|
||||||
"""
|
"""
|
||||||
@@ -1722,8 +1766,9 @@ class Config(google.protobuf.message.Message):
|
|||||||
ignore_incoming: collections.abc.Iterable[builtins.int] | None = ...,
|
ignore_incoming: collections.abc.Iterable[builtins.int] | None = ...,
|
||||||
ignore_mqtt: builtins.bool = ...,
|
ignore_mqtt: builtins.bool = ...,
|
||||||
config_ok_to_mqtt: builtins.bool = ...,
|
config_ok_to_mqtt: builtins.bool = ...,
|
||||||
|
fem_lna_mode: global___Config.LoRaConfig.FEM_LNA_Mode.ValueType = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "config_ok_to_mqtt", b"config_ok_to_mqtt", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "pa_fan_disabled", b"pa_fan_disabled", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "config_ok_to_mqtt", b"config_ok_to_mqtt", "fem_lna_mode", b"fem_lna_mode", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "pa_fan_disabled", b"pa_fan_disabled", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class BluetoothConfig(google.protobuf.message.Message):
|
class BluetoothConfig(google.protobuf.message.Message):
|
||||||
|
|||||||
1
meshtastic/protobuf/deviceonly_pb2.pyi
generated
1
meshtastic/protobuf/deviceonly_pb2.pyi
generated
@@ -197,6 +197,7 @@ class NodeInfoLite(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Bitfield for storing booleans.
|
Bitfield for storing booleans.
|
||||||
LSB 0 is_key_manually_verified
|
LSB 0 is_key_manually_verified
|
||||||
|
LSB 1 is_muted
|
||||||
"""
|
"""
|
||||||
@property
|
@property
|
||||||
def user(self) -> global___UserLite:
|
def user(self) -> global___UserLite:
|
||||||
|
|||||||
4
meshtastic/protobuf/localonly_pb2.py
generated
4
meshtastic/protobuf/localonly_pb2.py
generated
@@ -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
|
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\"\xf0\x07\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\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\"\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')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
@@ -26,5 +26,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
_globals['_LOCALCONFIG']._serialized_start=136
|
_globals['_LOCALCONFIG']._serialized_start=136
|
||||||
_globals['_LOCALCONFIG']._serialized_end=642
|
_globals['_LOCALCONFIG']._serialized_end=642
|
||||||
_globals['_LOCALMODULECONFIG']._serialized_start=645
|
_globals['_LOCALMODULECONFIG']._serialized_start=645
|
||||||
_globals['_LOCALMODULECONFIG']._serialized_end=1653
|
_globals['_LOCALMODULECONFIG']._serialized_end=1876
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
28
meshtastic/protobuf/localonly_pb2.pyi
generated
28
meshtastic/protobuf/localonly_pb2.pyi
generated
@@ -119,6 +119,9 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
|||||||
AMBIENT_LIGHTING_FIELD_NUMBER: builtins.int
|
AMBIENT_LIGHTING_FIELD_NUMBER: builtins.int
|
||||||
DETECTION_SENSOR_FIELD_NUMBER: builtins.int
|
DETECTION_SENSOR_FIELD_NUMBER: builtins.int
|
||||||
PAXCOUNTER_FIELD_NUMBER: builtins.int
|
PAXCOUNTER_FIELD_NUMBER: builtins.int
|
||||||
|
STATUSMESSAGE_FIELD_NUMBER: builtins.int
|
||||||
|
TRAFFIC_MANAGEMENT_FIELD_NUMBER: builtins.int
|
||||||
|
TAK_FIELD_NUMBER: builtins.int
|
||||||
VERSION_FIELD_NUMBER: builtins.int
|
VERSION_FIELD_NUMBER: builtins.int
|
||||||
version: builtins.int
|
version: builtins.int
|
||||||
"""
|
"""
|
||||||
@@ -204,6 +207,24 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
|||||||
Paxcounter Config
|
Paxcounter Config
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statusmessage(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.StatusMessageConfig:
|
||||||
|
"""
|
||||||
|
StatusMessage Config
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def traffic_management(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.TrafficManagementConfig:
|
||||||
|
"""
|
||||||
|
The part of the config that is specific to the Traffic Management module
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tak(self) -> meshtastic.protobuf.module_config_pb2.ModuleConfig.TAKConfig:
|
||||||
|
"""
|
||||||
|
TAK Config
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -220,9 +241,12 @@ class LocalModuleConfig(google.protobuf.message.Message):
|
|||||||
ambient_lighting: meshtastic.protobuf.module_config_pb2.ModuleConfig.AmbientLightingConfig | None = ...,
|
ambient_lighting: meshtastic.protobuf.module_config_pb2.ModuleConfig.AmbientLightingConfig | None = ...,
|
||||||
detection_sensor: meshtastic.protobuf.module_config_pb2.ModuleConfig.DetectionSensorConfig | None = ...,
|
detection_sensor: meshtastic.protobuf.module_config_pb2.ModuleConfig.DetectionSensorConfig | None = ...,
|
||||||
paxcounter: meshtastic.protobuf.module_config_pb2.ModuleConfig.PaxcounterConfig | None = ...,
|
paxcounter: meshtastic.protobuf.module_config_pb2.ModuleConfig.PaxcounterConfig | None = ...,
|
||||||
|
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 = ...,
|
||||||
version: builtins.int = ...,
|
version: builtins.int = ...,
|
||||||
) -> 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", "range_test", b"range_test", "remote_hardware", b"remote_hardware", "serial", b"serial", "store_forward", b"store_forward", "telemetry", b"telemetry"]) -> builtins.bool: ...
|
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", "store_forward", b"store_forward", "telemetry", b"telemetry", "version", b"version"]) -> None: ...
|
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: ...
|
||||||
|
|
||||||
global___LocalModuleConfig = LocalModuleConfig
|
global___LocalModuleConfig = LocalModuleConfig
|
||||||
|
|||||||
160
meshtastic/protobuf/mesh_pb2.py
generated
160
meshtastic/protobuf/mesh_pb2.py
generated
File diff suppressed because one or more lines are too long
110
meshtastic/protobuf/mesh_pb2.pyi
generated
110
meshtastic/protobuf/mesh_pb2.pyi
generated
@@ -168,9 +168,9 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
Less common/prototype boards listed here (needs one more byte over the air)
|
Less common/prototype boards listed here (needs one more byte over the air)
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
"""
|
"""
|
||||||
NRF52840DK: _HardwareModel.ValueType # 33
|
T_ECHO_PLUS: _HardwareModel.ValueType # 33
|
||||||
"""
|
"""
|
||||||
TODO: REPLACE
|
T-Echo Plus device from LilyGo
|
||||||
"""
|
"""
|
||||||
PPR: _HardwareModel.ValueType # 34
|
PPR: _HardwareModel.ValueType # 34
|
||||||
"""
|
"""
|
||||||
@@ -535,6 +535,34 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
Elecrow ThinkNode M6
|
Elecrow ThinkNode M6
|
||||||
"""
|
"""
|
||||||
|
MESHSTICK_1262: _HardwareModel.ValueType # 121
|
||||||
|
"""
|
||||||
|
Elecrow Meshstick 1262
|
||||||
|
"""
|
||||||
|
TBEAM_1_WATT: _HardwareModel.ValueType # 122
|
||||||
|
"""
|
||||||
|
LilyGo T-Beam 1W
|
||||||
|
"""
|
||||||
|
T5_S3_EPAPER_PRO: _HardwareModel.ValueType # 123
|
||||||
|
"""
|
||||||
|
LilyGo T5 S3 ePaper Pro (V1 and V2)
|
||||||
|
"""
|
||||||
|
TBEAM_BPF: _HardwareModel.ValueType # 124
|
||||||
|
"""
|
||||||
|
LilyGo T-Beam BPF (144-148Mhz)
|
||||||
|
"""
|
||||||
|
MINI_EPAPER_S3: _HardwareModel.ValueType # 125
|
||||||
|
"""
|
||||||
|
LilyGo T-Mini E-paper S3 Kit
|
||||||
|
"""
|
||||||
|
TDISPLAY_S3_PRO: _HardwareModel.ValueType # 126
|
||||||
|
"""
|
||||||
|
LilyGo T-Display S3 Pro LR1121
|
||||||
|
"""
|
||||||
|
HELTEC_MESH_NODE_T096: _HardwareModel.ValueType # 127
|
||||||
|
"""
|
||||||
|
Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen.
|
||||||
|
"""
|
||||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -686,9 +714,9 @@ LORA_RELAY_V1: HardwareModel.ValueType # 32
|
|||||||
Less common/prototype boards listed here (needs one more byte over the air)
|
Less common/prototype boards listed here (needs one more byte over the air)
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
"""
|
"""
|
||||||
NRF52840DK: HardwareModel.ValueType # 33
|
T_ECHO_PLUS: HardwareModel.ValueType # 33
|
||||||
"""
|
"""
|
||||||
TODO: REPLACE
|
T-Echo Plus device from LilyGo
|
||||||
"""
|
"""
|
||||||
PPR: HardwareModel.ValueType # 34
|
PPR: HardwareModel.ValueType # 34
|
||||||
"""
|
"""
|
||||||
@@ -1053,6 +1081,34 @@ THINKNODE_M6: HardwareModel.ValueType # 120
|
|||||||
"""
|
"""
|
||||||
Elecrow ThinkNode M6
|
Elecrow ThinkNode M6
|
||||||
"""
|
"""
|
||||||
|
MESHSTICK_1262: HardwareModel.ValueType # 121
|
||||||
|
"""
|
||||||
|
Elecrow Meshstick 1262
|
||||||
|
"""
|
||||||
|
TBEAM_1_WATT: HardwareModel.ValueType # 122
|
||||||
|
"""
|
||||||
|
LilyGo T-Beam 1W
|
||||||
|
"""
|
||||||
|
T5_S3_EPAPER_PRO: HardwareModel.ValueType # 123
|
||||||
|
"""
|
||||||
|
LilyGo T5 S3 ePaper Pro (V1 and V2)
|
||||||
|
"""
|
||||||
|
TBEAM_BPF: HardwareModel.ValueType # 124
|
||||||
|
"""
|
||||||
|
LilyGo T-Beam BPF (144-148Mhz)
|
||||||
|
"""
|
||||||
|
MINI_EPAPER_S3: HardwareModel.ValueType # 125
|
||||||
|
"""
|
||||||
|
LilyGo T-Mini E-paper S3 Kit
|
||||||
|
"""
|
||||||
|
TDISPLAY_S3_PRO: HardwareModel.ValueType # 126
|
||||||
|
"""
|
||||||
|
LilyGo T-Display S3 Pro LR1121
|
||||||
|
"""
|
||||||
|
HELTEC_MESH_NODE_T096: HardwareModel.ValueType # 127
|
||||||
|
"""
|
||||||
|
Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen.
|
||||||
|
"""
|
||||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1972,6 +2028,11 @@ class Routing(google.protobuf.message.Message):
|
|||||||
Airtime fairness rate limit exceeded for a packet
|
Airtime fairness rate limit exceeded for a packet
|
||||||
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
||||||
"""
|
"""
|
||||||
|
PKI_SEND_FAIL_PUBLIC_KEY: Routing._Error.ValueType # 39
|
||||||
|
"""
|
||||||
|
PKI encryption failed, due to no public key for the remote node
|
||||||
|
This is different from PKI_UNKNOWN_PUBKEY which indicates a failure upon receiving a packet
|
||||||
|
"""
|
||||||
|
|
||||||
class Error(_Error, metaclass=_ErrorEnumTypeWrapper):
|
class Error(_Error, metaclass=_ErrorEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -2050,6 +2111,11 @@ class Routing(google.protobuf.message.Message):
|
|||||||
Airtime fairness rate limit exceeded for a packet
|
Airtime fairness rate limit exceeded for a packet
|
||||||
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
||||||
"""
|
"""
|
||||||
|
PKI_SEND_FAIL_PUBLIC_KEY: Routing.Error.ValueType # 39
|
||||||
|
"""
|
||||||
|
PKI encryption failed, due to no public key for the remote node
|
||||||
|
This is different from PKI_UNKNOWN_PUBKEY which indicates a failure upon receiving a packet
|
||||||
|
"""
|
||||||
|
|
||||||
ROUTE_REQUEST_FIELD_NUMBER: builtins.int
|
ROUTE_REQUEST_FIELD_NUMBER: builtins.int
|
||||||
ROUTE_REPLY_FIELD_NUMBER: builtins.int
|
ROUTE_REPLY_FIELD_NUMBER: builtins.int
|
||||||
@@ -2281,6 +2347,7 @@ class StoreForwardPlusPlus(google.protobuf.message.Message):
|
|||||||
ENCAPSULATED_TO_FIELD_NUMBER: builtins.int
|
ENCAPSULATED_TO_FIELD_NUMBER: builtins.int
|
||||||
ENCAPSULATED_FROM_FIELD_NUMBER: builtins.int
|
ENCAPSULATED_FROM_FIELD_NUMBER: builtins.int
|
||||||
ENCAPSULATED_RXTIME_FIELD_NUMBER: builtins.int
|
ENCAPSULATED_RXTIME_FIELD_NUMBER: builtins.int
|
||||||
|
CHAIN_COUNT_FIELD_NUMBER: builtins.int
|
||||||
sfpp_message_type: global___StoreForwardPlusPlus.SFPP_message_type.ValueType
|
sfpp_message_type: global___StoreForwardPlusPlus.SFPP_message_type.ValueType
|
||||||
"""
|
"""
|
||||||
Which message type is this
|
Which message type is this
|
||||||
@@ -2317,6 +2384,10 @@ class StoreForwardPlusPlus(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
The receive time of the message in question
|
The receive time of the message in question
|
||||||
"""
|
"""
|
||||||
|
chain_count: builtins.int
|
||||||
|
"""
|
||||||
|
Used in a LINK_REQUEST to specify the message X spots back from head
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -2329,8 +2400,9 @@ class StoreForwardPlusPlus(google.protobuf.message.Message):
|
|||||||
encapsulated_to: builtins.int = ...,
|
encapsulated_to: builtins.int = ...,
|
||||||
encapsulated_from: builtins.int = ...,
|
encapsulated_from: builtins.int = ...,
|
||||||
encapsulated_rxtime: builtins.int = ...,
|
encapsulated_rxtime: builtins.int = ...,
|
||||||
|
chain_count: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["commit_hash", b"commit_hash", "encapsulated_from", b"encapsulated_from", "encapsulated_id", b"encapsulated_id", "encapsulated_rxtime", b"encapsulated_rxtime", "encapsulated_to", b"encapsulated_to", "message", b"message", "message_hash", b"message_hash", "root_hash", b"root_hash", "sfpp_message_type", b"sfpp_message_type"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["chain_count", b"chain_count", "commit_hash", b"commit_hash", "encapsulated_from", b"encapsulated_from", "encapsulated_id", b"encapsulated_id", "encapsulated_rxtime", b"encapsulated_rxtime", "encapsulated_to", b"encapsulated_to", "message", b"message", "message_hash", b"message_hash", "root_hash", b"root_hash", "sfpp_message_type", b"sfpp_message_type"]) -> None: ...
|
||||||
|
|
||||||
global___StoreForwardPlusPlus = StoreForwardPlusPlus
|
global___StoreForwardPlusPlus = StoreForwardPlusPlus
|
||||||
|
|
||||||
@@ -2404,6 +2476,25 @@ class Waypoint(google.protobuf.message.Message):
|
|||||||
|
|
||||||
global___Waypoint = Waypoint
|
global___Waypoint = Waypoint
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class StatusMessage(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
Message for node status
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
STATUS_FIELD_NUMBER: builtins.int
|
||||||
|
status: builtins.str
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
status: builtins.str = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["status", b"status"]) -> None: ...
|
||||||
|
|
||||||
|
global___StatusMessage = StatusMessage
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class MqttClientProxyMessage(google.protobuf.message.Message):
|
class MqttClientProxyMessage(google.protobuf.message.Message):
|
||||||
"""
|
"""
|
||||||
@@ -2895,6 +2986,7 @@ class NodeInfo(google.protobuf.message.Message):
|
|||||||
IS_FAVORITE_FIELD_NUMBER: builtins.int
|
IS_FAVORITE_FIELD_NUMBER: builtins.int
|
||||||
IS_IGNORED_FIELD_NUMBER: builtins.int
|
IS_IGNORED_FIELD_NUMBER: builtins.int
|
||||||
IS_KEY_MANUALLY_VERIFIED_FIELD_NUMBER: builtins.int
|
IS_KEY_MANUALLY_VERIFIED_FIELD_NUMBER: builtins.int
|
||||||
|
IS_MUTED_FIELD_NUMBER: builtins.int
|
||||||
num: builtins.int
|
num: builtins.int
|
||||||
"""
|
"""
|
||||||
The node number
|
The node number
|
||||||
@@ -2942,6 +3034,11 @@ class NodeInfo(google.protobuf.message.Message):
|
|||||||
Persists between NodeDB internal clean ups
|
Persists between NodeDB internal clean ups
|
||||||
LSB 0 of the bitfield
|
LSB 0 of the bitfield
|
||||||
"""
|
"""
|
||||||
|
is_muted: builtins.bool
|
||||||
|
"""
|
||||||
|
True if node has been muted
|
||||||
|
Persistes between NodeDB internal clean ups
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def user(self) -> global___User:
|
def user(self) -> global___User:
|
||||||
"""
|
"""
|
||||||
@@ -2976,9 +3073,10 @@ class NodeInfo(google.protobuf.message.Message):
|
|||||||
is_favorite: builtins.bool = ...,
|
is_favorite: builtins.bool = ...,
|
||||||
is_ignored: builtins.bool = ...,
|
is_ignored: builtins.bool = ...,
|
||||||
is_key_manually_verified: builtins.bool = ...,
|
is_key_manually_verified: builtins.bool = ...,
|
||||||
|
is_muted: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> 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 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", "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", "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: ...
|
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
|
||||||
|
|
||||||
global___NodeInfo = NodeInfo
|
global___NodeInfo = NodeInfo
|
||||||
|
|||||||
97
meshtastic/protobuf/module_config_pb2.py
generated
97
meshtastic/protobuf/module_config_pb2.py
generated
File diff suppressed because one or more lines are too long
198
meshtastic/protobuf/module_config_pb2.pyi
generated
198
meshtastic/protobuf/module_config_pb2.pyi
generated
@@ -9,6 +9,7 @@ import google.protobuf.descriptor
|
|||||||
import google.protobuf.internal.containers
|
import google.protobuf.internal.containers
|
||||||
import google.protobuf.internal.enum_type_wrapper
|
import google.protobuf.internal.enum_type_wrapper
|
||||||
import google.protobuf.message
|
import google.protobuf.message
|
||||||
|
import meshtastic.protobuf.atak_pb2
|
||||||
import sys
|
import sys
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
@@ -492,6 +493,105 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["ble_threshold", b"ble_threshold", "enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval", "wifi_threshold", b"wifi_threshold"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["ble_threshold", b"ble_threshold", "enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval", "wifi_threshold", b"wifi_threshold"]) -> None: ...
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class TrafficManagementConfig(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
Config for the Traffic Management module.
|
||||||
|
Provides packet inspection and traffic shaping to help reduce channel utilization
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
rate_limit_window_secs: builtins.int
|
||||||
|
"""
|
||||||
|
Time window in seconds for rate limiting calculations
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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: ...
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class SerialConfig(google.protobuf.message.Message):
|
class SerialConfig(google.protobuf.message.Message):
|
||||||
"""
|
"""
|
||||||
@@ -568,6 +668,12 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
"""Used to configure and view some parameters of MeshSolar.
|
"""Used to configure and view some parameters of MeshSolar.
|
||||||
https://heltec.org/project/meshsolar/
|
https://heltec.org/project/meshsolar/
|
||||||
"""
|
"""
|
||||||
|
LOG: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 9
|
||||||
|
"""Logs mesh traffic to the serial pins, ideal for logging via openLog or similar.
|
||||||
|
includes other packets
|
||||||
|
"""
|
||||||
|
LOGTEXT: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 10
|
||||||
|
"""only text (channel & DM)"""
|
||||||
|
|
||||||
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -591,6 +697,12 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
"""Used to configure and view some parameters of MeshSolar.
|
"""Used to configure and view some parameters of MeshSolar.
|
||||||
https://heltec.org/project/meshsolar/
|
https://heltec.org/project/meshsolar/
|
||||||
"""
|
"""
|
||||||
|
LOG: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 9
|
||||||
|
"""Logs mesh traffic to the serial pins, ideal for logging via openLog or similar.
|
||||||
|
includes other packets
|
||||||
|
"""
|
||||||
|
LOGTEXT: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 10
|
||||||
|
"""only text (channel & DM)"""
|
||||||
|
|
||||||
ENABLED_FIELD_NUMBER: builtins.int
|
ENABLED_FIELD_NUMBER: builtins.int
|
||||||
ECHO_FIELD_NUMBER: builtins.int
|
ECHO_FIELD_NUMBER: builtins.int
|
||||||
@@ -875,6 +987,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
||||||
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
||||||
DEVICE_TELEMETRY_ENABLED_FIELD_NUMBER: builtins.int
|
DEVICE_TELEMETRY_ENABLED_FIELD_NUMBER: builtins.int
|
||||||
|
AIR_QUALITY_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
||||||
device_update_interval: builtins.int
|
device_update_interval: builtins.int
|
||||||
"""
|
"""
|
||||||
Interval in seconds of how often we should try to send our
|
Interval in seconds of how often we should try to send our
|
||||||
@@ -940,6 +1053,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
Enable/Disable the device telemetry module to send metrics to the mesh
|
Enable/Disable the device telemetry module to send metrics to the mesh
|
||||||
Note: We will still send telemtry to the connected phone / client every minute over the API
|
Note: We will still send telemtry to the connected phone / client every minute over the API
|
||||||
"""
|
"""
|
||||||
|
air_quality_screen_enabled: builtins.bool
|
||||||
|
"""
|
||||||
|
Enable/Disable the air quality telemetry measurement module on-device display
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -957,8 +1074,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
health_update_interval: builtins.int = ...,
|
health_update_interval: builtins.int = ...,
|
||||||
health_screen_enabled: builtins.bool = ...,
|
health_screen_enabled: builtins.bool = ...,
|
||||||
device_telemetry_enabled: builtins.bool = ...,
|
device_telemetry_enabled: builtins.bool = ...,
|
||||||
|
air_quality_screen_enabled: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_telemetry_enabled", b"device_telemetry_enabled", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "air_quality_screen_enabled", b"air_quality_screen_enabled", "device_telemetry_enabled", b"device_telemetry_enabled", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class CannedMessageConfig(google.protobuf.message.Message):
|
class CannedMessageConfig(google.protobuf.message.Message):
|
||||||
@@ -1164,6 +1282,54 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["blue", b"blue", "current", b"current", "green", b"green", "led_state", b"led_state", "red", b"red"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["blue", b"blue", "current", b"current", "green", b"green", "led_state", b"led_state", "red", b"red"]) -> None: ...
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class StatusMessageConfig(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
StatusMessage config - Allows setting a status message for a node to periodically rebroadcast
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
NODE_STATUS_FIELD_NUMBER: builtins.int
|
||||||
|
node_status: builtins.str
|
||||||
|
"""
|
||||||
|
The actual status string
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
node_status: builtins.str = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["node_status", b"node_status"]) -> None: ...
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class TAKConfig(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
TAK team/role configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
TEAM_FIELD_NUMBER: builtins.int
|
||||||
|
ROLE_FIELD_NUMBER: builtins.int
|
||||||
|
team: meshtastic.protobuf.atak_pb2.Team.ValueType
|
||||||
|
"""
|
||||||
|
Team color.
|
||||||
|
Default Unspecifed_Color -> firmware uses Cyan
|
||||||
|
"""
|
||||||
|
role: meshtastic.protobuf.atak_pb2.MemberRole.ValueType
|
||||||
|
"""
|
||||||
|
Member role.
|
||||||
|
Default Unspecifed -> firmware uses TeamMember
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
team: meshtastic.protobuf.atak_pb2.Team.ValueType = ...,
|
||||||
|
role: meshtastic.protobuf.atak_pb2.MemberRole.ValueType = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["role", b"role", "team", b"team"]) -> None: ...
|
||||||
|
|
||||||
MQTT_FIELD_NUMBER: builtins.int
|
MQTT_FIELD_NUMBER: builtins.int
|
||||||
SERIAL_FIELD_NUMBER: builtins.int
|
SERIAL_FIELD_NUMBER: builtins.int
|
||||||
EXTERNAL_NOTIFICATION_FIELD_NUMBER: builtins.int
|
EXTERNAL_NOTIFICATION_FIELD_NUMBER: builtins.int
|
||||||
@@ -1177,6 +1343,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
AMBIENT_LIGHTING_FIELD_NUMBER: builtins.int
|
AMBIENT_LIGHTING_FIELD_NUMBER: builtins.int
|
||||||
DETECTION_SENSOR_FIELD_NUMBER: builtins.int
|
DETECTION_SENSOR_FIELD_NUMBER: builtins.int
|
||||||
PAXCOUNTER_FIELD_NUMBER: builtins.int
|
PAXCOUNTER_FIELD_NUMBER: builtins.int
|
||||||
|
STATUSMESSAGE_FIELD_NUMBER: builtins.int
|
||||||
|
TRAFFIC_MANAGEMENT_FIELD_NUMBER: builtins.int
|
||||||
|
TAK_FIELD_NUMBER: builtins.int
|
||||||
@property
|
@property
|
||||||
def mqtt(self) -> global___ModuleConfig.MQTTConfig:
|
def mqtt(self) -> global___ModuleConfig.MQTTConfig:
|
||||||
"""
|
"""
|
||||||
@@ -1255,6 +1424,24 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
TODO: REPLACE
|
TODO: REPLACE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statusmessage(self) -> global___ModuleConfig.StatusMessageConfig:
|
||||||
|
"""
|
||||||
|
TODO: REPLACE
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def traffic_management(self) -> global___ModuleConfig.TrafficManagementConfig:
|
||||||
|
"""
|
||||||
|
Traffic management module config for mesh network optimization
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tak(self) -> global___ModuleConfig.TAKConfig:
|
||||||
|
"""
|
||||||
|
TAK team/role configuration for TAK_TRACKER
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1271,10 +1458,13 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
ambient_lighting: global___ModuleConfig.AmbientLightingConfig | None = ...,
|
ambient_lighting: global___ModuleConfig.AmbientLightingConfig | None = ...,
|
||||||
detection_sensor: global___ModuleConfig.DetectionSensorConfig | None = ...,
|
detection_sensor: global___ModuleConfig.DetectionSensorConfig | None = ...,
|
||||||
paxcounter: global___ModuleConfig.PaxcounterConfig | None = ...,
|
paxcounter: global___ModuleConfig.PaxcounterConfig | None = ...,
|
||||||
|
statusmessage: global___ModuleConfig.StatusMessageConfig | None = ...,
|
||||||
|
traffic_management: global___ModuleConfig.TrafficManagementConfig | None = ...,
|
||||||
|
tak: global___ModuleConfig.TAKConfig | None = ...,
|
||||||
) -> 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", "store_forward", b"store_forward", "telemetry", b"telemetry"]) -> builtins.bool: ...
|
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", "store_forward", b"store_forward", "telemetry", b"telemetry"]) -> None: ...
|
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"] | 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: ...
|
||||||
|
|
||||||
global___ModuleConfig = ModuleConfig
|
global___ModuleConfig = ModuleConfig
|
||||||
|
|
||||||
|
|||||||
4
meshtastic/protobuf/portnums_pb2.py
generated
4
meshtastic/protobuf/portnums_pb2.py
generated
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\x96\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\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\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\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\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*\xd3\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\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\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()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
@@ -22,5 +22,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
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_start=60
|
||||||
_globals['_PORTNUM']._serialized_end=722
|
_globals['_PORTNUM']._serialized_end=783
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
36
meshtastic/protobuf/portnums_pb2.pyi
generated
36
meshtastic/protobuf/portnums_pb2.pyi
generated
@@ -124,6 +124,13 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
|||||||
This module is specifically for Native Linux nodes, and provides a Git-style
|
This module is specifically for Native Linux nodes, and provides a Git-style
|
||||||
chain of messages.
|
chain of messages.
|
||||||
"""
|
"""
|
||||||
|
NODE_STATUS_APP: _PortNum.ValueType # 36
|
||||||
|
"""
|
||||||
|
Node Status module
|
||||||
|
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.
|
||||||
|
"""
|
||||||
SERIAL_APP: _PortNum.ValueType # 64
|
SERIAL_APP: _PortNum.ValueType # 64
|
||||||
"""
|
"""
|
||||||
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||||
@@ -190,6 +197,11 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
|||||||
"""
|
"""
|
||||||
PowerStress based monitoring support (for automated power consumption testing)
|
PowerStress based monitoring support (for automated power consumption testing)
|
||||||
"""
|
"""
|
||||||
|
LORAWAN_BRIDGE: _PortNum.ValueType # 75
|
||||||
|
"""
|
||||||
|
LoraWAN Payload Transport
|
||||||
|
ENCODING: compact binary LoRaWAN uplink (10-byte RF metadata + PHY payload) - see LoRaWANBridgeModule
|
||||||
|
"""
|
||||||
RETICULUM_TUNNEL_APP: _PortNum.ValueType # 76
|
RETICULUM_TUNNEL_APP: _PortNum.ValueType # 76
|
||||||
"""
|
"""
|
||||||
Reticulum Network Stack Tunnel App
|
Reticulum Network Stack Tunnel App
|
||||||
@@ -201,6 +213,12 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
|||||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||||
ENCODING: CayenneLLP
|
ENCODING: CayenneLLP
|
||||||
"""
|
"""
|
||||||
|
GROUPALARM_APP: _PortNum.ValueType # 112
|
||||||
|
"""
|
||||||
|
GroupAlarm integration
|
||||||
|
Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||||
|
and companion applications/services.
|
||||||
|
"""
|
||||||
PRIVATE_APP: _PortNum.ValueType # 256
|
PRIVATE_APP: _PortNum.ValueType # 256
|
||||||
"""
|
"""
|
||||||
Private applications should use portnums >= 256.
|
Private applications should use portnums >= 256.
|
||||||
@@ -335,6 +353,13 @@ ENCODING: protobuf
|
|||||||
This module is specifically for Native Linux nodes, and provides a Git-style
|
This module is specifically for Native Linux nodes, and provides a Git-style
|
||||||
chain of messages.
|
chain of messages.
|
||||||
"""
|
"""
|
||||||
|
NODE_STATUS_APP: PortNum.ValueType # 36
|
||||||
|
"""
|
||||||
|
Node Status module
|
||||||
|
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.
|
||||||
|
"""
|
||||||
SERIAL_APP: PortNum.ValueType # 64
|
SERIAL_APP: PortNum.ValueType # 64
|
||||||
"""
|
"""
|
||||||
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||||
@@ -401,6 +426,11 @@ POWERSTRESS_APP: PortNum.ValueType # 74
|
|||||||
"""
|
"""
|
||||||
PowerStress based monitoring support (for automated power consumption testing)
|
PowerStress based monitoring support (for automated power consumption testing)
|
||||||
"""
|
"""
|
||||||
|
LORAWAN_BRIDGE: PortNum.ValueType # 75
|
||||||
|
"""
|
||||||
|
LoraWAN Payload Transport
|
||||||
|
ENCODING: compact binary LoRaWAN uplink (10-byte RF metadata + PHY payload) - see LoRaWANBridgeModule
|
||||||
|
"""
|
||||||
RETICULUM_TUNNEL_APP: PortNum.ValueType # 76
|
RETICULUM_TUNNEL_APP: PortNum.ValueType # 76
|
||||||
"""
|
"""
|
||||||
Reticulum Network Stack Tunnel App
|
Reticulum Network Stack Tunnel App
|
||||||
@@ -412,6 +442,12 @@ App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes
|
|||||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||||
ENCODING: CayenneLLP
|
ENCODING: CayenneLLP
|
||||||
"""
|
"""
|
||||||
|
GROUPALARM_APP: PortNum.ValueType # 112
|
||||||
|
"""
|
||||||
|
GroupAlarm integration
|
||||||
|
Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||||
|
and companion applications/services.
|
||||||
|
"""
|
||||||
PRIVATE_APP: PortNum.ValueType # 256
|
PRIVATE_APP: PortNum.ValueType # 256
|
||||||
"""
|
"""
|
||||||
Private applications should use portnums >= 256.
|
Private applications should use portnums >= 256.
|
||||||
|
|||||||
28
meshtastic/protobuf/telemetry_pb2.py
generated
28
meshtastic/protobuf/telemetry_pb2.py
generated
File diff suppressed because one or more lines are too long
191
meshtastic/protobuf/telemetry_pb2.pyi
generated
191
meshtastic/protobuf/telemetry_pb2.pyi
generated
@@ -53,7 +53,7 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
SHTC3: _TelemetrySensorType.ValueType # 7
|
SHTC3: _TelemetrySensorType.ValueType # 7
|
||||||
"""
|
"""
|
||||||
High accuracy temperature and humidity
|
TODO - REMOVE High accuracy temperature and humidity
|
||||||
"""
|
"""
|
||||||
LPS22: _TelemetrySensorType.ValueType # 8
|
LPS22: _TelemetrySensorType.ValueType # 8
|
||||||
"""
|
"""
|
||||||
@@ -73,7 +73,7 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
SHT31: _TelemetrySensorType.ValueType # 12
|
SHT31: _TelemetrySensorType.ValueType # 12
|
||||||
"""
|
"""
|
||||||
High accuracy temperature and humidity
|
TODO - REMOVE High accuracy temperature and humidity
|
||||||
"""
|
"""
|
||||||
PMSA003I: _TelemetrySensorType.ValueType # 13
|
PMSA003I: _TelemetrySensorType.ValueType # 13
|
||||||
"""
|
"""
|
||||||
@@ -93,7 +93,7 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
SHT4X: _TelemetrySensorType.ValueType # 17
|
SHT4X: _TelemetrySensorType.ValueType # 17
|
||||||
"""
|
"""
|
||||||
Sensirion High accuracy temperature and humidity
|
TODO - REMOVE Sensirion High accuracy temperature and humidity
|
||||||
"""
|
"""
|
||||||
VEML7700: _TelemetrySensorType.ValueType # 18
|
VEML7700: _TelemetrySensorType.ValueType # 18
|
||||||
"""
|
"""
|
||||||
@@ -207,6 +207,26 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
BH1750 light sensor
|
BH1750 light sensor
|
||||||
"""
|
"""
|
||||||
|
HDC1080: _TelemetrySensorType.ValueType # 46
|
||||||
|
"""
|
||||||
|
HDC1080 Temperature and Humidity Sensor
|
||||||
|
"""
|
||||||
|
SHT21: _TelemetrySensorType.ValueType # 47
|
||||||
|
"""
|
||||||
|
TODO - REMOVE STH21 Temperature and R. Humidity sensor
|
||||||
|
"""
|
||||||
|
STC31: _TelemetrySensorType.ValueType # 48
|
||||||
|
"""
|
||||||
|
Sensirion STC31 CO2 sensor
|
||||||
|
"""
|
||||||
|
SCD30: _TelemetrySensorType.ValueType # 49
|
||||||
|
"""
|
||||||
|
SCD30 CO2, humidity, temperature sensor
|
||||||
|
"""
|
||||||
|
SHTXX: _TelemetrySensorType.ValueType # 50
|
||||||
|
"""
|
||||||
|
SHT family of sensors for temperature and humidity
|
||||||
|
"""
|
||||||
|
|
||||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -243,7 +263,7 @@ High accuracy temperature and pressure
|
|||||||
"""
|
"""
|
||||||
SHTC3: TelemetrySensorType.ValueType # 7
|
SHTC3: TelemetrySensorType.ValueType # 7
|
||||||
"""
|
"""
|
||||||
High accuracy temperature and humidity
|
TODO - REMOVE High accuracy temperature and humidity
|
||||||
"""
|
"""
|
||||||
LPS22: TelemetrySensorType.ValueType # 8
|
LPS22: TelemetrySensorType.ValueType # 8
|
||||||
"""
|
"""
|
||||||
@@ -263,7 +283,7 @@ QMC5883L: TelemetrySensorType.ValueType # 11
|
|||||||
"""
|
"""
|
||||||
SHT31: TelemetrySensorType.ValueType # 12
|
SHT31: TelemetrySensorType.ValueType # 12
|
||||||
"""
|
"""
|
||||||
High accuracy temperature and humidity
|
TODO - REMOVE High accuracy temperature and humidity
|
||||||
"""
|
"""
|
||||||
PMSA003I: TelemetrySensorType.ValueType # 13
|
PMSA003I: TelemetrySensorType.ValueType # 13
|
||||||
"""
|
"""
|
||||||
@@ -283,7 +303,7 @@ RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
|
|||||||
"""
|
"""
|
||||||
SHT4X: TelemetrySensorType.ValueType # 17
|
SHT4X: TelemetrySensorType.ValueType # 17
|
||||||
"""
|
"""
|
||||||
Sensirion High accuracy temperature and humidity
|
TODO - REMOVE Sensirion High accuracy temperature and humidity
|
||||||
"""
|
"""
|
||||||
VEML7700: TelemetrySensorType.ValueType # 18
|
VEML7700: TelemetrySensorType.ValueType # 18
|
||||||
"""
|
"""
|
||||||
@@ -397,6 +417,26 @@ BH1750: TelemetrySensorType.ValueType # 45
|
|||||||
"""
|
"""
|
||||||
BH1750 light sensor
|
BH1750 light sensor
|
||||||
"""
|
"""
|
||||||
|
HDC1080: TelemetrySensorType.ValueType # 46
|
||||||
|
"""
|
||||||
|
HDC1080 Temperature and Humidity Sensor
|
||||||
|
"""
|
||||||
|
SHT21: TelemetrySensorType.ValueType # 47
|
||||||
|
"""
|
||||||
|
TODO - REMOVE STH21 Temperature and R. Humidity sensor
|
||||||
|
"""
|
||||||
|
STC31: TelemetrySensorType.ValueType # 48
|
||||||
|
"""
|
||||||
|
Sensirion STC31 CO2 sensor
|
||||||
|
"""
|
||||||
|
SCD30: TelemetrySensorType.ValueType # 49
|
||||||
|
"""
|
||||||
|
SCD30 CO2, humidity, temperature sensor
|
||||||
|
"""
|
||||||
|
SHTXX: TelemetrySensorType.ValueType # 50
|
||||||
|
"""
|
||||||
|
SHT family of sensors for temperature and humidity
|
||||||
|
"""
|
||||||
global___TelemetrySensorType = TelemetrySensorType
|
global___TelemetrySensorType = TelemetrySensorType
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
@@ -1035,6 +1075,7 @@ class LocalStats(google.protobuf.message.Message):
|
|||||||
HEAP_TOTAL_BYTES_FIELD_NUMBER: builtins.int
|
HEAP_TOTAL_BYTES_FIELD_NUMBER: builtins.int
|
||||||
HEAP_FREE_BYTES_FIELD_NUMBER: builtins.int
|
HEAP_FREE_BYTES_FIELD_NUMBER: builtins.int
|
||||||
NUM_TX_DROPPED_FIELD_NUMBER: builtins.int
|
NUM_TX_DROPPED_FIELD_NUMBER: builtins.int
|
||||||
|
NOISE_FLOOR_FIELD_NUMBER: builtins.int
|
||||||
uptime_seconds: builtins.int
|
uptime_seconds: builtins.int
|
||||||
"""
|
"""
|
||||||
How long the device has been running since the last reboot (in seconds)
|
How long the device has been running since the last reboot (in seconds)
|
||||||
@@ -1093,6 +1134,10 @@ class LocalStats(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Number of packets that were dropped because the transmit queue was full.
|
Number of packets that were dropped because the transmit queue was full.
|
||||||
"""
|
"""
|
||||||
|
noise_floor: builtins.int
|
||||||
|
"""
|
||||||
|
Noise floor value measured in dBm
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1110,11 +1155,70 @@ class LocalStats(google.protobuf.message.Message):
|
|||||||
heap_total_bytes: builtins.int = ...,
|
heap_total_bytes: builtins.int = ...,
|
||||||
heap_free_bytes: builtins.int = ...,
|
heap_free_bytes: builtins.int = ...,
|
||||||
num_tx_dropped: builtins.int = ...,
|
num_tx_dropped: builtins.int = ...,
|
||||||
|
noise_floor: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "heap_free_bytes", b"heap_free_bytes", "heap_total_bytes", b"heap_total_bytes", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_dropped", b"num_tx_dropped", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "heap_free_bytes", b"heap_free_bytes", "heap_total_bytes", b"heap_total_bytes", "noise_floor", b"noise_floor", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_dropped", b"num_tx_dropped", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
|
||||||
|
|
||||||
global___LocalStats = LocalStats
|
global___LocalStats = LocalStats
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class TrafficManagementStats(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
Traffic management statistics for mesh network optimization
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
PACKETS_INSPECTED_FIELD_NUMBER: builtins.int
|
||||||
|
POSITION_DEDUP_DROPS_FIELD_NUMBER: builtins.int
|
||||||
|
NODEINFO_CACHE_HITS_FIELD_NUMBER: builtins.int
|
||||||
|
RATE_LIMIT_DROPS_FIELD_NUMBER: builtins.int
|
||||||
|
UNKNOWN_PACKET_DROPS_FIELD_NUMBER: builtins.int
|
||||||
|
HOP_EXHAUSTED_PACKETS_FIELD_NUMBER: builtins.int
|
||||||
|
ROUTER_HOPS_PRESERVED_FIELD_NUMBER: builtins.int
|
||||||
|
packets_inspected: builtins.int
|
||||||
|
"""
|
||||||
|
Total number of packets inspected by traffic management
|
||||||
|
"""
|
||||||
|
position_dedup_drops: builtins.int
|
||||||
|
"""
|
||||||
|
Number of position packets dropped due to deduplication
|
||||||
|
"""
|
||||||
|
nodeinfo_cache_hits: builtins.int
|
||||||
|
"""
|
||||||
|
Number of NodeInfo requests answered from cache
|
||||||
|
"""
|
||||||
|
rate_limit_drops: builtins.int
|
||||||
|
"""
|
||||||
|
Number of packets dropped due to rate limiting
|
||||||
|
"""
|
||||||
|
unknown_packet_drops: builtins.int
|
||||||
|
"""
|
||||||
|
Number of unknown/undecryptable packets dropped
|
||||||
|
"""
|
||||||
|
hop_exhausted_packets: builtins.int
|
||||||
|
"""
|
||||||
|
Number of packets with hop_limit exhausted for local-only broadcast
|
||||||
|
"""
|
||||||
|
router_hops_preserved: builtins.int
|
||||||
|
"""
|
||||||
|
Number of times router hop preservation was applied
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
packets_inspected: builtins.int = ...,
|
||||||
|
position_dedup_drops: builtins.int = ...,
|
||||||
|
nodeinfo_cache_hits: builtins.int = ...,
|
||||||
|
rate_limit_drops: builtins.int = ...,
|
||||||
|
unknown_packet_drops: builtins.int = ...,
|
||||||
|
hop_exhausted_packets: builtins.int = ...,
|
||||||
|
router_hops_preserved: builtins.int = ...,
|
||||||
|
) -> None: ...
|
||||||
|
def ClearField(self, field_name: typing.Literal["hop_exhausted_packets", b"hop_exhausted_packets", "nodeinfo_cache_hits", b"nodeinfo_cache_hits", "packets_inspected", b"packets_inspected", "position_dedup_drops", b"position_dedup_drops", "rate_limit_drops", b"rate_limit_drops", "router_hops_preserved", b"router_hops_preserved", "unknown_packet_drops", b"unknown_packet_drops"]) -> None: ...
|
||||||
|
|
||||||
|
global___TrafficManagementStats = TrafficManagementStats
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class HealthMetrics(google.protobuf.message.Message):
|
class HealthMetrics(google.protobuf.message.Message):
|
||||||
"""
|
"""
|
||||||
@@ -1250,6 +1354,7 @@ class Telemetry(google.protobuf.message.Message):
|
|||||||
LOCAL_STATS_FIELD_NUMBER: builtins.int
|
LOCAL_STATS_FIELD_NUMBER: builtins.int
|
||||||
HEALTH_METRICS_FIELD_NUMBER: builtins.int
|
HEALTH_METRICS_FIELD_NUMBER: builtins.int
|
||||||
HOST_METRICS_FIELD_NUMBER: builtins.int
|
HOST_METRICS_FIELD_NUMBER: builtins.int
|
||||||
|
TRAFFIC_MANAGEMENT_STATS_FIELD_NUMBER: builtins.int
|
||||||
time: builtins.int
|
time: builtins.int
|
||||||
"""
|
"""
|
||||||
Seconds since 1970 - or 0 for unknown/unset
|
Seconds since 1970 - or 0 for unknown/unset
|
||||||
@@ -1296,6 +1401,12 @@ class Telemetry(google.protobuf.message.Message):
|
|||||||
Linux host metrics
|
Linux host metrics
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def traffic_management_stats(self) -> global___TrafficManagementStats:
|
||||||
|
"""
|
||||||
|
Traffic management statistics
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1307,10 +1418,11 @@ class Telemetry(google.protobuf.message.Message):
|
|||||||
local_stats: global___LocalStats | None = ...,
|
local_stats: global___LocalStats | None = ...,
|
||||||
health_metrics: global___HealthMetrics | None = ...,
|
health_metrics: global___HealthMetrics | None = ...,
|
||||||
host_metrics: global___HostMetrics | None = ...,
|
host_metrics: global___HostMetrics | None = ...,
|
||||||
|
traffic_management_stats: global___TrafficManagementStats | None = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def HasField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "host_metrics", b"host_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "variant", b"variant"]) -> builtins.bool: ...
|
def HasField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "host_metrics", b"host_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "traffic_management_stats", b"traffic_management_stats", "variant", b"variant"]) -> builtins.bool: ...
|
||||||
def ClearField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "host_metrics", b"host_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "time", b"time", "variant", b"variant"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "host_metrics", b"host_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "time", b"time", "traffic_management_stats", b"traffic_management_stats", "variant", b"variant"]) -> None: ...
|
||||||
def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics", "local_stats", "health_metrics", "host_metrics"] | None: ...
|
def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics", "local_stats", "health_metrics", "host_metrics", "traffic_management_stats"] | None: ...
|
||||||
|
|
||||||
global___Telemetry = Telemetry
|
global___Telemetry = Telemetry
|
||||||
|
|
||||||
@@ -1341,3 +1453,62 @@ class Nau7802Config(google.protobuf.message.Message):
|
|||||||
def ClearField(self, field_name: typing.Literal["calibrationFactor", b"calibrationFactor", "zeroOffset", b"zeroOffset"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["calibrationFactor", b"calibrationFactor", "zeroOffset", b"zeroOffset"]) -> None: ...
|
||||||
|
|
||||||
global___Nau7802Config = Nau7802Config
|
global___Nau7802Config = Nau7802Config
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
class SEN5XState(google.protobuf.message.Message):
|
||||||
|
"""
|
||||||
|
SEN5X 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 SEN5X
|
||||||
|
"""
|
||||||
|
last_cleaning_valid: builtins.bool
|
||||||
|
"""
|
||||||
|
Last cleaning time for SEN5X - 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 SEN55
|
||||||
|
"""
|
||||||
|
voc_state_valid: builtins.bool
|
||||||
|
"""
|
||||||
|
Last VOC state validity flag for SEN55
|
||||||
|
"""
|
||||||
|
voc_state_array: builtins.int
|
||||||
|
"""
|
||||||
|
VOC state array (8x uint8t) for SEN55
|
||||||
|
"""
|
||||||
|
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___SEN5XState = SEN5XState
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
from unittest.mock import mock_open, MagicMock, patch
|
from unittest.mock import mock_open, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -2900,3 +2901,68 @@ def test_main_set_ham_empty_string(capsys):
|
|||||||
out, _ = capsys.readouterr()
|
out, _ = capsys.readouterr()
|
||||||
assert "ERROR: Ham radio callsign cannot be empty or contain only whitespace characters" in out
|
assert "ERROR: Ham radio callsign cannot be empty or contain only whitespace characters" in out
|
||||||
assert excinfo.value.code == 1
|
assert excinfo.value.code == 1
|
||||||
|
|
||||||
|
|
||||||
|
# OTA-related tests
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
def test_main_ota_update_file_not_found(capsys):
|
||||||
|
"""Test --ota-update with non-existent file"""
|
||||||
|
sys.argv = [
|
||||||
|
"",
|
||||||
|
"--ota-update",
|
||||||
|
"/nonexistent/firmware.bin",
|
||||||
|
"--host",
|
||||||
|
"192.168.1.100",
|
||||||
|
]
|
||||||
|
mt_config.args = sys.argv
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit) as pytest_wrapped_e:
|
||||||
|
main()
|
||||||
|
|
||||||
|
assert pytest_wrapped_e.type == SystemExit
|
||||||
|
assert pytest_wrapped_e.value.code == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
@patch("meshtastic.ota.ESP32WiFiOTA")
|
||||||
|
@patch("meshtastic.__main__.meshtastic.util.our_exit")
|
||||||
|
def test_main_ota_update_retries(mock_our_exit, mock_ota_class, capsys):
|
||||||
|
"""Test --ota-update retries on failure"""
|
||||||
|
# Create a temporary firmware file
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"fake firmware data")
|
||||||
|
firmware_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.argv = ["", "--ota-update", firmware_file, "--host", "192.168.1.100"]
|
||||||
|
mt_config.args = sys.argv
|
||||||
|
|
||||||
|
# Mock the OTA class to fail all 5 retries
|
||||||
|
mock_ota = MagicMock()
|
||||||
|
mock_ota_class.return_value = mock_ota
|
||||||
|
mock_ota.hash_bytes.return_value = b"\x00" * 32
|
||||||
|
mock_ota.hash_hex.return_value = "a" * 64
|
||||||
|
mock_ota.update.side_effect = Exception("Connection failed")
|
||||||
|
|
||||||
|
# Mock isinstance to return True
|
||||||
|
with patch("meshtastic.__main__.isinstance", return_value=True):
|
||||||
|
with patch("meshtastic.tcp_interface.TCPInterface") as mock_tcp:
|
||||||
|
mock_iface = MagicMock()
|
||||||
|
mock_iface.hostname = "192.168.1.100"
|
||||||
|
mock_iface.localNode = MagicMock(autospec=Node)
|
||||||
|
mock_tcp.return_value = mock_iface
|
||||||
|
|
||||||
|
with patch("time.sleep"):
|
||||||
|
main()
|
||||||
|
|
||||||
|
# Should have exhausted all retries and called our_exit
|
||||||
|
# Note: our_exit might be called twice - once for TCP check, once for failure
|
||||||
|
assert mock_our_exit.call_count >= 1
|
||||||
|
# Check the last call was for OTA failure
|
||||||
|
last_call_args = mock_our_exit.call_args[0][0]
|
||||||
|
assert "OTA update failed" in last_call_args
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(firmware_file)
|
||||||
|
|||||||
22
meshtastic/tests/test_mesh_interface_traffic_management.py
Normal file
22
meshtastic/tests/test_mesh_interface_traffic_management.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""Meshtastic unit tests for traffic management handling in mesh_interface.py."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..mesh_interface import MeshInterface
|
||||||
|
from ..protobuf import mesh_pb2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
iface.close()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Meshtastic unit tests for node.py"""
|
"""Meshtastic unit tests for node.py"""
|
||||||
|
# pylint: disable=C0302
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
@@ -261,6 +262,36 @@ def test_shutdown(caplog):
|
|||||||
assert re.search(r"Telling node to shutdown", caplog.text, re.MULTILINE)
|
assert re.search(r"Telling node to shutdown", caplog.text, re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_factoryReset_config_uses_int_field():
|
||||||
|
"""Test factoryReset(config) sets int32 protobuf field with an int value."""
|
||||||
|
iface = MagicMock(autospec=MeshInterface)
|
||||||
|
anode = Node(iface, 1234567890, noProto=True)
|
||||||
|
|
||||||
|
amesg = admin_pb2.AdminMessage()
|
||||||
|
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
|
||||||
|
with patch.object(anode, "_sendAdmin") as mock_send_admin:
|
||||||
|
anode.factoryReset(full=False)
|
||||||
|
|
||||||
|
assert amesg.factory_reset_config == 1
|
||||||
|
mock_send_admin.assert_called_once_with(amesg, onResponse=anode.onAckNak)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_factoryReset_full_sets_device_field():
|
||||||
|
"""Test factoryReset(full=True) sets the full-device reset protobuf field."""
|
||||||
|
iface = MagicMock(autospec=MeshInterface)
|
||||||
|
anode = Node(iface, 1234567890, noProto=True)
|
||||||
|
|
||||||
|
amesg = admin_pb2.AdminMessage()
|
||||||
|
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
|
||||||
|
with patch.object(anode, "_sendAdmin") as mock_send_admin:
|
||||||
|
anode.factoryReset(full=True)
|
||||||
|
|
||||||
|
assert amesg.factory_reset_device is True
|
||||||
|
mock_send_admin.assert_called_once_with(amesg, onResponse=anode.onAckNak)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
def test_setURL_empty_url(capsys):
|
def test_setURL_empty_url(capsys):
|
||||||
"""Test reboot"""
|
"""Test reboot"""
|
||||||
@@ -794,6 +825,30 @@ def test_writeConfig_with_no_radioConfig(capsys):
|
|||||||
assert err == ""
|
assert err == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
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
|
||||||
|
|
||||||
|
sent_admin = []
|
||||||
|
|
||||||
|
def capture_send(p, *args, **kwargs): # pylint: disable=W0613
|
||||||
|
sent_admin.append(p)
|
||||||
|
|
||||||
|
with patch.object(anode, "_sendAdmin", side_effect=capture_send):
|
||||||
|
anode.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
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# @pytest.mark.unit
|
# @pytest.mark.unit
|
||||||
# def test_writeConfig(caplog):
|
# def test_writeConfig(caplog):
|
||||||
@@ -1550,6 +1605,41 @@ def test_setOwner_valid_names(caplog):
|
|||||||
assert re.search(r'p.set_owner.short_name:VN:', caplog.text, re.MULTILINE)
|
assert re.search(r'p.set_owner.short_name:VN:', caplog.text, re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_start_ota_local_node():
|
||||||
|
"""Test startOTA on local node"""
|
||||||
|
iface = MagicMock(autospec=MeshInterface)
|
||||||
|
anode = Node(iface, 1234567890, noProto=True)
|
||||||
|
# Set up as local node
|
||||||
|
iface.localNode = anode
|
||||||
|
|
||||||
|
amesg = admin_pb2.AdminMessage()
|
||||||
|
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
|
||||||
|
with patch.object(anode, "_sendAdmin") as mock_send_admin:
|
||||||
|
test_hash = b"\x01\x02\x03" * 8 # 24 bytes hash
|
||||||
|
anode.startOTA(ota_mode=admin_pb2.OTAMode.OTA_WIFI, ota_file_hash=test_hash)
|
||||||
|
|
||||||
|
# Verify the OTA request was set correctly
|
||||||
|
assert amesg.ota_request.reboot_ota_mode == admin_pb2.OTAMode.OTA_WIFI
|
||||||
|
assert amesg.ota_request.ota_hash == test_hash
|
||||||
|
mock_send_admin.assert_called_once_with(amesg)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_start_ota_remote_node_raises_error():
|
||||||
|
"""Test startOTA on remote node raises ValueError"""
|
||||||
|
iface = MagicMock(autospec=MeshInterface)
|
||||||
|
local_node = Node(iface, 1234567890, noProto=True)
|
||||||
|
remote_node = Node(iface, 9876543210, noProto=True)
|
||||||
|
iface.localNode = local_node
|
||||||
|
|
||||||
|
test_hash = b"\x01\x02\x03" * 8
|
||||||
|
with pytest.raises(ValueError, match="startOTA only possible in local node"):
|
||||||
|
remote_node.startOTA(
|
||||||
|
ota_mode=admin_pb2.OTAMode.OTA_WIFI, ota_file_hash=test_hash
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# @pytest.mark.unitslow
|
# @pytest.mark.unitslow
|
||||||
# def test_waitForConfig():
|
# def test_waitForConfig():
|
||||||
|
|||||||
455
meshtastic/tests/test_ota.py
Normal file
455
meshtastic/tests/test_ota.py
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
"""Meshtastic unit tests for ota.py"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import tempfile
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from meshtastic.ota import (
|
||||||
|
_file_sha256,
|
||||||
|
ESP32WiFiOTA,
|
||||||
|
OTAError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_file_sha256():
|
||||||
|
"""Test _file_sha256 calculates correct hash"""
|
||||||
|
# Create a temporary file with known content
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
test_data = b"Hello, World!"
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = _file_sha256(temp_file)
|
||||||
|
expected_hash = hashlib.sha256(test_data).hexdigest()
|
||||||
|
assert result.hexdigest() == expected_hash
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_file_sha256_large_file():
|
||||||
|
"""Test _file_sha256 handles files larger than chunk size"""
|
||||||
|
# Create a temporary file with more than 4096 bytes
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
test_data = b"A" * 8192 # More than 4096 bytes
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = _file_sha256(temp_file)
|
||||||
|
expected_hash = hashlib.sha256(test_data).hexdigest()
|
||||||
|
assert result.hexdigest() == expected_hash
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_init_file_not_found():
|
||||||
|
"""Test ESP32WiFiOTA raises FileNotFoundError for non-existent file"""
|
||||||
|
with pytest.raises(FileNotFoundError, match="does not exist"):
|
||||||
|
ESP32WiFiOTA("/nonexistent/firmware.bin", "192.168.1.1")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_init_success():
|
||||||
|
"""Test ESP32WiFiOTA initializes correctly with valid file"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"fake firmware data")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1", 3232)
|
||||||
|
assert ota._filename == temp_file
|
||||||
|
assert ota._hostname == "192.168.1.1"
|
||||||
|
assert ota._port == 3232
|
||||||
|
assert ota._socket is None
|
||||||
|
# Verify hash is calculated
|
||||||
|
assert ota._file_hash is not None
|
||||||
|
assert len(ota.hash_hex()) == 64 # SHA256 hex is 64 chars
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_init_default_port():
|
||||||
|
"""Test ESP32WiFiOTA uses default port 3232"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"fake firmware data")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
assert ota._port == 3232
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_hash_bytes():
|
||||||
|
"""Test hash_bytes returns correct bytes"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
test_data = b"firmware data"
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
hash_bytes = ota.hash_bytes()
|
||||||
|
expected_bytes = hashlib.sha256(test_data).digest()
|
||||||
|
assert hash_bytes == expected_bytes
|
||||||
|
assert len(hash_bytes) == 32 # SHA256 is 32 bytes
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_hash_hex():
|
||||||
|
"""Test hash_hex returns correct hex string"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
test_data = b"firmware data"
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
hash_hex = ota.hash_hex()
|
||||||
|
expected_hex = hashlib.sha256(test_data).hexdigest()
|
||||||
|
assert hash_hex == expected_hex
|
||||||
|
assert len(hash_hex) == 64 # SHA256 hex is 64 chars
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_read_line_not_connected():
|
||||||
|
"""Test _read_line raises ConnectionError when not connected"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
with pytest.raises(ConnectionError, match="Socket not connected"):
|
||||||
|
ota._read_line()
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_read_line_connection_closed():
|
||||||
|
"""Test _read_line raises ConnectionError when connection closed"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
# Simulate connection closed
|
||||||
|
mock_socket.recv.return_value = b""
|
||||||
|
ota._socket = mock_socket
|
||||||
|
|
||||||
|
with pytest.raises(ConnectionError, match="Connection closed"):
|
||||||
|
ota._read_line()
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_esp32_wifi_ota_read_line_success():
|
||||||
|
"""Test _read_line successfully reads a line"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
# Simulate receiving "OK\n"
|
||||||
|
mock_socket.recv.side_effect = [b"O", b"K", b"\n"]
|
||||||
|
ota._socket = mock_socket
|
||||||
|
|
||||||
|
result = ota._read_line()
|
||||||
|
assert result == "OK"
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_success(mock_socket_class):
|
||||||
|
"""Test update() with successful OTA"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
test_data = b"A" * 1024 # 1KB of data
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Setup mock socket
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
# Mock _read_line to return appropriate responses
|
||||||
|
# First call: ERASING, Second call: OK (ready), Third call: OK (complete)
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.side_effect = [
|
||||||
|
"ERASING", # Device is erasing flash
|
||||||
|
"OK", # Device ready for firmware
|
||||||
|
"OK", # Device finished successfully
|
||||||
|
]
|
||||||
|
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
# Verify socket was created and connected
|
||||||
|
mock_socket_class.assert_called_once_with(
|
||||||
|
socket.AF_INET, socket.SOCK_STREAM
|
||||||
|
)
|
||||||
|
mock_socket.settimeout.assert_called_once_with(15)
|
||||||
|
mock_socket.connect.assert_called_once_with(("192.168.1.1", 3232))
|
||||||
|
|
||||||
|
# Verify start command was sent
|
||||||
|
start_cmd = f"OTA {len(test_data)} {ota.hash_hex()}\n".encode("utf-8")
|
||||||
|
mock_socket.sendall.assert_any_call(start_cmd)
|
||||||
|
|
||||||
|
# Verify firmware was sent (at least one chunk)
|
||||||
|
assert mock_socket.sendall.call_count >= 2
|
||||||
|
|
||||||
|
# Verify socket was closed
|
||||||
|
mock_socket.close.assert_called_once()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_with_progress_callback(mock_socket_class):
|
||||||
|
"""Test update() with progress callback"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
test_data = b"A" * 1024 # 1KB of data
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Setup mock socket
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
# Track progress callback calls
|
||||||
|
progress_calls = []
|
||||||
|
|
||||||
|
def progress_callback(sent, total):
|
||||||
|
progress_calls.append((sent, total))
|
||||||
|
|
||||||
|
# Mock _read_line
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.side_effect = [
|
||||||
|
"OK", # Device ready
|
||||||
|
"OK", # Device finished
|
||||||
|
]
|
||||||
|
|
||||||
|
ota.update(progress_callback=progress_callback)
|
||||||
|
|
||||||
|
# Verify progress callback was called
|
||||||
|
assert len(progress_calls) > 0
|
||||||
|
# First call should show some progress
|
||||||
|
assert progress_calls[0][0] > 0
|
||||||
|
# Total should be the firmware size
|
||||||
|
assert progress_calls[0][1] == len(test_data)
|
||||||
|
# Last call should show all bytes sent
|
||||||
|
assert progress_calls[-1][0] == len(test_data)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_device_error_on_start(mock_socket_class):
|
||||||
|
"""Test update() raises OTAError when device reports error during start"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.return_value = "ERR BAD_HASH"
|
||||||
|
|
||||||
|
with pytest.raises(OTAError, match="Device reported error"):
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_device_error_on_finish(mock_socket_class):
|
||||||
|
"""Test update() raises OTAError when device reports error after firmware sent"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.side_effect = [
|
||||||
|
"OK", # Device ready
|
||||||
|
"ERR FLASH_ERR", # Error after firmware sent
|
||||||
|
]
|
||||||
|
|
||||||
|
with pytest.raises(OTAError, match="OTA update failed"):
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_socket_cleanup_on_error(mock_socket_class):
|
||||||
|
"""Test that socket is properly cleaned up on error"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
# Simulate connection error
|
||||||
|
mock_socket.connect.side_effect = ConnectionRefusedError("Connection refused")
|
||||||
|
|
||||||
|
with pytest.raises(ConnectionRefusedError):
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
# Verify socket was closed even on error
|
||||||
|
mock_socket.close.assert_called_once()
|
||||||
|
assert ota._socket is None
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_large_firmware(mock_socket_class):
|
||||||
|
"""Test update() correctly chunks large firmware files"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
# Create a file larger than chunk_size (1024)
|
||||||
|
test_data = b"B" * 3000
|
||||||
|
f.write(test_data)
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.side_effect = [
|
||||||
|
"OK", # Device ready
|
||||||
|
"OK", # Device finished
|
||||||
|
]
|
||||||
|
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
# Verify that all data was sent in chunks
|
||||||
|
# 3000 bytes should be sent in ~3 chunks of 1024 bytes
|
||||||
|
sendall_calls = [
|
||||||
|
call
|
||||||
|
for call in mock_socket.sendall.call_args_list
|
||||||
|
if call[0][0]
|
||||||
|
!= f"OTA {len(test_data)} {ota.hash_hex()}\n".encode("utf-8")
|
||||||
|
]
|
||||||
|
# Calculate total data sent (excluding the start command)
|
||||||
|
total_sent = sum(len(call[0][0]) for call in sendall_calls)
|
||||||
|
assert total_sent == len(test_data)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_unexpected_response_warning(mock_socket_class, caplog):
|
||||||
|
"""Test update() logs warning on unexpected response during startup"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.side_effect = [
|
||||||
|
"UNKNOWN", # Unexpected response
|
||||||
|
"OK", # Then proceed
|
||||||
|
"OK", # Device finished
|
||||||
|
]
|
||||||
|
|
||||||
|
with caplog.at_level(logging.WARNING):
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
# Check that warning was logged for unexpected response
|
||||||
|
assert "Unexpected response" in caplog.text
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@patch("meshtastic.ota.socket.socket")
|
||||||
|
def test_esp32_wifi_ota_update_unexpected_final_response(mock_socket_class, caplog):
|
||||||
|
"""Test update() logs warning on unexpected final response after firmware upload"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f:
|
||||||
|
f.write(b"firmware")
|
||||||
|
temp_file = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
mock_socket = MagicMock()
|
||||||
|
mock_socket_class.return_value = mock_socket
|
||||||
|
|
||||||
|
ota = ESP32WiFiOTA(temp_file, "192.168.1.1")
|
||||||
|
|
||||||
|
with patch.object(ota, "_read_line") as mock_read_line:
|
||||||
|
mock_read_line.side_effect = [
|
||||||
|
"OK", # Device ready for firmware
|
||||||
|
"UNKNOWN", # Unexpected final response (not OK, not ERR, not ACK)
|
||||||
|
"OK", # Then succeed
|
||||||
|
]
|
||||||
|
|
||||||
|
with caplog.at_level(logging.WARNING):
|
||||||
|
ota.update()
|
||||||
|
|
||||||
|
# Check that warning was logged for unexpected final response
|
||||||
|
assert "Unexpected final response" in caplog.text
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.unlink(temp_file)
|
||||||
221
meshtastic/tests/test_showNodes_favorite.py
Normal file
221
meshtastic/tests/test_showNodes_favorite.py
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
"""Meshtastic unit tests for showNodes favorite column feature"""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..mesh_interface import MeshInterface
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def _iface_with_favorite_nodes():
|
||||||
|
"""Fixture to setup nodes with favorite flags."""
|
||||||
|
nodesById = {
|
||||||
|
"!9388f81c": {
|
||||||
|
"num": 2475227164,
|
||||||
|
"user": {
|
||||||
|
"id": "!9388f81c",
|
||||||
|
"longName": "Favorite Node",
|
||||||
|
"shortName": "FAV1",
|
||||||
|
"macaddr": "RBeTiPgc",
|
||||||
|
"hwModel": "TBEAM",
|
||||||
|
},
|
||||||
|
"position": {},
|
||||||
|
"lastHeard": 1640204888,
|
||||||
|
"isFavorite": True,
|
||||||
|
},
|
||||||
|
"!12345678": {
|
||||||
|
"num": 305419896,
|
||||||
|
"user": {
|
||||||
|
"id": "!12345678",
|
||||||
|
"longName": "Regular Node",
|
||||||
|
"shortName": "REG1",
|
||||||
|
"macaddr": "ABCDEFGH",
|
||||||
|
"hwModel": "TLORA_V2",
|
||||||
|
},
|
||||||
|
"position": {},
|
||||||
|
"lastHeard": 1640204999,
|
||||||
|
"isFavorite": False,
|
||||||
|
},
|
||||||
|
"!abcdef00": {
|
||||||
|
"num": 2882400000,
|
||||||
|
"user": {
|
||||||
|
"id": "!abcdef00",
|
||||||
|
"longName": "Legacy Node",
|
||||||
|
"shortName": "LEG1",
|
||||||
|
"macaddr": "XYZABC00",
|
||||||
|
"hwModel": "HELTEC_V3",
|
||||||
|
},
|
||||||
|
"position": {},
|
||||||
|
"lastHeard": 1640205000,
|
||||||
|
# Note: No isFavorite field - testing backward compatibility
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesByNum = {
|
||||||
|
2475227164: {
|
||||||
|
"num": 2475227164,
|
||||||
|
"user": {
|
||||||
|
"id": "!9388f81c",
|
||||||
|
"longName": "Favorite Node",
|
||||||
|
"shortName": "FAV1",
|
||||||
|
"macaddr": "RBeTiPgc",
|
||||||
|
"hwModel": "TBEAM",
|
||||||
|
},
|
||||||
|
"position": {"time": 1640206266},
|
||||||
|
"lastHeard": 1640206266,
|
||||||
|
"isFavorite": True,
|
||||||
|
},
|
||||||
|
305419896: {
|
||||||
|
"num": 305419896,
|
||||||
|
"user": {
|
||||||
|
"id": "!12345678",
|
||||||
|
"longName": "Regular Node",
|
||||||
|
"shortName": "REG1",
|
||||||
|
"macaddr": "ABCDEFGH",
|
||||||
|
"hwModel": "TLORA_V2",
|
||||||
|
},
|
||||||
|
"position": {"time": 1640206200},
|
||||||
|
"lastHeard": 1640206200,
|
||||||
|
"isFavorite": False,
|
||||||
|
},
|
||||||
|
2882400000: {
|
||||||
|
"num": 2882400000,
|
||||||
|
"user": {
|
||||||
|
"id": "!abcdef00",
|
||||||
|
"longName": "Legacy Node",
|
||||||
|
"shortName": "LEG1",
|
||||||
|
"macaddr": "XYZABC00",
|
||||||
|
"hwModel": "HELTEC_V3",
|
||||||
|
},
|
||||||
|
"position": {"time": 1640206100},
|
||||||
|
"lastHeard": 1640206100,
|
||||||
|
# Note: No isFavorite field - testing backward compatibility
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
iface = MeshInterface(noProto=True)
|
||||||
|
iface.nodes = nodesById
|
||||||
|
iface.nodesByNum = nodesByNum
|
||||||
|
myInfo = MagicMock()
|
||||||
|
iface.myInfo = myInfo
|
||||||
|
iface.myInfo.my_node_num = 2475227164
|
||||||
|
return iface
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_showNodes_favorite_column_header(capsys, _iface_with_favorite_nodes):
|
||||||
|
"""Test that 'Fav' column header appears in showNodes output"""
|
||||||
|
iface = _iface_with_favorite_nodes
|
||||||
|
iface.showNodes()
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert "Fav" in out
|
||||||
|
assert err == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_showNodes_favorite_asterisk_display(capsys, _iface_with_favorite_nodes):
|
||||||
|
"""Test that favorite nodes show asterisk and non-favorites show empty"""
|
||||||
|
iface = _iface_with_favorite_nodes
|
||||||
|
iface.showNodes()
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
|
||||||
|
# Check that the output contains the "Fav" column
|
||||||
|
assert "Fav" in out
|
||||||
|
|
||||||
|
# Find lines containing our nodes
|
||||||
|
lines = out.split('\n')
|
||||||
|
favorite_line = None
|
||||||
|
regular_line = None
|
||||||
|
legacy_line = None
|
||||||
|
for line in lines:
|
||||||
|
if "Favorite Node" in line or "FAV1" in line:
|
||||||
|
favorite_line = line
|
||||||
|
if "Regular Node" in line or "REG1" in line:
|
||||||
|
regular_line = line
|
||||||
|
if "Legacy Node" in line or "LEG1" in line:
|
||||||
|
legacy_line = line
|
||||||
|
|
||||||
|
# Verify all nodes are present in the output
|
||||||
|
assert favorite_line is not None, "Favorite node should be in output"
|
||||||
|
assert regular_line is not None, "Regular node should be in output"
|
||||||
|
assert legacy_line is not None, "Legacy node should be in output"
|
||||||
|
|
||||||
|
# Verify the favorite node has an asterisk in its row
|
||||||
|
assert "*" in favorite_line, "Favorite node should have an asterisk"
|
||||||
|
|
||||||
|
# Verify the regular (non-favorite) node does NOT have an asterisk
|
||||||
|
assert regular_line.count("*") == 0, "Non-favorite node should not have an asterisk"
|
||||||
|
|
||||||
|
# Verify the legacy node (without isFavorite field) does NOT have an asterisk
|
||||||
|
assert legacy_line.count("*") == 0, "Legacy node without isFavorite field should not have an asterisk"
|
||||||
|
|
||||||
|
assert err == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_showNodes_favorite_field_formatting():
|
||||||
|
"""Test the formatting logic for isFavorite field"""
|
||||||
|
# Test favorite node
|
||||||
|
raw_value = True
|
||||||
|
formatted_value = "*" if raw_value else ""
|
||||||
|
assert formatted_value == "*"
|
||||||
|
|
||||||
|
# Test non-favorite node
|
||||||
|
raw_value = False
|
||||||
|
formatted_value = "*" if raw_value else ""
|
||||||
|
assert formatted_value == ""
|
||||||
|
|
||||||
|
# Test None/missing value
|
||||||
|
raw_value = None
|
||||||
|
formatted_value = "*" if raw_value else ""
|
||||||
|
assert formatted_value == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_showNodes_with_custom_fields_including_favorite(capsys, _iface_with_favorite_nodes):
|
||||||
|
"""Test that isFavorite can be specified in custom showFields"""
|
||||||
|
iface = _iface_with_favorite_nodes
|
||||||
|
custom_fields = ["user.longName", "isFavorite"]
|
||||||
|
iface.showNodes(showFields=custom_fields)
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
|
||||||
|
# Should still show the Fav column when explicitly requested
|
||||||
|
assert "Fav" in out
|
||||||
|
assert err == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_showNodes_default_fields_includes_favorite(_iface_with_favorite_nodes):
|
||||||
|
"""Test that isFavorite is included in default fields"""
|
||||||
|
iface = _iface_with_favorite_nodes
|
||||||
|
|
||||||
|
# Call showNodes which uses default fields
|
||||||
|
result = iface.showNodes()
|
||||||
|
|
||||||
|
# The result should contain the formatted table as a string
|
||||||
|
assert "Fav" in result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_showNodes_backward_compatibility_missing_field(capsys, _iface_with_favorite_nodes):
|
||||||
|
"""Test that nodes without isFavorite field are handled gracefully"""
|
||||||
|
iface = _iface_with_favorite_nodes
|
||||||
|
iface.showNodes()
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
|
||||||
|
# Find the legacy node line
|
||||||
|
lines = out.split('\n')
|
||||||
|
legacy_line = None
|
||||||
|
for line in lines:
|
||||||
|
if "Legacy Node" in line or "LEG1" in line:
|
||||||
|
legacy_line = line
|
||||||
|
break
|
||||||
|
|
||||||
|
# Verify the legacy node appears in output
|
||||||
|
assert legacy_line is not None, "Legacy node without isFavorite field should appear in output"
|
||||||
|
|
||||||
|
# Verify it doesn't have an asterisk (should be treated as non-favorite)
|
||||||
|
assert legacy_line.count("*") == 0, "Legacy node should not have asterisk (treated as non-favorite)"
|
||||||
|
|
||||||
|
assert err == ""
|
||||||
2
poetry.lock
generated
2
poetry.lock
generated
@@ -5941,4 +5941,4 @@ tunnel = ["pytap2"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.9,<3.15"
|
python-versions = "^3.9,<3.15"
|
||||||
content-hash = "e87e2eaffca4ad13aa7e1b8622ec4b37b23a4efe1f4febe0ca87b92db5fe6d1e"
|
content-hash = "674308d6eb7c3730031cc3e73c98b2413c7f59002a9317bfad387bc34a17c64d"
|
||||||
|
|||||||
Submodule protobufs updated: c474fd3f49...cb1f89372a
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "meshtastic"
|
name = "meshtastic"
|
||||||
version = "2.7.6"
|
version = "2.7.8"
|
||||||
description = "Python API & client shell for talking to Meshtastic devices"
|
description = "Python API & client shell for talking to Meshtastic devices"
|
||||||
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
@@ -15,7 +15,7 @@ requests = "^2.31.0"
|
|||||||
pyyaml = "^6.0.1"
|
pyyaml = "^6.0.1"
|
||||||
pypubsub = "^4.0.3"
|
pypubsub = "^4.0.3"
|
||||||
bleak = ">=0.22.3"
|
bleak = ">=0.22.3"
|
||||||
packaging = "^24.0"
|
packaging = ">=24.0"
|
||||||
argcomplete = { version = "^3.5.2", optional = true }
|
argcomplete = { version = "^3.5.2", optional = true }
|
||||||
pyqrcode = { version = "^1.2.1", optional = true }
|
pyqrcode = { version = "^1.2.1", optional = true }
|
||||||
dotmap = { version = "^1.3.30", optional = true }
|
dotmap = { version = "^1.3.30", optional = true }
|
||||||
|
|||||||
Reference in New Issue
Block a user