From 27cc76d5ed689d3fe61ff7dfb8f010454704735a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 13:18:21 -0500 Subject: [PATCH 01/22] Very WIP dmshell --- bin/dmshell_client.py | 375 +++++++++++++++++ src/mesh/generated/meshtastic/mesh.pb.cpp | 5 + src/mesh/generated/meshtastic/mesh.pb.h | 75 ++++ src/mesh/generated/meshtastic/portnums.pb.h | 2 + src/modules/DMShell.cpp | 397 ++++++++++++++++++ src/modules/DMShell.h | 71 ++++ src/modules/Modules.cpp | 2 + .../ports/test_dmshell.cpp | 112 +++++ .../test_serializer.cpp | 10 + 9 files changed, 1049 insertions(+) create mode 100644 bin/dmshell_client.py create mode 100644 src/modules/DMShell.cpp create mode 100644 src/modules/DMShell.h create mode 100644 test/test_meshpacket_serializer/ports/test_dmshell.cpp diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py new file mode 100644 index 00000000000..12da2610495 --- /dev/null +++ b/bin/dmshell_client.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python3 + +import argparse +import os +import queue +import random +import shutil +import socket +import subprocess +import sys +import tempfile +import threading +import time +from dataclasses import dataclass, field +from pathlib import Path +from typing import Optional + + +START1 = 0x94 +START2 = 0xC3 +HEADER_LEN = 4 +DEFAULT_API_PORT = 4403 +DEFAULT_HOP_LIMIT = 3 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Tiny DMShell client for Meshtastic native TCP API", + epilog=( + "Examples:\n" + " bin/dmshell_client.py --to !170896f7\n" + " bin/dmshell_client.py --to 0x170896f7 --command 'uname -a' --command 'id'" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("--host", default="127.0.0.1", help="meshtasticd API host") + parser.add_argument("--port", type=int, default=DEFAULT_API_PORT, help="meshtasticd API port") + parser.add_argument("--to", required=True, help="destination node number, e.g. !170896f7 or 0x170896f7") + parser.add_argument("--channel", type=int, default=0, help="channel index to use") + parser.add_argument("--cols", type=int, default=120, help="initial terminal columns") + parser.add_argument("--rows", type=int, default=40, help="initial terminal rows") + parser.add_argument("--command", action="append", default=[], help="send a command line after opening") + parser.add_argument("--close-after", type=float, default=2.0, help="seconds to wait before closing in command mode") + parser.add_argument("--timeout", type=float, default=10.0, help="seconds to wait for API/session events") + parser.add_argument("--verbose", action="store_true", help="print extra protocol events") + return parser.parse_args() + + +def repo_root() -> Path: + return Path(__file__).resolve().parent.parent + + +def load_proto_modules() -> object: + try: + import google.protobuf # noqa: F401 + except ImportError as exc: + raise SystemExit("python package 'protobuf' is required to run this client") from exc + + protoc = shutil.which("protoc") + if not protoc: + raise SystemExit("'protoc' is required to generate temporary Python protobuf bindings") + + out_dir = Path(tempfile.mkdtemp(prefix="meshtastic_dmshell_proto_")) + proto_dir = repo_root() / "protobufs" + + # Compile all required protos for DMShell client (mesh and dependencies) + # Excludes nanopb.proto and other complex build artifacts + required_protos = [ + "mesh.proto", + "channel.proto", + "config.proto", + "device_ui.proto", + "module_config.proto", + "atak.proto", + "portnums.proto", + "telemetry.proto", + "xmodem.proto", + ] + proto_files = [proto_dir / "meshtastic" / name for name in required_protos] + for pf in proto_files: + if not pf.exists(): + raise SystemExit(f"could not find required proto file: {pf}") + + # Create __init__.py to make meshtastic a package + (out_dir / "meshtastic").mkdir(exist_ok=True) + (out_dir / "meshtastic" / "__init__.py").touch() + + # Build protoc command with just the meshtastic proto directory as include path + # protoc will use its built-in includes for standard google protobuf types + cmd = [protoc, f"-I{proto_dir}", f"--python_out={out_dir}", *[str(path) for path in proto_files]] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(f"protoc stderr: {result.stderr}", file=sys.stderr) + print(f"protoc stdout: {result.stdout}", file=sys.stderr) + print(f"protoc command: {' '.join(cmd)}", file=sys.stderr) + raise SystemExit(f"protoc failed with return code {result.returncode}") + + # Create _pb2_grpc module stub if not present (protoc 3.20+) + mesh_pb2_file = out_dir / "meshtastic" / "mesh_pb2.py" + if not mesh_pb2_file.exists(): + raise SystemExit(f"protoc did not generate mesh_pb2.py in {out_dir / 'meshtastic'}") + + sys.path.insert(0, str(out_dir)) + try: + from meshtastic import mesh_pb2, portnums_pb2 # type: ignore + except ImportError as exc: + print(f"Failed to import protobuf modules. Output dir contents:", file=sys.stderr) + for item in (out_dir / "meshtastic").iterdir(): + print(f" {item.name}", file=sys.stderr) + raise SystemExit(f"could not import meshtastic proto modules: {exc}") from exc + + # Return an object that has both modules accessible + class ProtoModules: + pass + + pb2 = ProtoModules() + pb2.mesh = mesh_pb2 + pb2.portnums = portnums_pb2 + return pb2 + + +def parse_node_num(raw: str) -> int: + value = raw.strip() + if value.startswith("!"): + value = value[1:] + if value.lower().startswith("0x"): + return int(value, 16) + if any(ch in "abcdefABCDEF" for ch in value): + return int(value, 16) + return int(value, 10) + + +def recv_exact(sock: socket.socket, length: int) -> bytes: + chunks = bytearray() + while len(chunks) < length: + piece = sock.recv(length - len(chunks)) + if not piece: + raise ConnectionError("connection closed by server") + chunks.extend(piece) + return bytes(chunks) + + +def recv_stream_frame(sock: socket.socket) -> bytes: + while True: + start = recv_exact(sock, 1)[0] + if start != START1: + continue + if recv_exact(sock, 1)[0] != START2: + continue + header = recv_exact(sock, 2) + length = (header[0] << 8) | header[1] + return recv_exact(sock, length) + + +def send_stream_frame(sock: socket.socket, payload: bytes) -> None: + if len(payload) > 0xFFFF: + raise ValueError("payload too large for stream API") + header = bytes((START1, START2, (len(payload) >> 8) & 0xFF, len(payload) & 0xFF)) + sock.sendall(header + payload) + + +@dataclass +class SessionState: + pb2: object # ProtoModules with mesh and portnums attributes + target: int + channel: int + verbose: bool + session_id: int = field(default_factory=lambda: random.randint(1, 0x7FFFFFFF)) + next_seq: int = 1 + active: bool = False + stopped: bool = False + opened_event: threading.Event = field(default_factory=threading.Event) + closed_event: threading.Event = field(default_factory=threading.Event) + event_queue: "queue.Queue[str]" = field(default_factory=queue.Queue) + + def alloc_seq(self) -> int: + value = self.next_seq + self.next_seq += 1 + return value + + +def send_toradio(sock: socket.socket, toradio) -> None: + send_stream_frame(sock, toradio.SerializeToString()) + + +def make_toradio_packet(pb2, state: SessionState, shell_msg) -> object: + packet = pb2.mesh.MeshPacket() + packet.id = random.randint(1, 0x7FFFFFFF) + packet.to = state.target + # The 'from' field is a reserved keyword in Python, so use setattr + setattr(packet, "from", 0) + packet.channel = state.channel + packet.hop_limit = DEFAULT_HOP_LIMIT + packet.want_ack = False + packet.decoded.portnum = pb2.portnums.DM_SHELL_APP + packet.decoded.payload = shell_msg.SerializeToString() + packet.decoded.want_response = False + packet.decoded.dest = state.target + packet.decoded.source = 0 + + toradio = pb2.mesh.ToRadio() + toradio.packet.CopyFrom(packet) + return toradio + + +def send_shell_frame(sock: socket.socket, state: SessionState, op: int, payload: bytes = b"", cols: int = 0, rows: int = 0) -> None: + shell = state.pb2.mesh.DMShell() + shell.op = op + shell.session_id = state.session_id + shell.seq = state.alloc_seq() + shell.cols = cols + shell.rows = rows + if payload: + shell.payload = payload + send_toradio(sock, make_toradio_packet(state.pb2, state, shell)) + + +def wait_for_config_complete(sock: socket.socket, pb2, timeout: float, verbose: bool) -> None: + nonce = random.randint(1, 0x7FFFFFFF) + toradio = pb2.mesh.ToRadio() + toradio.want_config_id = nonce + send_toradio(sock, toradio) + + deadline = time.time() + timeout + while time.time() < deadline: + fromradio = pb2.mesh.FromRadio() + fromradio.ParseFromString(recv_stream_frame(sock)) + variant = fromradio.WhichOneof("payload_variant") + if verbose and variant: + print(f"[api] fromradio {variant}", file=sys.stderr) + if variant == "config_complete_id" and fromradio.config_complete_id == nonce: + return + raise TimeoutError("timed out waiting for config handshake to complete") + + +def decode_shell_packet(state: SessionState, packet) -> Optional[object]: + if packet.WhichOneof("payload_variant") != "decoded": + return None + if packet.decoded.portnum != state.pb2.portnums.DM_SHELL_APP: + return None + shell = state.pb2.mesh.DMShell() + shell.ParseFromString(packet.decoded.payload) + return shell + + +def reader_loop(sock: socket.socket, state: SessionState) -> None: + while not state.stopped: + try: + fromradio = state.pb2.mesh.FromRadio() + fromradio.ParseFromString(recv_stream_frame(sock)) + except Exception as exc: + if not state.stopped: + state.event_queue.put(f"connection error: {exc}") + state.closed_event.set() + return + + variant = fromradio.WhichOneof("payload_variant") + if variant == "packet": + shell = decode_shell_packet(state, fromradio.packet) + if not shell: + continue + if shell.op == state.pb2.mesh.DMShell.OPEN_OK: + state.session_id = shell.session_id + state.active = True + state.opened_event.set() + pid = int.from_bytes(shell.payload, "big") if shell.payload else 0 + state.event_queue.put( + f"opened session=0x{shell.session_id:08x} cols={shell.cols} rows={shell.rows} pid={pid}" + ) + elif shell.op == state.pb2.mesh.DMShell.OUTPUT: + if shell.payload: + sys.stdout.buffer.write(shell.payload) + sys.stdout.buffer.flush() + elif shell.op == state.pb2.mesh.DMShell.ERROR: + message = shell.payload.decode("utf-8", errors="replace") + state.event_queue.put(f"remote error: {message}") + elif shell.op == state.pb2.mesh.DMShell.CLOSED: + message = shell.payload.decode("utf-8", errors="replace") + state.event_queue.put(f"session closed: {message}") + state.closed_event.set() + state.active = False + return + elif shell.op == state.pb2.mesh.DMShell.PONG: + state.event_queue.put("pong") + elif state.verbose and variant: + state.event_queue.put(f"fromradio {variant}") + + +def drain_events(state: SessionState) -> None: + while True: + try: + event = state.event_queue.get_nowait() + except queue.Empty: + return + print(f"[dmshell] {event}", file=sys.stderr) + + +def run_command_mode(sock: socket.socket, state: SessionState, commands: list[str], close_after: float) -> None: + for command in commands: + send_shell_frame(sock, state, state.mesh_pb2.DMShell.INPUT, (command + "\n").encode("utf-8")) + time.sleep(close_after) + send_shell_frame(sock, state, state.mesh_pb2.DMShell.CLOSE) + state.closed_event.wait(timeout=close_after + 5.0) + + +def run_interactive_mode(sock: socket.socket, state: SessionState) -> None: + print("Enter shell lines to send. Commands: /close, /ping, /resize COLS ROWS", file=sys.stderr) + while not state.closed_event.is_set(): + drain_events(state) + try: + line = input("dmshell> ") + except EOFError: + send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + break + except KeyboardInterrupt: + print("", file=sys.stderr) + send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + break + + if not line: + continue + if line == "/close": + send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + break + if line == "/ping": + send_shell_frame(sock, state, state.pb2.mesh.DMShell.PING) + continue + if line.startswith("/resize "): + parts = line.split() + if len(parts) != 3: + print("usage: /resize COLS ROWS", file=sys.stderr) + continue + send_shell_frame(sock, state, state.pb2.mesh.DMShell.RESIZE, cols=int(parts[1]), rows=int(parts[2])) + continue + + send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, (line + "\n").encode("utf-8")) + + +def main() -> int: + args = parse_args() + pb2 = load_proto_modules() + + state = SessionState( + pb2=pb2, + target=parse_node_num(args.to), + channel=args.channel, + verbose=args.verbose, + ) + + with socket.create_connection((args.host, args.port), timeout=args.timeout) as sock: + sock.settimeout(None) + wait_for_config_complete(sock, pb2, args.timeout, args.verbose) + + reader = threading.Thread(target=reader_loop, args=(sock, state), daemon=True) + reader.start() + + send_shell_frame(sock, state, pb2.mesh.DMShell.OPEN, cols=args.cols, rows=args.rows) + if not state.opened_event.wait(timeout=args.timeout): + raise SystemExit("timed out waiting for OPEN_OK from remote DMShell") + + drain_events(state) + if args.command: + run_command_mode(sock, state, args.command, args.close_after) + else: + run_interactive_mode(sock, state) + + state.stopped = True + drain_events(state) + reader.join(timeout=1.0) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 7f1a738c652..fbcd1b0e318 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -27,6 +27,9 @@ PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO) PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) +PB_BIND(meshtastic_DMShell, meshtastic_DMShell, AUTO) + + PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) @@ -129,6 +132,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 477c3b31bab..02165f95778 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -308,6 +308,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126, /* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */ meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127, + /* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, GPS, button, buzzer, and sensors. */ + meshtastic_HardwareModel_TRACKER_T1000_E_PRO = 128, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -511,6 +513,27 @@ typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type { meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF = 6 } meshtastic_StoreForwardPlusPlus_SFPP_message_type; +/* Frame op code for PTY session control and stream transport. + + Values 1-63 are client->server requests. + Values 64-127 are server->client responses/events. */ +typedef enum _meshtastic_DMShell_OpCode { + meshtastic_DMShell_OpCode_OP_UNSET = 0, + /* Client -> server */ + meshtastic_DMShell_OpCode_OPEN = 1, + meshtastic_DMShell_OpCode_INPUT = 2, + meshtastic_DMShell_OpCode_RESIZE = 3, + meshtastic_DMShell_OpCode_CLOSE = 4, + meshtastic_DMShell_OpCode_PING = 5, + meshtastic_DMShell_OpCode_ACK = 6, + /* Server -> client */ + meshtastic_DMShell_OpCode_OPEN_OK = 64, + meshtastic_DMShell_OpCode_OUTPUT = 65, + meshtastic_DMShell_OpCode_CLOSED = 66, + meshtastic_DMShell_OpCode_ERROR = 67, + meshtastic_DMShell_OpCode_PONG = 68 +} meshtastic_DMShell_OpCode; + /* The priority of this message for sending. Higher priorities are sent first (when managing the transmit queue). This field is never sent over the air, it is only used internally inside of a local device node. @@ -843,6 +866,27 @@ typedef struct _meshtastic_StoreForwardPlusPlus { uint32_t chain_count; } meshtastic_StoreForwardPlusPlus; +typedef PB_BYTES_ARRAY_T(200) meshtastic_DMShell_payload_t; +/* The actual over-the-mesh message doing DMShell */ +typedef struct _meshtastic_DMShell { + /* Structured frame operation. */ + meshtastic_DMShell_OpCode op; + /* Logical PTY session identifier. */ + uint32_t session_id; + /* Monotonic sequence number for this frame. */ + uint32_t seq; + /* Cumulative ack sequence number. */ + uint32_t ack_seq; + /* Opaque bytes payload for INPUT/OUTPUT/ERROR and other frame bodies. */ + meshtastic_DMShell_payload_t payload; + /* Terminal size columns used for OPEN/RESIZE signaling. */ + uint32_t cols; + /* Terminal size rows used for OPEN/RESIZE signaling. */ + uint32_t rows; + /* Bit flags for protocol extensions. */ + uint32_t flags; +} meshtastic_DMShell; + /* Waypoint message, used to share arbitrary locations across the mesh */ typedef struct _meshtastic_Waypoint { /* Id of the waypoint */ @@ -1383,6 +1427,10 @@ extern "C" { #define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF #define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1)) +#define _meshtastic_DMShell_OpCode_MIN meshtastic_DMShell_OpCode_OP_UNSET +#define _meshtastic_DMShell_OpCode_MAX meshtastic_DMShell_OpCode_PONG +#define _meshtastic_DMShell_OpCode_ARRAYSIZE ((meshtastic_DMShell_OpCode)(meshtastic_DMShell_OpCode_PONG+1)) + #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX #define _meshtastic_MeshPacket_Priority_ARRAYSIZE ((meshtastic_MeshPacket_Priority)(meshtastic_MeshPacket_Priority_MAX+1)) @@ -1413,6 +1461,8 @@ extern "C" { #define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type +#define meshtastic_DMShell_op_ENUMTYPE meshtastic_DMShell_OpCode + @@ -1457,6 +1507,7 @@ extern "C" { #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_DMShell_init_default {_meshtastic_DMShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} @@ -1490,6 +1541,7 @@ extern "C" { #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} +#define meshtastic_DMShell_init_zero {_meshtastic_DMShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} @@ -1579,6 +1631,14 @@ extern "C" { #define meshtastic_StoreForwardPlusPlus_encapsulated_from_tag 8 #define meshtastic_StoreForwardPlusPlus_encapsulated_rxtime_tag 9 #define meshtastic_StoreForwardPlusPlus_chain_count_tag 10 +#define meshtastic_DMShell_op_tag 1 +#define meshtastic_DMShell_session_id_tag 2 +#define meshtastic_DMShell_seq_tag 3 +#define meshtastic_DMShell_ack_seq_tag 4 +#define meshtastic_DMShell_payload_tag 5 +#define meshtastic_DMShell_cols_tag 6 +#define meshtastic_DMShell_rows_tag 7 +#define meshtastic_DMShell_flags_tag 8 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1811,6 +1871,18 @@ X(a, STATIC, SINGULAR, UINT32, chain_count, 10) #define meshtastic_StoreForwardPlusPlus_CALLBACK NULL #define meshtastic_StoreForwardPlusPlus_DEFAULT NULL +#define meshtastic_DMShell_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, op, 1) \ +X(a, STATIC, SINGULAR, UINT32, session_id, 2) \ +X(a, STATIC, SINGULAR, UINT32, seq, 3) \ +X(a, STATIC, SINGULAR, UINT32, ack_seq, 4) \ +X(a, STATIC, SINGULAR, BYTES, payload, 5) \ +X(a, STATIC, SINGULAR, UINT32, cols, 6) \ +X(a, STATIC, SINGULAR, UINT32, rows, 7) \ +X(a, STATIC, SINGULAR, UINT32, flags, 8) +#define meshtastic_DMShell_CALLBACK NULL +#define meshtastic_DMShell_DEFAULT NULL + #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \ @@ -2093,6 +2165,7 @@ extern const pb_msgdesc_t meshtastic_Routing_msg; extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; +extern const pb_msgdesc_t meshtastic_DMShell_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; extern const pb_msgdesc_t meshtastic_StatusMessage_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; @@ -2128,6 +2201,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Data_fields &meshtastic_Data_msg #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg #define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg +#define meshtastic_DMShell_fields &meshtastic_DMShell_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg #define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg @@ -2162,6 +2236,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ChunkedPayload_size 245 #define meshtastic_ClientNotification_size 482 #define meshtastic_Compressed_size 239 +#define meshtastic_DMShell_size 241 #define meshtastic_Data_size 269 #define meshtastic_DeviceMetadata_size 54 #define meshtastic_DuplicatedPublicKey_size 0 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index bd1fe48c47d..5ca2fca5dc3 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -76,6 +76,8 @@ typedef enum _meshtastic_PortNum { meshtastic_PortNum_ALERT_APP = 11, /* Module/port for handling key verification requests. */ meshtastic_PortNum_KEY_VERIFICATION_APP = 12, + /* Module/port for handling key verification requests. */ + meshtastic_PortNum_DM_SHELL_APP = 13, /* Provides a 'ping' service that replies to any packet it receives. Also serves as a small example module. ENCODING: ASCII Plaintext */ diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp new file mode 100644 index 00000000000..ba7ad7acbdd --- /dev/null +++ b/src/modules/DMShell.cpp @@ -0,0 +1,397 @@ +#include "DMShell.h" + +#if defined(ARCH_PORTDUINO) + +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "Throttle.h" +#include "configuration.h" +#include "mesh/generated/meshtastic/mesh.pb.h" +#include "mesh/mesh-pb-constants.h" +#include "pb_decode.h" +#include "pb_encode.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DMShellModule *dmShellModule; + +namespace +{ +constexpr uint16_t PTY_COLS_DEFAULT = 120; +constexpr uint16_t PTY_ROWS_DEFAULT = 40; +constexpr size_t MAX_PTY_READ_SIZE = 200; + +struct BytesDecodeState { + uint8_t *buf; + size_t maxLen; + size_t outLen; +}; + +struct BytesEncodeState { + const uint8_t *buf; + size_t len; +}; + +bool decodeBytesField(pb_istream_t *stream, const pb_field_iter_t *field, void **arg) +{ + (void)field; + auto *state = static_cast(*arg); + const size_t fieldLen = stream->bytes_left; + if (fieldLen > state->maxLen) { + return false; + } + if (!pb_read(stream, state->buf, fieldLen)) { + return false; + } + state->outLen = fieldLen; + return true; +} + +bool encodeBytesField(pb_ostream_t *stream, const pb_field_iter_t *field, void *const *arg) +{ + auto *state = static_cast(*arg); + if (!state || !state->buf || state->len == 0) { + return true; + } + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + return pb_encode_string(stream, state->buf, state->len); +} +} // namespace + +DMShellModule::DMShellModule() + : SinglePortModule("DMShellModule", meshtastic_PortNum_DM_SHELL_APP), concurrency::OSThread("DMShell", 100) +{ + LOG_WARN("DMShell enabled on Portduino: remote shell access is dangerous and intended for trusted debugging only"); +} + +ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + DMShellFrame frame; + if (!parseFrame(mp, frame)) { + LOG_WARN("DMShell: ignoring malformed frame"); + return ProcessMessage::STOP; + } + + if (!isAuthorizedPacket(mp)) { + LOG_WARN("DMShell: unauthorized sender 0x%x", mp.from); + myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp); + return ProcessMessage::STOP; + } + + if (frame.op == meshtastic_DMShell_OpCode_OPEN) { + LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.sessionId); + if (!openSession(mp, frame)) { + const char *msg = "open_failed"; + sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, frame.seq, + reinterpret_cast(msg), strlen(msg)); + } + return ProcessMessage::STOP; + } + + if (!session.active || frame.sessionId != session.sessionId || getFrom(&mp) != session.peer) { + const char *msg = "invalid_session"; + sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, frame.seq, + reinterpret_cast(msg), strlen(msg)); + return ProcessMessage::STOP; + } + + session.lastActivityMs = millis(); + + switch (frame.op) { + case meshtastic_DMShell_OpCode_INPUT: + if (!writeSessionInput(frame)) { + sendError("input_write_failed"); + } + break; + case meshtastic_DMShell_OpCode_RESIZE: + if (frame.rows > 0 && frame.cols > 0) { + struct winsize ws = {}; + ws.ws_row = frame.rows; + ws.ws_col = frame.cols; + if (session.masterFd >= 0) { + ioctl(session.masterFd, TIOCSWINSZ, &ws); + } + } + break; + case meshtastic_DMShell_OpCode_PING: + sendControl(meshtastic_DMShell_OpCode_PONG, nullptr, 0, frame.seq); + break; + case meshtastic_DMShell_OpCode_CLOSE: + closeSession("peer_close", true); + break; + default: + sendError("unsupported_op"); + break; + } + + return ProcessMessage::STOP; +} + +int32_t DMShellModule::runOnce() +{ + if (!session.active) { + return 100; + } + + reapChildIfExited(); + if (!session.active) { + return 100; + } + + if (Throttle::isWithinTimespanMs(session.lastActivityMs, SESSION_IDLE_TIMEOUT_MS) == false) { + closeSession("idle_timeout", true); + return 100; + } + + uint8_t outBuf[MAX_PTY_READ_SIZE]; + while (session.masterFd >= 0) { + const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); + if (bytesRead > 0) { + LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); + sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead), session.txSeq++); + session.lastActivityMs = millis(); + continue; + } + + if (bytesRead == 0) { + closeSession("pty_eof", true); + break; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } + + LOG_WARN("DMShell: PTY read error errno=%d", errno); + closeSession("pty_read_error", true); + break; + } + + return 100; +} + +bool DMShellModule::parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &outFrame) +{ + if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + return false; + } + + meshtastic_DMShell decodedMsg = meshtastic_DMShell_init_zero; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_DMShell_fields, &decodedMsg)) { + LOG_INFO("Received a DMShell message"); + } else { + LOG_ERROR("Error decoding DMShell message!"); + return false; + } + + outFrame.op = decodedMsg.op; + outFrame.sessionId = decodedMsg.session_id; + outFrame.seq = decodedMsg.seq; + outFrame.ackSeq = decodedMsg.ack_seq; + outFrame.cols = decodedMsg.cols; + outFrame.rows = decodedMsg.rows; + outFrame.flags = decodedMsg.flags; + outFrame.payloadLen = decodedMsg.payload.size; + memcpy(outFrame.payload, decodedMsg.payload.bytes, outFrame.payloadLen); + + return true; +} + +bool DMShellModule::isAuthorizedPacket(const meshtastic_MeshPacket &mp) const +{ + if (mp.from == 0) { + return !config.security.is_managed; + } + + const meshtastic_Channel *ch = &channels.getByIndex(mp.channel); + if (strcasecmp(ch->settings.name, Channels::adminChannel) == 0) { + return config.security.admin_channel_enabled; + } + + if (mp.pki_encrypted) { + for (uint8_t i = 0; i < 3; ++i) { + if (config.security.admin_key[i].size == 32 && + memcmp(mp.public_key.bytes, config.security.admin_key[i].bytes, 32) == 0) { + return true; + } + } + } + + return false; +} + +bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFrame &frame) +{ + if (session.active) { + closeSession("preempted", true); + } + + int masterFd = -1; + struct winsize ws = {}; + ws.ws_col = PTY_COLS_DEFAULT; + ws.ws_row = PTY_ROWS_DEFAULT; + const pid_t childPid = forkpty(&masterFd, nullptr, nullptr, &ws); + if (childPid < 0) { + LOG_ERROR("DMShell: forkpty failed errno=%d", errno); + return false; + } + + if (childPid == 0) { + const char *shell = getenv("SHELL"); + if (!shell || !*shell) { + shell = "/bin/sh"; + } + execl(shell, shell, "-i", static_cast(nullptr)); + _exit(127); + } + + const int flags = fcntl(masterFd, F_GETFL, 0); + if (flags >= 0) { + fcntl(masterFd, F_SETFL, flags | O_NONBLOCK); + } + + session.active = true; + session.sessionId = (frame.sessionId != 0) ? frame.sessionId : static_cast(random(1, 0x7fffffff)); + session.peer = getFrom(&mp); + session.channel = mp.channel; + session.masterFd = masterFd; + session.childPid = childPid; + session.txSeq = 0; + session.lastActivityMs = millis(); + + uint8_t payload[sizeof(uint32_t)] = {0}; + uint32_t pidBE = (static_cast(session.childPid) << 24) | ((static_cast(session.childPid) >> 8) & 0xff00) | + ((static_cast(session.childPid) << 8) & 0xff0000) | + (static_cast(session.childPid) >> 24); + memcpy(payload, &pidBE, sizeof(payload)); + sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, frame.seq, payload, + sizeof(payload), PTY_COLS_DEFAULT, PTY_ROWS_DEFAULT); + + LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid); + return true; +} + +bool DMShellModule::writeSessionInput(const DMShellFrame &frame) +{ + if (session.masterFd < 0) { + return false; + } + if (frame.payloadLen == 0) { + return true; + } + + const ssize_t bytesWritten = write(session.masterFd, frame.payload, frame.payloadLen); + return bytesWritten >= 0; +} + +void DMShellModule::closeSession(const char *reason, bool notifyPeer) +{ + if (!session.active) { + return; + } + + if (notifyPeer) { + const size_t reasonLen = strnlen(reason, 256); + sendControl(meshtastic_DMShell_OpCode_CLOSED, reinterpret_cast(reason), reasonLen, session.txSeq++); + } + + if (session.masterFd >= 0) { + close(session.masterFd); + session.masterFd = -1; + } + + if (session.childPid > 0) { + kill(session.childPid, SIGTERM); + int status = 0; + waitpid(session.childPid, &status, WNOHANG); + session.childPid = -1; + } + + LOG_INFO("DMShell: closed session=0x%x reason=%s", session.sessionId, reason); + session = DMShellSession{}; +} + +void DMShellModule::reapChildIfExited() +{ + if (!session.active || session.childPid <= 0) { + return; + } + + int status = 0; + const pid_t result = waitpid(session.childPid, &status, WNOHANG); + if (result == session.childPid) { + closeSession("shell_exited", true); + } +} + +void DMShellModule::sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen, uint32_t seq) +{ + sendFrameToPeer(session.peer, session.channel, op, session.sessionId, seq, payload, payloadLen); +} + +void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, + const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, + uint32_t flags) +{ + meshtastic_MeshPacket *packet = buildFramePacket(op, sessionId, seq, payload, payloadLen, cols, rows, ackSeq, flags); + if (!packet) { + return; + } + + packet->to = peer; + packet->channel = channel; + packet->want_ack = false; + packet->priority = meshtastic_MeshPacket_Priority_RELIABLE; + service->sendToMesh(packet); +} + +void DMShellModule::sendError(const char *message) +{ + const size_t len = strnlen(message, meshtastic_Constants_DATA_PAYLOAD_LEN); + sendControl(meshtastic_DMShell_OpCode_ERROR, reinterpret_cast(message), len, session.txSeq++); +} + +meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, + const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, + uint32_t ackSeq, uint32_t flags) +{ + meshtastic_DMShell frame = meshtastic_DMShell_init_zero; + frame.op = op; + frame.session_id = sessionId; + frame.seq = seq; + frame.ack_seq = ackSeq; + frame.cols = cols; + frame.rows = rows; + frame.flags = flags; + + if (payload && payloadLen > 0) { + memcpy(frame.payload.bytes, payload, payloadLen); + frame.payload.size = payloadLen; + } + + meshtastic_MeshPacket *packet = allocDataPacket(); + if (!packet) { + return nullptr; + } + LOG_WARN("DMShell: building packet op=%d session=0x%x seq=%d payloadLen=%d", op, sessionId, seq, payloadLen); + const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes), + meshtastic_DMShell_fields, &frame); + if (encoded == 0) { + return nullptr; + } + packet->decoded.payload.size = encoded; + return packet; +} + +#endif \ No newline at end of file diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h new file mode 100644 index 00000000000..dde56a3c733 --- /dev/null +++ b/src/modules/DMShell.h @@ -0,0 +1,71 @@ +#pragma once + +#include "MeshModule.h" +#include "Router.h" +#include "SinglePortModule.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#include "mesh/generated/meshtastic/mesh.pb.h" +#include +#include + +#if defined(ARCH_PORTDUINO) + +struct DMShellFrame { + meshtastic_DMShell_OpCode op = meshtastic_DMShell_OpCode_ERROR; + uint32_t sessionId = 0; + uint32_t seq = 0; + uint32_t ackSeq = 0; + uint32_t cols = 0; + uint32_t rows = 0; + uint32_t flags = 0; + uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; + size_t payloadLen = 0; +}; + +struct DMShellSession { + bool active = false; + uint32_t sessionId = 0; + NodeNum peer = 0; + uint8_t channel = 0; + int masterFd = -1; + int childPid = -1; + uint32_t txSeq = 0; + uint32_t lastActivityMs = 0; +}; + +class DMShellModule : private concurrency::OSThread, public SinglePortModule +{ + + public: + DMShellModule(); + + protected: + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + virtual int32_t runOnce() override; + + private: + static constexpr uint32_t SESSION_IDLE_TIMEOUT_MS = 5 * 60 * 1000; + + DMShellSession session; + + bool parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &outFrame); + bool isAuthorizedPacket(const meshtastic_MeshPacket &mp) const; + bool openSession(const meshtastic_MeshPacket &mp, const DMShellFrame &frame); + bool writeSessionInput(const DMShellFrame &frame); + void closeSession(const char *reason, bool notifyPeer); + void reapChildIfExited(); + + void sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen, uint32_t seq = 0); + void sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, + const uint8_t *payload, size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0, uint32_t ackSeq = 0, + uint32_t flags = 0); + void sendError(const char *message); + meshtastic_MeshPacket *buildFramePacket(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, + const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, + uint32_t ackSeq, uint32_t flags); +}; + +extern DMShellModule *dmShellModule; + +#endif \ No newline at end of file diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 64e90c9c25b..bebd998faf2 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -46,6 +46,7 @@ #include "modules/WaypointModule.h" #endif #if ARCH_PORTDUINO +#include "modules/DMShell.h" #include "modules/Telemetry/HostMetrics.h" #if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/StoreForwardModule.h" @@ -184,6 +185,7 @@ void setupModules() #endif #if ARCH_PORTDUINO new HostMetricsModule(); + dmShellModule = new DMShellModule(); #endif #if HAS_TELEMETRY new DeviceTelemetryModule(); diff --git a/test/test_meshpacket_serializer/ports/test_dmshell.cpp b/test/test_meshpacket_serializer/ports/test_dmshell.cpp new file mode 100644 index 00000000000..05e901dc6c6 --- /dev/null +++ b/test/test_meshpacket_serializer/ports/test_dmshell.cpp @@ -0,0 +1,112 @@ +#include "../test_helpers.h" +#include "mesh/mesh-pb-constants.h" + +namespace +{ +struct BytesDecodeState { + uint8_t *buffer; + size_t capacity; + size_t length; +}; + +struct BytesEncodeState { + const uint8_t *buffer; + size_t length; +}; + +bool decodeBytesField(pb_istream_t *stream, const pb_field_iter_t *field, void **arg) +{ + (void)field; + auto *state = static_cast(*arg); + if (!state) { + return false; + } + + const size_t fieldLen = stream->bytes_left; + if (fieldLen > state->capacity) { + return false; + } + + if (!pb_read(stream, state->buffer, fieldLen)) { + return false; + } + + state->length = fieldLen; + return true; +} + +bool encodeBytesField(pb_ostream_t *stream, const pb_field_iter_t *field, void *const *arg) +{ + auto *state = static_cast(*arg); + if (!state || !state->buffer || state->length == 0) { + return true; + } + + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + return pb_encode_string(stream, state->buffer, state->length); +} + +void assert_dmshell_roundtrip(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, + size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0) +{ + meshtastic_DMShell tx = meshtastic_DMShell_init_zero; + tx.op = op; + tx.session_id = sessionId; + tx.seq = seq; + tx.cols = cols; + tx.rows = rows; + + BytesEncodeState txPayload = {payload, payloadLen}; + if (payload && payloadLen > 0) { + tx.payload.funcs.encode = encodeBytesField; + tx.payload.arg = &txPayload; + } + + uint8_t encoded[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; + size_t encodedLen = pb_encode_to_bytes(encoded, sizeof(encoded), meshtastic_DMShell_fields, &tx); + TEST_ASSERT_GREATER_THAN_UINT32(0, encodedLen); + + meshtastic_DMShell rx = meshtastic_DMShell_init_zero; + uint8_t decodedPayload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; + BytesDecodeState rxPayload = {decodedPayload, sizeof(decodedPayload), 0}; + rx.payload.funcs.decode = decodeBytesField; + rx.payload.arg = &rxPayload; + + TEST_ASSERT_TRUE(pb_decode_from_bytes(encoded, encodedLen, meshtastic_DMShell_fields, &rx)); + TEST_ASSERT_EQUAL(op, rx.op); + TEST_ASSERT_EQUAL_UINT32(sessionId, rx.session_id); + TEST_ASSERT_EQUAL_UINT32(seq, rx.seq); + TEST_ASSERT_EQUAL_UINT32(cols, rx.cols); + TEST_ASSERT_EQUAL_UINT32(rows, rx.rows); + TEST_ASSERT_EQUAL_UINT32(payloadLen, rxPayload.length); + + if (payloadLen > 0) { + TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, decodedPayload, payloadLen); + } +} +} // namespace + +void test_dmshell_open_roundtrip() +{ + assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_OPEN, 0x101, 1, nullptr, 0, 120, 40); +} + +void test_dmshell_input_roundtrip() +{ + const uint8_t payload[] = {'l', 's', '\n'}; + assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_INPUT, 0x202, 2, payload, sizeof(payload)); +} + +void test_dmshell_resize_roundtrip() +{ + assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_RESIZE, 0x303, 3, nullptr, 0, 180, 55); +} + +void test_dmshell_close_roundtrip() +{ + const uint8_t reason[] = {'b', 'y', 'e'}; + assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_CLOSE, 0x404, 4, reason, sizeof(reason)); +} \ No newline at end of file diff --git a/test/test_meshpacket_serializer/test_serializer.cpp b/test/test_meshpacket_serializer/test_serializer.cpp index 484db8d7486..3a19745d824 100644 --- a/test/test_meshpacket_serializer/test_serializer.cpp +++ b/test/test_meshpacket_serializer/test_serializer.cpp @@ -19,6 +19,10 @@ void test_telemetry_environment_metrics_complete_coverage(); void test_telemetry_environment_metrics_unset_fields(); void test_encrypted_packet_serialization(); void test_empty_encrypted_packet(); +void test_dmshell_open_roundtrip(); +void test_dmshell_input_roundtrip(); +void test_dmshell_resize_roundtrip(); +void test_dmshell_close_roundtrip(); void setup() { @@ -52,6 +56,12 @@ void setup() RUN_TEST(test_encrypted_packet_serialization); RUN_TEST(test_empty_encrypted_packet); + // DMShell protobuf transport tests + RUN_TEST(test_dmshell_open_roundtrip); + RUN_TEST(test_dmshell_input_roundtrip); + RUN_TEST(test_dmshell_resize_roundtrip); + RUN_TEST(test_dmshell_close_roundtrip); + UNITY_END(); } From f475be19c622bde013dacad655df2dd537b30ea7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 14:27:15 -0500 Subject: [PATCH 02/22] Dumb fixes --- bin/dmshell_client.py | 6 +++--- src/modules/DMShell.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index 12da2610495..c20eda5e70f 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -297,9 +297,9 @@ def drain_events(state: SessionState) -> None: def run_command_mode(sock: socket.socket, state: SessionState, commands: list[str], close_after: float) -> None: for command in commands: - send_shell_frame(sock, state, state.mesh_pb2.DMShell.INPUT, (command + "\n").encode("utf-8")) + send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, (command + "\n").encode("utf-8")) time.sleep(close_after) - send_shell_frame(sock, state, state.mesh_pb2.DMShell.CLOSE) + send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) state.closed_event.wait(timeout=close_after + 5.0) @@ -330,7 +330,7 @@ def run_interactive_mode(sock: socket.socket, state: SessionState) -> None: if len(parts) != 3: print("usage: /resize COLS ROWS", file=sys.stderr) continue - send_shell_frame(sock, state, state.pb2.mesh.DMShell.RESIZE, cols=int(parts[1]), rows=int(parts[2])) + send_shell_frame(sock, state, state.pb2.mesh.DMShell.RESIZE, cols=int(parts[1]), rows=int(parts[2])) continue send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, (line + "\n").encode("utf-8")) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index ba7ad7acbdd..84d8a8f9a48 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -83,6 +83,11 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) return ProcessMessage::STOP; } + if (frame.op >= 64) { + LOG_WARN("DMShell: ignoring frame with op code %d", frame.op); + return ProcessMessage::CONTINUE; + } + if (!isAuthorizedPacket(mp)) { LOG_WARN("DMShell: unauthorized sender 0x%x", mp.from); myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp); @@ -234,7 +239,7 @@ bool DMShellModule::isAuthorizedPacket(const meshtastic_MeshPacket &mp) const bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFrame &frame) { if (session.active) { - closeSession("preempted", true); + closeSession("preempted", false); } int masterFd = -1; From 608713470b5ba4e8ff2183dc3b894145553b064c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 14:55:37 -0500 Subject: [PATCH 03/22] Interactive mode --- bin/dmshell_client.py | 143 ++++++++++++++++++++++++++++++++-------- src/modules/DMShell.cpp | 14 +++- 2 files changed, 127 insertions(+), 30 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index c20eda5e70f..a5b8705a788 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -6,11 +6,14 @@ import random import shutil import socket +import select import subprocess import sys import tempfile +import termios import threading import time +import tty from dataclasses import dataclass, field from pathlib import Path from typing import Optional @@ -21,6 +24,7 @@ HEADER_LEN = 4 DEFAULT_API_PORT = 4403 DEFAULT_HOP_LIMIT = 3 +LOCAL_ESCAPE_BYTE = b"\x1d" # Ctrl+] def parse_args() -> argparse.Namespace: @@ -37,8 +41,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--port", type=int, default=DEFAULT_API_PORT, help="meshtasticd API port") parser.add_argument("--to", required=True, help="destination node number, e.g. !170896f7 or 0x170896f7") parser.add_argument("--channel", type=int, default=0, help="channel index to use") - parser.add_argument("--cols", type=int, default=120, help="initial terminal columns") - parser.add_argument("--rows", type=int, default=40, help="initial terminal rows") + parser.add_argument("--cols", type=int, default=None, help="initial terminal columns (default: detect local terminal)") + parser.add_argument("--rows", type=int, default=None, help="initial terminal rows (default: detect local terminal)") parser.add_argument("--command", action="append", default=[], help="send a command line after opening") parser.add_argument("--close-after", type=float, default=2.0, help="seconds to wait before closing in command mode") parser.add_argument("--timeout", type=float, default=10.0, help="seconds to wait for API/session events") @@ -140,6 +144,20 @@ def recv_exact(sock: socket.socket, length: int) -> bytes: return bytes(chunks) +def detect_local_terminal_size() -> tuple[int, int]: + size = shutil.get_terminal_size(fallback=(100, 40)) + cols = max(1, int(size.columns)) + rows = max(1, int(size.lines)) + return cols, rows + + +def resolve_initial_terminal_size(cols_override: Optional[int], rows_override: Optional[int]) -> tuple[int, int]: + detected_cols, detected_rows = detect_local_terminal_size() + cols = detected_cols if cols_override is None else max(1, cols_override) + rows = detected_rows if rows_override is None else max(1, rows_override) + return cols, rows + + def recv_stream_frame(sock: socket.socket) -> bytes: while True: start = recv_exact(sock, 1)[0] @@ -304,36 +322,105 @@ def run_command_mode(sock: socket.socket, state: SessionState, commands: list[st def run_interactive_mode(sock: socket.socket, state: SessionState) -> None: - print("Enter shell lines to send. Commands: /close, /ping, /resize COLS ROWS", file=sys.stderr) - while not state.closed_event.is_set(): - drain_events(state) - try: - line = input("dmshell> ") - except EOFError: - send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) - break - except KeyboardInterrupt: - print("", file=sys.stderr) - send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) - break + def read_local_command() -> str: + prompt = "\r\n[dmshell] local command (resume|close|ping|resize C R): " + sys.stderr.write(prompt) + sys.stderr.flush() + buf = bytearray() + + while True: + ch = os.read(sys.stdin.fileno(), 1) + if not ch: + sys.stderr.write("\r\n") + sys.stderr.flush() + return "close" + + b = ch[0] + if b in (10, 13): + sys.stderr.write("\r\n") + sys.stderr.flush() + return buf.decode("utf-8", errors="replace").strip() + + if b in (8, 127): + if buf: + buf.pop() + sys.stderr.write("\b \b") + sys.stderr.flush() + continue - if not line: - continue - if line == "/close": + if b < 32: + continue + + buf.append(b) + sys.stderr.write(chr(b)) + sys.stderr.flush() + + def handle_local_command(cmd: str) -> bool: + if cmd in ("", "resume"): + return True + if cmd == "close": send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) - break - if line == "/ping": + return False + if cmd == "ping": send_shell_frame(sock, state, state.pb2.mesh.DMShell.PING) - continue - if line.startswith("/resize "): - parts = line.split() + return True + if cmd.startswith("resize "): + parts = cmd.split() if len(parts) != 3: - print("usage: /resize COLS ROWS", file=sys.stderr) + state.event_queue.put("usage: resize COLS ROWS") + return True + try: + cols = int(parts[1]) + rows = int(parts[2]) + except ValueError: + state.event_queue.put("usage: resize COLS ROWS") + return True + send_shell_frame(sock, state, state.pb2.mesh.DMShell.RESIZE, cols=cols, rows=rows) + return True + + state.event_queue.put(f"unknown local command: {cmd}") + return True + + print( + "Raw input mode active. All keys (including Ctrl+C/Ctrl+X) are sent to remote. Ctrl+] for local commands.", + file=sys.stderr, + ) + + if not sys.stdin.isatty(): + # Fallback for non-TTY stdin: still send input as it arrives. + while not state.closed_event.is_set(): + drain_events(state) + data = sys.stdin.buffer.read(1) + if not data: + send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + break + send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, data) + return + + fd = sys.stdin.fileno() + old_attrs = termios.tcgetattr(fd) + try: + tty.setraw(fd) + while not state.closed_event.is_set(): + drain_events(state) + ready, _, _ = select.select([sys.stdin], [], [], 0.05) + if not ready: continue - send_shell_frame(sock, state, state.pb2.mesh.DMShell.RESIZE, cols=int(parts[1]), rows=int(parts[2])) - continue - send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, (line + "\n").encode("utf-8")) + data = os.read(fd, 1) + if not data: + send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + break + + if data == LOCAL_ESCAPE_BYTE: + keep_running = handle_local_command(read_local_command()) + if not keep_running: + break + continue + + send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, data) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_attrs) def main() -> int: @@ -347,6 +434,8 @@ def main() -> int: verbose=args.verbose, ) + cols, rows = resolve_initial_terminal_size(args.cols, args.rows) + with socket.create_connection((args.host, args.port), timeout=args.timeout) as sock: sock.settimeout(None) wait_for_config_complete(sock, pb2, args.timeout, args.verbose) @@ -354,7 +443,7 @@ def main() -> int: reader = threading.Thread(target=reader_loop, args=(sock, state), daemon=True) reader.start() - send_shell_frame(sock, state, pb2.mesh.DMShell.OPEN, cols=args.cols, rows=args.rows) + send_shell_frame(sock, state, pb2.mesh.DMShell.OPEN, cols=cols, rows=rows) if not state.opened_event.wait(timeout=args.timeout): raise SystemExit("timed out waiting for OPEN_OK from remote DMShell") diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 84d8a8f9a48..bf7d8679b20 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -244,8 +244,16 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr int masterFd = -1; struct winsize ws = {}; - ws.ws_col = PTY_COLS_DEFAULT; - ws.ws_row = PTY_ROWS_DEFAULT; + if (frame.rows > 0) { + ws.ws_row = frame.rows; + } else { + ws.ws_row = PTY_ROWS_DEFAULT; + } + if (frame.cols > 0) { + ws.ws_col = frame.cols; + } else { + ws.ws_col = PTY_COLS_DEFAULT; + } const pid_t childPid = forkpty(&masterFd, nullptr, nullptr, &ws); if (childPid < 0) { LOG_ERROR("DMShell: forkpty failed errno=%d", errno); @@ -281,7 +289,7 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr (static_cast(session.childPid) >> 24); memcpy(payload, &pidBE, sizeof(payload)); sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, frame.seq, payload, - sizeof(payload), PTY_COLS_DEFAULT, PTY_ROWS_DEFAULT); + sizeof(payload), ws.ws_col, ws.ws_row); LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid); return true; From 4a3f44955571d439a51b6621b7561434c0742919 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 15:16:33 -0500 Subject: [PATCH 04/22] Try to re-request missed sequences --- bin/dmshell_client.py | 149 ++++++++++++++++++++++++++++++-- src/modules/DMShell.cpp | 182 ++++++++++++++++++++++++++++++++++++---- src/modules/DMShell.h | 31 ++++++- 3 files changed, 339 insertions(+), 23 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index a5b8705a788..6f76d79a4d8 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import struct import os import queue import random @@ -14,6 +15,7 @@ import threading import time import tty +from collections import deque from dataclasses import dataclass, field from pathlib import Path from typing import Optional @@ -25,6 +27,7 @@ DEFAULT_API_PORT = 4403 DEFAULT_HOP_LIMIT = 3 LOCAL_ESCAPE_BYTE = b"\x1d" # Ctrl+] +REPLAY_REQUEST_SIZE = 4 def parse_args() -> argparse.Namespace: @@ -177,6 +180,18 @@ def send_stream_frame(sock: socket.socket, payload: bytes) -> None: sock.sendall(header + payload) +@dataclass +class SentShellFrame: + op: int + session_id: int + seq: int + ack_seq: int + payload: bytes = b"" + cols: int = 0 + rows: int = 0 + flags: int = 0 + + @dataclass class SessionState: pb2: object # ProtoModules with mesh and portnums attributes @@ -185,16 +200,68 @@ class SessionState: verbose: bool session_id: int = field(default_factory=lambda: random.randint(1, 0x7FFFFFFF)) next_seq: int = 1 + last_rx_seq: int = 0 + next_expected_rx_seq: int = 1 active: bool = False stopped: bool = False opened_event: threading.Event = field(default_factory=threading.Event) closed_event: threading.Event = field(default_factory=threading.Event) event_queue: "queue.Queue[str]" = field(default_factory=queue.Queue) + tx_lock: threading.Lock = field(default_factory=threading.Lock) + tx_history: deque[SentShellFrame] = field(default_factory=lambda: deque(maxlen=10)) def alloc_seq(self) -> int: - value = self.next_seq - self.next_seq += 1 - return value + with self.tx_lock: + value = self.next_seq + self.next_seq += 1 + return value + + def current_ack_seq(self) -> int: + with self.tx_lock: + return self.last_rx_seq + + def note_received_seq(self, seq: int) -> tuple[str, Optional[int]]: + with self.tx_lock: + if seq == 0: + return ("process", None) + if seq < self.next_expected_rx_seq: + return ("duplicate", None) + if seq > self.next_expected_rx_seq: + return ("gap", self.next_expected_rx_seq) + self.last_rx_seq = seq + self.next_expected_rx_seq = seq + 1 + return ("process", None) + + def set_receive_cursor(self, seq: int) -> None: + with self.tx_lock: + self.last_rx_seq = seq + self.next_expected_rx_seq = seq + 1 + + def remember_sent_frame(self, frame: SentShellFrame) -> None: + if frame.seq == 0 or frame.op == self.pb2.mesh.DMShell.ACK: + return + with self.tx_lock: + self.tx_history.append(frame) + + def prune_sent_frames(self, ack_seq: int) -> None: + if ack_seq <= 0: + return + with self.tx_lock: + self.tx_history = deque((frame for frame in self.tx_history if frame.seq > ack_seq), maxlen=10) + + def replay_frames_from(self, start_seq: int) -> list[SentShellFrame]: + with self.tx_lock: + return [frame for frame in self.tx_history if frame.seq >= start_seq] + + +def encode_replay_request(start_seq: int) -> bytes: + return struct.pack(">I", start_seq) + + +def decode_replay_request(payload: bytes) -> Optional[int]: + if len(payload) < REPLAY_REQUEST_SIZE: + return None + return struct.unpack(">I", payload[:REPLAY_REQUEST_SIZE])[0] def send_toradio(sock: socket.socket, toradio) -> None: @@ -221,16 +288,68 @@ def make_toradio_packet(pb2, state: SessionState, shell_msg) -> object: return toradio -def send_shell_frame(sock: socket.socket, state: SessionState, op: int, payload: bytes = b"", cols: int = 0, rows: int = 0) -> None: +def send_shell_frame( + sock: socket.socket, + state: SessionState, + op: int, + payload: bytes = b"", + cols: int = 0, + rows: int = 0, + session_id: Optional[int] = None, + ack_seq: Optional[int] = None, + seq: Optional[int] = None, + flags: int = 0, + remember: bool = True, +) -> int: + if seq is None: + seq = 0 if op == state.pb2.mesh.DMShell.ACK else state.alloc_seq() + if ack_seq is None: + ack_seq = state.current_ack_seq() + if session_id is None: + session_id = state.session_id + shell = state.pb2.mesh.DMShell() shell.op = op - shell.session_id = state.session_id - shell.seq = state.alloc_seq() + shell.session_id = session_id + shell.seq = seq + shell.ack_seq = ack_seq shell.cols = cols shell.rows = rows + shell.flags = flags if payload: shell.payload = payload send_toradio(sock, make_toradio_packet(state.pb2, state, shell)) + if remember: + state.remember_sent_frame( + SentShellFrame(op=op, session_id=session_id, seq=seq, ack_seq=ack_seq, payload=payload, cols=cols, rows=rows, flags=flags) + ) + return seq + + +def send_ack_frame(sock: socket.socket, state: SessionState, replay_from: Optional[int] = None) -> None: + payload = b"" if replay_from is None else encode_replay_request(replay_from) + send_shell_frame(sock, state, state.pb2.mesh.DMShell.ACK, payload=payload, seq=0, remember=False) + + +def replay_frames_from(sock: socket.socket, state: SessionState, start_seq: int) -> None: + frames = state.replay_frames_from(start_seq) + if not frames: + state.event_queue.put(f"replay unavailable from seq={start_seq}") + return + for frame in frames: + send_shell_frame( + sock, + state, + frame.op, + payload=frame.payload, + cols=frame.cols, + rows=frame.rows, + session_id=frame.session_id, + ack_seq=frame.ack_seq, + seq=frame.seq, + flags=frame.flags, + remember=False, + ) def wait_for_config_complete(sock: socket.socket, pb2, timeout: float, verbose: bool) -> None: @@ -277,8 +396,26 @@ def reader_loop(sock: socket.socket, state: SessionState) -> None: shell = decode_shell_packet(state, fromradio.packet) if not shell: continue + state.prune_sent_frames(shell.ack_seq) + if shell.op == state.pb2.mesh.DMShell.ACK: + replay_from = decode_replay_request(shell.payload) + if replay_from is not None: + state.event_queue.put(f"peer requested replay from seq={replay_from}") + replay_frames_from(sock, state, replay_from) + continue + + action, missing_from = state.note_received_seq(shell.seq) + if action == "duplicate": + send_ack_frame(sock, state) + continue + if action == "gap": + state.event_queue.put(f"missing frame before seq={shell.seq}, requesting replay from seq={missing_from}") + send_ack_frame(sock, state, replay_from=missing_from) + continue + if shell.op == state.pb2.mesh.DMShell.OPEN_OK: state.session_id = shell.session_id + state.set_receive_cursor(shell.seq) state.active = True state.opened_event.set() pid = int.from_bytes(shell.payload, "big") if shell.payload else 0 diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index bf7d8679b20..fed1a526e96 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -29,6 +29,7 @@ namespace constexpr uint16_t PTY_COLS_DEFAULT = 120; constexpr uint16_t PTY_ROWS_DEFAULT = 40; constexpr size_t MAX_PTY_READ_SIZE = 200; +constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); struct BytesDecodeState { uint8_t *buf; @@ -67,6 +68,20 @@ bool encodeBytesField(pb_ostream_t *stream, const pb_field_iter_t *field, void * } return pb_encode_string(stream, state->buf, state->len); } + +void encodeUint32BE(uint8_t *dest, uint32_t value) +{ + dest[0] = static_cast((value >> 24) & 0xff); + dest[1] = static_cast((value >> 16) & 0xff); + dest[2] = static_cast((value >> 8) & 0xff); + dest[3] = static_cast(value & 0xff); +} + +uint32_t decodeUint32BE(const uint8_t *src) +{ + return (static_cast(src[0]) << 24) | (static_cast(src[1]) << 16) | (static_cast(src[2]) << 8) | + static_cast(src[3]); +} } // namespace DMShellModule::DMShellModule() @@ -94,20 +109,35 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) return ProcessMessage::STOP; } + if (frame.ackSeq > 0) { + pruneSentFrames(frame.ackSeq); + } + + if (frame.op == meshtastic_DMShell_OpCode_ACK) { + if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { + handleAckFrame(frame); + } + return ProcessMessage::STOP; + } + if (frame.op == meshtastic_DMShell_OpCode_OPEN) { LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.sessionId); if (!openSession(mp, frame)) { const char *msg = "open_failed"; - sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, frame.seq, - reinterpret_cast(msg), strlen(msg)); + sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, 0, + reinterpret_cast(msg), strlen(msg), 0, 0, frame.seq); } return ProcessMessage::STOP; } if (!session.active || frame.sessionId != session.sessionId || getFrom(&mp) != session.peer) { const char *msg = "invalid_session"; - sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, frame.seq, - reinterpret_cast(msg), strlen(msg)); + sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, 0, + reinterpret_cast(msg), strlen(msg), 0, 0, frame.seq); + return ProcessMessage::STOP; + } + + if (!shouldProcessIncomingFrame(frame)) { return ProcessMessage::STOP; } @@ -130,7 +160,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } break; case meshtastic_DMShell_OpCode_PING: - sendControl(meshtastic_DMShell_OpCode_PONG, nullptr, 0, frame.seq); + sendControl(meshtastic_DMShell_OpCode_PONG, nullptr, 0); break; case meshtastic_DMShell_OpCode_CLOSE: closeSession("peer_close", true); @@ -164,7 +194,7 @@ int32_t DMShellModule::runOnce() const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); - sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead), session.txSeq++); + sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); continue; } @@ -280,7 +310,9 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr session.channel = mp.channel; session.masterFd = masterFd; session.childPid = childPid; - session.txSeq = 0; + session.nextTxSeq = 1; + session.lastAckedRxSeq = frame.seq; + session.nextExpectedRxSeq = frame.seq + 1; session.lastActivityMs = millis(); uint8_t payload[sizeof(uint32_t)] = {0}; @@ -288,8 +320,8 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr ((static_cast(session.childPid) << 8) & 0xff0000) | (static_cast(session.childPid) >> 24); memcpy(payload, &pidBE, sizeof(payload)); - sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, frame.seq, payload, - sizeof(payload), ws.ws_col, ws.ws_row); + sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, allocTxSeq(), payload, + sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid); return true; @@ -316,7 +348,7 @@ void DMShellModule::closeSession(const char *reason, bool notifyPeer) if (notifyPeer) { const size_t reasonLen = strnlen(reason, 256); - sendControl(meshtastic_DMShell_OpCode_CLOSED, reinterpret_cast(reason), reasonLen, session.txSeq++); + sendControl(meshtastic_DMShell_OpCode_CLOSED, reinterpret_cast(reason), reasonLen); } if (session.masterFd >= 0) { @@ -348,20 +380,142 @@ void DMShellModule::reapChildIfExited() } } -void DMShellModule::sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen, uint32_t seq) +uint32_t DMShellModule::allocTxSeq() +{ + return session.nextTxSeq++; +} + +void DMShellModule::rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, + size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags) +{ + if (seq == 0 || op == meshtastic_DMShell_OpCode_ACK) { + return; + } + + auto &entry = session.txHistory[session.txHistoryNext]; + entry.valid = true; + entry.op = op; + entry.sessionId = sessionId; + entry.seq = seq; + entry.ackSeq = ackSeq; + entry.cols = cols; + entry.rows = rows; + entry.flags = flags; + entry.payloadLen = payloadLen; + if (payload && payloadLen > 0) { + memcpy(entry.payload, payload, payloadLen); + } + + session.txHistoryNext = (session.txHistoryNext + 1) % session.txHistory.size(); +} + +void DMShellModule::pruneSentFrames(uint32_t ackSeq) +{ + if (ackSeq == 0) { + return; + } + + for (auto &entry : session.txHistory) { + if (entry.valid && entry.seq <= ackSeq) { + entry.valid = false; + } + } +} + +void DMShellModule::resendFramesFrom(uint32_t startSeq) +{ + if (startSeq == 0) { + return; + } + + uint32_t lastSeq = startSeq - 1; + for (size_t sentCount = 0; sentCount < session.txHistory.size(); ++sentCount) { + DMShellSession::SentFrame *best = nullptr; + for (auto &entry : session.txHistory) { + if (!entry.valid || entry.seq < startSeq || entry.seq <= lastSeq) { + continue; + } + if (!best || entry.seq < best->seq) { + best = &entry; + } + } + + if (!best) { + break; + } + + LOG_INFO("DMShell: replaying frame seq=%u op=%d", best->seq, best->op); + sendFrameToPeer(session.peer, session.channel, best->op, best->sessionId, best->seq, best->payload, best->payloadLen, + best->cols, best->rows, best->ackSeq, best->flags, false); + lastSeq = best->seq; + } +} + +void DMShellModule::sendAck(uint32_t replayFromSeq) +{ + uint8_t payload[REPLAY_REQUEST_SIZE] = {0}; + const uint8_t *payloadPtr = nullptr; + size_t payloadLen = 0; + if (replayFromSeq > 0) { + encodeUint32BE(payload, replayFromSeq); + payloadPtr = payload; + payloadLen = sizeof(payload); + LOG_WARN("DMShell: requesting replay from seq=%u", replayFromSeq); + } + + sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_ACK, session.sessionId, 0, payloadPtr, payloadLen, 0, + 0, session.lastAckedRxSeq, 0, false); +} + +void DMShellModule::sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen) +{ + sendFrameToPeer(session.peer, session.channel, op, session.sessionId, allocTxSeq(), payload, payloadLen, 0, 0, + session.lastAckedRxSeq); +} + +bool DMShellModule::handleAckFrame(const DMShellFrame &frame) +{ + if (frame.payloadLen >= REPLAY_REQUEST_SIZE) { + const uint32_t replayFromSeq = decodeUint32BE(frame.payload); + resendFramesFrom(replayFromSeq); + } + return true; +} + +bool DMShellModule::shouldProcessIncomingFrame(const DMShellFrame &frame) { - sendFrameToPeer(session.peer, session.channel, op, session.sessionId, seq, payload, payloadLen); + if (frame.seq == 0) { + return true; + } + + if (frame.seq < session.nextExpectedRxSeq) { + sendAck(); + return false; + } + + if (frame.seq > session.nextExpectedRxSeq) { + sendAck(session.nextExpectedRxSeq); + return false; + } + + session.lastAckedRxSeq = frame.seq; + session.nextExpectedRxSeq = frame.seq + 1; + return true; } void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, - uint32_t flags) + uint32_t flags, bool remember) { meshtastic_MeshPacket *packet = buildFramePacket(op, sessionId, seq, payload, payloadLen, cols, rows, ackSeq, flags); if (!packet) { return; } + if (remember) { + rememberSentFrame(op, sessionId, seq, payload, payloadLen, cols, rows, ackSeq, flags); + } + packet->to = peer; packet->channel = channel; packet->want_ack = false; @@ -372,7 +526,7 @@ void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DM void DMShellModule::sendError(const char *message) { const size_t len = strnlen(message, meshtastic_Constants_DATA_PAYLOAD_LEN); - sendControl(meshtastic_DMShell_OpCode_ERROR, reinterpret_cast(message), len, session.txSeq++); + sendControl(meshtastic_DMShell_OpCode_ERROR, reinterpret_cast(message), len); } meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index dde56a3c733..d528dfbd322 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -7,6 +7,7 @@ #include "configuration.h" #include "mesh/generated/meshtastic/mesh.pb.h" #include +#include #include #if defined(ARCH_PORTDUINO) @@ -30,8 +31,24 @@ struct DMShellSession { uint8_t channel = 0; int masterFd = -1; int childPid = -1; - uint32_t txSeq = 0; + uint32_t nextTxSeq = 1; + uint32_t lastAckedRxSeq = 0; + uint32_t nextExpectedRxSeq = 1; uint32_t lastActivityMs = 0; + struct SentFrame { + bool valid = false; + meshtastic_DMShell_OpCode op = meshtastic_DMShell_OpCode_ERROR; + uint32_t sessionId = 0; + uint32_t seq = 0; + uint32_t ackSeq = 0; + uint32_t cols = 0; + uint32_t rows = 0; + uint32_t flags = 0; + uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; + size_t payloadLen = 0; + }; + std::array txHistory = {}; + size_t txHistoryNext = 0; }; class DMShellModule : private concurrency::OSThread, public SinglePortModule @@ -52,14 +69,22 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule bool parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &outFrame); bool isAuthorizedPacket(const meshtastic_MeshPacket &mp) const; bool openSession(const meshtastic_MeshPacket &mp, const DMShellFrame &frame); + bool handleAckFrame(const DMShellFrame &frame); + bool shouldProcessIncomingFrame(const DMShellFrame &frame); bool writeSessionInput(const DMShellFrame &frame); void closeSession(const char *reason, bool notifyPeer); void reapChildIfExited(); - void sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen, uint32_t seq = 0); + uint32_t allocTxSeq(); + void rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, + size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags); + void pruneSentFrames(uint32_t ackSeq); + void resendFramesFrom(uint32_t startSeq); + void sendAck(uint32_t replayFromSeq = 0); + void sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen); void sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0, uint32_t ackSeq = 0, - uint32_t flags = 0); + uint32_t flags = 0, bool remember = true); void sendError(const char *message); meshtastic_MeshPacket *buildFramePacket(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, From f5335f22eac45a6f0c7f21423c7c4606bf554bef Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 16:42:46 -0500 Subject: [PATCH 05/22] troubleshoot dropped packets --- bin/dmshell_client.py | 2 +- src/mesh/RadioLibInterface.h | 2 ++ src/modules/DMShell.cpp | 26 ++++++++++++++++---------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index 6f76d79a4d8..df082eaf796 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -409,7 +409,7 @@ def reader_loop(sock: socket.socket, state: SessionState) -> None: send_ack_frame(sock, state) continue if action == "gap": - state.event_queue.put(f"missing frame before seq={shell.seq}, requesting replay from seq={missing_from}") + # state.event_queue.put(f"missing frame before seq={shell.seq}, requesting replay from seq={missing_from}") send_ack_frame(sock, state, replay_from=missing_from) continue diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index ca3d78503ed..5ef68862928 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -172,6 +172,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified /** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */ virtual bool findInTxQueue(NodeNum from, PacketId id) override; + uint8_t packetsInTxQueue() { return txQueue.getMaxLen() - txQueue.getFree(); } + private: /** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually * doing the transmit */ diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index fed1a526e96..36a1b0247e0 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -98,13 +98,21 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) return ProcessMessage::STOP; } + // TODO: double-check the sender is the same as the one we have an active session with before processing ACKs + if (frame.op == meshtastic_DMShell_OpCode_ACK) { + if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { + handleAckFrame(frame); + } + return ProcessMessage::STOP; + } + if (frame.op >= 64) { - LOG_WARN("DMShell: ignoring frame with op code %d", frame.op); + LOG_WARN("DMShell: ignoring frame with op code %d, seq %d", frame.op, frame.seq); return ProcessMessage::CONTINUE; } if (!isAuthorizedPacket(mp)) { - LOG_WARN("DMShell: unauthorized sender 0x%x", mp.from); + LOG_WARN("DMShell: unauthorized sender 0x%x, %u", mp.from, frame.op); myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp); return ProcessMessage::STOP; } @@ -113,13 +121,6 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) pruneSentFrames(frame.ackSeq); } - if (frame.op == meshtastic_DMShell_OpCode_ACK) { - if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { - handleAckFrame(frame); - } - return ProcessMessage::STOP; - } - if (frame.op == meshtastic_DMShell_OpCode_OPEN) { LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.sessionId); if (!openSession(mp, frame)) { @@ -189,6 +190,10 @@ int32_t DMShellModule::runOnce() return 100; } + if (RadioLibInterface::instance->packetsInTxQueue() > 1) { + return 50; + } + uint8_t outBuf[MAX_PTY_READ_SIZE]; while (session.masterFd >= 0) { const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); @@ -196,7 +201,8 @@ int32_t DMShellModule::runOnce() LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); - continue; + // continue; + return 50; } if (bytesRead == 0) { From 8d3f9222ffc090132da0adfba1d4236d322efc30 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 17:52:47 -0500 Subject: [PATCH 06/22] Don't firehose missing packets --- bin/dmshell_client.py | 57 ++++++++++++++++++++++++++++------------- src/modules/DMShell.cpp | 52 ++++++++++++++++++++++--------------- src/modules/DMShell.h | 3 ++- 3 files changed, 73 insertions(+), 39 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index df082eaf796..a9d718c48bb 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -202,13 +202,14 @@ class SessionState: next_seq: int = 1 last_rx_seq: int = 0 next_expected_rx_seq: int = 1 + highest_seen_rx_seq: int = 0 active: bool = False stopped: bool = False opened_event: threading.Event = field(default_factory=threading.Event) closed_event: threading.Event = field(default_factory=threading.Event) event_queue: "queue.Queue[str]" = field(default_factory=queue.Queue) tx_lock: threading.Lock = field(default_factory=threading.Lock) - tx_history: deque[SentShellFrame] = field(default_factory=lambda: deque(maxlen=10)) + tx_history: deque[SentShellFrame] = field(default_factory=lambda: deque(maxlen=50)) def alloc_seq(self) -> int: with self.tx_lock: @@ -225,17 +226,32 @@ def note_received_seq(self, seq: int) -> tuple[str, Optional[int]]: if seq == 0: return ("process", None) if seq < self.next_expected_rx_seq: + if self.highest_seen_rx_seq >= self.next_expected_rx_seq: + return ("gap", self.next_expected_rx_seq) return ("duplicate", None) if seq > self.next_expected_rx_seq: + if seq > self.highest_seen_rx_seq: + self.highest_seen_rx_seq = seq return ("gap", self.next_expected_rx_seq) self.last_rx_seq = seq self.next_expected_rx_seq = seq + 1 + if seq > self.highest_seen_rx_seq: + self.highest_seen_rx_seq = seq + if self.highest_seen_rx_seq < self.next_expected_rx_seq: + self.highest_seen_rx_seq = 0 return ("process", None) + def pending_missing_seq(self) -> Optional[int]: + with self.tx_lock: + if self.highest_seen_rx_seq >= self.next_expected_rx_seq: + return self.next_expected_rx_seq + return None + def set_receive_cursor(self, seq: int) -> None: with self.tx_lock: self.last_rx_seq = seq self.next_expected_rx_seq = seq + 1 + self.highest_seen_rx_seq = seq def remember_sent_frame(self, frame: SentShellFrame) -> None: if frame.seq == 0 or frame.op == self.pb2.mesh.DMShell.ACK: @@ -247,7 +263,7 @@ def prune_sent_frames(self, ack_seq: int) -> None: if ack_seq <= 0: return with self.tx_lock: - self.tx_history = deque((frame for frame in self.tx_history if frame.seq > ack_seq), maxlen=10) + self.tx_history = deque((frame for frame in self.tx_history if frame.seq > ack_seq), maxlen=50) def replay_frames_from(self, start_seq: int) -> list[SentShellFrame]: with self.tx_lock: @@ -332,24 +348,24 @@ def send_ack_frame(sock: socket.socket, state: SessionState, replay_from: Option def replay_frames_from(sock: socket.socket, state: SessionState, start_seq: int) -> None: - frames = state.replay_frames_from(start_seq) - if not frames: + frame = next((f for f in state.replay_frames_from(start_seq) if f.seq == start_seq), None) + if frame is None: state.event_queue.put(f"replay unavailable from seq={start_seq}") return - for frame in frames: - send_shell_frame( - sock, - state, - frame.op, - payload=frame.payload, - cols=frame.cols, - rows=frame.rows, - session_id=frame.session_id, - ack_seq=frame.ack_seq, - seq=frame.seq, - flags=frame.flags, - remember=False, - ) + state.event_queue.put(f"replay frame seq={start_seq}") + send_shell_frame( + sock, + state, + frame.op, + payload=frame.payload, + cols=frame.cols, + rows=frame.rows, + session_id=frame.session_id, + ack_seq=frame.ack_seq, + seq=frame.seq, + flags=frame.flags, + remember=False, + ) def wait_for_config_complete(sock: socket.socket, pb2, timeout: float, verbose: bool) -> None: @@ -398,6 +414,7 @@ def reader_loop(sock: socket.socket, state: SessionState) -> None: continue state.prune_sent_frames(shell.ack_seq) if shell.op == state.pb2.mesh.DMShell.ACK: + state.event_queue.put("peer requested replay") replay_from = decode_replay_request(shell.payload) if replay_from is not None: state.event_queue.put(f"peer requested replay from seq={replay_from}") @@ -437,6 +454,10 @@ def reader_loop(sock: socket.socket, state: SessionState) -> None: return elif shell.op == state.pb2.mesh.DMShell.PONG: state.event_queue.put("pong") + + missing_seq = state.pending_missing_seq() + if missing_seq is not None: + send_ack_frame(sock, state, replay_from=missing_seq) elif state.verbose and variant: state.event_queue.put(f"fromradio {variant}") diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 36a1b0247e0..001d001aac0 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -103,7 +103,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { handleAckFrame(frame); } - return ProcessMessage::STOP; + return ProcessMessage::CONTINUE; } if (frame.op >= 64) { @@ -319,6 +319,7 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr session.nextTxSeq = 1; session.lastAckedRxSeq = frame.seq; session.nextExpectedRxSeq = frame.seq + 1; + session.highestSeenRxSeq = frame.seq; session.lastActivityMs = millis(); uint8_t payload[sizeof(uint32_t)] = {0}; @@ -434,27 +435,23 @@ void DMShellModule::resendFramesFrom(uint32_t startSeq) return; } - uint32_t lastSeq = startSeq - 1; - for (size_t sentCount = 0; sentCount < session.txHistory.size(); ++sentCount) { - DMShellSession::SentFrame *best = nullptr; - for (auto &entry : session.txHistory) { - if (!entry.valid || entry.seq < startSeq || entry.seq <= lastSeq) { - continue; - } - if (!best || entry.seq < best->seq) { - best = &entry; - } - } - - if (!best) { - break; + DMShellSession::SentFrame *match = nullptr; + for (auto &entry : session.txHistory) { + if (!entry.valid || entry.seq != startSeq) { + continue; } + match = &entry; + break; + } - LOG_INFO("DMShell: replaying frame seq=%u op=%d", best->seq, best->op); - sendFrameToPeer(session.peer, session.channel, best->op, best->sessionId, best->seq, best->payload, best->payloadLen, - best->cols, best->rows, best->ackSeq, best->flags, false); - lastSeq = best->seq; + if (!match) { + LOG_WARN("DMShell: replay request for seq=%u not found in history", startSeq); + return; } + + LOG_INFO("DMShell: replaying frame seq=%u op=%d", match->seq, match->op); + sendFrameToPeer(session.peer, session.channel, match->op, match->sessionId, match->seq, match->payload, match->payloadLen, + match->cols, match->rows, match->ackSeq, match->flags, false); } void DMShellModule::sendAck(uint32_t replayFromSeq) @@ -495,17 +492,32 @@ bool DMShellModule::shouldProcessIncomingFrame(const DMShellFrame &frame) } if (frame.seq < session.nextExpectedRxSeq) { - sendAck(); + if (session.highestSeenRxSeq >= session.nextExpectedRxSeq) { + sendAck(session.nextExpectedRxSeq); + } else { + sendAck(); + } return false; } if (frame.seq > session.nextExpectedRxSeq) { + if (frame.seq > session.highestSeenRxSeq) { + session.highestSeenRxSeq = frame.seq; + } sendAck(session.nextExpectedRxSeq); return false; } session.lastAckedRxSeq = frame.seq; session.nextExpectedRxSeq = frame.seq + 1; + if (frame.seq > session.highestSeenRxSeq) { + session.highestSeenRxSeq = frame.seq; + } + if (session.highestSeenRxSeq >= session.nextExpectedRxSeq) { + sendAck(session.nextExpectedRxSeq); + } else { + session.highestSeenRxSeq = 0; + } return true; } diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index d528dfbd322..302c6cac2a2 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -34,6 +34,7 @@ struct DMShellSession { uint32_t nextTxSeq = 1; uint32_t lastAckedRxSeq = 0; uint32_t nextExpectedRxSeq = 1; + uint32_t highestSeenRxSeq = 0; uint32_t lastActivityMs = 0; struct SentFrame { bool valid = false; @@ -47,7 +48,7 @@ struct DMShellSession { uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; size_t payloadLen = 0; }; - std::array txHistory = {}; + std::array txHistory = {}; size_t txHistoryNext = 0; }; From 5a619c90313650e6a1cf1d806331205a385d1ac1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 21:15:23 -0500 Subject: [PATCH 07/22] Attempt at better responsiveness --- bin/dmshell_client.py | 175 ++++++++++++++++++++++++++++++++-------- src/modules/DMShell.cpp | 10 +++ 2 files changed, 152 insertions(+), 33 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index a9d718c48bb..10d9a76e792 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -18,7 +18,7 @@ from collections import deque from dataclasses import dataclass, field from pathlib import Path -from typing import Optional +from typing import Optional, TextIO START1 = 0x94 @@ -28,6 +28,7 @@ DEFAULT_HOP_LIMIT = 3 LOCAL_ESCAPE_BYTE = b"\x1d" # Ctrl+] REPLAY_REQUEST_SIZE = 4 +MISSING_SEQ_RETRY_INTERVAL_SEC = 1.0 def parse_args() -> argparse.Namespace: @@ -210,6 +211,13 @@ class SessionState: event_queue: "queue.Queue[str]" = field(default_factory=queue.Queue) tx_lock: threading.Lock = field(default_factory=threading.Lock) tx_history: deque[SentShellFrame] = field(default_factory=lambda: deque(maxlen=50)) + pending_rx_frames: dict[int, object] = field(default_factory=dict) + last_requested_missing_seq: int = 0 + last_missing_request_time: float = 0.0 + requested_missing_seqs: set[int] = field(default_factory=set) + replay_log_lock: threading.Lock = field(default_factory=threading.Lock) + replay_log_file: Optional[TextIO] = None + replay_log_path: Optional[Path] = None def alloc_seq(self) -> int: with self.tx_lock: @@ -235,24 +243,95 @@ def note_received_seq(self, seq: int) -> tuple[str, Optional[int]]: return ("gap", self.next_expected_rx_seq) self.last_rx_seq = seq self.next_expected_rx_seq = seq + 1 + if self.last_requested_missing_seq != 0 and self.next_expected_rx_seq > self.last_requested_missing_seq: + self.last_requested_missing_seq = 0 if seq > self.highest_seen_rx_seq: self.highest_seen_rx_seq = seq if self.highest_seen_rx_seq < self.next_expected_rx_seq: self.highest_seen_rx_seq = 0 return ("process", None) + def remember_out_of_order_frame(self, shell) -> None: + with self.tx_lock: + if shell.seq <= self.next_expected_rx_seq: + return + if shell.seq not in self.pending_rx_frames: + self.pending_rx_frames[shell.seq] = shell + if shell.seq > self.highest_seen_rx_seq: + self.highest_seen_rx_seq = shell.seq + + def pop_next_buffered_frame(self): + with self.tx_lock: + return self.pending_rx_frames.pop(self.next_expected_rx_seq, None) + def pending_missing_seq(self) -> Optional[int]: with self.tx_lock: if self.highest_seen_rx_seq >= self.next_expected_rx_seq: return self.next_expected_rx_seq return None + def request_missing_seq_once(self) -> Optional[int]: + with self.tx_lock: + if self.highest_seen_rx_seq < self.next_expected_rx_seq: + return None + now = time.monotonic() + if ( + self.last_requested_missing_seq == self.next_expected_rx_seq + and (now - self.last_missing_request_time) < MISSING_SEQ_RETRY_INTERVAL_SEC + ): + return None + self.last_requested_missing_seq = self.next_expected_rx_seq + self.last_missing_request_time = now + return self.last_requested_missing_seq + def set_receive_cursor(self, seq: int) -> None: with self.tx_lock: self.last_rx_seq = seq self.next_expected_rx_seq = seq + 1 self.highest_seen_rx_seq = seq + def open_replay_log(self, session_id: int) -> None: + with self.replay_log_lock: + if self.replay_log_file is not None: + return + path = Path.cwd() / f"{session_id:08x}.log" + self.replay_log_file = path.open("a", encoding="utf-8") + self.replay_log_path = path + self.replay_log_file.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} session_open session=0x{session_id:08x}\n") + self.replay_log_file.flush() + + def log_replay_event(self, event: str, seq: int, detail: str = "") -> None: + with self.replay_log_lock: + if self.replay_log_file is None: + return + extra = f" {detail}" if detail else "" + self.replay_log_file.write( + f"{time.strftime('%Y-%m-%d %H:%M:%S')} {event} seq={seq}{extra}\n" + ) + self.replay_log_file.flush() + + def note_missing_seq_requested(self, seq: int, reason: str) -> None: + with self.tx_lock: + self.requested_missing_seqs.add(seq) + self.log_replay_event("missing_requested", seq, f"reason={reason}") + + def note_replayed_seq_received(self, seq: int) -> None: + with self.tx_lock: + was_requested = seq in self.requested_missing_seqs + if was_requested: + self.requested_missing_seqs.remove(seq) + if was_requested: + self.log_replay_event("replay_received", seq) + + def close_replay_log(self) -> None: + with self.replay_log_lock: + if self.replay_log_file is None: + return + self.replay_log_file.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} session_close\n") + self.replay_log_file.flush() + self.replay_log_file.close() + self.replay_log_file = None + def remember_sent_frame(self, frame: SentShellFrame) -> None: if frame.seq == 0 or frame.op == self.pb2.mesh.DMShell.ACK: return @@ -351,8 +430,10 @@ def replay_frames_from(sock: socket.socket, state: SessionState, start_seq: int) frame = next((f for f in state.replay_frames_from(start_seq) if f.seq == start_seq), None) if frame is None: state.event_queue.put(f"replay unavailable from seq={start_seq}") + state.log_replay_event("replay_unavailable", start_seq) return - state.event_queue.put(f"replay frame seq={start_seq}") + state.log_replay_event("replay_sent", start_seq) + #state.event_queue.put(f"replay frame seq={start_seq}") send_shell_frame( sock, state, @@ -397,6 +478,37 @@ def decode_shell_packet(state: SessionState, packet) -> Optional[object]: def reader_loop(sock: socket.socket, state: SessionState) -> None: + def handle_in_order_shell(shell) -> bool: + state.note_replayed_seq_received(shell.seq) + if shell.op == state.pb2.mesh.DMShell.OPEN_OK: + state.session_id = shell.session_id + state.open_replay_log(state.session_id) + state.set_receive_cursor(shell.seq) + state.active = True + state.opened_event.set() + pid = int.from_bytes(shell.payload, "big") if shell.payload else 0 + state.event_queue.put( + f"opened session=0x{shell.session_id:08x} cols={shell.cols} rows={shell.rows} pid={pid}" + ) + if state.replay_log_path is not None: + state.event_queue.put(f"replay log: {state.replay_log_path}") + elif shell.op == state.pb2.mesh.DMShell.OUTPUT: + if shell.payload: + sys.stdout.buffer.write(shell.payload) + sys.stdout.buffer.flush() + elif shell.op == state.pb2.mesh.DMShell.ERROR: + message = shell.payload.decode("utf-8", errors="replace") + state.event_queue.put(f"remote error: {message}") + elif shell.op == state.pb2.mesh.DMShell.CLOSED: + message = shell.payload.decode("utf-8", errors="replace") + state.event_queue.put(f"session closed: {message}") + state.closed_event.set() + state.active = False + return True + elif shell.op == state.pb2.mesh.DMShell.PONG: + state.event_queue.put("pong") + return False + while not state.stopped: try: fromradio = state.pb2.mesh.FromRadio() @@ -414,50 +526,46 @@ def reader_loop(sock: socket.socket, state: SessionState) -> None: continue state.prune_sent_frames(shell.ack_seq) if shell.op == state.pb2.mesh.DMShell.ACK: - state.event_queue.put("peer requested replay") + #state.event_queue.put("peer requested replay") replay_from = decode_replay_request(shell.payload) if replay_from is not None: - state.event_queue.put(f"peer requested replay from seq={replay_from}") + #state.event_queue.put(f"peer requested replay from seq={replay_from}") replay_frames_from(sock, state, replay_from) continue action, missing_from = state.note_received_seq(shell.seq) if action == "duplicate": - send_ack_frame(sock, state) + req = state.request_missing_seq_once() + if req is not None: + state.note_missing_seq_requested(req, "duplicate") + send_ack_frame(sock, state, replay_from=req) continue if action == "gap": - # state.event_queue.put(f"missing frame before seq={shell.seq}, requesting replay from seq={missing_from}") - send_ack_frame(sock, state, replay_from=missing_from) + state.remember_out_of_order_frame(shell) + req = state.request_missing_seq_once() + if req is not None: + state.note_missing_seq_requested(req, "gap") + send_ack_frame(sock, state, replay_from=req) continue - if shell.op == state.pb2.mesh.DMShell.OPEN_OK: - state.session_id = shell.session_id - state.set_receive_cursor(shell.seq) - state.active = True - state.opened_event.set() - pid = int.from_bytes(shell.payload, "big") if shell.payload else 0 - state.event_queue.put( - f"opened session=0x{shell.session_id:08x} cols={shell.cols} rows={shell.rows} pid={pid}" - ) - elif shell.op == state.pb2.mesh.DMShell.OUTPUT: - if shell.payload: - sys.stdout.buffer.write(shell.payload) - sys.stdout.buffer.flush() - elif shell.op == state.pb2.mesh.DMShell.ERROR: - message = shell.payload.decode("utf-8", errors="replace") - state.event_queue.put(f"remote error: {message}") - elif shell.op == state.pb2.mesh.DMShell.CLOSED: - message = shell.payload.decode("utf-8", errors="replace") - state.event_queue.put(f"session closed: {message}") - state.closed_event.set() - state.active = False + if handle_in_order_shell(shell): return - elif shell.op == state.pb2.mesh.DMShell.PONG: - state.event_queue.put("pong") - missing_seq = state.pending_missing_seq() - if missing_seq is not None: - send_ack_frame(sock, state, replay_from=missing_seq) + while True: + buffered_shell = state.pop_next_buffered_frame() + if buffered_shell is None: + break + buffered_action, _ = state.note_received_seq(buffered_shell.seq) + if buffered_action != "process": + state.remember_out_of_order_frame(buffered_shell) + break + if handle_in_order_shell(buffered_shell): + return + + req = state.request_missing_seq_once() + if req is not None: + state.note_missing_seq_requested(req, "post_process_gap") + send_ack_frame(sock, state, replay_from=req) elif state.verbose and variant: state.event_queue.put(f"fromradio {variant}") @@ -614,6 +722,7 @@ def main() -> int: state.stopped = True drain_events(state) reader.join(timeout=1.0) + state.close_replay_log() return 0 diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 001d001aac0..686f6d64dfb 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -148,6 +148,14 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) case meshtastic_DMShell_OpCode_INPUT: if (!writeSessionInput(frame)) { sendError("input_write_failed"); + } else { + uint8_t outBuf[MAX_PTY_READ_SIZE]; + const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); + if (bytesRead > 0) { + LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); + sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); + session.lastActivityMs = millis(); + } } break; case meshtastic_DMShell_OpCode_RESIZE: @@ -202,6 +210,8 @@ int32_t DMShellModule::runOnce() sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); // continue; + // do we want to ack every data message, and only send the next on ack? + // would require some retry logic. Maybe re-use the wantAck bit return 50; } From f9bedd8adc9135cc427dc245d73138970c404198 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 8 Apr 2026 23:19:02 -0500 Subject: [PATCH 08/22] DMShell heartbeat --- bin/dmshell_client.py | 127 +++++++++++++++++++++++++++++++++++++++- src/modules/DMShell.cpp | 33 ++++++++++- 2 files changed, 155 insertions(+), 5 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index 10d9a76e792..227a5a03b69 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -28,7 +28,13 @@ DEFAULT_HOP_LIMIT = 3 LOCAL_ESCAPE_BYTE = b"\x1d" # Ctrl+] REPLAY_REQUEST_SIZE = 4 +HEARTBEAT_STATUS_SIZE = 8 MISSING_SEQ_RETRY_INTERVAL_SEC = 1.0 +INPUT_BATCH_WINDOW_SEC = 0.15 +INPUT_BATCH_MAX_BYTES = 64 +HEARTBEAT_IDLE_DELAY_SEC = 5.0 +HEARTBEAT_REPEAT_SEC = 15.0 +HEARTBEAT_POLL_INTERVAL_SEC = 0.25 def parse_args() -> argparse.Namespace: @@ -210,6 +216,7 @@ class SessionState: closed_event: threading.Event = field(default_factory=threading.Event) event_queue: "queue.Queue[str]" = field(default_factory=queue.Queue) tx_lock: threading.Lock = field(default_factory=threading.Lock) + socket_lock: threading.Lock = field(default_factory=threading.Lock) tx_history: deque[SentShellFrame] = field(default_factory=lambda: deque(maxlen=50)) pending_rx_frames: dict[int, object] = field(default_factory=dict) last_requested_missing_seq: int = 0 @@ -218,6 +225,8 @@ class SessionState: replay_log_lock: threading.Lock = field(default_factory=threading.Lock) replay_log_file: Optional[TextIO] = None replay_log_path: Optional[Path] = None + last_transport_activity_time: float = field(default_factory=time.monotonic) + last_heartbeat_sent_time: float = 0.0 def alloc_seq(self) -> int: with self.tx_lock: @@ -229,6 +238,40 @@ def current_ack_seq(self) -> int: with self.tx_lock: return self.last_rx_seq + def highest_sent_seq(self) -> int: + with self.tx_lock: + return max(0, self.next_seq - 1) + + def heartbeat_payload(self) -> bytes: + with self.tx_lock: + return encode_heartbeat_status(max(0, self.next_seq - 1), self.last_rx_seq) + + def note_outbound_packet(self, heartbeat: bool = False) -> None: + with self.tx_lock: + now = time.monotonic() + if heartbeat: + self.last_heartbeat_sent_time = now + else: + self.last_transport_activity_time = now + + def note_inbound_packet(self) -> None: + with self.tx_lock: + self.last_transport_activity_time = time.monotonic() + + def heartbeat_due(self) -> bool: + with self.tx_lock: + now = time.monotonic() + if (now - self.last_transport_activity_time) < HEARTBEAT_IDLE_DELAY_SEC: + return False + if self.last_heartbeat_sent_time <= self.last_transport_activity_time: + return True + return (now - self.last_heartbeat_sent_time) >= HEARTBEAT_REPEAT_SEC + + def note_peer_reported_tx_seq(self, seq: int) -> None: + with self.tx_lock: + if seq > self.highest_seen_rx_seq: + self.highest_seen_rx_seq = seq + def note_received_seq(self, seq: int) -> tuple[str, Optional[int]]: with self.tx_lock: if seq == 0: @@ -359,6 +402,16 @@ def decode_replay_request(payload: bytes) -> Optional[int]: return struct.unpack(">I", payload[:REPLAY_REQUEST_SIZE])[0] +def encode_heartbeat_status(last_tx_seq: int, last_rx_seq: int) -> bytes: + return struct.pack(">II", last_tx_seq, last_rx_seq) + + +def decode_heartbeat_status(payload: bytes) -> Optional[tuple[int, int]]: + if len(payload) < HEARTBEAT_STATUS_SIZE: + return None + return struct.unpack(">II", payload[:HEARTBEAT_STATUS_SIZE]) + + def send_toradio(sock: socket.socket, toradio) -> None: send_stream_frame(sock, toradio.SerializeToString()) @@ -395,6 +448,7 @@ def send_shell_frame( seq: Optional[int] = None, flags: int = 0, remember: bool = True, + heartbeat: bool = False, ) -> int: if seq is None: seq = 0 if op == state.pb2.mesh.DMShell.ACK else state.alloc_seq() @@ -413,11 +467,13 @@ def send_shell_frame( shell.flags = flags if payload: shell.payload = payload - send_toradio(sock, make_toradio_packet(state.pb2, state, shell)) + with state.socket_lock: + send_toradio(sock, make_toradio_packet(state.pb2, state, shell)) if remember: state.remember_sent_frame( SentShellFrame(op=op, session_id=session_id, seq=seq, ack_seq=ack_seq, payload=payload, cols=cols, rows=rows, flags=flags) ) + state.note_outbound_packet(heartbeat=heartbeat) return seq @@ -506,6 +562,18 @@ def handle_in_order_shell(shell) -> bool: state.active = False return True elif shell.op == state.pb2.mesh.DMShell.PONG: + heartbeat_status = decode_heartbeat_status(shell.payload) + if heartbeat_status is not None: + remote_last_tx_seq, remote_last_rx_seq = heartbeat_status + local_latest_tx_seq = state.highest_sent_seq() + if remote_last_rx_seq < local_latest_tx_seq: + replay_frames_from(sock, state, remote_last_rx_seq + 1) + if remote_last_tx_seq > state.current_ack_seq(): + state.note_peer_reported_tx_seq(remote_last_tx_seq) + req = state.request_missing_seq_once() + if req is not None: + state.note_missing_seq_requested(req, "heartbeat_status") + send_ack_frame(sock, state, replay_from=req) state.event_queue.put("pong") return False @@ -524,6 +592,7 @@ def handle_in_order_shell(shell) -> bool: shell = decode_shell_packet(state, fromradio.packet) if not shell: continue + state.note_inbound_packet() state.prune_sent_frames(shell.ack_seq) if shell.op == state.pb2.mesh.DMShell.ACK: #state.event_queue.put("peer requested replay") @@ -579,6 +648,29 @@ def drain_events(state: SessionState) -> None: print(f"[dmshell] {event}", file=sys.stderr) +def heartbeat_loop(sock: socket.socket, state: SessionState) -> None: + while not state.stopped and not state.closed_event.is_set(): + if not state.active: + time.sleep(HEARTBEAT_POLL_INTERVAL_SEC) + continue + if state.heartbeat_due(): + try: + send_shell_frame( + sock, + state, + state.pb2.mesh.DMShell.PING, + payload=state.heartbeat_payload(), + remember=True, + heartbeat=True, + ) + except Exception as exc: + if not state.stopped: + state.event_queue.put(f"heartbeat error: {exc}") + state.closed_event.set() + return + time.sleep(HEARTBEAT_POLL_INTERVAL_SEC) + + def run_command_mode(sock: socket.socket, state: SessionState, commands: list[str], close_after: float) -> None: for command in commands: send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, (command + "\n").encode("utf-8")) @@ -656,7 +748,7 @@ def handle_local_command(cmd: str) -> bool: # Fallback for non-TTY stdin: still send input as it arrives. while not state.closed_event.is_set(): drain_events(state) - data = sys.stdin.buffer.read(1) + data = sys.stdin.buffer.read(INPUT_BATCH_MAX_BYTES) if not data: send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) break @@ -684,7 +776,32 @@ def handle_local_command(cmd: str) -> bool: break continue - send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, data) + # Coalesce a short burst of bytes to reduce packet overhead for fast typing. + batched = bytearray(data) + enter_local_command = False + deadline = time.monotonic() + INPUT_BATCH_WINDOW_SEC + while len(batched) < INPUT_BATCH_MAX_BYTES: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + more_ready, _, _ = select.select([sys.stdin], [], [], remaining) + if not more_ready: + break + next_byte = os.read(fd, 1) + if not next_byte: + break + if next_byte == LOCAL_ESCAPE_BYTE: + enter_local_command = True + break + batched.extend(next_byte) + + if batched: + send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, bytes(batched)) + + if enter_local_command: + keep_running = handle_local_command(read_local_command()) + if not keep_running: + break finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_attrs) @@ -713,6 +830,9 @@ def main() -> int: if not state.opened_event.wait(timeout=args.timeout): raise SystemExit("timed out waiting for OPEN_OK from remote DMShell") + heartbeat = threading.Thread(target=heartbeat_loop, args=(sock, state), daemon=True) + heartbeat.start() + drain_events(state) if args.command: run_command_mode(sock, state, args.command, args.close_after) @@ -722,6 +842,7 @@ def main() -> int: state.stopped = True drain_events(state) reader.join(timeout=1.0) + heartbeat.join(timeout=1.0) state.close_replay_log() return 0 diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 686f6d64dfb..5a2246ed167 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -30,6 +30,7 @@ constexpr uint16_t PTY_COLS_DEFAULT = 120; constexpr uint16_t PTY_ROWS_DEFAULT = 40; constexpr size_t MAX_PTY_READ_SIZE = 200; constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); +constexpr size_t HEARTBEAT_STATUS_SIZE = sizeof(uint32_t) * 2; struct BytesDecodeState { uint8_t *buf; @@ -82,6 +83,22 @@ uint32_t decodeUint32BE(const uint8_t *src) return (static_cast(src[0]) << 24) | (static_cast(src[1]) << 16) | (static_cast(src[2]) << 8) | static_cast(src[3]); } + +void encodeHeartbeatStatus(uint8_t *dest, uint32_t lastTxSeq, uint32_t lastRxSeq) +{ + encodeUint32BE(dest, lastTxSeq); + encodeUint32BE(dest + sizeof(uint32_t), lastRxSeq); +} + +bool decodeHeartbeatStatus(const uint8_t *src, size_t len, uint32_t &lastTxSeq, uint32_t &lastRxSeq) +{ + if (len < HEARTBEAT_STATUS_SIZE) { + return false; + } + lastTxSeq = decodeUint32BE(src); + lastRxSeq = decodeUint32BE(src + sizeof(uint32_t)); + return true; +} } // namespace DMShellModule::DMShellModule() @@ -168,9 +185,21 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } } break; - case meshtastic_DMShell_OpCode_PING: - sendControl(meshtastic_DMShell_OpCode_PONG, nullptr, 0); + case meshtastic_DMShell_OpCode_PING: { + uint32_t peerLastTxSeq = 0; + uint32_t peerLastRxSeq = frame.ackSeq; + if (decodeHeartbeatStatus(frame.payload, frame.payloadLen, peerLastTxSeq, peerLastRxSeq)) { + const uint32_t nextMissingForPeer = peerLastRxSeq + 1; + if (nextMissingForPeer > 0 && nextMissingForPeer < session.nextTxSeq) { + resendFramesFrom(nextMissingForPeer); + } + } + + uint8_t heartbeatPayload[HEARTBEAT_STATUS_SIZE] = {0}; + encodeHeartbeatStatus(heartbeatPayload, session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0, session.lastAckedRxSeq); + sendControl(meshtastic_DMShell_OpCode_PONG, heartbeatPayload, sizeof(heartbeatPayload)); break; + } case meshtastic_DMShell_OpCode_CLOSE: closeSession("peer_close", true); break; From 50e1fe88e80213024a736bc1d6eda19d9f288c00 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 9 Apr 2026 18:31:29 -0500 Subject: [PATCH 09/22] dmshell client serial support and tweaks --- bin/dmshell_client.py | 198 ++++++++++++++++++++++++++++++------------ 1 file changed, 143 insertions(+), 55 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index 227a5a03b69..d46ecde7971 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -25,12 +25,12 @@ START2 = 0xC3 HEADER_LEN = 4 DEFAULT_API_PORT = 4403 -DEFAULT_HOP_LIMIT = 3 +DEFAULT_HOP_LIMIT = 0 LOCAL_ESCAPE_BYTE = b"\x1d" # Ctrl+] REPLAY_REQUEST_SIZE = 4 HEARTBEAT_STATUS_SIZE = 8 MISSING_SEQ_RETRY_INTERVAL_SEC = 1.0 -INPUT_BATCH_WINDOW_SEC = 0.15 +INPUT_BATCH_WINDOW_SEC = .5 INPUT_BATCH_MAX_BYTES = 64 HEARTBEAT_IDLE_DELAY_SEC = 5.0 HEARTBEAT_REPEAT_SEC = 15.0 @@ -49,6 +49,14 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument("--host", default="127.0.0.1", help="meshtasticd API host") parser.add_argument("--port", type=int, default=DEFAULT_API_PORT, help="meshtasticd API port") + parser.add_argument( + "--serial", + nargs="?", + const="auto", + default=None, + help="use USB serial transport (optionally provide device path, default: auto-detect)", + ) + parser.add_argument("--baud", type=int, default=115200, help="serial baud rate when using --serial") parser.add_argument("--to", required=True, help="destination node number, e.g. !170896f7 or 0x170896f7") parser.add_argument("--channel", type=int, default=0, help="channel index to use") parser.add_argument("--cols", type=int, default=None, help="initial terminal columns (default: detect local terminal)") @@ -144,12 +152,87 @@ def parse_node_num(raw: str) -> int: return int(value, 10) -def recv_exact(sock: socket.socket, length: int) -> bytes: +class SerialTransport: + def __init__(self, serial_obj): + self._serial = serial_obj + + def recv(self, length: int) -> bytes: + return self._serial.read(length) + + def sendall(self, data: bytes) -> None: + self._serial.write(data) + self._serial.flush() + + def close(self) -> None: + self._serial.close() + + +def detect_meshtastic_serial_port() -> str: + try: + from serial.tools import list_ports + except ImportError as exc: + raise SystemExit("python package 'pyserial' is required for --serial mode") from exc + + ports = list(list_ports.comports()) + if not ports: + raise SystemExit("no serial ports found for --serial mode") + + scored: list[tuple[int, str]] = [] + for port in ports: + text = " ".join( + filter( + None, + [port.device, port.description, port.manufacturer, port.product, port.hwid], + ) + ).lower() + score = 0 + if "meshtastic" in text: + score += 100 + if "lora" in text or "mesh" in text: + score += 10 + if "ttyacm" in (port.device or "").lower() or "ttyusb" in (port.device or "").lower(): + score += 1 + scored.append((score, port.device)) + + scored.sort(reverse=True) + best_score, best_device = scored[0] + if best_score <= 0 and len(scored) > 1: + raise SystemExit( + "could not confidently auto-detect a Meshtastic serial port; pass --serial /dev/ttyXXX explicitly" + ) + return best_device + + +def open_transport(args: argparse.Namespace): + if args.serial is None: + sock = socket.create_connection((args.host, args.port), timeout=args.timeout) + sock.settimeout(None) + return sock + + serial_path = args.serial + if serial_path == "auto": + serial_path = detect_meshtastic_serial_port() + print(f"[dmshell] using serial port {serial_path}", file=sys.stderr) + + try: + import serial + except ImportError as exc: + raise SystemExit("python package 'pyserial' is required for --serial mode") from exc + + try: + serial_obj = serial.Serial(serial_path, baudrate=args.baud, timeout=None, write_timeout=2) + except Exception as exc: + raise SystemExit(f"failed to open serial device {serial_path}: {exc}") from exc + + return SerialTransport(serial_obj) + + +def recv_exact(transport, length: int) -> bytes: chunks = bytearray() while len(chunks) < length: - piece = sock.recv(length - len(chunks)) + piece = transport.recv(length - len(chunks)) if not piece: - raise ConnectionError("connection closed by server") + raise ConnectionError("connection closed by transport") chunks.extend(piece) return bytes(chunks) @@ -168,23 +251,23 @@ def resolve_initial_terminal_size(cols_override: Optional[int], rows_override: O return cols, rows -def recv_stream_frame(sock: socket.socket) -> bytes: +def recv_stream_frame(transport) -> bytes: while True: - start = recv_exact(sock, 1)[0] + start = recv_exact(transport, 1)[0] if start != START1: continue - if recv_exact(sock, 1)[0] != START2: + if recv_exact(transport, 1)[0] != START2: continue - header = recv_exact(sock, 2) + header = recv_exact(transport, 2) length = (header[0] << 8) | header[1] - return recv_exact(sock, length) + return recv_exact(transport, length) -def send_stream_frame(sock: socket.socket, payload: bytes) -> None: +def send_stream_frame(transport, payload: bytes) -> None: if len(payload) > 0xFFFF: raise ValueError("payload too large for stream API") header = bytes((START1, START2, (len(payload) >> 8) & 0xFF, len(payload) & 0xFF)) - sock.sendall(header + payload) + transport.sendall(header + payload) @dataclass @@ -412,8 +495,8 @@ def decode_heartbeat_status(payload: bytes) -> Optional[tuple[int, int]]: return struct.unpack(">II", payload[:HEARTBEAT_STATUS_SIZE]) -def send_toradio(sock: socket.socket, toradio) -> None: - send_stream_frame(sock, toradio.SerializeToString()) +def send_toradio(transport, toradio) -> None: + send_stream_frame(transport, toradio.SerializeToString()) def make_toradio_packet(pb2, state: SessionState, shell_msg) -> object: @@ -437,7 +520,7 @@ def make_toradio_packet(pb2, state: SessionState, shell_msg) -> object: def send_shell_frame( - sock: socket.socket, + transport, state: SessionState, op: int, payload: bytes = b"", @@ -468,7 +551,7 @@ def send_shell_frame( if payload: shell.payload = payload with state.socket_lock: - send_toradio(sock, make_toradio_packet(state.pb2, state, shell)) + send_toradio(transport, make_toradio_packet(state.pb2, state, shell)) if remember: state.remember_sent_frame( SentShellFrame(op=op, session_id=session_id, seq=seq, ack_seq=ack_seq, payload=payload, cols=cols, rows=rows, flags=flags) @@ -477,21 +560,21 @@ def send_shell_frame( return seq -def send_ack_frame(sock: socket.socket, state: SessionState, replay_from: Optional[int] = None) -> None: +def send_ack_frame(transport, state: SessionState, replay_from: Optional[int] = None) -> None: payload = b"" if replay_from is None else encode_replay_request(replay_from) - send_shell_frame(sock, state, state.pb2.mesh.DMShell.ACK, payload=payload, seq=0, remember=False) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.ACK, payload=payload, seq=0, remember=False) -def replay_frames_from(sock: socket.socket, state: SessionState, start_seq: int) -> None: +def replay_frames_from(transport, state: SessionState, start_seq: int) -> None: frame = next((f for f in state.replay_frames_from(start_seq) if f.seq == start_seq), None) if frame is None: - state.event_queue.put(f"replay unavailable from seq={start_seq}") + #state.event_queue.put(f"replay unavailable from seq={start_seq}") state.log_replay_event("replay_unavailable", start_seq) return state.log_replay_event("replay_sent", start_seq) #state.event_queue.put(f"replay frame seq={start_seq}") send_shell_frame( - sock, + transport, state, frame.op, payload=frame.payload, @@ -505,16 +588,16 @@ def replay_frames_from(sock: socket.socket, state: SessionState, start_seq: int) ) -def wait_for_config_complete(sock: socket.socket, pb2, timeout: float, verbose: bool) -> None: +def wait_for_config_complete(transport, pb2, timeout: float, verbose: bool) -> None: nonce = random.randint(1, 0x7FFFFFFF) toradio = pb2.mesh.ToRadio() toradio.want_config_id = nonce - send_toradio(sock, toradio) + send_toradio(transport, toradio) deadline = time.time() + timeout while time.time() < deadline: fromradio = pb2.mesh.FromRadio() - fromradio.ParseFromString(recv_stream_frame(sock)) + fromradio.ParseFromString(recv_stream_frame(transport)) variant = fromradio.WhichOneof("payload_variant") if verbose and variant: print(f"[api] fromradio {variant}", file=sys.stderr) @@ -533,7 +616,7 @@ def decode_shell_packet(state: SessionState, packet) -> Optional[object]: return shell -def reader_loop(sock: socket.socket, state: SessionState) -> None: +def reader_loop(transport, state: SessionState) -> None: def handle_in_order_shell(shell) -> bool: state.note_replayed_seq_received(shell.seq) if shell.op == state.pb2.mesh.DMShell.OPEN_OK: @@ -567,20 +650,20 @@ def handle_in_order_shell(shell) -> bool: remote_last_tx_seq, remote_last_rx_seq = heartbeat_status local_latest_tx_seq = state.highest_sent_seq() if remote_last_rx_seq < local_latest_tx_seq: - replay_frames_from(sock, state, remote_last_rx_seq + 1) + replay_frames_from(transport, state, remote_last_rx_seq + 1) if remote_last_tx_seq > state.current_ack_seq(): state.note_peer_reported_tx_seq(remote_last_tx_seq) req = state.request_missing_seq_once() if req is not None: state.note_missing_seq_requested(req, "heartbeat_status") - send_ack_frame(sock, state, replay_from=req) - state.event_queue.put("pong") + send_ack_frame(transport, state, replay_from=req) + #state.event_queue.put("pong") return False while not state.stopped: try: fromradio = state.pb2.mesh.FromRadio() - fromradio.ParseFromString(recv_stream_frame(sock)) + fromradio.ParseFromString(recv_stream_frame(transport)) except Exception as exc: if not state.stopped: state.event_queue.put(f"connection error: {exc}") @@ -593,13 +676,13 @@ def handle_in_order_shell(shell) -> bool: if not shell: continue state.note_inbound_packet() - state.prune_sent_frames(shell.ack_seq) + #state.prune_sent_frames(shell.ack_seq) if shell.op == state.pb2.mesh.DMShell.ACK: #state.event_queue.put("peer requested replay") replay_from = decode_replay_request(shell.payload) if replay_from is not None: #state.event_queue.put(f"peer requested replay from seq={replay_from}") - replay_frames_from(sock, state, replay_from) + replay_frames_from(transport, state, replay_from) continue action, missing_from = state.note_received_seq(shell.seq) @@ -607,14 +690,14 @@ def handle_in_order_shell(shell) -> bool: req = state.request_missing_seq_once() if req is not None: state.note_missing_seq_requested(req, "duplicate") - send_ack_frame(sock, state, replay_from=req) + send_ack_frame(transport, state, replay_from=req) continue if action == "gap": state.remember_out_of_order_frame(shell) req = state.request_missing_seq_once() if req is not None: state.note_missing_seq_requested(req, "gap") - send_ack_frame(sock, state, replay_from=req) + send_ack_frame(transport, state, replay_from=req) continue if handle_in_order_shell(shell): @@ -634,7 +717,7 @@ def handle_in_order_shell(shell) -> bool: req = state.request_missing_seq_once() if req is not None: state.note_missing_seq_requested(req, "post_process_gap") - send_ack_frame(sock, state, replay_from=req) + send_ack_frame(transport, state, replay_from=req) elif state.verbose and variant: state.event_queue.put(f"fromradio {variant}") @@ -648,7 +731,7 @@ def drain_events(state: SessionState) -> None: print(f"[dmshell] {event}", file=sys.stderr) -def heartbeat_loop(sock: socket.socket, state: SessionState) -> None: +def heartbeat_loop(transport, state: SessionState) -> None: while not state.stopped and not state.closed_event.is_set(): if not state.active: time.sleep(HEARTBEAT_POLL_INTERVAL_SEC) @@ -656,7 +739,7 @@ def heartbeat_loop(sock: socket.socket, state: SessionState) -> None: if state.heartbeat_due(): try: send_shell_frame( - sock, + transport, state, state.pb2.mesh.DMShell.PING, payload=state.heartbeat_payload(), @@ -671,15 +754,15 @@ def heartbeat_loop(sock: socket.socket, state: SessionState) -> None: time.sleep(HEARTBEAT_POLL_INTERVAL_SEC) -def run_command_mode(sock: socket.socket, state: SessionState, commands: list[str], close_after: float) -> None: +def run_command_mode(transport, state: SessionState, commands: list[str], close_after: float) -> None: for command in commands: - send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, (command + "\n").encode("utf-8")) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.INPUT, (command + "\n").encode("utf-8")) time.sleep(close_after) - send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) state.closed_event.wait(timeout=close_after + 5.0) -def run_interactive_mode(sock: socket.socket, state: SessionState) -> None: +def run_interactive_mode(transport, state: SessionState) -> None: def read_local_command() -> str: prompt = "\r\n[dmshell] local command (resume|close|ping|resize C R): " sys.stderr.write(prompt) @@ -717,10 +800,10 @@ def handle_local_command(cmd: str) -> bool: if cmd in ("", "resume"): return True if cmd == "close": - send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) return False if cmd == "ping": - send_shell_frame(sock, state, state.pb2.mesh.DMShell.PING) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.PING) return True if cmd.startswith("resize "): parts = cmd.split() @@ -733,7 +816,7 @@ def handle_local_command(cmd: str) -> bool: except ValueError: state.event_queue.put("usage: resize COLS ROWS") return True - send_shell_frame(sock, state, state.pb2.mesh.DMShell.RESIZE, cols=cols, rows=rows) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.RESIZE, cols=cols, rows=rows) return True state.event_queue.put(f"unknown local command: {cmd}") @@ -750,9 +833,9 @@ def handle_local_command(cmd: str) -> bool: drain_events(state) data = sys.stdin.buffer.read(INPUT_BATCH_MAX_BYTES) if not data: - send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) break - send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, data) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.INPUT, data) return fd = sys.stdin.fileno() @@ -767,7 +850,7 @@ def handle_local_command(cmd: str) -> bool: data = os.read(fd, 1) if not data: - send_shell_frame(sock, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) break if data == LOCAL_ESCAPE_BYTE: @@ -794,9 +877,12 @@ def handle_local_command(cmd: str) -> bool: enter_local_command = True break batched.extend(next_byte) + if next_byte == b'\r' or next_byte == b'\t': + break + deadline = time.monotonic() + INPUT_BATCH_WINDOW_SEC if batched: - send_shell_frame(sock, state, state.pb2.mesh.DMShell.INPUT, bytes(batched)) + send_shell_frame(transport, state, state.pb2.mesh.DMShell.INPUT, bytes(batched)) if enter_local_command: keep_running = handle_local_command(read_local_command()) @@ -819,31 +905,33 @@ def main() -> int: cols, rows = resolve_initial_terminal_size(args.cols, args.rows) - with socket.create_connection((args.host, args.port), timeout=args.timeout) as sock: - sock.settimeout(None) - wait_for_config_complete(sock, pb2, args.timeout, args.verbose) + transport = open_transport(args) + try: + wait_for_config_complete(transport, pb2, args.timeout, args.verbose) - reader = threading.Thread(target=reader_loop, args=(sock, state), daemon=True) + reader = threading.Thread(target=reader_loop, args=(transport, state), daemon=True) reader.start() - send_shell_frame(sock, state, pb2.mesh.DMShell.OPEN, cols=cols, rows=rows) + send_shell_frame(transport, state, pb2.mesh.DMShell.OPEN, cols=cols, rows=rows) if not state.opened_event.wait(timeout=args.timeout): raise SystemExit("timed out waiting for OPEN_OK from remote DMShell") - heartbeat = threading.Thread(target=heartbeat_loop, args=(sock, state), daemon=True) + heartbeat = threading.Thread(target=heartbeat_loop, args=(transport, state), daemon=True) heartbeat.start() drain_events(state) if args.command: - run_command_mode(sock, state, args.command, args.close_after) + run_command_mode(transport, state, args.command, args.close_after) else: - run_interactive_mode(sock, state) + run_interactive_mode(transport, state) state.stopped = True drain_events(state) reader.join(timeout=1.0) heartbeat.join(timeout=1.0) state.close_replay_log() + finally: + transport.close() return 0 From 8c248927c88cdca13c1abfd87fb4da66d218d950 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 10 Apr 2026 16:00:29 -0500 Subject: [PATCH 10/22] Remove some dead code and LLM overcomplication --- src/modules/DMShell.cpp | 72 ++++++----------------------------------- src/modules/DMShell.h | 1 - 2 files changed, 9 insertions(+), 64 deletions(-) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 5a2246ed167..f0c8e6e0faf 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -32,44 +32,6 @@ constexpr size_t MAX_PTY_READ_SIZE = 200; constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); constexpr size_t HEARTBEAT_STATUS_SIZE = sizeof(uint32_t) * 2; -struct BytesDecodeState { - uint8_t *buf; - size_t maxLen; - size_t outLen; -}; - -struct BytesEncodeState { - const uint8_t *buf; - size_t len; -}; - -bool decodeBytesField(pb_istream_t *stream, const pb_field_iter_t *field, void **arg) -{ - (void)field; - auto *state = static_cast(*arg); - const size_t fieldLen = stream->bytes_left; - if (fieldLen > state->maxLen) { - return false; - } - if (!pb_read(stream, state->buf, fieldLen)) { - return false; - } - state->outLen = fieldLen; - return true; -} - -bool encodeBytesField(pb_ostream_t *stream, const pb_field_iter_t *field, void *const *arg) -{ - auto *state = static_cast(*arg); - if (!state || !state->buf || state->len == 0) { - return true; - } - if (!pb_encode_tag_for_field(stream, field)) { - return false; - } - return pb_encode_string(stream, state->buf, state->len); -} - void encodeUint32BE(uint8_t *dest, uint32_t value) { dest[0] = static_cast((value >> 24) & 0xff); @@ -110,6 +72,11 @@ DMShellModule::DMShellModule() ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) { DMShellFrame frame; + if (!mp.pki_encrypted) { + LOG_WARN("DMShell: ignoring packet without PKI from 0x%x", mp.from); + return ProcessMessage::STOP; + } + if (!parseFrame(mp, frame)) { LOG_WARN("DMShell: ignoring malformed frame"); return ProcessMessage::STOP; @@ -134,10 +101,6 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) return ProcessMessage::STOP; } - if (frame.ackSeq > 0) { - pruneSentFrames(frame.ackSeq); - } - if (frame.op == meshtastic_DMShell_OpCode_OPEN) { LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.sessionId); if (!openSession(mp, frame)) { @@ -366,8 +329,8 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr ((static_cast(session.childPid) << 8) & 0xff0000) | (static_cast(session.childPid) >> 24); memcpy(payload, &pidBE, sizeof(payload)); - sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, allocTxSeq(), payload, - sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); + sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, session.nextTxSeq++, + payload, sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid); return true; @@ -426,11 +389,6 @@ void DMShellModule::reapChildIfExited() } } -uint32_t DMShellModule::allocTxSeq() -{ - return session.nextTxSeq++; -} - void DMShellModule::rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags) { @@ -455,19 +413,6 @@ void DMShellModule::rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t ses session.txHistoryNext = (session.txHistoryNext + 1) % session.txHistory.size(); } -void DMShellModule::pruneSentFrames(uint32_t ackSeq) -{ - if (ackSeq == 0) { - return; - } - - for (auto &entry : session.txHistory) { - if (entry.valid && entry.seq <= ackSeq) { - entry.valid = false; - } - } -} - void DMShellModule::resendFramesFrom(uint32_t startSeq) { if (startSeq == 0) { @@ -511,7 +456,7 @@ void DMShellModule::sendAck(uint32_t replayFromSeq) void DMShellModule::sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen) { - sendFrameToPeer(session.peer, session.channel, op, session.sessionId, allocTxSeq(), payload, payloadLen, 0, 0, + sendFrameToPeer(session.peer, session.channel, op, session.sessionId, session.nextTxSeq++, payload, payloadLen, 0, 0, session.lastAckedRxSeq); } @@ -576,6 +521,7 @@ void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DM packet->to = peer; packet->channel = channel; packet->want_ack = false; + packet->pki_encrypted = true; packet->priority = meshtastic_MeshPacket_Priority_RELIABLE; service->sendToMesh(packet); } diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index 302c6cac2a2..3f0e709c6c1 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -76,7 +76,6 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule void closeSession(const char *reason, bool notifyPeer); void reapChildIfExited(); - uint32_t allocTxSeq(); void rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags); void pruneSentFrames(uint32_t ackSeq); From 322f0262a89c7122e7d19fcd691b719046cfe6c9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 12:11:31 -0500 Subject: [PATCH 11/22] Trunk --- src/mesh/RadioLibInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a53c9b09e7a..fb5fe312f68 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -173,7 +173,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified virtual bool findInTxQueue(NodeNum from, PacketId id) override; uint8_t packetsInTxQueue() { return txQueue.getMaxLen() - txQueue.getFree(); } - + /** * Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface. * @return true if len bytes were produced, false otherwise. From 8f7dea05800537318c833c1fbf961b0098acfb68 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 19:25:09 -0500 Subject: [PATCH 12/22] Use RemoteShell in protobufs --- bin/dmshell_client.py | 48 +-- src/mesh/generated/meshtastic/atak.pb.cpp | 12 - src/mesh/generated/meshtastic/atak.pb.h | 394 +------------------- src/mesh/generated/meshtastic/mesh.pb.cpp | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 80 ++-- src/mesh/generated/meshtastic/portnums.pb.h | 6 +- src/modules/DMShell.cpp | 56 +-- src/modules/DMShell.h | 12 +- 8 files changed, 101 insertions(+), 509 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index d46ecde7971..483ac17ba3a 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -459,7 +459,7 @@ def close_replay_log(self) -> None: self.replay_log_file = None def remember_sent_frame(self, frame: SentShellFrame) -> None: - if frame.seq == 0 or frame.op == self.pb2.mesh.DMShell.ACK: + if frame.seq == 0 or frame.op == self.pb2.mesh.RemoteShell.ACK: return with self.tx_lock: self.tx_history.append(frame) @@ -508,7 +508,7 @@ def make_toradio_packet(pb2, state: SessionState, shell_msg) -> object: packet.channel = state.channel packet.hop_limit = DEFAULT_HOP_LIMIT packet.want_ack = False - packet.decoded.portnum = pb2.portnums.DM_SHELL_APP + packet.decoded.portnum = pb2.portnums.REMOTE_SHELL_APP packet.decoded.payload = shell_msg.SerializeToString() packet.decoded.want_response = False packet.decoded.dest = state.target @@ -534,13 +534,13 @@ def send_shell_frame( heartbeat: bool = False, ) -> int: if seq is None: - seq = 0 if op == state.pb2.mesh.DMShell.ACK else state.alloc_seq() + seq = 0 if op == state.pb2.mesh.RemoteShell.ACK else state.alloc_seq() if ack_seq is None: ack_seq = state.current_ack_seq() if session_id is None: session_id = state.session_id - shell = state.pb2.mesh.DMShell() + shell = state.pb2.mesh.RemoteShell() shell.op = op shell.session_id = session_id shell.seq = seq @@ -562,7 +562,7 @@ def send_shell_frame( def send_ack_frame(transport, state: SessionState, replay_from: Optional[int] = None) -> None: payload = b"" if replay_from is None else encode_replay_request(replay_from) - send_shell_frame(transport, state, state.pb2.mesh.DMShell.ACK, payload=payload, seq=0, remember=False) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.ACK, payload=payload, seq=0, remember=False) def replay_frames_from(transport, state: SessionState, start_seq: int) -> None: @@ -609,9 +609,9 @@ def wait_for_config_complete(transport, pb2, timeout: float, verbose: bool) -> N def decode_shell_packet(state: SessionState, packet) -> Optional[object]: if packet.WhichOneof("payload_variant") != "decoded": return None - if packet.decoded.portnum != state.pb2.portnums.DM_SHELL_APP: + if packet.decoded.portnum != state.pb2.portnums.REMOTE_SHELL_APP: return None - shell = state.pb2.mesh.DMShell() + shell = state.pb2.mesh.RemoteShell() shell.ParseFromString(packet.decoded.payload) return shell @@ -619,7 +619,7 @@ def decode_shell_packet(state: SessionState, packet) -> Optional[object]: def reader_loop(transport, state: SessionState) -> None: def handle_in_order_shell(shell) -> bool: state.note_replayed_seq_received(shell.seq) - if shell.op == state.pb2.mesh.DMShell.OPEN_OK: + if shell.op == state.pb2.mesh.RemoteShell.OPEN_OK: state.session_id = shell.session_id state.open_replay_log(state.session_id) state.set_receive_cursor(shell.seq) @@ -631,20 +631,20 @@ def handle_in_order_shell(shell) -> bool: ) if state.replay_log_path is not None: state.event_queue.put(f"replay log: {state.replay_log_path}") - elif shell.op == state.pb2.mesh.DMShell.OUTPUT: + elif shell.op == state.pb2.mesh.RemoteShell.OUTPUT: if shell.payload: sys.stdout.buffer.write(shell.payload) sys.stdout.buffer.flush() - elif shell.op == state.pb2.mesh.DMShell.ERROR: + elif shell.op == state.pb2.mesh.RemoteShell.ERROR: message = shell.payload.decode("utf-8", errors="replace") state.event_queue.put(f"remote error: {message}") - elif shell.op == state.pb2.mesh.DMShell.CLOSED: + elif shell.op == state.pb2.mesh.RemoteShell.CLOSED: message = shell.payload.decode("utf-8", errors="replace") state.event_queue.put(f"session closed: {message}") state.closed_event.set() state.active = False return True - elif shell.op == state.pb2.mesh.DMShell.PONG: + elif shell.op == state.pb2.mesh.RemoteShell.PONG: heartbeat_status = decode_heartbeat_status(shell.payload) if heartbeat_status is not None: remote_last_tx_seq, remote_last_rx_seq = heartbeat_status @@ -677,7 +677,7 @@ def handle_in_order_shell(shell) -> bool: continue state.note_inbound_packet() #state.prune_sent_frames(shell.ack_seq) - if shell.op == state.pb2.mesh.DMShell.ACK: + if shell.op == state.pb2.mesh.RemoteShell.ACK: #state.event_queue.put("peer requested replay") replay_from = decode_replay_request(shell.payload) if replay_from is not None: @@ -741,7 +741,7 @@ def heartbeat_loop(transport, state: SessionState) -> None: send_shell_frame( transport, state, - state.pb2.mesh.DMShell.PING, + state.pb2.mesh.RemoteShell.PING, payload=state.heartbeat_payload(), remember=True, heartbeat=True, @@ -756,9 +756,9 @@ def heartbeat_loop(transport, state: SessionState) -> None: def run_command_mode(transport, state: SessionState, commands: list[str], close_after: float) -> None: for command in commands: - send_shell_frame(transport, state, state.pb2.mesh.DMShell.INPUT, (command + "\n").encode("utf-8")) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.INPUT, (command + "\n").encode("utf-8")) time.sleep(close_after) - send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.CLOSE) state.closed_event.wait(timeout=close_after + 5.0) @@ -800,10 +800,10 @@ def handle_local_command(cmd: str) -> bool: if cmd in ("", "resume"): return True if cmd == "close": - send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.CLOSE) return False if cmd == "ping": - send_shell_frame(transport, state, state.pb2.mesh.DMShell.PING) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.PING) return True if cmd.startswith("resize "): parts = cmd.split() @@ -816,7 +816,7 @@ def handle_local_command(cmd: str) -> bool: except ValueError: state.event_queue.put("usage: resize COLS ROWS") return True - send_shell_frame(transport, state, state.pb2.mesh.DMShell.RESIZE, cols=cols, rows=rows) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.RESIZE, cols=cols, rows=rows) return True state.event_queue.put(f"unknown local command: {cmd}") @@ -833,9 +833,9 @@ def handle_local_command(cmd: str) -> bool: drain_events(state) data = sys.stdin.buffer.read(INPUT_BATCH_MAX_BYTES) if not data: - send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.CLOSE) break - send_shell_frame(transport, state, state.pb2.mesh.DMShell.INPUT, data) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.INPUT, data) return fd = sys.stdin.fileno() @@ -850,7 +850,7 @@ def handle_local_command(cmd: str) -> bool: data = os.read(fd, 1) if not data: - send_shell_frame(transport, state, state.pb2.mesh.DMShell.CLOSE) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.CLOSE) break if data == LOCAL_ESCAPE_BYTE: @@ -882,7 +882,7 @@ def handle_local_command(cmd: str) -> bool: deadline = time.monotonic() + INPUT_BATCH_WINDOW_SEC if batched: - send_shell_frame(transport, state, state.pb2.mesh.DMShell.INPUT, bytes(batched)) + send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.INPUT, bytes(batched)) if enter_local_command: keep_running = handle_local_command(read_local_command()) @@ -912,7 +912,7 @@ def main() -> int: reader = threading.Thread(target=reader_loop, args=(transport, state), daemon=True) reader.start() - send_shell_frame(transport, state, pb2.mesh.DMShell.OPEN, cols=cols, rows=rows) + send_shell_frame(transport, state, pb2.mesh.RemoteShell.OPEN, cols=cols, rows=rows) if not state.opened_event.wait(timeout=args.timeout): raise SystemExit("timed out waiting for OPEN_OK from remote DMShell") diff --git a/src/mesh/generated/meshtastic/atak.pb.cpp b/src/mesh/generated/meshtastic/atak.pb.cpp index bbafa33e2c7..a0368cf6b22 100644 --- a/src/mesh/generated/meshtastic/atak.pb.cpp +++ b/src/mesh/generated/meshtastic/atak.pb.cpp @@ -24,18 +24,6 @@ PB_BIND(meshtastic_Contact, meshtastic_Contact, AUTO) PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO) -PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO) - - -PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2) - - - - - - - - diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index c12b042f444..8533bcbf9de 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -65,197 +65,6 @@ typedef enum _meshtastic_MemberRole { meshtastic_MemberRole_K9 = 8 } meshtastic_MemberRole; -/* CoT how field values. - Represents how the coordinates were generated. */ -typedef enum _meshtastic_CotHow { - /* Unspecified */ - meshtastic_CotHow_CotHow_Unspecified = 0, - /* Human entered */ - meshtastic_CotHow_CotHow_h_e = 1, - /* Machine generated */ - meshtastic_CotHow_CotHow_m_g = 2, - /* Human GPS/INS derived */ - meshtastic_CotHow_CotHow_h_g_i_g_o = 3, - /* Machine relayed (imported from another system/gateway) */ - meshtastic_CotHow_CotHow_m_r = 4, - /* Machine fused (corroborated from multiple sources) */ - meshtastic_CotHow_CotHow_m_f = 5, - /* Machine predicted */ - meshtastic_CotHow_CotHow_m_p = 6, - /* Machine simulated */ - meshtastic_CotHow_CotHow_m_s = 7 -} meshtastic_CotHow; - -/* Well-known CoT event types. - When the type is known, use the enum value for efficient encoding. - For unknown types, set cot_type_id to CotType_Other and populate cot_type_str. */ -typedef enum _meshtastic_CotType { - /* Unknown or unmapped type, use cot_type_str */ - meshtastic_CotType_CotType_Other = 0, - /* a-f-G-U-C: Friendly ground unit combat */ - meshtastic_CotType_CotType_a_f_G_U_C = 1, - /* a-f-G-U-C-I: Friendly ground unit combat infantry */ - meshtastic_CotType_CotType_a_f_G_U_C_I = 2, - /* a-n-A-C-F: Neutral aircraft civilian fixed-wing */ - meshtastic_CotType_CotType_a_n_A_C_F = 3, - /* a-n-A-C-H: Neutral aircraft civilian helicopter */ - meshtastic_CotType_CotType_a_n_A_C_H = 4, - /* a-n-A-C: Neutral aircraft civilian */ - meshtastic_CotType_CotType_a_n_A_C = 5, - /* a-f-A-M-H: Friendly aircraft military helicopter */ - meshtastic_CotType_CotType_a_f_A_M_H = 6, - /* a-f-A-M: Friendly aircraft military */ - meshtastic_CotType_CotType_a_f_A_M = 7, - /* a-f-A-M-F-F: Friendly aircraft military fixed-wing fighter */ - meshtastic_CotType_CotType_a_f_A_M_F_F = 8, - /* a-f-A-M-H-A: Friendly aircraft military helicopter attack */ - meshtastic_CotType_CotType_a_f_A_M_H_A = 9, - /* a-f-A-M-H-U-M: Friendly aircraft military helicopter utility medium */ - meshtastic_CotType_CotType_a_f_A_M_H_U_M = 10, - /* a-h-A-M-F-F: Hostile aircraft military fixed-wing fighter */ - meshtastic_CotType_CotType_a_h_A_M_F_F = 11, - /* a-h-A-M-H-A: Hostile aircraft military helicopter attack */ - meshtastic_CotType_CotType_a_h_A_M_H_A = 12, - /* a-u-A-C: Unknown aircraft civilian */ - meshtastic_CotType_CotType_a_u_A_C = 13, - /* t-x-d-d: Tasking delete/disconnect */ - meshtastic_CotType_CotType_t_x_d_d = 14, - /* a-f-G-E-S-E: Friendly ground equipment sensor */ - meshtastic_CotType_CotType_a_f_G_E_S_E = 15, - /* a-f-G-E-V-C: Friendly ground equipment vehicle */ - meshtastic_CotType_CotType_a_f_G_E_V_C = 16, - /* a-f-S: Friendly sea */ - meshtastic_CotType_CotType_a_f_S = 17, - /* a-f-A-M-F: Friendly aircraft military fixed-wing */ - meshtastic_CotType_CotType_a_f_A_M_F = 18, - /* a-f-A-M-F-C-H: Friendly aircraft military fixed-wing cargo heavy */ - meshtastic_CotType_CotType_a_f_A_M_F_C_H = 19, - /* a-f-A-M-F-U-L: Friendly aircraft military fixed-wing utility light */ - meshtastic_CotType_CotType_a_f_A_M_F_U_L = 20, - /* a-f-A-M-F-L: Friendly aircraft military fixed-wing liaison */ - meshtastic_CotType_CotType_a_f_A_M_F_L = 21, - /* a-f-A-M-F-P: Friendly aircraft military fixed-wing patrol */ - meshtastic_CotType_CotType_a_f_A_M_F_P = 22, - /* a-f-A-C-H: Friendly aircraft civilian helicopter */ - meshtastic_CotType_CotType_a_f_A_C_H = 23, - /* a-n-A-M-F-Q: Neutral aircraft military fixed-wing drone */ - meshtastic_CotType_CotType_a_n_A_M_F_Q = 24, - /* b-t-f: GeoChat message */ - meshtastic_CotType_CotType_b_t_f = 25, - /* b-r-f-h-c: CASEVAC/MEDEVAC report */ - meshtastic_CotType_CotType_b_r_f_h_c = 26, - /* b-a-o-pan: Ring the bell / alert all */ - meshtastic_CotType_CotType_b_a_o_pan = 27, - /* b-a-o-opn: Troops in contact */ - meshtastic_CotType_CotType_b_a_o_opn = 28, - /* b-a-o-can: Cancel alert */ - meshtastic_CotType_CotType_b_a_o_can = 29, - /* b-a-o-tbl: 911 alert */ - meshtastic_CotType_CotType_b_a_o_tbl = 30, - /* b-a-g: Geofence breach alert */ - meshtastic_CotType_CotType_b_a_g = 31, - /* a-f-G: Friendly ground (generic) */ - meshtastic_CotType_CotType_a_f_G = 32, - /* a-f-G-U: Friendly ground unit (generic) */ - meshtastic_CotType_CotType_a_f_G_U = 33, - /* a-h-G: Hostile ground (generic) */ - meshtastic_CotType_CotType_a_h_G = 34, - /* a-u-G: Unknown ground (generic) */ - meshtastic_CotType_CotType_a_u_G = 35, - /* a-n-G: Neutral ground (generic) */ - meshtastic_CotType_CotType_a_n_G = 36, - /* b-m-r: Route */ - meshtastic_CotType_CotType_b_m_r = 37, - /* b-m-p-w: Route waypoint */ - meshtastic_CotType_CotType_b_m_p_w = 38, - /* b-m-p-s-p-i: Self-position marker */ - meshtastic_CotType_CotType_b_m_p_s_p_i = 39, - /* u-d-f: Freeform shape (line/polygon) */ - meshtastic_CotType_CotType_u_d_f = 40, - /* u-d-r: Rectangle */ - meshtastic_CotType_CotType_u_d_r = 41, - /* u-d-c-c: Circle */ - meshtastic_CotType_CotType_u_d_c_c = 42, - /* u-rb-a: Range/bearing line */ - meshtastic_CotType_CotType_u_rb_a = 43, - /* a-h-A: Hostile aircraft (generic) */ - meshtastic_CotType_CotType_a_h_A = 44, - /* a-u-A: Unknown aircraft (generic) */ - meshtastic_CotType_CotType_a_u_A = 45, - /* a-f-A-M-H-Q: Friendly aircraft military helicopter observation */ - meshtastic_CotType_CotType_a_f_A_M_H_Q = 46, - /* a-f-A-C-F: Friendly aircraft civilian fixed-wing */ - meshtastic_CotType_CotType_a_f_A_C_F = 47, - /* a-f-A-C: Friendly aircraft civilian (generic) */ - meshtastic_CotType_CotType_a_f_A_C = 48, - /* a-f-A-C-L: Friendly aircraft civilian lighter-than-air */ - meshtastic_CotType_CotType_a_f_A_C_L = 49, - /* a-f-A: Friendly aircraft (generic) */ - meshtastic_CotType_CotType_a_f_A = 50, - /* a-f-A-M-H-C: Friendly aircraft military helicopter cargo */ - meshtastic_CotType_CotType_a_f_A_M_H_C = 51, - /* a-n-A-M-F-F: Neutral aircraft military fixed-wing fighter */ - meshtastic_CotType_CotType_a_n_A_M_F_F = 52, - /* a-u-A-C-F: Unknown aircraft civilian fixed-wing */ - meshtastic_CotType_CotType_a_u_A_C_F = 53, - /* a-f-G-U-C-F-T-A: Friendly ground unit combat forces theater aviation */ - meshtastic_CotType_CotType_a_f_G_U_C_F_T_A = 54, - /* a-f-G-U-C-V-S: Friendly ground unit combat vehicle support */ - meshtastic_CotType_CotType_a_f_G_U_C_V_S = 55, - /* a-f-G-U-C-R-X: Friendly ground unit combat reconnaissance exploitation */ - meshtastic_CotType_CotType_a_f_G_U_C_R_X = 56, - /* a-f-G-U-C-I-Z: Friendly ground unit combat infantry mechanized */ - meshtastic_CotType_CotType_a_f_G_U_C_I_Z = 57, - /* a-f-G-U-C-E-C-W: Friendly ground unit combat engineer construction wheeled */ - meshtastic_CotType_CotType_a_f_G_U_C_E_C_W = 58, - /* a-f-G-U-C-I-L: Friendly ground unit combat infantry light */ - meshtastic_CotType_CotType_a_f_G_U_C_I_L = 59, - /* a-f-G-U-C-R-O: Friendly ground unit combat reconnaissance other */ - meshtastic_CotType_CotType_a_f_G_U_C_R_O = 60, - /* a-f-G-U-C-R-V: Friendly ground unit combat reconnaissance cavalry */ - meshtastic_CotType_CotType_a_f_G_U_C_R_V = 61, - /* a-f-G-U-H: Friendly ground unit headquarters */ - meshtastic_CotType_CotType_a_f_G_U_H = 62, - /* a-f-G-U-U-M-S-E: Friendly ground unit support medical surgical evacuation */ - meshtastic_CotType_CotType_a_f_G_U_U_M_S_E = 63, - /* a-f-G-U-S-M-C: Friendly ground unit support maintenance collection */ - meshtastic_CotType_CotType_a_f_G_U_S_M_C = 64, - /* a-f-G-E-S: Friendly ground equipment sensor (generic) */ - meshtastic_CotType_CotType_a_f_G_E_S = 65, - /* a-f-G-E: Friendly ground equipment (generic) */ - meshtastic_CotType_CotType_a_f_G_E = 66, - /* a-f-G-E-V-C-U: Friendly ground equipment vehicle utility */ - meshtastic_CotType_CotType_a_f_G_E_V_C_U = 67, - /* a-f-G-E-V-C-ps: Friendly ground equipment vehicle public safety */ - meshtastic_CotType_CotType_a_f_G_E_V_C_ps = 68, - /* a-u-G-E-V: Unknown ground equipment vehicle */ - meshtastic_CotType_CotType_a_u_G_E_V = 69, - /* a-f-S-N-N-R: Friendly sea surface non-naval rescue */ - meshtastic_CotType_CotType_a_f_S_N_N_R = 70, - /* a-f-F-B: Friendly force boundary */ - meshtastic_CotType_CotType_a_f_F_B = 71, - /* b-m-p-s-p-loc: Self-position location marker */ - meshtastic_CotType_CotType_b_m_p_s_p_loc = 72, - /* b-i-v: Imagery/video */ - meshtastic_CotType_CotType_b_i_v = 73, - /* b-f-t-r: File transfer request */ - meshtastic_CotType_CotType_b_f_t_r = 74, - /* b-f-t-a: File transfer acknowledgment */ - meshtastic_CotType_CotType_b_f_t_a = 75 -} meshtastic_CotType; - -/* Geopoint and altitude source */ -typedef enum _meshtastic_GeoPointSource { - /* Unspecified */ - meshtastic_GeoPointSource_GeoPointSource_Unspecified = 0, - /* GPS derived */ - meshtastic_GeoPointSource_GeoPointSource_GPS = 1, - /* User entered */ - meshtastic_GeoPointSource_GeoPointSource_USER = 2, - /* Network/external */ - meshtastic_GeoPointSource_GeoPointSource_NETWORK = 3 -} meshtastic_GeoPointSource; - /* Struct definitions */ /* ATAK GeoChat message */ typedef struct _meshtastic_GeoChat { @@ -337,95 +146,6 @@ typedef struct _meshtastic_TAKPacket { } payload_variant; } meshtastic_TAKPacket; -/* Aircraft track information from ADS-B or military air tracking. - Covers the majority of observed real-world CoT traffic. */ -typedef struct _meshtastic_AircraftTrack { - /* ICAO hex identifier (e.g. "AD237C") */ - char icao[8]; - /* Aircraft registration (e.g. "N946AK") */ - char registration[16]; - /* Flight number/callsign (e.g. "ASA864") */ - char flight[16]; - /* ICAO aircraft type designator (e.g. "B39M") */ - char aircraft_type[8]; - /* Transponder squawk code (0-7777 octal) */ - uint16_t squawk; - /* ADS-B emitter category (e.g. "A3") */ - char category[4]; - /* Received signal strength * 10 (e.g. -194 for -19.4 dBm) */ - int32_t rssi_x10; - /* Whether receiver has GPS fix */ - bool gps; - /* CoT host ID for source attribution */ - char cot_host_id[64]; -} meshtastic_AircraftTrack; - -typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacketV2_raw_detail_t; -/* ATAK v2 packet with expanded CoT field support and zstd dictionary compression. - Sent on ATAK_PLUGIN_V2 port. The wire payload is: - [1 byte flags][zstd-compressed TAKPacketV2 protobuf] - Flags byte: bits 0-5 = dictionary ID, bits 6-7 = reserved. */ -typedef struct _meshtastic_TAKPacketV2 { - /* Well-known CoT event type enum. - Use CotType_Other with cot_type_str for unknown types. */ - meshtastic_CotType cot_type_id; - /* How the coordinates were generated */ - meshtastic_CotHow how; - /* Callsign */ - char callsign[120]; - /* Team color assignment */ - meshtastic_Team team; - /* Role of the group member */ - meshtastic_MemberRole role; - /* Latitude, multiply by 1e-7 to get degrees in floating point */ - int32_t latitude_i; - /* Longitude, multiply by 1e-7 to get degrees in floating point */ - int32_t longitude_i; - /* Altitude in meters (HAE) */ - int32_t altitude; - /* Speed in cm/s */ - uint32_t speed; - /* Course in degrees * 100 (0-36000) */ - uint16_t course; - /* Battery level 0-100 */ - uint8_t battery; - /* Geopoint source */ - meshtastic_GeoPointSource geo_src; - /* Altitude source */ - meshtastic_GeoPointSource alt_src; - /* Device UID (UUID string or device ID like "ANDROID-xxxx") */ - char uid[48]; - /* Device callsign */ - char device_callsign[120]; - /* Stale time as seconds offset from event time */ - uint16_t stale_seconds; - /* TAK client version string */ - char tak_version[64]; - /* TAK device model */ - char tak_device[32]; - /* TAK platform (ATAK-CIV, WebTAK, etc.) */ - char tak_platform[32]; - /* TAK OS version */ - char tak_os[16]; - /* Connection endpoint */ - char endpoint[32]; - /* Phone number */ - char phone[20]; - /* CoT event type string, only populated when cot_type_id is CotType_Other */ - char cot_type_str[32]; - pb_size_t which_payload_variant; - union { - /* Position report (true = PLI, no extra fields beyond the common ones above) */ - bool pli; - /* ATAK GeoChat message */ - meshtastic_GeoChat chat; - /* Aircraft track data (ADS-B, military air) */ - meshtastic_AircraftTrack aircraft; - /* Generic CoT detail XML for unmapped types */ - meshtastic_TAKPacketV2_raw_detail_t raw_detail; - } payload_variant; -} meshtastic_TAKPacketV2; - #ifdef __cplusplus extern "C" { @@ -440,18 +160,6 @@ extern "C" { #define _meshtastic_MemberRole_MAX meshtastic_MemberRole_K9 #define _meshtastic_MemberRole_ARRAYSIZE ((meshtastic_MemberRole)(meshtastic_MemberRole_K9+1)) -#define _meshtastic_CotHow_MIN meshtastic_CotHow_CotHow_Unspecified -#define _meshtastic_CotHow_MAX meshtastic_CotHow_CotHow_m_s -#define _meshtastic_CotHow_ARRAYSIZE ((meshtastic_CotHow)(meshtastic_CotHow_CotHow_m_s+1)) - -#define _meshtastic_CotType_MIN meshtastic_CotType_CotType_Other -#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_b_f_t_a -#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_b_f_t_a+1)) - -#define _meshtastic_GeoPointSource_MIN meshtastic_GeoPointSource_GeoPointSource_Unspecified -#define _meshtastic_GeoPointSource_MAX meshtastic_GeoPointSource_GeoPointSource_NETWORK -#define _meshtastic_GeoPointSource_ARRAYSIZE ((meshtastic_GeoPointSource)(meshtastic_GeoPointSource_GeoPointSource_NETWORK+1)) - #define meshtastic_Group_role_ENUMTYPE meshtastic_MemberRole @@ -461,14 +169,6 @@ extern "C" { -#define meshtastic_TAKPacketV2_cot_type_id_ENUMTYPE meshtastic_CotType -#define meshtastic_TAKPacketV2_how_ENUMTYPE meshtastic_CotHow -#define meshtastic_TAKPacketV2_team_ENUMTYPE meshtastic_Team -#define meshtastic_TAKPacketV2_role_ENUMTYPE meshtastic_MemberRole -#define meshtastic_TAKPacketV2_geo_src_ENUMTYPE meshtastic_GeoPointSource -#define meshtastic_TAKPacketV2_alt_src_ENUMTYPE meshtastic_GeoPointSource - - /* Initializer values for message structs */ #define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}} #define meshtastic_GeoChat_init_default {"", false, "", false, ""} @@ -476,16 +176,12 @@ extern "C" { #define meshtastic_Status_init_default {0} #define meshtastic_Contact_init_default {"", ""} #define meshtastic_PLI_init_default {0, 0, 0, 0, 0} -#define meshtastic_AircraftTrack_init_default {"", "", "", "", 0, "", 0, 0, ""} -#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", 0, {0}} #define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}} #define meshtastic_GeoChat_init_zero {"", false, "", false, ""} #define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_zero {0} #define meshtastic_Contact_init_zero {"", ""} #define meshtastic_PLI_init_zero {0, 0, 0, 0, 0} -#define meshtastic_AircraftTrack_init_zero {"", "", "", "", 0, "", 0, 0, ""} -#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", 0, {0}} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_GeoChat_message_tag 1 @@ -508,42 +204,6 @@ extern "C" { #define meshtastic_TAKPacket_pli_tag 5 #define meshtastic_TAKPacket_chat_tag 6 #define meshtastic_TAKPacket_detail_tag 7 -#define meshtastic_AircraftTrack_icao_tag 1 -#define meshtastic_AircraftTrack_registration_tag 2 -#define meshtastic_AircraftTrack_flight_tag 3 -#define meshtastic_AircraftTrack_aircraft_type_tag 4 -#define meshtastic_AircraftTrack_squawk_tag 5 -#define meshtastic_AircraftTrack_category_tag 6 -#define meshtastic_AircraftTrack_rssi_x10_tag 7 -#define meshtastic_AircraftTrack_gps_tag 8 -#define meshtastic_AircraftTrack_cot_host_id_tag 9 -#define meshtastic_TAKPacketV2_cot_type_id_tag 1 -#define meshtastic_TAKPacketV2_how_tag 2 -#define meshtastic_TAKPacketV2_callsign_tag 3 -#define meshtastic_TAKPacketV2_team_tag 4 -#define meshtastic_TAKPacketV2_role_tag 5 -#define meshtastic_TAKPacketV2_latitude_i_tag 6 -#define meshtastic_TAKPacketV2_longitude_i_tag 7 -#define meshtastic_TAKPacketV2_altitude_tag 8 -#define meshtastic_TAKPacketV2_speed_tag 9 -#define meshtastic_TAKPacketV2_course_tag 10 -#define meshtastic_TAKPacketV2_battery_tag 11 -#define meshtastic_TAKPacketV2_geo_src_tag 12 -#define meshtastic_TAKPacketV2_alt_src_tag 13 -#define meshtastic_TAKPacketV2_uid_tag 14 -#define meshtastic_TAKPacketV2_device_callsign_tag 15 -#define meshtastic_TAKPacketV2_stale_seconds_tag 16 -#define meshtastic_TAKPacketV2_tak_version_tag 17 -#define meshtastic_TAKPacketV2_tak_device_tag 18 -#define meshtastic_TAKPacketV2_tak_platform_tag 19 -#define meshtastic_TAKPacketV2_tak_os_tag 20 -#define meshtastic_TAKPacketV2_endpoint_tag 21 -#define meshtastic_TAKPacketV2_phone_tag 22 -#define meshtastic_TAKPacketV2_cot_type_str_tag 23 -#define meshtastic_TAKPacketV2_pli_tag 30 -#define meshtastic_TAKPacketV2_chat_tag 31 -#define meshtastic_TAKPacketV2_aircraft_tag 32 -#define meshtastic_TAKPacketV2_raw_detail_tag 33 /* Struct field encoding specification for nanopb */ #define meshtastic_TAKPacket_FIELDLIST(X, a) \ @@ -595,60 +255,12 @@ X(a, STATIC, SINGULAR, UINT32, course, 5) #define meshtastic_PLI_CALLBACK NULL #define meshtastic_PLI_DEFAULT NULL -#define meshtastic_AircraftTrack_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, STRING, icao, 1) \ -X(a, STATIC, SINGULAR, STRING, registration, 2) \ -X(a, STATIC, SINGULAR, STRING, flight, 3) \ -X(a, STATIC, SINGULAR, STRING, aircraft_type, 4) \ -X(a, STATIC, SINGULAR, UINT32, squawk, 5) \ -X(a, STATIC, SINGULAR, STRING, category, 6) \ -X(a, STATIC, SINGULAR, SINT32, rssi_x10, 7) \ -X(a, STATIC, SINGULAR, BOOL, gps, 8) \ -X(a, STATIC, SINGULAR, STRING, cot_host_id, 9) -#define meshtastic_AircraftTrack_CALLBACK NULL -#define meshtastic_AircraftTrack_DEFAULT NULL - -#define meshtastic_TAKPacketV2_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, cot_type_id, 1) \ -X(a, STATIC, SINGULAR, UENUM, how, 2) \ -X(a, STATIC, SINGULAR, STRING, callsign, 3) \ -X(a, STATIC, SINGULAR, UENUM, team, 4) \ -X(a, STATIC, SINGULAR, UENUM, role, 5) \ -X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 6) \ -X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 7) \ -X(a, STATIC, SINGULAR, SINT32, altitude, 8) \ -X(a, STATIC, SINGULAR, UINT32, speed, 9) \ -X(a, STATIC, SINGULAR, UINT32, course, 10) \ -X(a, STATIC, SINGULAR, UINT32, battery, 11) \ -X(a, STATIC, SINGULAR, UENUM, geo_src, 12) \ -X(a, STATIC, SINGULAR, UENUM, alt_src, 13) \ -X(a, STATIC, SINGULAR, STRING, uid, 14) \ -X(a, STATIC, SINGULAR, STRING, device_callsign, 15) \ -X(a, STATIC, SINGULAR, UINT32, stale_seconds, 16) \ -X(a, STATIC, SINGULAR, STRING, tak_version, 17) \ -X(a, STATIC, SINGULAR, STRING, tak_device, 18) \ -X(a, STATIC, SINGULAR, STRING, tak_platform, 19) \ -X(a, STATIC, SINGULAR, STRING, tak_os, 20) \ -X(a, STATIC, SINGULAR, STRING, endpoint, 21) \ -X(a, STATIC, SINGULAR, STRING, phone, 22) \ -X(a, STATIC, SINGULAR, STRING, cot_type_str, 23) \ -X(a, STATIC, ONEOF, BOOL, (payload_variant,pli,payload_variant.pli), 30) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 31) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,aircraft,payload_variant.aircraft), 32) \ -X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33) -#define meshtastic_TAKPacketV2_CALLBACK NULL -#define meshtastic_TAKPacketV2_DEFAULT NULL -#define meshtastic_TAKPacketV2_payload_variant_chat_MSGTYPE meshtastic_GeoChat -#define meshtastic_TAKPacketV2_payload_variant_aircraft_MSGTYPE meshtastic_AircraftTrack - extern const pb_msgdesc_t meshtastic_TAKPacket_msg; extern const pb_msgdesc_t meshtastic_GeoChat_msg; extern const pb_msgdesc_t meshtastic_Group_msg; extern const pb_msgdesc_t meshtastic_Status_msg; extern const pb_msgdesc_t meshtastic_Contact_msg; extern const pb_msgdesc_t meshtastic_PLI_msg; -extern const pb_msgdesc_t meshtastic_AircraftTrack_msg; -extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_TAKPacket_fields &meshtastic_TAKPacket_msg @@ -657,18 +269,14 @@ extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg; #define meshtastic_Status_fields &meshtastic_Status_msg #define meshtastic_Contact_fields &meshtastic_Contact_msg #define meshtastic_PLI_fields &meshtastic_PLI_msg -#define meshtastic_AircraftTrack_fields &meshtastic_AircraftTrack_msg -#define meshtastic_TAKPacketV2_fields &meshtastic_TAKPacketV2_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacketV2_size -#define meshtastic_AircraftTrack_size 134 +#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size #define meshtastic_Contact_size 242 #define meshtastic_GeoChat_size 444 #define meshtastic_Group_size 4 #define meshtastic_PLI_size 31 #define meshtastic_Status_size 3 -#define meshtastic_TAKPacketV2_size 1027 #define meshtastic_TAKPacket_size 705 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index fbcd1b0e318..3648d88502a 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -27,7 +27,7 @@ PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO) PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) -PB_BIND(meshtastic_DMShell, meshtastic_DMShell, AUTO) +PB_BIND(meshtastic_RemoteShell, meshtastic_RemoteShell, AUTO) PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 02165f95778..d342d926ee0 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -517,22 +517,22 @@ typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type { Values 1-63 are client->server requests. Values 64-127 are server->client responses/events. */ -typedef enum _meshtastic_DMShell_OpCode { - meshtastic_DMShell_OpCode_OP_UNSET = 0, +typedef enum _meshtastic_RemoteShell_OpCode { + meshtastic_RemoteShell_OpCode_OP_UNSET = 0, /* Client -> server */ - meshtastic_DMShell_OpCode_OPEN = 1, - meshtastic_DMShell_OpCode_INPUT = 2, - meshtastic_DMShell_OpCode_RESIZE = 3, - meshtastic_DMShell_OpCode_CLOSE = 4, - meshtastic_DMShell_OpCode_PING = 5, - meshtastic_DMShell_OpCode_ACK = 6, + meshtastic_RemoteShell_OpCode_OPEN = 1, + meshtastic_RemoteShell_OpCode_INPUT = 2, + meshtastic_RemoteShell_OpCode_RESIZE = 3, + meshtastic_RemoteShell_OpCode_CLOSE = 4, + meshtastic_RemoteShell_OpCode_PING = 5, + meshtastic_RemoteShell_OpCode_ACK = 6, /* Server -> client */ - meshtastic_DMShell_OpCode_OPEN_OK = 64, - meshtastic_DMShell_OpCode_OUTPUT = 65, - meshtastic_DMShell_OpCode_CLOSED = 66, - meshtastic_DMShell_OpCode_ERROR = 67, - meshtastic_DMShell_OpCode_PONG = 68 -} meshtastic_DMShell_OpCode; + meshtastic_RemoteShell_OpCode_OPEN_OK = 64, + meshtastic_RemoteShell_OpCode_OUTPUT = 65, + meshtastic_RemoteShell_OpCode_CLOSED = 66, + meshtastic_RemoteShell_OpCode_ERROR = 67, + meshtastic_RemoteShell_OpCode_PONG = 68 +} meshtastic_RemoteShell_OpCode; /* The priority of this message for sending. Higher priorities are sent first (when managing the transmit queue). @@ -866,11 +866,11 @@ typedef struct _meshtastic_StoreForwardPlusPlus { uint32_t chain_count; } meshtastic_StoreForwardPlusPlus; -typedef PB_BYTES_ARRAY_T(200) meshtastic_DMShell_payload_t; -/* The actual over-the-mesh message doing DMShell */ -typedef struct _meshtastic_DMShell { +typedef PB_BYTES_ARRAY_T(200) meshtastic_RemoteShell_payload_t; +/* The actual over-the-mesh message doing RemoteShell */ +typedef struct _meshtastic_RemoteShell { /* Structured frame operation. */ - meshtastic_DMShell_OpCode op; + meshtastic_RemoteShell_OpCode op; /* Logical PTY session identifier. */ uint32_t session_id; /* Monotonic sequence number for this frame. */ @@ -878,14 +878,14 @@ typedef struct _meshtastic_DMShell { /* Cumulative ack sequence number. */ uint32_t ack_seq; /* Opaque bytes payload for INPUT/OUTPUT/ERROR and other frame bodies. */ - meshtastic_DMShell_payload_t payload; + meshtastic_RemoteShell_payload_t payload; /* Terminal size columns used for OPEN/RESIZE signaling. */ uint32_t cols; /* Terminal size rows used for OPEN/RESIZE signaling. */ uint32_t rows; /* Bit flags for protocol extensions. */ uint32_t flags; -} meshtastic_DMShell; +} meshtastic_RemoteShell; /* Waypoint message, used to share arbitrary locations across the mesh */ typedef struct _meshtastic_Waypoint { @@ -1427,9 +1427,9 @@ extern "C" { #define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF #define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1)) -#define _meshtastic_DMShell_OpCode_MIN meshtastic_DMShell_OpCode_OP_UNSET -#define _meshtastic_DMShell_OpCode_MAX meshtastic_DMShell_OpCode_PONG -#define _meshtastic_DMShell_OpCode_ARRAYSIZE ((meshtastic_DMShell_OpCode)(meshtastic_DMShell_OpCode_PONG+1)) +#define _meshtastic_RemoteShell_OpCode_MIN meshtastic_RemoteShell_OpCode_OP_UNSET +#define _meshtastic_RemoteShell_OpCode_MAX meshtastic_RemoteShell_OpCode_PONG +#define _meshtastic_RemoteShell_OpCode_ARRAYSIZE ((meshtastic_RemoteShell_OpCode)(meshtastic_RemoteShell_OpCode_PONG+1)) #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX @@ -1461,7 +1461,7 @@ extern "C" { #define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type -#define meshtastic_DMShell_op_ENUMTYPE meshtastic_DMShell_OpCode +#define meshtastic_RemoteShell_op_ENUMTYPE meshtastic_RemoteShell_OpCode @@ -1507,7 +1507,7 @@ extern "C" { #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} -#define meshtastic_DMShell_init_default {_meshtastic_DMShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} +#define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} @@ -1541,7 +1541,7 @@ extern "C" { #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} -#define meshtastic_DMShell_init_zero {_meshtastic_DMShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} +#define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} @@ -1631,14 +1631,14 @@ extern "C" { #define meshtastic_StoreForwardPlusPlus_encapsulated_from_tag 8 #define meshtastic_StoreForwardPlusPlus_encapsulated_rxtime_tag 9 #define meshtastic_StoreForwardPlusPlus_chain_count_tag 10 -#define meshtastic_DMShell_op_tag 1 -#define meshtastic_DMShell_session_id_tag 2 -#define meshtastic_DMShell_seq_tag 3 -#define meshtastic_DMShell_ack_seq_tag 4 -#define meshtastic_DMShell_payload_tag 5 -#define meshtastic_DMShell_cols_tag 6 -#define meshtastic_DMShell_rows_tag 7 -#define meshtastic_DMShell_flags_tag 8 +#define meshtastic_RemoteShell_op_tag 1 +#define meshtastic_RemoteShell_session_id_tag 2 +#define meshtastic_RemoteShell_seq_tag 3 +#define meshtastic_RemoteShell_ack_seq_tag 4 +#define meshtastic_RemoteShell_payload_tag 5 +#define meshtastic_RemoteShell_cols_tag 6 +#define meshtastic_RemoteShell_rows_tag 7 +#define meshtastic_RemoteShell_flags_tag 8 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1871,7 +1871,7 @@ X(a, STATIC, SINGULAR, UINT32, chain_count, 10) #define meshtastic_StoreForwardPlusPlus_CALLBACK NULL #define meshtastic_StoreForwardPlusPlus_DEFAULT NULL -#define meshtastic_DMShell_FIELDLIST(X, a) \ +#define meshtastic_RemoteShell_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, op, 1) \ X(a, STATIC, SINGULAR, UINT32, session_id, 2) \ X(a, STATIC, SINGULAR, UINT32, seq, 3) \ @@ -1880,8 +1880,8 @@ X(a, STATIC, SINGULAR, BYTES, payload, 5) \ X(a, STATIC, SINGULAR, UINT32, cols, 6) \ X(a, STATIC, SINGULAR, UINT32, rows, 7) \ X(a, STATIC, SINGULAR, UINT32, flags, 8) -#define meshtastic_DMShell_CALLBACK NULL -#define meshtastic_DMShell_DEFAULT NULL +#define meshtastic_RemoteShell_CALLBACK NULL +#define meshtastic_RemoteShell_DEFAULT NULL #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ @@ -2165,7 +2165,7 @@ extern const pb_msgdesc_t meshtastic_Routing_msg; extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; -extern const pb_msgdesc_t meshtastic_DMShell_msg; +extern const pb_msgdesc_t meshtastic_RemoteShell_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; extern const pb_msgdesc_t meshtastic_StatusMessage_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; @@ -2201,7 +2201,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Data_fields &meshtastic_Data_msg #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg #define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg -#define meshtastic_DMShell_fields &meshtastic_DMShell_msg +#define meshtastic_RemoteShell_fields &meshtastic_RemoteShell_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg #define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg @@ -2236,7 +2236,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_ChunkedPayload_size 245 #define meshtastic_ClientNotification_size 482 #define meshtastic_Compressed_size 239 -#define meshtastic_DMShell_size 241 #define meshtastic_Data_size 269 #define meshtastic_DeviceMetadata_size 54 #define meshtastic_DuplicatedPublicKey_size 0 @@ -2258,6 +2257,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 +#define meshtastic_RemoteShell_size 241 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 #define meshtastic_StatusMessage_size 81 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 823e034a35b..7d9e8597ae7 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -77,7 +77,7 @@ typedef enum _meshtastic_PortNum { /* Module/port for handling key verification requests. */ meshtastic_PortNum_KEY_VERIFICATION_APP = 12, /* Module/port for handling key verification requests. */ - meshtastic_PortNum_DM_SHELL_APP = 13, + meshtastic_PortNum_REMOTE_SHELL_APP = 13, /* Provides a 'ping' service that replies to any packet it receives. Also serves as a small example module. ENCODING: ASCII Plaintext */ @@ -152,10 +152,6 @@ typedef enum _meshtastic_PortNum { arbitrary telemetry over meshtastic that is not covered by telemetry.proto ENCODING: CayenneLLP */ meshtastic_PortNum_CAYENNE_APP = 77, - /* ATAK Plugin V2 - Portnum for payloads from the official Meshtastic ATAK plugin using - TAKPacketV2 with zstd dictionary compression. */ - meshtastic_PortNum_ATAK_PLUGIN_V2 = 78, /* GroupAlarm integration Used for transporting GroupAlarm-related messages between Meshtastic nodes and companion applications/services. */ diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index f0c8e6e0faf..07a01f5e1d6 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -64,7 +64,7 @@ bool decodeHeartbeatStatus(const uint8_t *src, size_t len, uint32_t &lastTxSeq, } // namespace DMShellModule::DMShellModule() - : SinglePortModule("DMShellModule", meshtastic_PortNum_DM_SHELL_APP), concurrency::OSThread("DMShell", 100) + : SinglePortModule("DMShellModule", meshtastic_PortNum_REMOTE_SHELL_APP), concurrency::OSThread("DMShell", 100) { LOG_WARN("DMShell enabled on Portduino: remote shell access is dangerous and intended for trusted debugging only"); } @@ -83,7 +83,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } // TODO: double-check the sender is the same as the one we have an active session with before processing ACKs - if (frame.op == meshtastic_DMShell_OpCode_ACK) { + if (frame.op == meshtastic_RemoteShell_OpCode_ACK) { if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { handleAckFrame(frame); } @@ -101,11 +101,11 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) return ProcessMessage::STOP; } - if (frame.op == meshtastic_DMShell_OpCode_OPEN) { + if (frame.op == meshtastic_RemoteShell_OpCode_OPEN) { LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.sessionId); if (!openSession(mp, frame)) { const char *msg = "open_failed"; - sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, 0, + sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_RemoteShell_OpCode_ERROR, frame.sessionId, 0, reinterpret_cast(msg), strlen(msg), 0, 0, frame.seq); } return ProcessMessage::STOP; @@ -113,7 +113,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) if (!session.active || frame.sessionId != session.sessionId || getFrom(&mp) != session.peer) { const char *msg = "invalid_session"; - sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_DMShell_OpCode_ERROR, frame.sessionId, 0, + sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_RemoteShell_OpCode_ERROR, frame.sessionId, 0, reinterpret_cast(msg), strlen(msg), 0, 0, frame.seq); return ProcessMessage::STOP; } @@ -125,7 +125,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) session.lastActivityMs = millis(); switch (frame.op) { - case meshtastic_DMShell_OpCode_INPUT: + case meshtastic_RemoteShell_OpCode_INPUT: if (!writeSessionInput(frame)) { sendError("input_write_failed"); } else { @@ -133,12 +133,12 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); - sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); + sendControl(meshtastic_RemoteShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); } } break; - case meshtastic_DMShell_OpCode_RESIZE: + case meshtastic_RemoteShell_OpCode_RESIZE: if (frame.rows > 0 && frame.cols > 0) { struct winsize ws = {}; ws.ws_row = frame.rows; @@ -148,7 +148,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } } break; - case meshtastic_DMShell_OpCode_PING: { + case meshtastic_RemoteShell_OpCode_PING: { uint32_t peerLastTxSeq = 0; uint32_t peerLastRxSeq = frame.ackSeq; if (decodeHeartbeatStatus(frame.payload, frame.payloadLen, peerLastTxSeq, peerLastRxSeq)) { @@ -160,10 +160,10 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) uint8_t heartbeatPayload[HEARTBEAT_STATUS_SIZE] = {0}; encodeHeartbeatStatus(heartbeatPayload, session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0, session.lastAckedRxSeq); - sendControl(meshtastic_DMShell_OpCode_PONG, heartbeatPayload, sizeof(heartbeatPayload)); + sendControl(meshtastic_RemoteShell_OpCode_PONG, heartbeatPayload, sizeof(heartbeatPayload)); break; } - case meshtastic_DMShell_OpCode_CLOSE: + case meshtastic_RemoteShell_OpCode_CLOSE: closeSession("peer_close", true); break; default: @@ -199,7 +199,7 @@ int32_t DMShellModule::runOnce() const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); - sendControl(meshtastic_DMShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); + sendControl(meshtastic_RemoteShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); // continue; // do we want to ack every data message, and only send the next on ack? @@ -230,8 +230,8 @@ bool DMShellModule::parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &ou return false; } - meshtastic_DMShell decodedMsg = meshtastic_DMShell_init_zero; - if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_DMShell_fields, &decodedMsg)) { + meshtastic_RemoteShell decodedMsg = meshtastic_RemoteShell_init_zero; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_RemoteShell_fields, &decodedMsg)) { LOG_INFO("Received a DMShell message"); } else { LOG_ERROR("Error decoding DMShell message!"); @@ -329,7 +329,7 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr ((static_cast(session.childPid) << 8) & 0xff0000) | (static_cast(session.childPid) >> 24); memcpy(payload, &pidBE, sizeof(payload)); - sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_OPEN_OK, session.sessionId, session.nextTxSeq++, + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OPEN_OK, session.sessionId, session.nextTxSeq++, payload, sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid); @@ -357,7 +357,7 @@ void DMShellModule::closeSession(const char *reason, bool notifyPeer) if (notifyPeer) { const size_t reasonLen = strnlen(reason, 256); - sendControl(meshtastic_DMShell_OpCode_CLOSED, reinterpret_cast(reason), reasonLen); + sendControl(meshtastic_RemoteShell_OpCode_CLOSED, reinterpret_cast(reason), reasonLen); } if (session.masterFd >= 0) { @@ -389,10 +389,10 @@ void DMShellModule::reapChildIfExited() } } -void DMShellModule::rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, +void DMShellModule::rememberSentFrame(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags) { - if (seq == 0 || op == meshtastic_DMShell_OpCode_ACK) { + if (seq == 0 || op == meshtastic_RemoteShell_OpCode_ACK) { return; } @@ -450,11 +450,11 @@ void DMShellModule::sendAck(uint32_t replayFromSeq) LOG_WARN("DMShell: requesting replay from seq=%u", replayFromSeq); } - sendFrameToPeer(session.peer, session.channel, meshtastic_DMShell_OpCode_ACK, session.sessionId, 0, payloadPtr, payloadLen, 0, - 0, session.lastAckedRxSeq, 0, false); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_ACK, session.sessionId, 0, payloadPtr, + payloadLen, 0, 0, session.lastAckedRxSeq, 0, false); } -void DMShellModule::sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen) +void DMShellModule::sendControl(meshtastic_RemoteShell_OpCode op, const uint8_t *payload, size_t payloadLen) { sendFrameToPeer(session.peer, session.channel, op, session.sessionId, session.nextTxSeq++, payload, payloadLen, 0, 0, session.lastAckedRxSeq); @@ -505,9 +505,9 @@ bool DMShellModule::shouldProcessIncomingFrame(const DMShellFrame &frame) return true; } -void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, - const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, - uint32_t flags, bool remember) +void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_RemoteShell_OpCode op, uint32_t sessionId, + uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, + uint32_t ackSeq, uint32_t flags, bool remember) { meshtastic_MeshPacket *packet = buildFramePacket(op, sessionId, seq, payload, payloadLen, cols, rows, ackSeq, flags); if (!packet) { @@ -529,14 +529,14 @@ void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DM void DMShellModule::sendError(const char *message) { const size_t len = strnlen(message, meshtastic_Constants_DATA_PAYLOAD_LEN); - sendControl(meshtastic_DMShell_OpCode_ERROR, reinterpret_cast(message), len); + sendControl(meshtastic_RemoteShell_OpCode_ERROR, reinterpret_cast(message), len); } -meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, +meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags) { - meshtastic_DMShell frame = meshtastic_DMShell_init_zero; + meshtastic_RemoteShell frame = meshtastic_RemoteShell_init_zero; frame.op = op; frame.session_id = sessionId; frame.seq = seq; @@ -556,7 +556,7 @@ meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_DMShell_OpCode } LOG_WARN("DMShell: building packet op=%d session=0x%x seq=%d payloadLen=%d", op, sessionId, seq, payloadLen); const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes), - meshtastic_DMShell_fields, &frame); + meshtastic_RemoteShell_fields, &frame); if (encoded == 0) { return nullptr; } diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index 3f0e709c6c1..2ae519dc5f3 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -13,7 +13,7 @@ #if defined(ARCH_PORTDUINO) struct DMShellFrame { - meshtastic_DMShell_OpCode op = meshtastic_DMShell_OpCode_ERROR; + meshtastic_RemoteShell_OpCode op = meshtastic_RemoteShell_OpCode_ERROR; uint32_t sessionId = 0; uint32_t seq = 0; uint32_t ackSeq = 0; @@ -38,7 +38,7 @@ struct DMShellSession { uint32_t lastActivityMs = 0; struct SentFrame { bool valid = false; - meshtastic_DMShell_OpCode op = meshtastic_DMShell_OpCode_ERROR; + meshtastic_RemoteShell_OpCode op = meshtastic_RemoteShell_OpCode_ERROR; uint32_t sessionId = 0; uint32_t seq = 0; uint32_t ackSeq = 0; @@ -76,17 +76,17 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule void closeSession(const char *reason, bool notifyPeer); void reapChildIfExited(); - void rememberSentFrame(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, + void rememberSentFrame(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags); void pruneSentFrames(uint32_t ackSeq); void resendFramesFrom(uint32_t startSeq); void sendAck(uint32_t replayFromSeq = 0); - void sendControl(meshtastic_DMShell_OpCode op, const uint8_t *payload, size_t payloadLen); - void sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, + void sendControl(meshtastic_RemoteShell_OpCode op, const uint8_t *payload, size_t payloadLen); + void sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0, uint32_t ackSeq = 0, uint32_t flags = 0, bool remember = true); void sendError(const char *message); - meshtastic_MeshPacket *buildFramePacket(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, + meshtastic_MeshPacket *buildFramePacket(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags); }; From 3a498fbbe40c960b3e286e40f66447e7892a6a84 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 19:47:33 -0500 Subject: [PATCH 13/22] Make the DMShell tests compile --- .../ports/test_dmshell.cpp | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/test/test_meshpacket_serializer/ports/test_dmshell.cpp b/test/test_meshpacket_serializer/ports/test_dmshell.cpp index 05e901dc6c6..29bc8bef69c 100644 --- a/test/test_meshpacket_serializer/ports/test_dmshell.cpp +++ b/test/test_meshpacket_serializer/ports/test_dmshell.cpp @@ -49,64 +49,49 @@ bool encodeBytesField(pb_ostream_t *stream, const pb_field_iter_t *field, void * return pb_encode_string(stream, state->buffer, state->length); } -void assert_dmshell_roundtrip(meshtastic_DMShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, +void assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0) { - meshtastic_DMShell tx = meshtastic_DMShell_init_zero; + meshtastic_RemoteShell tx = meshtastic_RemoteShell_init_zero; tx.op = op; tx.session_id = sessionId; tx.seq = seq; tx.cols = cols; tx.rows = rows; - BytesEncodeState txPayload = {payload, payloadLen}; - if (payload && payloadLen > 0) { - tx.payload.funcs.encode = encodeBytesField; - tx.payload.arg = &txPayload; - } - uint8_t encoded[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; - size_t encodedLen = pb_encode_to_bytes(encoded, sizeof(encoded), meshtastic_DMShell_fields, &tx); + size_t encodedLen = pb_encode_to_bytes(encoded, sizeof(encoded), meshtastic_RemoteShell_fields, &tx); TEST_ASSERT_GREATER_THAN_UINT32(0, encodedLen); - meshtastic_DMShell rx = meshtastic_DMShell_init_zero; - uint8_t decodedPayload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; - BytesDecodeState rxPayload = {decodedPayload, sizeof(decodedPayload), 0}; - rx.payload.funcs.decode = decodeBytesField; - rx.payload.arg = &rxPayload; + meshtastic_RemoteShell rx = meshtastic_RemoteShell_init_zero; - TEST_ASSERT_TRUE(pb_decode_from_bytes(encoded, encodedLen, meshtastic_DMShell_fields, &rx)); + TEST_ASSERT_TRUE(pb_decode_from_bytes(encoded, encodedLen, meshtastic_RemoteShell_fields, &rx)); TEST_ASSERT_EQUAL(op, rx.op); TEST_ASSERT_EQUAL_UINT32(sessionId, rx.session_id); TEST_ASSERT_EQUAL_UINT32(seq, rx.seq); TEST_ASSERT_EQUAL_UINT32(cols, rx.cols); TEST_ASSERT_EQUAL_UINT32(rows, rx.rows); - TEST_ASSERT_EQUAL_UINT32(payloadLen, rxPayload.length); - - if (payloadLen > 0) { - TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, decodedPayload, payloadLen); - } } } // namespace void test_dmshell_open_roundtrip() { - assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_OPEN, 0x101, 1, nullptr, 0, 120, 40); + assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_OPEN, 0x101, 1, nullptr, 0, 120, 40); } void test_dmshell_input_roundtrip() { const uint8_t payload[] = {'l', 's', '\n'}; - assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_INPUT, 0x202, 2, payload, sizeof(payload)); + assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_INPUT, 0x202, 2, payload, sizeof(payload)); } void test_dmshell_resize_roundtrip() { - assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_RESIZE, 0x303, 3, nullptr, 0, 180, 55); + assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_RESIZE, 0x303, 3, nullptr, 0, 180, 55); } void test_dmshell_close_roundtrip() { const uint8_t reason[] = {'b', 'y', 'e'}; - assert_dmshell_roundtrip(meshtastic_DMShell_OpCode_CLOSE, 0x404, 4, reason, sizeof(reason)); + assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_CLOSE, 0x404, 4, reason, sizeof(reason)); } \ No newline at end of file From 69f1b502cc89174011f19c46def3f29b4ca1501c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 20:05:50 -0500 Subject: [PATCH 14/22] Harden against possible memory overflows --- src/modules/DMShell.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 07a01f5e1d6..921c5fe8ff4 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -28,7 +28,7 @@ namespace { constexpr uint16_t PTY_COLS_DEFAULT = 120; constexpr uint16_t PTY_ROWS_DEFAULT = 40; -constexpr size_t MAX_PTY_READ_SIZE = 200; +constexpr size_t MAX_MESSAGE_SIZE = 200; constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); constexpr size_t HEARTBEAT_STATUS_SIZE = sizeof(uint32_t) * 2; @@ -129,7 +129,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) if (!writeSessionInput(frame)) { sendError("input_write_failed"); } else { - uint8_t outBuf[MAX_PTY_READ_SIZE]; + uint8_t outBuf[MAX_MESSAGE_SIZE]; const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); @@ -194,7 +194,7 @@ int32_t DMShellModule::runOnce() return 50; } - uint8_t outBuf[MAX_PTY_READ_SIZE]; + uint8_t outBuf[MAX_MESSAGE_SIZE]; while (session.masterFd >= 0) { const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { @@ -528,7 +528,7 @@ void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_Re void DMShellModule::sendError(const char *message) { - const size_t len = strnlen(message, meshtastic_Constants_DATA_PAYLOAD_LEN); + const size_t len = strnlen(message, MAX_MESSAGE_SIZE); sendControl(meshtastic_RemoteShell_OpCode_ERROR, reinterpret_cast(message), len); } @@ -546,6 +546,7 @@ meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_RemoteShell_Op frame.flags = flags; if (payload && payloadLen > 0) { + assert(payloadLen <= sizeof(frame.payload.bytes)); memcpy(frame.payload.bytes, payload, payloadLen); frame.payload.size = payloadLen; } From 6c28d11cee2935983f676dcf58dde400540845b6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 20:23:31 -0500 Subject: [PATCH 15/22] Minor cleanups --- src/modules/DMShell.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 921c5fe8ff4..beae8208068 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -31,6 +31,8 @@ constexpr uint16_t PTY_ROWS_DEFAULT = 40; constexpr size_t MAX_MESSAGE_SIZE = 200; constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); constexpr size_t HEARTBEAT_STATUS_SIZE = sizeof(uint32_t) * 2; +constexpr uint32_t CHILD_EXIT_WAIT_TIMEOUT_MS = 1500; +constexpr uint32_t CHILD_EXIT_POLL_INTERVAL_MS = 50; void encodeUint32BE(uint8_t *dest, uint32_t value) { @@ -82,7 +84,6 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) return ProcessMessage::STOP; } - // TODO: double-check the sender is the same as the one we have an active session with before processing ACKs if (frame.op == meshtastic_RemoteShell_OpCode_ACK) { if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { handleAckFrame(frame); @@ -132,7 +133,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) uint8_t outBuf[MAX_MESSAGE_SIZE]; const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { - LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); + LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead); sendControl(meshtastic_RemoteShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); } @@ -198,7 +199,7 @@ int32_t DMShellModule::runOnce() while (session.masterFd >= 0) { const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { - LOG_WARN("DMShell: read %d bytes from PTY", bytesRead); + LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead); sendControl(meshtastic_RemoteShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); session.lastActivityMs = millis(); // continue; @@ -325,10 +326,7 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr session.lastActivityMs = millis(); uint8_t payload[sizeof(uint32_t)] = {0}; - uint32_t pidBE = (static_cast(session.childPid) << 24) | ((static_cast(session.childPid) >> 8) & 0xff00) | - ((static_cast(session.childPid) << 8) & 0xff0000) | - (static_cast(session.childPid) >> 24); - memcpy(payload, &pidBE, sizeof(payload)); + encodeUint32BE(payload, session.childPid); sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OPEN_OK, session.sessionId, session.nextTxSeq++, payload, sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); @@ -555,7 +553,7 @@ meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_RemoteShell_Op if (!packet) { return nullptr; } - LOG_WARN("DMShell: building packet op=%d session=0x%x seq=%d payloadLen=%d", op, sessionId, seq, payloadLen); + LOG_WARN("DMShell: building packet op=%u session=0x%x seq=%u payloadLen=%zu", op, sessionId, seq, payloadLen); const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes), meshtastic_RemoteShell_fields, &frame); if (encoded == 0) { From 8f2ecbdb4d2ad0b3b3a38e80615ae76cc73f7705 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 20:32:38 -0500 Subject: [PATCH 16/22] No Child Left Behind --- src/modules/DMShell.cpp | 43 ++++++++++++++++++++++++++++++++++++----- src/modules/DMShell.h | 2 ++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index beae8208068..9acc790d25c 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -31,8 +31,6 @@ constexpr uint16_t PTY_ROWS_DEFAULT = 40; constexpr size_t MAX_MESSAGE_SIZE = 200; constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); constexpr size_t HEARTBEAT_STATUS_SIZE = sizeof(uint32_t) * 2; -constexpr uint32_t CHILD_EXIT_WAIT_TIMEOUT_MS = 1500; -constexpr uint32_t CHILD_EXIT_POLL_INTERVAL_MS = 50; void encodeUint32BE(uint8_t *dest, uint32_t value) { @@ -177,6 +175,8 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) int32_t DMShellModule::runOnce() { + processPendingChildReap(); + if (!session.active) { return 100; } @@ -364,9 +364,14 @@ void DMShellModule::closeSession(const char *reason, bool notifyPeer) } if (session.childPid > 0) { - kill(session.childPid, SIGTERM); - int status = 0; - waitpid(session.childPid, &status, WNOHANG); + // Run this to avoid forgetting a child + processPendingChildReap(); + + if (kill(session.childPid, SIGTERM) < 0 && errno != ESRCH) { + LOG_WARN("DMShell: failed to send SIGTERM to pid=%d errno=%d", session.childPid, errno); + } + + pendingChildPid = session.childPid; session.childPid = -1; } @@ -387,6 +392,34 @@ void DMShellModule::reapChildIfExited() } } +void DMShellModule::processPendingChildReap() +{ + if (pendingChildPid <= 0) { + return; + } + + int status = 0; + const pid_t result = waitpid(pendingChildPid, &status, WNOHANG); + + if (result == pendingChildPid || (result < 0 && errno == ECHILD)) { + pendingChildPid = -1; + return; + } + + if (result < 0) { + LOG_WARN("DMShell: waitpid failed for pid=%d errno=%d", pendingChildPid, errno); + pendingChildPid = -1; + return; + } + + if (pendingChildPid > 0) { + if (kill(pendingChildPid, SIGKILL) < 0 && errno != ESRCH) { + LOG_WARN("DMShell: failed to send SIGKILL to pid=%d errno=%d", pendingChildPid, errno); + } + pendingChildPid = -1; + } +} + void DMShellModule::rememberSentFrame(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags) { diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index 2ae519dc5f3..06596ff3b4e 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -66,6 +66,7 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule static constexpr uint32_t SESSION_IDLE_TIMEOUT_MS = 5 * 60 * 1000; DMShellSession session; + pid_t pendingChildPid = -1; bool parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &outFrame); bool isAuthorizedPacket(const meshtastic_MeshPacket &mp) const; @@ -75,6 +76,7 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule bool writeSessionInput(const DMShellFrame &frame); void closeSession(const char *reason, bool notifyPeer); void reapChildIfExited(); + void processPendingChildReap(); void rememberSentFrame(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags); From e393a5c41080e66080d4146ac70c6db66054b041 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 20:50:50 -0500 Subject: [PATCH 17/22] Make consoleInit() Reentrant, and initialize it earlier on native --- src/SerialConsole.cpp | 3 +++ src/platform/portduino/PortduinoGlue.cpp | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index e24aa3c5748..2a3f08cbc85 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -30,6 +30,9 @@ SerialConsole *console; void consoleInit() { + if (console) { + return; + } auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread #if defined(SERIAL_HAS_ON_RECEIVE) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 9e0a1b2a57e..5f51ee0835d 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -182,6 +182,10 @@ void portduinoSetup() // Force stdout to be line buffered setvbuf(stdout, stdoutBuffer, _IOLBF, sizeof(stdoutBuffer)); + // We do this super early so that we can log from the rest of the init code + concurrency::hasBeenSetup = true; + consoleInit(); + if (portduino_config.force_simradio == true) { portduino_config.lora_module = use_simradio; } else if (configPath != nullptr) { From a6d61413c3daacaea79417ce60447966c7559ada Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 13 Apr 2026 22:19:24 -0500 Subject: [PATCH 18/22] Add PortduinoSetOptions to overwrite the realhardware bool --- src/platform/portduino/PortduinoGlue.cpp | 4 +++- variants/native/portduino.ini | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 5f51ee0835d..7833b36030a 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -654,7 +654,9 @@ void portduinoSetup() if (verboseEnabled && portduino_config.logoutputlevel != level_trace) { portduino_config.logoutputlevel = level_debug; } - + if (portduino_config.lora_spi_dev != "") { + portduinoSetOptions({.realHardware = true}); + } return; } diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 86b1fe60a2b..87d8431a3e8 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -2,7 +2,7 @@ [portduino_base] platform = # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop - https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip + https://github.com/meshtastic/platform-native/archive/71ed55bb95feb3c43ebde1ec1e2e17643a424c04.zip framework = arduino build_src_filter = From 583195263670c131114cd5a9430694b795b4d833 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 14 Apr 2026 12:57:29 -0500 Subject: [PATCH 19/22] simplify pt 1 --- src/modules/DMShell.cpp | 27 +++++++++++++-------------- src/modules/DMShell.h | 1 - 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 9acc790d25c..8b844f179e3 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -132,7 +132,9 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead); - sendControl(meshtastic_RemoteShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OUTPUT, session.sessionId, + session.nextTxSeq++, outBuf, static_cast(bytesRead), 0, 0, session.lastAckedRxSeq); + session.lastActivityMs = millis(); } } @@ -159,7 +161,8 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) uint8_t heartbeatPayload[HEARTBEAT_STATUS_SIZE] = {0}; encodeHeartbeatStatus(heartbeatPayload, session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0, session.lastAckedRxSeq); - sendControl(meshtastic_RemoteShell_OpCode_PONG, heartbeatPayload, sizeof(heartbeatPayload)); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_PONG, session.sessionId, session.nextTxSeq++, + heartbeatPayload, sizeof(heartbeatPayload), 0, 0, session.lastAckedRxSeq); break; } case meshtastic_RemoteShell_OpCode_CLOSE: @@ -200,7 +203,8 @@ int32_t DMShellModule::runOnce() const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead); - sendControl(meshtastic_RemoteShell_OpCode_OUTPUT, outBuf, static_cast(bytesRead)); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OUTPUT, session.sessionId, + session.nextTxSeq++, outBuf, static_cast(bytesRead), 0, 0, session.lastAckedRxSeq); session.lastActivityMs = millis(); // continue; // do we want to ack every data message, and only send the next on ack? @@ -326,7 +330,6 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr session.lastActivityMs = millis(); uint8_t payload[sizeof(uint32_t)] = {0}; - encodeUint32BE(payload, session.childPid); sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OPEN_OK, session.sessionId, session.nextTxSeq++, payload, sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); @@ -355,7 +358,8 @@ void DMShellModule::closeSession(const char *reason, bool notifyPeer) if (notifyPeer) { const size_t reasonLen = strnlen(reason, 256); - sendControl(meshtastic_RemoteShell_OpCode_CLOSED, reinterpret_cast(reason), reasonLen); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_CLOSED, session.sessionId, + session.nextTxSeq++, reinterpret_cast(reason), reasonLen, 0, 0, session.lastAckedRxSeq); } if (session.masterFd >= 0) { @@ -481,14 +485,8 @@ void DMShellModule::sendAck(uint32_t replayFromSeq) LOG_WARN("DMShell: requesting replay from seq=%u", replayFromSeq); } - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_ACK, session.sessionId, 0, payloadPtr, - payloadLen, 0, 0, session.lastAckedRxSeq, 0, false); -} - -void DMShellModule::sendControl(meshtastic_RemoteShell_OpCode op, const uint8_t *payload, size_t payloadLen) -{ - sendFrameToPeer(session.peer, session.channel, op, session.sessionId, session.nextTxSeq++, payload, payloadLen, 0, 0, - session.lastAckedRxSeq); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_ACK, session.sessionId, 0, nullptr, 0, 0, 0, + session.lastAckedRxSeq, 0, false); } bool DMShellModule::handleAckFrame(const DMShellFrame &frame) @@ -560,7 +558,8 @@ void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_Re void DMShellModule::sendError(const char *message) { const size_t len = strnlen(message, MAX_MESSAGE_SIZE); - sendControl(meshtastic_RemoteShell_OpCode_ERROR, reinterpret_cast(message), len); + sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_ERROR, session.sessionId, session.nextTxSeq++, + reinterpret_cast(message), len, 0, 0, session.lastAckedRxSeq); } meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index 06596ff3b4e..d03a836d2c6 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -83,7 +83,6 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule void pruneSentFrames(uint32_t ackSeq); void resendFramesFrom(uint32_t startSeq); void sendAck(uint32_t replayFromSeq = 0); - void sendControl(meshtastic_RemoteShell_OpCode op, const uint8_t *payload, size_t payloadLen); void sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0, uint32_t ackSeq = 0, uint32_t flags = 0, bool remember = true); From 87d0850f95d10e82a990f5695d176411a852d4ce Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 14 Apr 2026 17:16:36 -0500 Subject: [PATCH 20/22] Refactor and Simplify --- bin/dmshell_client.py | 89 +- src/mesh/generated/meshtastic/atak.pb.cpp | 63 + src/mesh/generated/meshtastic/atak.pb.h | 1302 +++++++++++++++++- src/mesh/generated/meshtastic/mesh.pb.h | 23 +- src/mesh/generated/meshtastic/portnums.pb.h | 6 +- src/mesh/generated/meshtastic/telemetry.pb.h | 21 +- src/modules/DMShell.cpp | 315 +++-- src/modules/DMShell.h | 34 +- 8 files changed, 1601 insertions(+), 252 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index 483ac17ba3a..c955a83edbb 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import argparse -import struct import os import queue import random @@ -27,8 +26,6 @@ DEFAULT_API_PORT = 4403 DEFAULT_HOP_LIMIT = 0 LOCAL_ESCAPE_BYTE = b"\x1d" # Ctrl+] -REPLAY_REQUEST_SIZE = 4 -HEARTBEAT_STATUS_SIZE = 8 MISSING_SEQ_RETRY_INTERVAL_SEC = 1.0 INPUT_BATCH_WINDOW_SEC = .5 INPUT_BATCH_MAX_BYTES = 64 @@ -280,6 +277,8 @@ class SentShellFrame: cols: int = 0 rows: int = 0 flags: int = 0 + last_tx_seq: int = 0 + last_rx_seq: int = 0 @dataclass @@ -325,10 +324,6 @@ def highest_sent_seq(self) -> int: with self.tx_lock: return max(0, self.next_seq - 1) - def heartbeat_payload(self) -> bytes: - with self.tx_lock: - return encode_heartbeat_status(max(0, self.next_seq - 1), self.last_rx_seq) - def note_outbound_packet(self, heartbeat: bool = False) -> None: with self.tx_lock: now = time.monotonic() @@ -475,26 +470,6 @@ def replay_frames_from(self, start_seq: int) -> list[SentShellFrame]: return [frame for frame in self.tx_history if frame.seq >= start_seq] -def encode_replay_request(start_seq: int) -> bytes: - return struct.pack(">I", start_seq) - - -def decode_replay_request(payload: bytes) -> Optional[int]: - if len(payload) < REPLAY_REQUEST_SIZE: - return None - return struct.unpack(">I", payload[:REPLAY_REQUEST_SIZE])[0] - - -def encode_heartbeat_status(last_tx_seq: int, last_rx_seq: int) -> bytes: - return struct.pack(">II", last_tx_seq, last_rx_seq) - - -def decode_heartbeat_status(payload: bytes) -> Optional[tuple[int, int]]: - if len(payload) < HEARTBEAT_STATUS_SIZE: - return None - return struct.unpack(">II", payload[:HEARTBEAT_STATUS_SIZE]) - - def send_toradio(transport, toradio) -> None: send_stream_frame(transport, toradio.SerializeToString()) @@ -530,6 +505,8 @@ def send_shell_frame( ack_seq: Optional[int] = None, seq: Optional[int] = None, flags: int = 0, + last_tx_seq: int = 0, + last_rx_seq: int = 0, remember: bool = True, heartbeat: bool = False, ) -> int: @@ -548,21 +525,40 @@ def send_shell_frame( shell.cols = cols shell.rows = rows shell.flags = flags + shell.last_tx_seq = last_tx_seq + shell.last_rx_seq = last_rx_seq if payload: shell.payload = payload with state.socket_lock: send_toradio(transport, make_toradio_packet(state.pb2, state, shell)) if remember: state.remember_sent_frame( - SentShellFrame(op=op, session_id=session_id, seq=seq, ack_seq=ack_seq, payload=payload, cols=cols, rows=rows, flags=flags) + SentShellFrame( + op=op, + session_id=session_id, + seq=seq, + ack_seq=ack_seq, + payload=payload, + cols=cols, + rows=rows, + flags=flags, + last_tx_seq=last_tx_seq, + last_rx_seq=last_rx_seq, + ) ) state.note_outbound_packet(heartbeat=heartbeat) return seq def send_ack_frame(transport, state: SessionState, replay_from: Optional[int] = None) -> None: - payload = b"" if replay_from is None else encode_replay_request(replay_from) - send_shell_frame(transport, state, state.pb2.mesh.RemoteShell.ACK, payload=payload, seq=0, remember=False) + send_shell_frame( + transport, + state, + state.pb2.mesh.RemoteShell.ACK, + seq=0, + last_rx_seq=0 if replay_from is None else replay_from, + remember=False, + ) def replay_frames_from(transport, state: SessionState, start_seq: int) -> None: @@ -584,6 +580,8 @@ def replay_frames_from(transport, state: SessionState, start_seq: int) -> None: ack_seq=frame.ack_seq, seq=frame.seq, flags=frame.flags, + last_tx_seq=frame.last_tx_seq, + last_rx_seq=frame.last_rx_seq, remember=False, ) @@ -625,9 +623,8 @@ def handle_in_order_shell(shell) -> bool: state.set_receive_cursor(shell.seq) state.active = True state.opened_event.set() - pid = int.from_bytes(shell.payload, "big") if shell.payload else 0 state.event_queue.put( - f"opened session=0x{shell.session_id:08x} cols={shell.cols} rows={shell.rows} pid={pid}" + f"opened session=0x{shell.session_id:08x} cols={shell.cols} rows={shell.rows}" ) if state.replay_log_path is not None: state.event_queue.put(f"replay log: {state.replay_log_path}") @@ -645,18 +642,17 @@ def handle_in_order_shell(shell) -> bool: state.active = False return True elif shell.op == state.pb2.mesh.RemoteShell.PONG: - heartbeat_status = decode_heartbeat_status(shell.payload) - if heartbeat_status is not None: - remote_last_tx_seq, remote_last_rx_seq = heartbeat_status - local_latest_tx_seq = state.highest_sent_seq() - if remote_last_rx_seq < local_latest_tx_seq: - replay_frames_from(transport, state, remote_last_rx_seq + 1) - if remote_last_tx_seq > state.current_ack_seq(): - state.note_peer_reported_tx_seq(remote_last_tx_seq) - req = state.request_missing_seq_once() - if req is not None: - state.note_missing_seq_requested(req, "heartbeat_status") - send_ack_frame(transport, state, replay_from=req) + remote_last_tx_seq = shell.last_tx_seq + remote_last_rx_seq = shell.last_rx_seq + local_latest_tx_seq = state.highest_sent_seq() + if remote_last_rx_seq < local_latest_tx_seq: + replay_frames_from(transport, state, remote_last_rx_seq + 1) + if remote_last_tx_seq > state.current_ack_seq(): + state.note_peer_reported_tx_seq(remote_last_tx_seq) + req = state.request_missing_seq_once() + if req is not None: + state.note_missing_seq_requested(req, "heartbeat_status") + send_ack_frame(transport, state, replay_from=req) #state.event_queue.put("pong") return False @@ -679,7 +675,7 @@ def handle_in_order_shell(shell) -> bool: #state.prune_sent_frames(shell.ack_seq) if shell.op == state.pb2.mesh.RemoteShell.ACK: #state.event_queue.put("peer requested replay") - replay_from = decode_replay_request(shell.payload) + replay_from = shell.last_rx_seq if shell.last_rx_seq > 0 else None if replay_from is not None: #state.event_queue.put(f"peer requested replay from seq={replay_from}") replay_frames_from(transport, state, replay_from) @@ -742,7 +738,8 @@ def heartbeat_loop(transport, state: SessionState) -> None: transport, state, state.pb2.mesh.RemoteShell.PING, - payload=state.heartbeat_payload(), + last_tx_seq=state.highest_sent_seq(), + last_rx_seq=state.current_ack_seq(), remember=True, heartbeat=True, ) diff --git a/src/mesh/generated/meshtastic/atak.pb.cpp b/src/mesh/generated/meshtastic/atak.pb.cpp index a0368cf6b22..3f1adea5389 100644 --- a/src/mesh/generated/meshtastic/atak.pb.cpp +++ b/src/mesh/generated/meshtastic/atak.pb.cpp @@ -22,6 +22,69 @@ PB_BIND(meshtastic_Contact, meshtastic_Contact, AUTO) PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO) + + +PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO) + + +PB_BIND(meshtastic_CotGeoPoint, meshtastic_CotGeoPoint, AUTO) + + +PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, 2) + + +PB_BIND(meshtastic_Marker, meshtastic_Marker, AUTO) + + +PB_BIND(meshtastic_RangeAndBearing, meshtastic_RangeAndBearing, AUTO) + + +PB_BIND(meshtastic_Route, meshtastic_Route, 2) + + +PB_BIND(meshtastic_Route_Link, meshtastic_Route_Link, AUTO) + + +PB_BIND(meshtastic_CasevacReport, meshtastic_CasevacReport, AUTO) + + +PB_BIND(meshtastic_EmergencyAlert, meshtastic_EmergencyAlert, AUTO) + + +PB_BIND(meshtastic_TaskRequest, meshtastic_TaskRequest, AUTO) + + +PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index 8533bcbf9de..d25fa65434b 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -65,10 +65,466 @@ typedef enum _meshtastic_MemberRole { meshtastic_MemberRole_K9 = 8 } meshtastic_MemberRole; +/* CoT how field values. + Represents how the coordinates were generated. */ +typedef enum _meshtastic_CotHow { + /* Unspecified */ + meshtastic_CotHow_CotHow_Unspecified = 0, + /* Human entered */ + meshtastic_CotHow_CotHow_h_e = 1, + /* Machine generated */ + meshtastic_CotHow_CotHow_m_g = 2, + /* Human GPS/INS derived */ + meshtastic_CotHow_CotHow_h_g_i_g_o = 3, + /* Machine relayed (imported from another system/gateway) */ + meshtastic_CotHow_CotHow_m_r = 4, + /* Machine fused (corroborated from multiple sources) */ + meshtastic_CotHow_CotHow_m_f = 5, + /* Machine predicted */ + meshtastic_CotHow_CotHow_m_p = 6, + /* Machine simulated */ + meshtastic_CotHow_CotHow_m_s = 7 +} meshtastic_CotHow; + +/* Well-known CoT event types. + When the type is known, use the enum value for efficient encoding. + For unknown types, set cot_type_id to CotType_Other and populate cot_type_str. */ +typedef enum _meshtastic_CotType { + /* Unknown or unmapped type, use cot_type_str */ + meshtastic_CotType_CotType_Other = 0, + /* a-f-G-U-C: Friendly ground unit combat */ + meshtastic_CotType_CotType_a_f_G_U_C = 1, + /* a-f-G-U-C-I: Friendly ground unit combat infantry */ + meshtastic_CotType_CotType_a_f_G_U_C_I = 2, + /* a-n-A-C-F: Neutral aircraft civilian fixed-wing */ + meshtastic_CotType_CotType_a_n_A_C_F = 3, + /* a-n-A-C-H: Neutral aircraft civilian helicopter */ + meshtastic_CotType_CotType_a_n_A_C_H = 4, + /* a-n-A-C: Neutral aircraft civilian */ + meshtastic_CotType_CotType_a_n_A_C = 5, + /* a-f-A-M-H: Friendly aircraft military helicopter */ + meshtastic_CotType_CotType_a_f_A_M_H = 6, + /* a-f-A-M: Friendly aircraft military */ + meshtastic_CotType_CotType_a_f_A_M = 7, + /* a-f-A-M-F-F: Friendly aircraft military fixed-wing fighter */ + meshtastic_CotType_CotType_a_f_A_M_F_F = 8, + /* a-f-A-M-H-A: Friendly aircraft military helicopter attack */ + meshtastic_CotType_CotType_a_f_A_M_H_A = 9, + /* a-f-A-M-H-U-M: Friendly aircraft military helicopter utility medium */ + meshtastic_CotType_CotType_a_f_A_M_H_U_M = 10, + /* a-h-A-M-F-F: Hostile aircraft military fixed-wing fighter */ + meshtastic_CotType_CotType_a_h_A_M_F_F = 11, + /* a-h-A-M-H-A: Hostile aircraft military helicopter attack */ + meshtastic_CotType_CotType_a_h_A_M_H_A = 12, + /* a-u-A-C: Unknown aircraft civilian */ + meshtastic_CotType_CotType_a_u_A_C = 13, + /* t-x-d-d: Tasking delete/disconnect */ + meshtastic_CotType_CotType_t_x_d_d = 14, + /* a-f-G-E-S-E: Friendly ground equipment sensor */ + meshtastic_CotType_CotType_a_f_G_E_S_E = 15, + /* a-f-G-E-V-C: Friendly ground equipment vehicle */ + meshtastic_CotType_CotType_a_f_G_E_V_C = 16, + /* a-f-S: Friendly sea */ + meshtastic_CotType_CotType_a_f_S = 17, + /* a-f-A-M-F: Friendly aircraft military fixed-wing */ + meshtastic_CotType_CotType_a_f_A_M_F = 18, + /* a-f-A-M-F-C-H: Friendly aircraft military fixed-wing cargo heavy */ + meshtastic_CotType_CotType_a_f_A_M_F_C_H = 19, + /* a-f-A-M-F-U-L: Friendly aircraft military fixed-wing utility light */ + meshtastic_CotType_CotType_a_f_A_M_F_U_L = 20, + /* a-f-A-M-F-L: Friendly aircraft military fixed-wing liaison */ + meshtastic_CotType_CotType_a_f_A_M_F_L = 21, + /* a-f-A-M-F-P: Friendly aircraft military fixed-wing patrol */ + meshtastic_CotType_CotType_a_f_A_M_F_P = 22, + /* a-f-A-C-H: Friendly aircraft civilian helicopter */ + meshtastic_CotType_CotType_a_f_A_C_H = 23, + /* a-n-A-M-F-Q: Neutral aircraft military fixed-wing drone */ + meshtastic_CotType_CotType_a_n_A_M_F_Q = 24, + /* b-t-f: GeoChat message */ + meshtastic_CotType_CotType_b_t_f = 25, + /* b-r-f-h-c: CASEVAC/MEDEVAC report */ + meshtastic_CotType_CotType_b_r_f_h_c = 26, + /* b-a-o-pan: Ring the bell / alert all */ + meshtastic_CotType_CotType_b_a_o_pan = 27, + /* b-a-o-opn: Troops in contact */ + meshtastic_CotType_CotType_b_a_o_opn = 28, + /* b-a-o-can: Cancel alert */ + meshtastic_CotType_CotType_b_a_o_can = 29, + /* b-a-o-tbl: 911 alert */ + meshtastic_CotType_CotType_b_a_o_tbl = 30, + /* b-a-g: Geofence breach alert */ + meshtastic_CotType_CotType_b_a_g = 31, + /* a-f-G: Friendly ground (generic) */ + meshtastic_CotType_CotType_a_f_G = 32, + /* a-f-G-U: Friendly ground unit (generic) */ + meshtastic_CotType_CotType_a_f_G_U = 33, + /* a-h-G: Hostile ground (generic) */ + meshtastic_CotType_CotType_a_h_G = 34, + /* a-u-G: Unknown ground (generic) */ + meshtastic_CotType_CotType_a_u_G = 35, + /* a-n-G: Neutral ground (generic) */ + meshtastic_CotType_CotType_a_n_G = 36, + /* b-m-r: Route */ + meshtastic_CotType_CotType_b_m_r = 37, + /* b-m-p-w: Route waypoint */ + meshtastic_CotType_CotType_b_m_p_w = 38, + /* b-m-p-s-p-i: Self-position marker */ + meshtastic_CotType_CotType_b_m_p_s_p_i = 39, + /* u-d-f: Freeform shape (line/polygon) */ + meshtastic_CotType_CotType_u_d_f = 40, + /* u-d-r: Rectangle */ + meshtastic_CotType_CotType_u_d_r = 41, + /* u-d-c-c: Circle */ + meshtastic_CotType_CotType_u_d_c_c = 42, + /* u-rb-a: Range/bearing line */ + meshtastic_CotType_CotType_u_rb_a = 43, + /* a-h-A: Hostile aircraft (generic) */ + meshtastic_CotType_CotType_a_h_A = 44, + /* a-u-A: Unknown aircraft (generic) */ + meshtastic_CotType_CotType_a_u_A = 45, + /* a-f-A-M-H-Q: Friendly aircraft military helicopter observation */ + meshtastic_CotType_CotType_a_f_A_M_H_Q = 46, + /* a-f-A-C-F: Friendly aircraft civilian fixed-wing */ + meshtastic_CotType_CotType_a_f_A_C_F = 47, + /* a-f-A-C: Friendly aircraft civilian (generic) */ + meshtastic_CotType_CotType_a_f_A_C = 48, + /* a-f-A-C-L: Friendly aircraft civilian lighter-than-air */ + meshtastic_CotType_CotType_a_f_A_C_L = 49, + /* a-f-A: Friendly aircraft (generic) */ + meshtastic_CotType_CotType_a_f_A = 50, + /* a-f-A-M-H-C: Friendly aircraft military helicopter cargo */ + meshtastic_CotType_CotType_a_f_A_M_H_C = 51, + /* a-n-A-M-F-F: Neutral aircraft military fixed-wing fighter */ + meshtastic_CotType_CotType_a_n_A_M_F_F = 52, + /* a-u-A-C-F: Unknown aircraft civilian fixed-wing */ + meshtastic_CotType_CotType_a_u_A_C_F = 53, + /* a-f-G-U-C-F-T-A: Friendly ground unit combat forces theater aviation */ + meshtastic_CotType_CotType_a_f_G_U_C_F_T_A = 54, + /* a-f-G-U-C-V-S: Friendly ground unit combat vehicle support */ + meshtastic_CotType_CotType_a_f_G_U_C_V_S = 55, + /* a-f-G-U-C-R-X: Friendly ground unit combat reconnaissance exploitation */ + meshtastic_CotType_CotType_a_f_G_U_C_R_X = 56, + /* a-f-G-U-C-I-Z: Friendly ground unit combat infantry mechanized */ + meshtastic_CotType_CotType_a_f_G_U_C_I_Z = 57, + /* a-f-G-U-C-E-C-W: Friendly ground unit combat engineer construction wheeled */ + meshtastic_CotType_CotType_a_f_G_U_C_E_C_W = 58, + /* a-f-G-U-C-I-L: Friendly ground unit combat infantry light */ + meshtastic_CotType_CotType_a_f_G_U_C_I_L = 59, + /* a-f-G-U-C-R-O: Friendly ground unit combat reconnaissance other */ + meshtastic_CotType_CotType_a_f_G_U_C_R_O = 60, + /* a-f-G-U-C-R-V: Friendly ground unit combat reconnaissance cavalry */ + meshtastic_CotType_CotType_a_f_G_U_C_R_V = 61, + /* a-f-G-U-H: Friendly ground unit headquarters */ + meshtastic_CotType_CotType_a_f_G_U_H = 62, + /* a-f-G-U-U-M-S-E: Friendly ground unit support medical surgical evacuation */ + meshtastic_CotType_CotType_a_f_G_U_U_M_S_E = 63, + /* a-f-G-U-S-M-C: Friendly ground unit support maintenance collection */ + meshtastic_CotType_CotType_a_f_G_U_S_M_C = 64, + /* a-f-G-E-S: Friendly ground equipment sensor (generic) */ + meshtastic_CotType_CotType_a_f_G_E_S = 65, + /* a-f-G-E: Friendly ground equipment (generic) */ + meshtastic_CotType_CotType_a_f_G_E = 66, + /* a-f-G-E-V-C-U: Friendly ground equipment vehicle utility */ + meshtastic_CotType_CotType_a_f_G_E_V_C_U = 67, + /* a-f-G-E-V-C-ps: Friendly ground equipment vehicle public safety */ + meshtastic_CotType_CotType_a_f_G_E_V_C_ps = 68, + /* a-u-G-E-V: Unknown ground equipment vehicle */ + meshtastic_CotType_CotType_a_u_G_E_V = 69, + /* a-f-S-N-N-R: Friendly sea surface non-naval rescue */ + meshtastic_CotType_CotType_a_f_S_N_N_R = 70, + /* a-f-F-B: Friendly force boundary */ + meshtastic_CotType_CotType_a_f_F_B = 71, + /* b-m-p-s-p-loc: Self-position location marker */ + meshtastic_CotType_CotType_b_m_p_s_p_loc = 72, + /* b-i-v: Imagery/video */ + meshtastic_CotType_CotType_b_i_v = 73, + /* b-f-t-r: File transfer request */ + meshtastic_CotType_CotType_b_f_t_r = 74, + /* b-f-t-a: File transfer acknowledgment */ + meshtastic_CotType_CotType_b_f_t_a = 75, + /* u-d-f-m: Freehand telestration / annotation. Anchor at event point, + geometry carried via DrawnShape.vertices. May be truncated to + MAX_VERTICES by the sender. */ + meshtastic_CotType_CotType_u_d_f_m = 76, + /* u-d-p: Closed polygon. Geometry carried via DrawnShape.vertices, + implicitly closed (receiver duplicates first vertex as needed). */ + meshtastic_CotType_CotType_u_d_p = 77, + /* b-m-p-s-m: Spot map marker (colored dot at a point of interest). */ + meshtastic_CotType_CotType_b_m_p_s_m = 78, + /* b-m-p-c: Checkpoint (intermediate route control point). */ + meshtastic_CotType_CotType_b_m_p_c = 79, + /* u-r-b-c-c: Ranging circle (range rings centered on the event point). */ + meshtastic_CotType_CotType_u_r_b_c_c = 80, + /* u-r-b-bullseye: Bullseye with configurable range rings and bearing + reference (magnetic / true / grid). */ + meshtastic_CotType_CotType_u_r_b_bullseye = 81, + /* a-f-G-E-V-A: Friendly armored vehicle, user-selectable self PLI. */ + meshtastic_CotType_CotType_a_f_G_E_V_A = 82, + /* a-n-A: Neutral aircraft (friendly/hostile/unknown already present). */ + meshtastic_CotType_CotType_a_n_A = 83, + /* --- 2525 quick-drop: artillery (4) ---------------------------------- */ + meshtastic_CotType_CotType_a_u_G_U_C_F = 84, + meshtastic_CotType_CotType_a_n_G_U_C_F = 85, + meshtastic_CotType_CotType_a_h_G_U_C_F = 86, + meshtastic_CotType_CotType_a_f_G_U_C_F = 87, + /* --- 2525 quick-drop: building (4) ----------------------------------- */ + meshtastic_CotType_CotType_a_u_G_I = 88, + meshtastic_CotType_CotType_a_n_G_I = 89, + meshtastic_CotType_CotType_a_h_G_I = 90, + meshtastic_CotType_CotType_a_f_G_I = 91, + /* --- 2525 quick-drop: mine (4) --------------------------------------- */ + meshtastic_CotType_CotType_a_u_G_E_X_M = 92, + meshtastic_CotType_CotType_a_n_G_E_X_M = 93, + meshtastic_CotType_CotType_a_h_G_E_X_M = 94, + meshtastic_CotType_CotType_a_f_G_E_X_M = 95, + /* --- 2525 quick-drop: ship (3; a-f-S already at 17) ------------------ */ + meshtastic_CotType_CotType_a_u_S = 96, + meshtastic_CotType_CotType_a_n_S = 97, + meshtastic_CotType_CotType_a_h_S = 98, + /* --- 2525 quick-drop: sniper (4) ------------------------------------- */ + meshtastic_CotType_CotType_a_u_G_U_C_I_d = 99, + meshtastic_CotType_CotType_a_n_G_U_C_I_d = 100, + meshtastic_CotType_CotType_a_h_G_U_C_I_d = 101, + meshtastic_CotType_CotType_a_f_G_U_C_I_d = 102, + /* --- 2525 quick-drop: tank (4) --------------------------------------- */ + meshtastic_CotType_CotType_a_u_G_E_V_A_T = 103, + meshtastic_CotType_CotType_a_n_G_E_V_A_T = 104, + meshtastic_CotType_CotType_a_h_G_E_V_A_T = 105, + meshtastic_CotType_CotType_a_f_G_E_V_A_T = 106, + /* --- 2525 quick-drop: troops (3; a-f-G-U-C-I already at 2) ----------- */ + meshtastic_CotType_CotType_a_u_G_U_C_I = 107, + meshtastic_CotType_CotType_a_n_G_U_C_I = 108, + meshtastic_CotType_CotType_a_h_G_U_C_I = 109, + /* --- 2525 quick-drop: generic vehicle (3; a-u-G-E-V already at 69) --- */ + meshtastic_CotType_CotType_a_n_G_E_V = 110, + meshtastic_CotType_CotType_a_h_G_E_V = 111, + meshtastic_CotType_CotType_a_f_G_E_V = 112, + /* b-m-p-w-GOTO: Go To / bloodhound navigation target. */ + meshtastic_CotType_CotType_b_m_p_w_GOTO = 113, + /* b-m-p-c-ip: Initial point (mission planning). */ + meshtastic_CotType_CotType_b_m_p_c_ip = 114, + /* b-m-p-c-cp: Contact point (mission planning). */ + meshtastic_CotType_CotType_b_m_p_c_cp = 115, + /* b-m-p-s-p-op: Observation post. */ + meshtastic_CotType_CotType_b_m_p_s_p_op = 116, + /* u-d-v: 2D vehicle outline drawn on the map. */ + meshtastic_CotType_CotType_u_d_v = 117, + /* u-d-v-m: 3D vehicle model reference. */ + meshtastic_CotType_CotType_u_d_v_m = 118, + /* u-d-c-e: Non-circular ellipse (circle with distinct major/minor axes). */ + meshtastic_CotType_CotType_u_d_c_e = 119, + /* b-i-x-i: Quick Pic geotagged image marker. The image itself does not + ride on LoRa; this event references the image via iconset metadata. */ + meshtastic_CotType_CotType_b_i_x_i = 120, + /* b-t-f-d: GeoChat delivered receipt. Carried on the existing `chat` + payload_variant via GeoChat.receipt_for_uid + receipt_type. */ + meshtastic_CotType_CotType_b_t_f_d = 121, + /* b-t-f-r: GeoChat read receipt. Same wire slot as b-t-f-d. */ + meshtastic_CotType_CotType_b_t_f_r = 122, + /* b-a-o-c: Custom / generic emergency beacon. */ + meshtastic_CotType_CotType_b_a_o_c = 123, + /* t-s: Task / engage request. Structured payload carried via the new + TaskRequest typed variant. */ + meshtastic_CotType_CotType_t_s = 124 +} meshtastic_CotType; + +/* Geopoint and altitude source */ +typedef enum _meshtastic_GeoPointSource { + /* Unspecified */ + meshtastic_GeoPointSource_GeoPointSource_Unspecified = 0, + /* GPS derived */ + meshtastic_GeoPointSource_GeoPointSource_GPS = 1, + /* User entered */ + meshtastic_GeoPointSource_GeoPointSource_USER = 2, + /* Network/external */ + meshtastic_GeoPointSource_GeoPointSource_NETWORK = 3 +} meshtastic_GeoPointSource; + +/* Receipt discriminator. Set alongside cot_type_id = b-t-f-d (delivered) + or b-t-f-r (read). ReceiptType_None is the default for a normal chat + message (cot_type_id = b-t-f). + + Receivers can detect a receipt by checking receipt_type != ReceiptType_None + without re-parsing the envelope cot_type_id. */ +typedef enum _meshtastic_GeoChat_ReceiptType { + meshtastic_GeoChat_ReceiptType_ReceiptType_None = 0, /* normal chat message */ + meshtastic_GeoChat_ReceiptType_ReceiptType_Delivered = 1, /* b-t-f-d delivered receipt */ + meshtastic_GeoChat_ReceiptType_ReceiptType_Read = 2 /* b-t-f-r read receipt */ +} meshtastic_GeoChat_ReceiptType; + +/* Shape kind discriminator. Drives receiver rendering and also controls + which optional fields below are meaningful. */ +typedef enum _meshtastic_DrawnShape_Kind { + /* Unspecified (do not use on the wire) */ + meshtastic_DrawnShape_Kind_Kind_Unspecified = 0, + /* u-d-c-c: User-drawn circle (uses major/minor/angle, anchor = event point) */ + meshtastic_DrawnShape_Kind_Kind_Circle = 1, + /* u-d-r: User-drawn rectangle (uses vertices = 4 corners) */ + meshtastic_DrawnShape_Kind_Kind_Rectangle = 2, + /* u-d-f: User-drawn polyline (uses vertices, not closed) */ + meshtastic_DrawnShape_Kind_Kind_Freeform = 3, + /* u-d-f-m: Freehand telestration / annotation (uses vertices, may be truncated) */ + meshtastic_DrawnShape_Kind_Kind_Telestration = 4, + /* u-d-p: Closed polygon (uses vertices, implicitly closed) */ + meshtastic_DrawnShape_Kind_Kind_Polygon = 5, + /* u-r-b-c-c: Ranging circle (major/minor/angle, stroke + optional fill) */ + meshtastic_DrawnShape_Kind_Kind_RangingCircle = 6, + /* u-r-b-bullseye: Bullseye ring with range rings and bearing reference */ + meshtastic_DrawnShape_Kind_Kind_Bullseye = 7, + /* u-d-c-e: Ellipse with distinct major/minor axes (same storage as + Kind_Circle — uses major_cm/minor_cm/angle_deg — but receivers + render it as a non-circular ellipse rather than a round circle). */ + meshtastic_DrawnShape_Kind_Kind_Ellipse = 8, + /* u-d-v: 2D vehicle outline drawn on the map. Vertices carry the + outline polygon; receivers draw it as a filled polygon. */ + meshtastic_DrawnShape_Kind_Kind_Vehicle2D = 9, + /* u-d-v-m: 3D vehicle model reference. Same vertex polygon as + Kind_Vehicle2D; receivers that support 3D rendering extrude it. */ + meshtastic_DrawnShape_Kind_Kind_Vehicle3D = 10 +} meshtastic_DrawnShape_Kind; + +/* Explicit stroke/fill/both discriminator. + + ATAK's source XML distinguishes "stroke-only polyline" from "closed shape + with both stroke and fill" by the presence of the element. + Both states can hash to all-zero color fields, so we carry the signal + explicitly. Parser sets this from (sawStrokeColor, sawFillColor) at the + end of parse; builder uses it to decide which of / + to emit in the reconstructed XML. */ +typedef enum _meshtastic_DrawnShape_StyleMode { + /* Unspecified — receiver infers from which color fields are non-zero. */ + meshtastic_DrawnShape_StyleMode_StyleMode_Unspecified = 0, + /* Stroke only. No in the source XML. Used for polylines, + ranging lines, bullseye rings. */ + meshtastic_DrawnShape_StyleMode_StyleMode_StrokeOnly = 1, + /* Fill only. No in the source XML. Rare but valid in + ATAK (solid region with no outline). */ + meshtastic_DrawnShape_StyleMode_StyleMode_FillOnly = 2, + /* Both stroke and fill present. Closed shapes: circle, rectangle, + polygon, ranging circle. */ + meshtastic_DrawnShape_StyleMode_StyleMode_StrokeAndFill = 3 +} meshtastic_DrawnShape_StyleMode; + +/* Marker kind. Used to pick sensible receiver defaults when the CoT type + alone is ambiguous (e.g. a-u-G could be a 2525 symbol or a custom icon + depending on the iconset path). */ +typedef enum _meshtastic_Marker_Kind { + /* Unspecified — fall back to TAKPacketV2.cot_type_id */ + meshtastic_Marker_Kind_Kind_Unspecified = 0, + /* b-m-p-s-m: Spot map marker */ + meshtastic_Marker_Kind_Kind_Spot = 1, + /* b-m-p-w: Route waypoint */ + meshtastic_Marker_Kind_Kind_Waypoint = 2, + /* b-m-p-c: Checkpoint */ + meshtastic_Marker_Kind_Kind_Checkpoint = 3, + /* b-m-p-s-p-i / b-m-p-s-p-loc: Self-position marker */ + meshtastic_Marker_Kind_Kind_SelfPosition = 4, + /* 2525B/C military symbol (iconsetpath = COT_MAPPING_2525B/...) */ + meshtastic_Marker_Kind_Kind_Symbol2525 = 5, + /* COT_MAPPING_SPOTMAP icon (e.g. colored dot) */ + meshtastic_Marker_Kind_Kind_SpotMap = 6, + /* Custom icon set (UUID/GroupName/filename.png) */ + meshtastic_Marker_Kind_Kind_CustomIcon = 7, + /* b-m-p-w-GOTO: Go To / bloodhound navigation waypoint. */ + meshtastic_Marker_Kind_Kind_GoToPoint = 8, + /* b-m-p-c-ip: Initial point (mission planning control point). */ + meshtastic_Marker_Kind_Kind_InitialPoint = 9, + /* b-m-p-c-cp: Contact point (mission planning control point). */ + meshtastic_Marker_Kind_Kind_ContactPoint = 10, + /* b-m-p-s-p-op: Observation post. */ + meshtastic_Marker_Kind_Kind_ObservationPost = 11, + /* b-i-x-i: Quick Pic geotagged image marker. iconset carries the + image reference (local filename or remote URL); the image itself + does not ride on the LoRa wire. */ + meshtastic_Marker_Kind_Kind_ImageMarker = 12 +} meshtastic_Marker_Kind; + +/* Travel method for the route. */ +typedef enum _meshtastic_Route_Method { + /* Unspecified / unknown */ + meshtastic_Route_Method_Method_Unspecified = 0, + /* Driving / vehicle */ + meshtastic_Route_Method_Method_Driving = 1, + /* Walking / foot */ + meshtastic_Route_Method_Method_Walking = 2, + /* Flying */ + meshtastic_Route_Method_Method_Flying = 3, + /* Swimming (individual) */ + meshtastic_Route_Method_Method_Swimming = 4, + /* Watercraft (boat) */ + meshtastic_Route_Method_Method_Watercraft = 5 +} meshtastic_Route_Method; + +/* Route direction (infil = ingress, exfil = egress). */ +typedef enum _meshtastic_Route_Direction { + /* Unspecified */ + meshtastic_Route_Direction_Direction_Unspecified = 0, + /* Infiltration (ingress) */ + meshtastic_Route_Direction_Direction_Infil = 1, + /* Exfiltration (egress) */ + meshtastic_Route_Direction_Direction_Exfil = 2 +} meshtastic_Route_Direction; + +/* Line 3: precedence / urgency. */ +typedef enum _meshtastic_CasevacReport_Precedence { + meshtastic_CasevacReport_Precedence_Precedence_Unspecified = 0, + meshtastic_CasevacReport_Precedence_Precedence_Urgent = 1, /* A - immediate, life-threatening */ + meshtastic_CasevacReport_Precedence_Precedence_UrgentSurgical = 2, /* B - needs surgery */ + meshtastic_CasevacReport_Precedence_Precedence_Priority = 3, /* C - within 4 hours */ + meshtastic_CasevacReport_Precedence_Precedence_Routine = 4, /* D - within 24 hours */ + meshtastic_CasevacReport_Precedence_Precedence_Convenience = 5 /* E - convenience */ +} meshtastic_CasevacReport_Precedence; + +/* Line 7: HLZ marking method. */ +typedef enum _meshtastic_CasevacReport_HlzMarking { + meshtastic_CasevacReport_HlzMarking_HlzMarking_Unspecified = 0, + meshtastic_CasevacReport_HlzMarking_HlzMarking_Panels = 1, + meshtastic_CasevacReport_HlzMarking_HlzMarking_PyroSignal = 2, + meshtastic_CasevacReport_HlzMarking_HlzMarking_Smoke = 3, + meshtastic_CasevacReport_HlzMarking_HlzMarking_None = 4, + meshtastic_CasevacReport_HlzMarking_HlzMarking_Other = 5 +} meshtastic_CasevacReport_HlzMarking; + +/* Line 6: security situation at the pickup zone. */ +typedef enum _meshtastic_CasevacReport_Security { + meshtastic_CasevacReport_Security_Security_Unspecified = 0, + meshtastic_CasevacReport_Security_Security_NoEnemy = 1, /* N - no enemy activity */ + meshtastic_CasevacReport_Security_Security_PossibleEnemy = 2, /* P - possible enemy */ + meshtastic_CasevacReport_Security_Security_EnemyInArea = 3, /* E - enemy, approach with caution */ + meshtastic_CasevacReport_Security_Security_EnemyInArmedContact = 4 /* X - armed escort required */ +} meshtastic_CasevacReport_Security; + +typedef enum _meshtastic_EmergencyAlert_Type { + meshtastic_EmergencyAlert_Type_Type_Unspecified = 0, + meshtastic_EmergencyAlert_Type_Type_Alert911 = 1, /* b-a-o-tbl */ + meshtastic_EmergencyAlert_Type_Type_RingTheBell = 2, /* b-a-o-pan */ + meshtastic_EmergencyAlert_Type_Type_InContact = 3, /* b-a-o-opn */ + meshtastic_EmergencyAlert_Type_Type_GeoFenceBreached = 4, /* b-a-g */ + meshtastic_EmergencyAlert_Type_Type_Custom = 5, /* b-a-o-c */ + meshtastic_EmergencyAlert_Type_Type_Cancel = 6 /* b-a-o-can */ +} meshtastic_EmergencyAlert_Type; + +typedef enum _meshtastic_TaskRequest_Priority { + meshtastic_TaskRequest_Priority_Priority_Unspecified = 0, + meshtastic_TaskRequest_Priority_Priority_Low = 1, + meshtastic_TaskRequest_Priority_Priority_Normal = 2, + meshtastic_TaskRequest_Priority_Priority_High = 3, + meshtastic_TaskRequest_Priority_Priority_Critical = 4 +} meshtastic_TaskRequest_Priority; + +typedef enum _meshtastic_TaskRequest_Status { + meshtastic_TaskRequest_Status_Status_Unspecified = 0, + meshtastic_TaskRequest_Status_Status_Pending = 1, /* assigned, not yet acknowledged */ + meshtastic_TaskRequest_Status_Status_Acknowledged = 2, /* assignee has seen it */ + meshtastic_TaskRequest_Status_Status_InProgress = 3, /* assignee is working it */ + meshtastic_TaskRequest_Status_Status_Completed = 4, /* task done */ + meshtastic_TaskRequest_Status_Status_Cancelled = 5 /* cancelled before completion */ +} meshtastic_TaskRequest_Status; + /* Struct definitions */ /* ATAK GeoChat message */ typedef struct _meshtastic_GeoChat { - /* The text message */ + /* The text message. Empty for receipts. */ char message[200]; /* Uid recipient of the message */ bool has_to; @@ -76,6 +532,14 @@ typedef struct _meshtastic_GeoChat { /* Callsign of the recipient for the message */ bool has_to_callsign; char to_callsign[120]; + /* UID of the chat message this event is acknowledging. Empty for a + normal chat message; set for delivered / read receipts. Paired with + receipt_type so receivers can match the ack back to the original + outbound GeoChat by its event uid. */ + char receipt_for_uid[48]; + /* Receipt kind discriminator. See ReceiptType doc. Default ReceiptType_None + means this is a regular chat message, not a receipt. */ + meshtastic_GeoChat_ReceiptType receipt_type; } meshtastic_GeoChat; /* ATAK Group @@ -146,6 +610,397 @@ typedef struct _meshtastic_TAKPacket { } payload_variant; } meshtastic_TAKPacket; +/* Aircraft track information from ADS-B or military air tracking. + Covers the majority of observed real-world CoT traffic. */ +typedef struct _meshtastic_AircraftTrack { + /* ICAO hex identifier (e.g. "AD237C") */ + char icao[8]; + /* Aircraft registration (e.g. "N946AK") */ + char registration[16]; + /* Flight number/callsign (e.g. "ASA864") */ + char flight[16]; + /* ICAO aircraft type designator (e.g. "B39M") */ + char aircraft_type[8]; + /* Transponder squawk code (0-7777 octal) */ + uint16_t squawk; + /* ADS-B emitter category (e.g. "A3") */ + char category[4]; + /* Received signal strength * 10 (e.g. -194 for -19.4 dBm) */ + int32_t rssi_x10; + /* Whether receiver has GPS fix */ + bool gps; + /* CoT host ID for source attribution */ + char cot_host_id[64]; +} meshtastic_AircraftTrack; + +/* Compact geographic vertex used by repeated vertex lists in TAK geometry + payloads. Named with a `Cot` prefix to avoid a namespace collision with + `meshtastic.GeoPoint` in `device_ui.proto`, which is an unrelated zoom/ + latitude/longitude type used by the on-device map UI. + + Encoded as a signed DELTA from TAKPacketV2.latitude_i / longitude_i (the + enclosing event's anchor point). The absolute coordinate is recovered by + the receiver as `event.latitude_i + vertex.lat_delta_i` (and likewise for + longitude). + + Why deltas: a 32-vertex telestration with vertices clustered within a few + hundred meters of the anchor has per-vertex deltas in the ±10^4 range. + Under sint32+zigzag those encode as 2 bytes each (tag+varint), versus the + 4 bytes that sfixed32 would always require. At 32 vertices that is ~128 + bytes of savings — the difference between fitting under the LoRa MTU or + not. Absolute coordinates (values ~10^9) would cost sint32 varint 5 bytes + per field, which is why TAKPacketV2's top-level latitude_i / longitude_i + stay sfixed32 — only small values win with sint32. */ +typedef struct _meshtastic_CotGeoPoint { + /* Latitude delta from TAKPacketV2.latitude_i, in 1e-7 degree units. + Add to the enclosing event's latitude_i to recover the absolute latitude. */ + int32_t lat_delta_i; + /* Longitude delta from TAKPacketV2.longitude_i, in 1e-7 degree units. */ + int32_t lon_delta_i; +} meshtastic_CotGeoPoint; + +/* User-drawn tactical graphic: circle, rectangle, polygon, polyline, freehand + telestration, ranging circle, or bullseye. + + Covers CoT types u-d-c-c, u-d-r, u-d-f, u-d-f-m, u-d-p, u-r-b-c-c, + u-r-b-bullseye. The shape's anchor position is carried on + TAKPacketV2.latitude_i/longitude_i; polyline/polygon vertices are in the + `vertices` repeated field as `CotGeoPoint` deltas from that anchor. + + Colors use the Team enum as a 14-color palette (see color encoding below) + with a fixed32 exact-ARGB fallback for custom user-picked colors that + don't map to a palette entry. */ +typedef struct _meshtastic_DrawnShape { + /* Shape kind (circle, rectangle, freeform, etc.) */ + meshtastic_DrawnShape_Kind kind; + /* Explicit stroke/fill/both discriminator. See StyleMode doc. */ + meshtastic_DrawnShape_StyleMode style; + /* Ellipse major radius in centimeters. 0 for non-ellipse kinds. */ + uint32_t major_cm; + /* Ellipse minor radius in centimeters. 0 for non-ellipse kinds. */ + uint32_t minor_cm; + /* Ellipse rotation angle in degrees. Valid values are 0..360 inclusive; + 0 and 360 are equivalent rotations. In proto3, an unset uint32 reads + as 0, so senders should emit 0 when the angle is unspecified. */ + uint16_t angle_deg; + /* Stroke color as a named palette entry from the Team enum. If + Unspecifed_Color, the exact ARGB is carried in stroke_argb. + Valid only when style is StrokeOnly or StrokeAndFill. */ + meshtastic_Team stroke_color; + /* Stroke color as an exact 32-bit ARGB bit pattern. Always populated + on the wire; readers MUST use this value when stroke_color == + Unspecifed_Color and MAY use it to recover the exact original bytes + even when a palette entry is set. */ + uint32_t stroke_argb; + /* Stroke weight in tenths of a unit (e.g. 30 = 3.0). Typical ATAK + range 10..60. */ + uint16_t stroke_weight_x10; + /* Fill color as a named palette entry. See stroke_color docs. + Valid only when style is FillOnly or StrokeAndFill. */ + meshtastic_Team fill_color; + /* Fill color exact ARGB fallback. See stroke_argb docs. */ + uint32_t fill_argb; + /* Whether labels are rendered on this shape. */ + bool labels_on; + /* Vertex list for polyline/polygon/rectangle shapes. Capped at 32 by + the nanopb pool; senders MUST truncate longer inputs and set + `truncated = true`. */ + pb_size_t vertices_count; + meshtastic_CotGeoPoint vertices[32]; + /* True if the sender truncated `vertices` to fit the pool. */ + bool truncated; /* --- Bullseye-only fields. All ignored unless kind == Kind_Bullseye. --- */ + /* Bullseye distance in meters * 10 (e.g. 3285 = 328.5 m). 0 = unset. */ + uint32_t bullseye_distance_dm; + /* Bullseye bearing reference: 0 unset, 1 Magnetic, 2 True, 3 Grid. */ + uint8_t bullseye_bearing_ref; + /* Bullseye attribute bit flags: + bit 0: rangeRingVisible + bit 1: hasRangeRings + bit 2: edgeToCenter + bit 3: mils */ + uint8_t bullseye_flags; + /* Bullseye reference UID (anchor marker). Empty = anchor is self. */ + char bullseye_uid_ref[48]; +} meshtastic_DrawnShape; + +/* Fixed point of interest: spot marker, waypoint, checkpoint, 2525 symbol, + or custom icon. + + Covers CoT types b-m-p-s-m, b-m-p-w, b-m-p-c, b-m-p-s-p-i, b-m-p-s-p-loc, + plus a-u-G / a-f-G / a-h-G / a-n-G with iconset paths. The marker position + is carried on TAKPacketV2.latitude_i/longitude_i; fields below carry only + the marker-specific metadata. */ +typedef struct _meshtastic_Marker { + /* Marker kind */ + meshtastic_Marker_Kind kind; + /* Marker color as a named palette entry. If Unspecifed_Color, the exact + ARGB is in color_argb. */ + meshtastic_Team color; + /* Marker color exact ARGB bit pattern. Always populated on the wire. */ + uint32_t color_argb; + /* Status readiness flag (ATAK ). */ + bool readiness; + /* Parent link UID (ATAK ). Empty = no parent. + For spot/waypoint markers this is typically the producing TAK user's UID. */ + char parent_uid[48]; + /* Parent CoT type (e.g. "a-f-G-U-C"). Usually the parent TAK user's type. */ + char parent_type[24]; + /* Parent callsign (e.g. "HOPE"). */ + char parent_callsign[24]; + /* Iconset path stored verbatim. ATAK emits three flavors: + Kind_Symbol2525 -> "COT_MAPPING_2525B//" + Kind_SpotMap -> "COT_MAPPING_SPOTMAP//" + Kind_CustomIcon -> "//.png" + Stored end-to-end without prefix stripping; the ~19 bytes saved by + stripping well-known prefixes are not worth the builder-side bug + surface, and the dict compresses the repetition effectively. */ + char iconset[80]; +} meshtastic_Marker; + +/* Range and bearing measurement line from the event anchor to a target point. + + Covers CoT type u-rb-a. The anchor position is on + TAKPacketV2.latitude_i/longitude_i; the target endpoint is carried as a + CotGeoPoint — same delta-from-anchor encoding used by DrawnShape.vertices + so a self-anchored RAB (common case) encodes in zero bytes. */ +typedef struct _meshtastic_RangeAndBearing { + /* Target/anchor endpoint (delta-encoded from TAKPacketV2.latitude_i/longitude_i). */ + bool has_anchor; + meshtastic_CotGeoPoint anchor; + /* Anchor UID (from ). Empty = free-standing. */ + char anchor_uid[48]; + /* Range in centimeters (value * 100). Range 0..4294 km. */ + uint32_t range_cm; + /* Bearing in degrees * 100 (0..36000). */ + uint16_t bearing_cdeg; + /* Stroke color as a Team palette entry. See DrawnShape.stroke_color doc. */ + meshtastic_Team stroke_color; + /* Stroke color exact ARGB fallback. */ + uint32_t stroke_argb; + /* Stroke weight * 10 (e.g. 30 = 3.0). */ + uint16_t stroke_weight_x10; +} meshtastic_RangeAndBearing; + +/* Route waypoint or control point. Each link corresponds to one ATAK + entry inside the b-m-r event. */ +typedef struct _meshtastic_Route_Link { + /* Waypoint position (delta-encoded from TAKPacketV2.latitude_i/longitude_i). */ + bool has_point; + meshtastic_CotGeoPoint point; + /* Optional UID (empty = receiver derives). */ + char uid[48]; + /* Optional display callsign (e.g. "CP1"). Empty for unnamed control points. */ + char callsign[16]; + /* Link role: 0 = waypoint (b-m-p-w), 1 = checkpoint (b-m-p-c). */ + uint8_t link_type; +} meshtastic_Route_Link; + +/* Named route consisting of ordered waypoints and control points. + + Covers CoT type b-m-r. The first waypoint's position is on + TAKPacketV2.latitude_i/longitude_i; subsequent waypoints and checkpoints + are in `links`. Link count is capped at 16 by the nanopb pool; senders + MUST truncate longer routes and set `truncated = true`. */ +typedef struct _meshtastic_Route { + /* Travel method */ + meshtastic_Route_Method method; + /* Direction (infil/exfil) */ + meshtastic_Route_Direction direction; + /* Waypoint name prefix (e.g. "CP"). */ + char prefix[8]; + /* Stroke weight * 10 (e.g. 30 = 3.0). 0 = default. */ + uint16_t stroke_weight_x10; + /* Ordered list of route control points. Capped at 16. */ + pb_size_t links_count; + meshtastic_Route_Link links[16]; + /* True if the sender truncated `links` to fit the pool. */ + bool truncated; +} meshtastic_Route; + +/* 9-line MEDEVAC request (CoT type b-r-f-h-c). + + Mirrors the ATAK MedLine tool's <_medevac_> detail element. Every field + is optional (proto3 default); senders omit lines they don't have. The + envelope (TAKPacketV2.uid, cot_type_id=b-r-f-h-c, latitude_i/longitude_i, + altitude, callsign) carries Line 1 (location) and Line 2 (callsign). + + All numeric fields are tight varints so a complete 9-line request fits + in well under 100 bytes of proto on the wire. */ +typedef struct _meshtastic_CasevacReport { + /* Line 3: precedence / urgency. */ + meshtastic_CasevacReport_Precedence precedence; + /* Line 4: special equipment required, as a bitfield. + bit 0: none + bit 1: hoist + bit 2: extraction equipment + bit 3: ventilator + bit 4: blood */ + uint8_t equipment_flags; + /* Line 5: number of litter (stretcher-bound) patients. */ + uint8_t litter_patients; + /* Line 5: number of ambulatory (walking-wounded) patients. */ + uint8_t ambulatory_patients; + /* Line 6: security situation at the PZ. */ + meshtastic_CasevacReport_Security security; + /* Line 7: HLZ marking method. */ + meshtastic_CasevacReport_HlzMarking hlz_marking; + /* Line 7 supplementary: short free-text describing the zone marker + (e.g. "Green smoke", "VS-17 panel west"). Capped tight in options. */ + char zone_marker[16]; + /* --- Line 8: patient nationality counts --- */ + uint8_t us_military; + uint8_t us_civilian; + uint8_t non_us_military; + uint8_t non_us_civilian; + uint8_t epw; /* enemy prisoner of war */ + uint8_t child; + /* Line 9: terrain and obstacles at the PZ, as a bitfield. + bit 0: slope + bit 1: rough + bit 2: loose + bit 3: trees + bit 4: wires + bit 5: other */ + uint8_t terrain_flags; + /* Line 2: radio frequency / callsign metadata (e.g. "38.90 Mhz" or + "Victor 6"). Capped tight in options. */ + char frequency[16]; +} meshtastic_CasevacReport; + +/* Emergency alert / 911 beacon (CoT types b-a-o-tbl, b-a-o-pan, b-a-o-opn, + b-a-o-can, b-a-o-c, b-a-g). + + Small, high-priority structured record. The CoT type string is still set + on cot_type_id so receivers that ignore payload_variant can still display + the alert from the enum alone; the typed fields let modern receivers show + the authoring unit and handle cancel-referencing without XML parsing. */ +typedef struct _meshtastic_EmergencyAlert { + /* Alert discriminator. */ + meshtastic_EmergencyAlert_Type type; + /* UID of the unit that raised the alert. Often the same as + TAKPacketV2.uid but can be a parent device uid when a tracker raises + an alert on behalf of a dismount. */ + char authoring_uid[48]; + /* For Type_Cancel: the uid of the alert being cancelled. Empty for + non-cancel alert types. */ + char cancel_reference_uid[48]; +} meshtastic_EmergencyAlert; + +/* Task / engage request (CoT type t-s). + + Mirrors ATAK's TaskCotReceiver / CotTaskBuilder workflow. The envelope + carries the task's originating uid (implicit requester), position, and + creation time; the fields below carry structured metadata the raw-detail + fallback currently loses. + + Fields are deliberately lean — this variant is closer to the MTU ceiling + than the others, so every string is capped in options. */ +typedef struct _meshtastic_TaskRequest { + /* Short tag for the task category (e.g. "engage", "observe", "recon", + "rescue"). Free text on the wire so ATAK-specific task taxonomies + don't need proto coordination; capped tight in options. */ + char task_type[12]; + /* UID of the target / map item being tasked. */ + char target_uid[32]; + /* UID of the assigned unit. Empty = unassigned / broadcast task. */ + char assignee_uid[32]; + meshtastic_TaskRequest_Priority priority; + meshtastic_TaskRequest_Status status; + /* Optional short note (reason, constraints, grid reference). Capped + tight in options to keep the worst-case under the LoRa MTU. */ + char note[48]; +} meshtastic_TaskRequest; + +typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacketV2_raw_detail_t; +/* ATAK v2 packet with expanded CoT field support and zstd dictionary compression. + Sent on ATAK_PLUGIN_V2 port. The wire payload is: + [1 byte flags][zstd-compressed TAKPacketV2 protobuf] + Flags byte: bits 0-5 = dictionary ID, bits 6-7 = reserved. */ +typedef struct _meshtastic_TAKPacketV2 { + /* Well-known CoT event type enum. + Use CotType_Other with cot_type_str for unknown types. */ + meshtastic_CotType cot_type_id; + /* How the coordinates were generated */ + meshtastic_CotHow how; + /* Callsign */ + char callsign[120]; + /* Team color assignment */ + meshtastic_Team team; + /* Role of the group member */ + meshtastic_MemberRole role; + /* Latitude, multiply by 1e-7 to get degrees in floating point */ + int32_t latitude_i; + /* Longitude, multiply by 1e-7 to get degrees in floating point */ + int32_t longitude_i; + /* Altitude in meters (HAE) */ + int32_t altitude; + /* Speed in cm/s */ + uint32_t speed; + /* Course in degrees * 100 (0-36000) */ + uint16_t course; + /* Battery level 0-100 */ + uint8_t battery; + /* Geopoint source */ + meshtastic_GeoPointSource geo_src; + /* Altitude source */ + meshtastic_GeoPointSource alt_src; + /* Device UID (UUID string or device ID like "ANDROID-xxxx") */ + char uid[48]; + /* Device callsign */ + char device_callsign[120]; + /* Stale time as seconds offset from event time */ + uint16_t stale_seconds; + /* TAK client version string */ + char tak_version[64]; + /* TAK device model */ + char tak_device[32]; + /* TAK platform (ATAK-CIV, WebTAK, etc.) */ + char tak_platform[32]; + /* TAK OS version */ + char tak_os[16]; + /* Connection endpoint */ + char endpoint[32]; + /* Phone number */ + char phone[20]; + /* CoT event type string, only populated when cot_type_id is CotType_Other */ + char cot_type_str[32]; + /* Optional remarks / free-text annotation from the element. + Populated for non-GeoChat payload types (shapes, markers, routes, etc.) + when the original CoT event carried non-empty remarks text. + GeoChat messages carry their text in GeoChat.message instead. + Empty string (proto3 default) means no remarks were present. */ + pb_callback_t remarks; + pb_size_t which_payload_variant; + union { + /* Position report (true = PLI, no extra fields beyond the common ones above) */ + bool pli; + /* ATAK GeoChat message */ + meshtastic_GeoChat chat; + /* Aircraft track data (ADS-B, military air) */ + meshtastic_AircraftTrack aircraft; + /* Generic CoT detail XML for unmapped types. Kept as a fallback for CoT + types not yet promoted to a typed variant; drawings, markers, ranging + tools, and routes have dedicated variants below and should not land here. */ + meshtastic_TAKPacketV2_raw_detail_t raw_detail; + /* User-drawn tactical graphic: circle, rectangle, polygon, polyline, + telestration, ranging circle, or bullseye. See DrawnShape. */ + meshtastic_DrawnShape shape; + /* Fixed point of interest: spot marker, waypoint, checkpoint, 2525 + symbol, or custom icon. See Marker. */ + meshtastic_Marker marker; + /* Range and bearing measurement line. See RangeAndBearing. */ + meshtastic_RangeAndBearing rab; + /* Named route with ordered waypoints and control points. See Route. */ + meshtastic_Route route; + /* 9-line MEDEVAC request. See CasevacReport. */ + meshtastic_CasevacReport casevac; + /* Emergency beacon / 911 alert. See EmergencyAlert. */ + meshtastic_EmergencyAlert emergency; + /* Task / engage request. See TaskRequest. */ + meshtastic_TaskRequest task; + } payload_variant; +} meshtastic_TAKPacketV2; + #ifdef __cplusplus extern "C" { @@ -160,8 +1015,69 @@ extern "C" { #define _meshtastic_MemberRole_MAX meshtastic_MemberRole_K9 #define _meshtastic_MemberRole_ARRAYSIZE ((meshtastic_MemberRole)(meshtastic_MemberRole_K9+1)) +#define _meshtastic_CotHow_MIN meshtastic_CotHow_CotHow_Unspecified +#define _meshtastic_CotHow_MAX meshtastic_CotHow_CotHow_m_s +#define _meshtastic_CotHow_ARRAYSIZE ((meshtastic_CotHow)(meshtastic_CotHow_CotHow_m_s+1)) + +#define _meshtastic_CotType_MIN meshtastic_CotType_CotType_Other +#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_t_s +#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_t_s+1)) + +#define _meshtastic_GeoPointSource_MIN meshtastic_GeoPointSource_GeoPointSource_Unspecified +#define _meshtastic_GeoPointSource_MAX meshtastic_GeoPointSource_GeoPointSource_NETWORK +#define _meshtastic_GeoPointSource_ARRAYSIZE ((meshtastic_GeoPointSource)(meshtastic_GeoPointSource_GeoPointSource_NETWORK+1)) + +#define _meshtastic_GeoChat_ReceiptType_MIN meshtastic_GeoChat_ReceiptType_ReceiptType_None +#define _meshtastic_GeoChat_ReceiptType_MAX meshtastic_GeoChat_ReceiptType_ReceiptType_Read +#define _meshtastic_GeoChat_ReceiptType_ARRAYSIZE ((meshtastic_GeoChat_ReceiptType)(meshtastic_GeoChat_ReceiptType_ReceiptType_Read+1)) + +#define _meshtastic_DrawnShape_Kind_MIN meshtastic_DrawnShape_Kind_Kind_Unspecified +#define _meshtastic_DrawnShape_Kind_MAX meshtastic_DrawnShape_Kind_Kind_Vehicle3D +#define _meshtastic_DrawnShape_Kind_ARRAYSIZE ((meshtastic_DrawnShape_Kind)(meshtastic_DrawnShape_Kind_Kind_Vehicle3D+1)) + +#define _meshtastic_DrawnShape_StyleMode_MIN meshtastic_DrawnShape_StyleMode_StyleMode_Unspecified +#define _meshtastic_DrawnShape_StyleMode_MAX meshtastic_DrawnShape_StyleMode_StyleMode_StrokeAndFill +#define _meshtastic_DrawnShape_StyleMode_ARRAYSIZE ((meshtastic_DrawnShape_StyleMode)(meshtastic_DrawnShape_StyleMode_StyleMode_StrokeAndFill+1)) + +#define _meshtastic_Marker_Kind_MIN meshtastic_Marker_Kind_Kind_Unspecified +#define _meshtastic_Marker_Kind_MAX meshtastic_Marker_Kind_Kind_ImageMarker +#define _meshtastic_Marker_Kind_ARRAYSIZE ((meshtastic_Marker_Kind)(meshtastic_Marker_Kind_Kind_ImageMarker+1)) + +#define _meshtastic_Route_Method_MIN meshtastic_Route_Method_Method_Unspecified +#define _meshtastic_Route_Method_MAX meshtastic_Route_Method_Method_Watercraft +#define _meshtastic_Route_Method_ARRAYSIZE ((meshtastic_Route_Method)(meshtastic_Route_Method_Method_Watercraft+1)) + +#define _meshtastic_Route_Direction_MIN meshtastic_Route_Direction_Direction_Unspecified +#define _meshtastic_Route_Direction_MAX meshtastic_Route_Direction_Direction_Exfil +#define _meshtastic_Route_Direction_ARRAYSIZE ((meshtastic_Route_Direction)(meshtastic_Route_Direction_Direction_Exfil+1)) + +#define _meshtastic_CasevacReport_Precedence_MIN meshtastic_CasevacReport_Precedence_Precedence_Unspecified +#define _meshtastic_CasevacReport_Precedence_MAX meshtastic_CasevacReport_Precedence_Precedence_Convenience +#define _meshtastic_CasevacReport_Precedence_ARRAYSIZE ((meshtastic_CasevacReport_Precedence)(meshtastic_CasevacReport_Precedence_Precedence_Convenience+1)) + +#define _meshtastic_CasevacReport_HlzMarking_MIN meshtastic_CasevacReport_HlzMarking_HlzMarking_Unspecified +#define _meshtastic_CasevacReport_HlzMarking_MAX meshtastic_CasevacReport_HlzMarking_HlzMarking_Other +#define _meshtastic_CasevacReport_HlzMarking_ARRAYSIZE ((meshtastic_CasevacReport_HlzMarking)(meshtastic_CasevacReport_HlzMarking_HlzMarking_Other+1)) + +#define _meshtastic_CasevacReport_Security_MIN meshtastic_CasevacReport_Security_Security_Unspecified +#define _meshtastic_CasevacReport_Security_MAX meshtastic_CasevacReport_Security_Security_EnemyInArmedContact +#define _meshtastic_CasevacReport_Security_ARRAYSIZE ((meshtastic_CasevacReport_Security)(meshtastic_CasevacReport_Security_Security_EnemyInArmedContact+1)) + +#define _meshtastic_EmergencyAlert_Type_MIN meshtastic_EmergencyAlert_Type_Type_Unspecified +#define _meshtastic_EmergencyAlert_Type_MAX meshtastic_EmergencyAlert_Type_Type_Cancel +#define _meshtastic_EmergencyAlert_Type_ARRAYSIZE ((meshtastic_EmergencyAlert_Type)(meshtastic_EmergencyAlert_Type_Type_Cancel+1)) + +#define _meshtastic_TaskRequest_Priority_MIN meshtastic_TaskRequest_Priority_Priority_Unspecified +#define _meshtastic_TaskRequest_Priority_MAX meshtastic_TaskRequest_Priority_Priority_Critical +#define _meshtastic_TaskRequest_Priority_ARRAYSIZE ((meshtastic_TaskRequest_Priority)(meshtastic_TaskRequest_Priority_Priority_Critical+1)) + +#define _meshtastic_TaskRequest_Status_MIN meshtastic_TaskRequest_Status_Status_Unspecified +#define _meshtastic_TaskRequest_Status_MAX meshtastic_TaskRequest_Status_Status_Cancelled +#define _meshtastic_TaskRequest_Status_ARRAYSIZE ((meshtastic_TaskRequest_Status)(meshtastic_TaskRequest_Status_Status_Cancelled+1)) +#define meshtastic_GeoChat_receipt_type_ENUMTYPE meshtastic_GeoChat_ReceiptType + #define meshtastic_Group_role_ENUMTYPE meshtastic_MemberRole #define meshtastic_Group_team_ENUMTYPE meshtastic_Team @@ -169,24 +1085,80 @@ extern "C" { + +#define meshtastic_DrawnShape_kind_ENUMTYPE meshtastic_DrawnShape_Kind +#define meshtastic_DrawnShape_style_ENUMTYPE meshtastic_DrawnShape_StyleMode +#define meshtastic_DrawnShape_stroke_color_ENUMTYPE meshtastic_Team +#define meshtastic_DrawnShape_fill_color_ENUMTYPE meshtastic_Team + +#define meshtastic_Marker_kind_ENUMTYPE meshtastic_Marker_Kind +#define meshtastic_Marker_color_ENUMTYPE meshtastic_Team + +#define meshtastic_RangeAndBearing_stroke_color_ENUMTYPE meshtastic_Team + +#define meshtastic_Route_method_ENUMTYPE meshtastic_Route_Method +#define meshtastic_Route_direction_ENUMTYPE meshtastic_Route_Direction + + +#define meshtastic_CasevacReport_precedence_ENUMTYPE meshtastic_CasevacReport_Precedence +#define meshtastic_CasevacReport_security_ENUMTYPE meshtastic_CasevacReport_Security +#define meshtastic_CasevacReport_hlz_marking_ENUMTYPE meshtastic_CasevacReport_HlzMarking + +#define meshtastic_EmergencyAlert_type_ENUMTYPE meshtastic_EmergencyAlert_Type + +#define meshtastic_TaskRequest_priority_ENUMTYPE meshtastic_TaskRequest_Priority +#define meshtastic_TaskRequest_status_ENUMTYPE meshtastic_TaskRequest_Status + +#define meshtastic_TAKPacketV2_cot_type_id_ENUMTYPE meshtastic_CotType +#define meshtastic_TAKPacketV2_how_ENUMTYPE meshtastic_CotHow +#define meshtastic_TAKPacketV2_team_ENUMTYPE meshtastic_Team +#define meshtastic_TAKPacketV2_role_ENUMTYPE meshtastic_MemberRole +#define meshtastic_TAKPacketV2_geo_src_ENUMTYPE meshtastic_GeoPointSource +#define meshtastic_TAKPacketV2_alt_src_ENUMTYPE meshtastic_GeoPointSource + + /* Initializer values for message structs */ #define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}} -#define meshtastic_GeoChat_init_default {"", false, "", false, ""} +#define meshtastic_GeoChat_init_default {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN} #define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_default {0} #define meshtastic_Contact_init_default {"", ""} #define meshtastic_PLI_init_default {0, 0, 0, 0, 0} +#define meshtastic_AircraftTrack_init_default {"", "", "", "", 0, "", 0, 0, ""} +#define meshtastic_CotGeoPoint_init_default {0, 0} +#define meshtastic_DrawnShape_init_default {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default}, 0, 0, 0, 0, ""} +#define meshtastic_Marker_init_default {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""} +#define meshtastic_RangeAndBearing_init_default {false, meshtastic_CotGeoPoint_init_default, "", 0, 0, _meshtastic_Team_MIN, 0, 0} +#define meshtastic_Route_init_default {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default}, 0} +#define meshtastic_Route_Link_init_default {false, meshtastic_CotGeoPoint_init_default, "", "", 0} +#define meshtastic_CasevacReport_init_default {_meshtastic_CasevacReport_Precedence_MIN, 0, 0, 0, _meshtastic_CasevacReport_Security_MIN, _meshtastic_CasevacReport_HlzMarking_MIN, "", 0, 0, 0, 0, 0, 0, 0, ""} +#define meshtastic_EmergencyAlert_init_default {_meshtastic_EmergencyAlert_Type_MIN, "", ""} +#define meshtastic_TaskRequest_init_default {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""} +#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, 0, {0}} #define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}} -#define meshtastic_GeoChat_init_zero {"", false, "", false, ""} +#define meshtastic_GeoChat_init_zero {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN} #define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_zero {0} #define meshtastic_Contact_init_zero {"", ""} #define meshtastic_PLI_init_zero {0, 0, 0, 0, 0} +#define meshtastic_AircraftTrack_init_zero {"", "", "", "", 0, "", 0, 0, ""} +#define meshtastic_CotGeoPoint_init_zero {0, 0} +#define meshtastic_DrawnShape_init_zero {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero}, 0, 0, 0, 0, ""} +#define meshtastic_Marker_init_zero {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""} +#define meshtastic_RangeAndBearing_init_zero {false, meshtastic_CotGeoPoint_init_zero, "", 0, 0, _meshtastic_Team_MIN, 0, 0} +#define meshtastic_Route_init_zero {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero}, 0} +#define meshtastic_Route_Link_init_zero {false, meshtastic_CotGeoPoint_init_zero, "", "", 0} +#define meshtastic_CasevacReport_init_zero {_meshtastic_CasevacReport_Precedence_MIN, 0, 0, 0, _meshtastic_CasevacReport_Security_MIN, _meshtastic_CasevacReport_HlzMarking_MIN, "", 0, 0, 0, 0, 0, 0, 0, ""} +#define meshtastic_EmergencyAlert_init_zero {_meshtastic_EmergencyAlert_Type_MIN, "", ""} +#define meshtastic_TaskRequest_init_zero {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""} +#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, 0, {0}} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_GeoChat_message_tag 1 #define meshtastic_GeoChat_to_tag 2 #define meshtastic_GeoChat_to_callsign_tag 3 +#define meshtastic_GeoChat_receipt_for_uid_tag 4 +#define meshtastic_GeoChat_receipt_type_tag 5 #define meshtastic_Group_role_tag 1 #define meshtastic_Group_team_tag 2 #define meshtastic_Status_battery_tag 1 @@ -204,6 +1176,118 @@ extern "C" { #define meshtastic_TAKPacket_pli_tag 5 #define meshtastic_TAKPacket_chat_tag 6 #define meshtastic_TAKPacket_detail_tag 7 +#define meshtastic_AircraftTrack_icao_tag 1 +#define meshtastic_AircraftTrack_registration_tag 2 +#define meshtastic_AircraftTrack_flight_tag 3 +#define meshtastic_AircraftTrack_aircraft_type_tag 4 +#define meshtastic_AircraftTrack_squawk_tag 5 +#define meshtastic_AircraftTrack_category_tag 6 +#define meshtastic_AircraftTrack_rssi_x10_tag 7 +#define meshtastic_AircraftTrack_gps_tag 8 +#define meshtastic_AircraftTrack_cot_host_id_tag 9 +#define meshtastic_CotGeoPoint_lat_delta_i_tag 1 +#define meshtastic_CotGeoPoint_lon_delta_i_tag 2 +#define meshtastic_DrawnShape_kind_tag 1 +#define meshtastic_DrawnShape_style_tag 2 +#define meshtastic_DrawnShape_major_cm_tag 3 +#define meshtastic_DrawnShape_minor_cm_tag 4 +#define meshtastic_DrawnShape_angle_deg_tag 5 +#define meshtastic_DrawnShape_stroke_color_tag 6 +#define meshtastic_DrawnShape_stroke_argb_tag 7 +#define meshtastic_DrawnShape_stroke_weight_x10_tag 8 +#define meshtastic_DrawnShape_fill_color_tag 9 +#define meshtastic_DrawnShape_fill_argb_tag 10 +#define meshtastic_DrawnShape_labels_on_tag 11 +#define meshtastic_DrawnShape_vertices_tag 12 +#define meshtastic_DrawnShape_truncated_tag 13 +#define meshtastic_DrawnShape_bullseye_distance_dm_tag 14 +#define meshtastic_DrawnShape_bullseye_bearing_ref_tag 15 +#define meshtastic_DrawnShape_bullseye_flags_tag 16 +#define meshtastic_DrawnShape_bullseye_uid_ref_tag 17 +#define meshtastic_Marker_kind_tag 1 +#define meshtastic_Marker_color_tag 2 +#define meshtastic_Marker_color_argb_tag 3 +#define meshtastic_Marker_readiness_tag 4 +#define meshtastic_Marker_parent_uid_tag 5 +#define meshtastic_Marker_parent_type_tag 6 +#define meshtastic_Marker_parent_callsign_tag 7 +#define meshtastic_Marker_iconset_tag 8 +#define meshtastic_RangeAndBearing_anchor_tag 1 +#define meshtastic_RangeAndBearing_anchor_uid_tag 2 +#define meshtastic_RangeAndBearing_range_cm_tag 3 +#define meshtastic_RangeAndBearing_bearing_cdeg_tag 4 +#define meshtastic_RangeAndBearing_stroke_color_tag 5 +#define meshtastic_RangeAndBearing_stroke_argb_tag 6 +#define meshtastic_RangeAndBearing_stroke_weight_x10_tag 7 +#define meshtastic_Route_Link_point_tag 1 +#define meshtastic_Route_Link_uid_tag 2 +#define meshtastic_Route_Link_callsign_tag 3 +#define meshtastic_Route_Link_link_type_tag 4 +#define meshtastic_Route_method_tag 1 +#define meshtastic_Route_direction_tag 2 +#define meshtastic_Route_prefix_tag 3 +#define meshtastic_Route_stroke_weight_x10_tag 4 +#define meshtastic_Route_links_tag 5 +#define meshtastic_Route_truncated_tag 6 +#define meshtastic_CasevacReport_precedence_tag 1 +#define meshtastic_CasevacReport_equipment_flags_tag 2 +#define meshtastic_CasevacReport_litter_patients_tag 3 +#define meshtastic_CasevacReport_ambulatory_patients_tag 4 +#define meshtastic_CasevacReport_security_tag 5 +#define meshtastic_CasevacReport_hlz_marking_tag 6 +#define meshtastic_CasevacReport_zone_marker_tag 7 +#define meshtastic_CasevacReport_us_military_tag 8 +#define meshtastic_CasevacReport_us_civilian_tag 9 +#define meshtastic_CasevacReport_non_us_military_tag 10 +#define meshtastic_CasevacReport_non_us_civilian_tag 11 +#define meshtastic_CasevacReport_epw_tag 12 +#define meshtastic_CasevacReport_child_tag 13 +#define meshtastic_CasevacReport_terrain_flags_tag 14 +#define meshtastic_CasevacReport_frequency_tag 15 +#define meshtastic_EmergencyAlert_type_tag 1 +#define meshtastic_EmergencyAlert_authoring_uid_tag 2 +#define meshtastic_EmergencyAlert_cancel_reference_uid_tag 3 +#define meshtastic_TaskRequest_task_type_tag 1 +#define meshtastic_TaskRequest_target_uid_tag 2 +#define meshtastic_TaskRequest_assignee_uid_tag 3 +#define meshtastic_TaskRequest_priority_tag 4 +#define meshtastic_TaskRequest_status_tag 5 +#define meshtastic_TaskRequest_note_tag 6 +#define meshtastic_TAKPacketV2_cot_type_id_tag 1 +#define meshtastic_TAKPacketV2_how_tag 2 +#define meshtastic_TAKPacketV2_callsign_tag 3 +#define meshtastic_TAKPacketV2_team_tag 4 +#define meshtastic_TAKPacketV2_role_tag 5 +#define meshtastic_TAKPacketV2_latitude_i_tag 6 +#define meshtastic_TAKPacketV2_longitude_i_tag 7 +#define meshtastic_TAKPacketV2_altitude_tag 8 +#define meshtastic_TAKPacketV2_speed_tag 9 +#define meshtastic_TAKPacketV2_course_tag 10 +#define meshtastic_TAKPacketV2_battery_tag 11 +#define meshtastic_TAKPacketV2_geo_src_tag 12 +#define meshtastic_TAKPacketV2_alt_src_tag 13 +#define meshtastic_TAKPacketV2_uid_tag 14 +#define meshtastic_TAKPacketV2_device_callsign_tag 15 +#define meshtastic_TAKPacketV2_stale_seconds_tag 16 +#define meshtastic_TAKPacketV2_tak_version_tag 17 +#define meshtastic_TAKPacketV2_tak_device_tag 18 +#define meshtastic_TAKPacketV2_tak_platform_tag 19 +#define meshtastic_TAKPacketV2_tak_os_tag 20 +#define meshtastic_TAKPacketV2_endpoint_tag 21 +#define meshtastic_TAKPacketV2_phone_tag 22 +#define meshtastic_TAKPacketV2_cot_type_str_tag 23 +#define meshtastic_TAKPacketV2_remarks_tag 24 +#define meshtastic_TAKPacketV2_pli_tag 30 +#define meshtastic_TAKPacketV2_chat_tag 31 +#define meshtastic_TAKPacketV2_aircraft_tag 32 +#define meshtastic_TAKPacketV2_raw_detail_tag 33 +#define meshtastic_TAKPacketV2_shape_tag 34 +#define meshtastic_TAKPacketV2_marker_tag 35 +#define meshtastic_TAKPacketV2_rab_tag 36 +#define meshtastic_TAKPacketV2_route_tag 37 +#define meshtastic_TAKPacketV2_casevac_tag 38 +#define meshtastic_TAKPacketV2_emergency_tag 39 +#define meshtastic_TAKPacketV2_task_tag 40 /* Struct field encoding specification for nanopb */ #define meshtastic_TAKPacket_FIELDLIST(X, a) \ @@ -225,7 +1309,9 @@ X(a, STATIC, ONEOF, BYTES, (payload_variant,detail,payload_variant.detai #define meshtastic_GeoChat_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, message, 1) \ X(a, STATIC, OPTIONAL, STRING, to, 2) \ -X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) +X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) \ +X(a, STATIC, SINGULAR, STRING, receipt_for_uid, 4) \ +X(a, STATIC, SINGULAR, UENUM, receipt_type, 5) #define meshtastic_GeoChat_CALLBACK NULL #define meshtastic_GeoChat_DEFAULT NULL @@ -255,12 +1341,192 @@ X(a, STATIC, SINGULAR, UINT32, course, 5) #define meshtastic_PLI_CALLBACK NULL #define meshtastic_PLI_DEFAULT NULL +#define meshtastic_AircraftTrack_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, icao, 1) \ +X(a, STATIC, SINGULAR, STRING, registration, 2) \ +X(a, STATIC, SINGULAR, STRING, flight, 3) \ +X(a, STATIC, SINGULAR, STRING, aircraft_type, 4) \ +X(a, STATIC, SINGULAR, UINT32, squawk, 5) \ +X(a, STATIC, SINGULAR, STRING, category, 6) \ +X(a, STATIC, SINGULAR, SINT32, rssi_x10, 7) \ +X(a, STATIC, SINGULAR, BOOL, gps, 8) \ +X(a, STATIC, SINGULAR, STRING, cot_host_id, 9) +#define meshtastic_AircraftTrack_CALLBACK NULL +#define meshtastic_AircraftTrack_DEFAULT NULL + +#define meshtastic_CotGeoPoint_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, SINT32, lat_delta_i, 1) \ +X(a, STATIC, SINGULAR, SINT32, lon_delta_i, 2) +#define meshtastic_CotGeoPoint_CALLBACK NULL +#define meshtastic_CotGeoPoint_DEFAULT NULL + +#define meshtastic_DrawnShape_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, kind, 1) \ +X(a, STATIC, SINGULAR, UENUM, style, 2) \ +X(a, STATIC, SINGULAR, UINT32, major_cm, 3) \ +X(a, STATIC, SINGULAR, UINT32, minor_cm, 4) \ +X(a, STATIC, SINGULAR, UINT32, angle_deg, 5) \ +X(a, STATIC, SINGULAR, UENUM, stroke_color, 6) \ +X(a, STATIC, SINGULAR, FIXED32, stroke_argb, 7) \ +X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 8) \ +X(a, STATIC, SINGULAR, UENUM, fill_color, 9) \ +X(a, STATIC, SINGULAR, FIXED32, fill_argb, 10) \ +X(a, STATIC, SINGULAR, BOOL, labels_on, 11) \ +X(a, STATIC, REPEATED, MESSAGE, vertices, 12) \ +X(a, STATIC, SINGULAR, BOOL, truncated, 13) \ +X(a, STATIC, SINGULAR, UINT32, bullseye_distance_dm, 14) \ +X(a, STATIC, SINGULAR, UINT32, bullseye_bearing_ref, 15) \ +X(a, STATIC, SINGULAR, UINT32, bullseye_flags, 16) \ +X(a, STATIC, SINGULAR, STRING, bullseye_uid_ref, 17) +#define meshtastic_DrawnShape_CALLBACK NULL +#define meshtastic_DrawnShape_DEFAULT NULL +#define meshtastic_DrawnShape_vertices_MSGTYPE meshtastic_CotGeoPoint + +#define meshtastic_Marker_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, kind, 1) \ +X(a, STATIC, SINGULAR, UENUM, color, 2) \ +X(a, STATIC, SINGULAR, FIXED32, color_argb, 3) \ +X(a, STATIC, SINGULAR, BOOL, readiness, 4) \ +X(a, STATIC, SINGULAR, STRING, parent_uid, 5) \ +X(a, STATIC, SINGULAR, STRING, parent_type, 6) \ +X(a, STATIC, SINGULAR, STRING, parent_callsign, 7) \ +X(a, STATIC, SINGULAR, STRING, iconset, 8) +#define meshtastic_Marker_CALLBACK NULL +#define meshtastic_Marker_DEFAULT NULL + +#define meshtastic_RangeAndBearing_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, anchor, 1) \ +X(a, STATIC, SINGULAR, STRING, anchor_uid, 2) \ +X(a, STATIC, SINGULAR, UINT32, range_cm, 3) \ +X(a, STATIC, SINGULAR, UINT32, bearing_cdeg, 4) \ +X(a, STATIC, SINGULAR, UENUM, stroke_color, 5) \ +X(a, STATIC, SINGULAR, FIXED32, stroke_argb, 6) \ +X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 7) +#define meshtastic_RangeAndBearing_CALLBACK NULL +#define meshtastic_RangeAndBearing_DEFAULT NULL +#define meshtastic_RangeAndBearing_anchor_MSGTYPE meshtastic_CotGeoPoint + +#define meshtastic_Route_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, method, 1) \ +X(a, STATIC, SINGULAR, UENUM, direction, 2) \ +X(a, STATIC, SINGULAR, STRING, prefix, 3) \ +X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 4) \ +X(a, STATIC, REPEATED, MESSAGE, links, 5) \ +X(a, STATIC, SINGULAR, BOOL, truncated, 6) +#define meshtastic_Route_CALLBACK NULL +#define meshtastic_Route_DEFAULT NULL +#define meshtastic_Route_links_MSGTYPE meshtastic_Route_Link + +#define meshtastic_Route_Link_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, point, 1) \ +X(a, STATIC, SINGULAR, STRING, uid, 2) \ +X(a, STATIC, SINGULAR, STRING, callsign, 3) \ +X(a, STATIC, SINGULAR, UINT32, link_type, 4) +#define meshtastic_Route_Link_CALLBACK NULL +#define meshtastic_Route_Link_DEFAULT NULL +#define meshtastic_Route_Link_point_MSGTYPE meshtastic_CotGeoPoint + +#define meshtastic_CasevacReport_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, precedence, 1) \ +X(a, STATIC, SINGULAR, UINT32, equipment_flags, 2) \ +X(a, STATIC, SINGULAR, UINT32, litter_patients, 3) \ +X(a, STATIC, SINGULAR, UINT32, ambulatory_patients, 4) \ +X(a, STATIC, SINGULAR, UENUM, security, 5) \ +X(a, STATIC, SINGULAR, UENUM, hlz_marking, 6) \ +X(a, STATIC, SINGULAR, STRING, zone_marker, 7) \ +X(a, STATIC, SINGULAR, UINT32, us_military, 8) \ +X(a, STATIC, SINGULAR, UINT32, us_civilian, 9) \ +X(a, STATIC, SINGULAR, UINT32, non_us_military, 10) \ +X(a, STATIC, SINGULAR, UINT32, non_us_civilian, 11) \ +X(a, STATIC, SINGULAR, UINT32, epw, 12) \ +X(a, STATIC, SINGULAR, UINT32, child, 13) \ +X(a, STATIC, SINGULAR, UINT32, terrain_flags, 14) \ +X(a, STATIC, SINGULAR, STRING, frequency, 15) +#define meshtastic_CasevacReport_CALLBACK NULL +#define meshtastic_CasevacReport_DEFAULT NULL + +#define meshtastic_EmergencyAlert_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, type, 1) \ +X(a, STATIC, SINGULAR, STRING, authoring_uid, 2) \ +X(a, STATIC, SINGULAR, STRING, cancel_reference_uid, 3) +#define meshtastic_EmergencyAlert_CALLBACK NULL +#define meshtastic_EmergencyAlert_DEFAULT NULL + +#define meshtastic_TaskRequest_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, task_type, 1) \ +X(a, STATIC, SINGULAR, STRING, target_uid, 2) \ +X(a, STATIC, SINGULAR, STRING, assignee_uid, 3) \ +X(a, STATIC, SINGULAR, UENUM, priority, 4) \ +X(a, STATIC, SINGULAR, UENUM, status, 5) \ +X(a, STATIC, SINGULAR, STRING, note, 6) +#define meshtastic_TaskRequest_CALLBACK NULL +#define meshtastic_TaskRequest_DEFAULT NULL + +#define meshtastic_TAKPacketV2_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, cot_type_id, 1) \ +X(a, STATIC, SINGULAR, UENUM, how, 2) \ +X(a, STATIC, SINGULAR, STRING, callsign, 3) \ +X(a, STATIC, SINGULAR, UENUM, team, 4) \ +X(a, STATIC, SINGULAR, UENUM, role, 5) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 6) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 7) \ +X(a, STATIC, SINGULAR, SINT32, altitude, 8) \ +X(a, STATIC, SINGULAR, UINT32, speed, 9) \ +X(a, STATIC, SINGULAR, UINT32, course, 10) \ +X(a, STATIC, SINGULAR, UINT32, battery, 11) \ +X(a, STATIC, SINGULAR, UENUM, geo_src, 12) \ +X(a, STATIC, SINGULAR, UENUM, alt_src, 13) \ +X(a, STATIC, SINGULAR, STRING, uid, 14) \ +X(a, STATIC, SINGULAR, STRING, device_callsign, 15) \ +X(a, STATIC, SINGULAR, UINT32, stale_seconds, 16) \ +X(a, STATIC, SINGULAR, STRING, tak_version, 17) \ +X(a, STATIC, SINGULAR, STRING, tak_device, 18) \ +X(a, STATIC, SINGULAR, STRING, tak_platform, 19) \ +X(a, STATIC, SINGULAR, STRING, tak_os, 20) \ +X(a, STATIC, SINGULAR, STRING, endpoint, 21) \ +X(a, STATIC, SINGULAR, STRING, phone, 22) \ +X(a, STATIC, SINGULAR, STRING, cot_type_str, 23) \ +X(a, CALLBACK, SINGULAR, STRING, remarks, 24) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,pli,payload_variant.pli), 30) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 31) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,aircraft,payload_variant.aircraft), 32) \ +X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,shape,payload_variant.shape), 34) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,marker,payload_variant.marker), 35) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,rab,payload_variant.rab), 36) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,route,payload_variant.route), 37) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,casevac,payload_variant.casevac), 38) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,emergency,payload_variant.emergency), 39) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task), 40) +#define meshtastic_TAKPacketV2_CALLBACK pb_default_field_callback +#define meshtastic_TAKPacketV2_DEFAULT NULL +#define meshtastic_TAKPacketV2_payload_variant_chat_MSGTYPE meshtastic_GeoChat +#define meshtastic_TAKPacketV2_payload_variant_aircraft_MSGTYPE meshtastic_AircraftTrack +#define meshtastic_TAKPacketV2_payload_variant_shape_MSGTYPE meshtastic_DrawnShape +#define meshtastic_TAKPacketV2_payload_variant_marker_MSGTYPE meshtastic_Marker +#define meshtastic_TAKPacketV2_payload_variant_rab_MSGTYPE meshtastic_RangeAndBearing +#define meshtastic_TAKPacketV2_payload_variant_route_MSGTYPE meshtastic_Route +#define meshtastic_TAKPacketV2_payload_variant_casevac_MSGTYPE meshtastic_CasevacReport +#define meshtastic_TAKPacketV2_payload_variant_emergency_MSGTYPE meshtastic_EmergencyAlert +#define meshtastic_TAKPacketV2_payload_variant_task_MSGTYPE meshtastic_TaskRequest + extern const pb_msgdesc_t meshtastic_TAKPacket_msg; extern const pb_msgdesc_t meshtastic_GeoChat_msg; extern const pb_msgdesc_t meshtastic_Group_msg; extern const pb_msgdesc_t meshtastic_Status_msg; extern const pb_msgdesc_t meshtastic_Contact_msg; extern const pb_msgdesc_t meshtastic_PLI_msg; +extern const pb_msgdesc_t meshtastic_AircraftTrack_msg; +extern const pb_msgdesc_t meshtastic_CotGeoPoint_msg; +extern const pb_msgdesc_t meshtastic_DrawnShape_msg; +extern const pb_msgdesc_t meshtastic_Marker_msg; +extern const pb_msgdesc_t meshtastic_RangeAndBearing_msg; +extern const pb_msgdesc_t meshtastic_Route_msg; +extern const pb_msgdesc_t meshtastic_Route_Link_msg; +extern const pb_msgdesc_t meshtastic_CasevacReport_msg; +extern const pb_msgdesc_t meshtastic_EmergencyAlert_msg; +extern const pb_msgdesc_t meshtastic_TaskRequest_msg; +extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_TAKPacket_fields &meshtastic_TAKPacket_msg @@ -269,15 +1535,37 @@ extern const pb_msgdesc_t meshtastic_PLI_msg; #define meshtastic_Status_fields &meshtastic_Status_msg #define meshtastic_Contact_fields &meshtastic_Contact_msg #define meshtastic_PLI_fields &meshtastic_PLI_msg +#define meshtastic_AircraftTrack_fields &meshtastic_AircraftTrack_msg +#define meshtastic_CotGeoPoint_fields &meshtastic_CotGeoPoint_msg +#define meshtastic_DrawnShape_fields &meshtastic_DrawnShape_msg +#define meshtastic_Marker_fields &meshtastic_Marker_msg +#define meshtastic_RangeAndBearing_fields &meshtastic_RangeAndBearing_msg +#define meshtastic_Route_fields &meshtastic_Route_msg +#define meshtastic_Route_Link_fields &meshtastic_Route_Link_msg +#define meshtastic_CasevacReport_fields &meshtastic_CasevacReport_msg +#define meshtastic_EmergencyAlert_fields &meshtastic_EmergencyAlert_msg +#define meshtastic_TaskRequest_fields &meshtastic_TaskRequest_msg +#define meshtastic_TAKPacketV2_fields &meshtastic_TAKPacketV2_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size +/* meshtastic_TAKPacketV2_size depends on runtime parameters */ +#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_Route_size +#define meshtastic_AircraftTrack_size 134 +#define meshtastic_CasevacReport_size 70 #define meshtastic_Contact_size 242 -#define meshtastic_GeoChat_size 444 +#define meshtastic_CotGeoPoint_size 12 +#define meshtastic_DrawnShape_size 553 +#define meshtastic_EmergencyAlert_size 100 +#define meshtastic_GeoChat_size 495 #define meshtastic_Group_size 4 +#define meshtastic_Marker_size 191 #define meshtastic_PLI_size 31 +#define meshtastic_RangeAndBearing_size 84 +#define meshtastic_Route_Link_size 83 +#define meshtastic_Route_size 1379 #define meshtastic_Status_size 3 -#define meshtastic_TAKPacket_size 705 +#define meshtastic_TAKPacket_size 756 +#define meshtastic_TaskRequest_size 132 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index d342d926ee0..11f3d9f01c2 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -308,8 +308,13 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126, /* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */ meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127, - /* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, GPS, button, buzzer, and sensors. */ + /* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, + GPS, button, buzzer, and sensors. */ meshtastic_HardwareModel_TRACKER_T1000_E_PRO = 128, + /* Elecrow ThinkNode M7, M8 and M9 */ + meshtastic_HardwareModel_THINKNODE_M7 = 129, + meshtastic_HardwareModel_THINKNODE_M8 = 130, + meshtastic_HardwareModel_THINKNODE_M9 = 131, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -885,6 +890,10 @@ typedef struct _meshtastic_RemoteShell { uint32_t rows; /* Bit flags for protocol extensions. */ uint32_t flags; + /* Terminal size rows used for OPEN/RESIZE signaling. */ + uint32_t last_tx_seq; + /* Bit flags for protocol extensions. */ + uint32_t last_rx_seq; } meshtastic_RemoteShell; /* Waypoint message, used to share arbitrary locations across the mesh */ @@ -1507,7 +1516,7 @@ extern "C" { #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} -#define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} +#define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} @@ -1541,7 +1550,7 @@ extern "C" { #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} -#define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0} +#define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} @@ -1639,6 +1648,8 @@ extern "C" { #define meshtastic_RemoteShell_cols_tag 6 #define meshtastic_RemoteShell_rows_tag 7 #define meshtastic_RemoteShell_flags_tag 8 +#define meshtastic_RemoteShell_last_tx_seq_tag 9 +#define meshtastic_RemoteShell_last_rx_seq_tag 10 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1879,7 +1890,9 @@ X(a, STATIC, SINGULAR, UINT32, ack_seq, 4) \ X(a, STATIC, SINGULAR, BYTES, payload, 5) \ X(a, STATIC, SINGULAR, UINT32, cols, 6) \ X(a, STATIC, SINGULAR, UINT32, rows, 7) \ -X(a, STATIC, SINGULAR, UINT32, flags, 8) +X(a, STATIC, SINGULAR, UINT32, flags, 8) \ +X(a, STATIC, SINGULAR, UINT32, last_tx_seq, 9) \ +X(a, STATIC, SINGULAR, UINT32, last_rx_seq, 10) #define meshtastic_RemoteShell_CALLBACK NULL #define meshtastic_RemoteShell_DEFAULT NULL @@ -2257,7 +2270,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 -#define meshtastic_RemoteShell_size 241 +#define meshtastic_RemoteShell_size 253 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 #define meshtastic_StatusMessage_size 81 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 7d9e8597ae7..494ef4a5497 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -76,7 +76,7 @@ typedef enum _meshtastic_PortNum { meshtastic_PortNum_ALERT_APP = 11, /* Module/port for handling key verification requests. */ meshtastic_PortNum_KEY_VERIFICATION_APP = 12, - /* Module/port for handling key verification requests. */ + /* Module/port for handling primitive remote shell access. */ meshtastic_PortNum_REMOTE_SHELL_APP = 13, /* Provides a 'ping' service that replies to any packet it receives. Also serves as a small example module. @@ -152,6 +152,10 @@ typedef enum _meshtastic_PortNum { arbitrary telemetry over meshtastic that is not covered by telemetry.proto ENCODING: CayenneLLP */ meshtastic_PortNum_CAYENNE_APP = 77, + /* ATAK Plugin V2 + Portnum for payloads from the official Meshtastic ATAK plugin using + TAKPacketV2 with zstd dictionary compression. */ + meshtastic_PortNum_ATAK_PLUGIN_V2 = 78, /* GroupAlarm integration Used for transporting GroupAlarm-related messages between Meshtastic nodes and companion applications/services. */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index f48d946a4ae..8c0fdd56314 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -113,7 +113,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* SCD30 CO2, humidity, temperature sensor */ meshtastic_TelemetrySensorType_SCD30 = 49, /* SHT family of sensors for temperature and humidity */ - meshtastic_TelemetrySensorType_SHTXX = 50 + meshtastic_TelemetrySensorType_SHTXX = 50, + /* DS248X Bridge for one-wire temperature sensors */ + meshtastic_TelemetrySensorType_DS248X = 51 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -206,6 +208,9 @@ typedef struct _meshtastic_EnvironmentMetrics { /* Soil temperature measured (*C) */ bool has_soil_temperature; float soil_temperature; + /* One-wire temperature (*C) */ + pb_size_t one_wire_temperature_count; + float one_wire_temperature[8]; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -491,8 +496,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHTXX -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHTXX+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DS248X +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DS248X+1)) @@ -508,7 +513,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -519,7 +524,7 @@ extern "C" { #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -558,6 +563,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_rainfall_24h_tag 20 #define meshtastic_EnvironmentMetrics_soil_moisture_tag 21 #define meshtastic_EnvironmentMetrics_soil_temperature_tag 22 +#define meshtastic_EnvironmentMetrics_one_wire_temperature_tag 23 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -683,7 +689,8 @@ X(a, STATIC, OPTIONAL, FLOAT, radiation, 18) \ X(a, STATIC, OPTIONAL, FLOAT, rainfall_1h, 19) \ X(a, STATIC, OPTIONAL, FLOAT, rainfall_24h, 20) \ X(a, STATIC, OPTIONAL, UINT32, soil_moisture, 21) \ -X(a, STATIC, OPTIONAL, FLOAT, soil_temperature, 22) +X(a, STATIC, OPTIONAL, FLOAT, soil_temperature, 22) \ +X(a, STATIC, REPEATED, FLOAT, one_wire_temperature, 23) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -852,7 +859,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 150 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 113 +#define meshtastic_EnvironmentMetrics_size 161 #define meshtastic_HealthMetrics_size 11 #define meshtastic_HostMetrics_size 264 #define meshtastic_LocalStats_size 87 diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 8b844f179e3..48154f370a1 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -29,38 +29,6 @@ namespace constexpr uint16_t PTY_COLS_DEFAULT = 120; constexpr uint16_t PTY_ROWS_DEFAULT = 40; constexpr size_t MAX_MESSAGE_SIZE = 200; -constexpr size_t REPLAY_REQUEST_SIZE = sizeof(uint32_t); -constexpr size_t HEARTBEAT_STATUS_SIZE = sizeof(uint32_t) * 2; - -void encodeUint32BE(uint8_t *dest, uint32_t value) -{ - dest[0] = static_cast((value >> 24) & 0xff); - dest[1] = static_cast((value >> 16) & 0xff); - dest[2] = static_cast((value >> 8) & 0xff); - dest[3] = static_cast(value & 0xff); -} - -uint32_t decodeUint32BE(const uint8_t *src) -{ - return (static_cast(src[0]) << 24) | (static_cast(src[1]) << 16) | (static_cast(src[2]) << 8) | - static_cast(src[3]); -} - -void encodeHeartbeatStatus(uint8_t *dest, uint32_t lastTxSeq, uint32_t lastRxSeq) -{ - encodeUint32BE(dest, lastTxSeq); - encodeUint32BE(dest + sizeof(uint32_t), lastRxSeq); -} - -bool decodeHeartbeatStatus(const uint8_t *src, size_t len, uint32_t &lastTxSeq, uint32_t &lastRxSeq) -{ - if (len < HEARTBEAT_STATUS_SIZE) { - return false; - } - lastTxSeq = decodeUint32BE(src); - lastRxSeq = decodeUint32BE(src + sizeof(uint32_t)); - return true; -} } // namespace DMShellModule::DMShellModule() @@ -71,7 +39,7 @@ DMShellModule::DMShellModule() ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) { - DMShellFrame frame; + meshtastic_RemoteShell frame = meshtastic_RemoteShell_init_zero; if (!mp.pki_encrypted) { LOG_WARN("DMShell: ignoring packet without PKI from 0x%x", mp.from); return ProcessMessage::STOP; @@ -83,8 +51,8 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } if (frame.op == meshtastic_RemoteShell_OpCode_ACK) { - if (session.active && frame.sessionId == session.sessionId && getFrom(&mp) == session.peer) { - handleAckFrame(frame); + if (session.active && frame.session_id == session.sessionId && getFrom(&mp) == session.peer && frame.last_rx_seq > 0) { + resendFramesFrom(frame.last_rx_seq); } return ProcessMessage::CONTINUE; } @@ -101,19 +69,15 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } if (frame.op == meshtastic_RemoteShell_OpCode_OPEN) { - LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.sessionId); + LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.session_id); if (!openSession(mp, frame)) { - const char *msg = "open_failed"; - sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_RemoteShell_OpCode_ERROR, frame.sessionId, 0, - reinterpret_cast(msg), strlen(msg), 0, 0, frame.seq); + sendError("open_failed", getFrom(&mp)); } return ProcessMessage::STOP; } - if (!session.active || frame.sessionId != session.sessionId || getFrom(&mp) != session.peer) { - const char *msg = "invalid_session"; - sendFrameToPeer(getFrom(&mp), mp.channel, meshtastic_RemoteShell_OpCode_ERROR, frame.sessionId, 0, - reinterpret_cast(msg), strlen(msg), 0, 0, frame.seq); + if (!session.active || frame.session_id != session.sessionId || getFrom(&mp) != session.peer) { + sendError("invalid_session", getFrom(&mp)); return ProcessMessage::STOP; } @@ -132,9 +96,19 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead); - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OUTPUT, session.sessionId, - session.nextTxSeq++, outBuf, static_cast(bytesRead), 0, 0, session.lastAckedRxSeq); - + meshtastic_RemoteShell frame = { + .op = meshtastic_RemoteShell_OpCode_OUTPUT, + .session_id = session.sessionId, + .seq = session.nextTxSeq++, + .ack_seq = session.lastAckedRxSeq, + .cols = 0, + .rows = 0, + .flags = 0, + }; + assert(bytesRead <= sizeof(frame.payload.bytes)); + memcpy(frame.payload.bytes, outBuf, bytesRead); + frame.payload.size = bytesRead; + sendFrameToPeer(session.peer, frame, true); session.lastActivityMs = millis(); } } @@ -150,19 +124,29 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } break; case meshtastic_RemoteShell_OpCode_PING: { - uint32_t peerLastTxSeq = 0; - uint32_t peerLastRxSeq = frame.ackSeq; - if (decodeHeartbeatStatus(frame.payload, frame.payloadLen, peerLastTxSeq, peerLastRxSeq)) { - const uint32_t nextMissingForPeer = peerLastRxSeq + 1; - if (nextMissingForPeer > 0 && nextMissingForPeer < session.nextTxSeq) { - resendFramesFrom(nextMissingForPeer); - } + uint32_t peerLastRxSeq = frame.ack_seq; + if (frame.last_rx_seq > 0) { + peerLastRxSeq = frame.last_rx_seq; + } + + const uint32_t nextMissingForPeer = peerLastRxSeq + 1; + if (nextMissingForPeer > 0 && nextMissingForPeer < session.nextTxSeq) { + resendFramesFrom(nextMissingForPeer); } - uint8_t heartbeatPayload[HEARTBEAT_STATUS_SIZE] = {0}; - encodeHeartbeatStatus(heartbeatPayload, session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0, session.lastAckedRxSeq); - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_PONG, session.sessionId, session.nextTxSeq++, - heartbeatPayload, sizeof(heartbeatPayload), 0, 0, session.lastAckedRxSeq); + meshtastic_RemoteShell frame = { + .op = meshtastic_RemoteShell_OpCode_PONG, + .session_id = session.sessionId, + .seq = session.nextTxSeq++, + .ack_seq = session.lastAckedRxSeq, + .cols = 0, + .rows = 0, + .flags = 0, + .last_tx_seq = session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0, + .last_rx_seq = session.lastAckedRxSeq, + }; + frame.payload.size = 0; + sendFrameToPeer(session.peer, frame, true); break; } case meshtastic_RemoteShell_OpCode_CLOSE: @@ -203,8 +187,21 @@ int32_t DMShellModule::runOnce() const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf)); if (bytesRead > 0) { LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead); - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OUTPUT, session.sessionId, - session.nextTxSeq++, outBuf, static_cast(bytesRead), 0, 0, session.lastAckedRxSeq); + + meshtastic_RemoteShell frame = { + .op = meshtastic_RemoteShell_OpCode_OUTPUT, + .session_id = session.sessionId, + .seq = session.nextTxSeq++, + .ack_seq = session.lastAckedRxSeq, + .cols = 0, + .rows = 0, + .flags = 0, + }; + assert(bytesRead <= sizeof(frame.payload.bytes)); + memcpy(frame.payload.bytes, outBuf, bytesRead); + frame.payload.size = bytesRead; + sendFrameToPeer(session.peer, frame, true); + session.lastActivityMs = millis(); // continue; // do we want to ack every data message, and only send the next on ack? @@ -229,30 +226,19 @@ int32_t DMShellModule::runOnce() return 100; } -bool DMShellModule::parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &outFrame) +bool DMShellModule::parseFrame(const meshtastic_MeshPacket &mp, meshtastic_RemoteShell &outFrame) { if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { return false; } - meshtastic_RemoteShell decodedMsg = meshtastic_RemoteShell_init_zero; - if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_RemoteShell_fields, &decodedMsg)) { + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_RemoteShell_fields, &outFrame)) { LOG_INFO("Received a DMShell message"); } else { LOG_ERROR("Error decoding DMShell message!"); return false; } - outFrame.op = decodedMsg.op; - outFrame.sessionId = decodedMsg.session_id; - outFrame.seq = decodedMsg.seq; - outFrame.ackSeq = decodedMsg.ack_seq; - outFrame.cols = decodedMsg.cols; - outFrame.rows = decodedMsg.rows; - outFrame.flags = decodedMsg.flags; - outFrame.payloadLen = decodedMsg.payload.size; - memcpy(outFrame.payload, decodedMsg.payload.bytes, outFrame.payloadLen); - return true; } @@ -279,7 +265,7 @@ bool DMShellModule::isAuthorizedPacket(const meshtastic_MeshPacket &mp) const return false; } -bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFrame &frame) +bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const meshtastic_RemoteShell &frame) { if (session.active) { closeSession("preempted", false); @@ -318,7 +304,7 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr } session.active = true; - session.sessionId = (frame.sessionId != 0) ? frame.sessionId : static_cast(random(1, 0x7fffffff)); + session.sessionId = (frame.session_id != 0) ? frame.session_id : static_cast(random(1, 0x7fffffff)); session.peer = getFrom(&mp); session.channel = mp.channel; session.masterFd = masterFd; @@ -329,24 +315,32 @@ bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const DMShellFr session.highestSeenRxSeq = frame.seq; session.lastActivityMs = millis(); - uint8_t payload[sizeof(uint32_t)] = {0}; - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_OPEN_OK, session.sessionId, session.nextTxSeq++, - payload, sizeof(payload), ws.ws_col, ws.ws_row, frame.seq); + meshtastic_RemoteShell newFrame = { + .op = meshtastic_RemoteShell_OpCode_OPEN_OK, + .session_id = session.sessionId, + .seq = session.nextTxSeq++, + .ack_seq = frame.seq, + .cols = ws.ws_col, + .rows = ws.ws_row, + .flags = 0, + }; + newFrame.payload.size = 0; + sendFrameToPeer(session.peer, newFrame, true); LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid); return true; } -bool DMShellModule::writeSessionInput(const DMShellFrame &frame) +bool DMShellModule::writeSessionInput(const meshtastic_RemoteShell &frame) { if (session.masterFd < 0) { return false; } - if (frame.payloadLen == 0) { + if (frame.payload.size == 0) { return true; } - const ssize_t bytesWritten = write(session.masterFd, frame.payload, frame.payloadLen); + const ssize_t bytesWritten = write(session.masterFd, frame.payload.bytes, frame.payload.size); return bytesWritten >= 0; } @@ -358,8 +352,19 @@ void DMShellModule::closeSession(const char *reason, bool notifyPeer) if (notifyPeer) { const size_t reasonLen = strnlen(reason, 256); - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_CLOSED, session.sessionId, - session.nextTxSeq++, reinterpret_cast(reason), reasonLen, 0, 0, session.lastAckedRxSeq); + meshtastic_RemoteShell frame = { + .op = meshtastic_RemoteShell_OpCode_CLOSED, + .session_id = session.sessionId, + .seq = session.nextTxSeq++, + .ack_seq = session.lastAckedRxSeq, + .cols = 0, + .rows = 0, + .flags = 0, + }; + assert(reasonLen <= sizeof(frame.payload.bytes)); + memcpy(frame.payload.bytes, reason, reasonLen); + frame.payload.size = reasonLen; + sendFrameToPeer(session.peer, frame, true); } if (session.masterFd >= 0) { @@ -424,25 +429,24 @@ void DMShellModule::processPendingChildReap() } } -void DMShellModule::rememberSentFrame(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, - size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags) +void DMShellModule::rememberSentFrame(meshtastic_RemoteShell frame) { - if (seq == 0 || op == meshtastic_RemoteShell_OpCode_ACK) { + if (frame.seq == 0 || frame.op == meshtastic_RemoteShell_OpCode_ACK) { return; } auto &entry = session.txHistory[session.txHistoryNext]; entry.valid = true; - entry.op = op; - entry.sessionId = sessionId; - entry.seq = seq; - entry.ackSeq = ackSeq; - entry.cols = cols; - entry.rows = rows; - entry.flags = flags; - entry.payloadLen = payloadLen; - if (payload && payloadLen > 0) { - memcpy(entry.payload, payload, payloadLen); + entry.op = frame.op; + entry.sessionId = frame.session_id; + entry.seq = frame.seq; + entry.ackSeq = frame.ack_seq; + entry.cols = frame.cols; + entry.rows = frame.rows; + entry.flags = frame.flags; + entry.payloadLen = frame.payload.size; + if (frame.payload.size > 0) { + memcpy(entry.payload, frame.payload.bytes, frame.payload.size); } session.txHistoryNext = (session.txHistoryNext + 1) % session.txHistory.size(); @@ -469,36 +473,41 @@ void DMShellModule::resendFramesFrom(uint32_t startSeq) } LOG_INFO("DMShell: replaying frame seq=%u op=%d", match->seq, match->op); - sendFrameToPeer(session.peer, session.channel, match->op, match->sessionId, match->seq, match->payload, match->payloadLen, - match->cols, match->rows, match->ackSeq, match->flags, false); + meshtastic_RemoteShell frame = { + .op = match->op, + .session_id = match->sessionId, + .seq = match->seq, + .ack_seq = match->ackSeq, + .cols = match->cols, + .rows = match->rows, + .flags = match->flags, + }; + assert(match->payloadLen <= sizeof(frame.payload.bytes)); + memcpy(frame.payload.bytes, match->payload, match->payloadLen); + frame.payload.size = match->payloadLen; + sendFrameToPeer(session.peer, frame, false); } void DMShellModule::sendAck(uint32_t replayFromSeq) { - uint8_t payload[REPLAY_REQUEST_SIZE] = {0}; - const uint8_t *payloadPtr = nullptr; - size_t payloadLen = 0; if (replayFromSeq > 0) { - encodeUint32BE(payload, replayFromSeq); - payloadPtr = payload; - payloadLen = sizeof(payload); LOG_WARN("DMShell: requesting replay from seq=%u", replayFromSeq); } - - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_ACK, session.sessionId, 0, nullptr, 0, 0, 0, - session.lastAckedRxSeq, 0, false); -} - -bool DMShellModule::handleAckFrame(const DMShellFrame &frame) -{ - if (frame.payloadLen >= REPLAY_REQUEST_SIZE) { - const uint32_t replayFromSeq = decodeUint32BE(frame.payload); - resendFramesFrom(replayFromSeq); - } - return true; + meshtastic_RemoteShell frame = { + .op = meshtastic_RemoteShell_OpCode_ACK, + .session_id = session.sessionId, + .seq = 0, + .ack_seq = session.lastAckedRxSeq, + .cols = 0, + .rows = 0, + .flags = 0, + .last_rx_seq = replayFromSeq, + }; + frame.payload.size = 0; + sendFrameToPeer(session.peer, frame, false); } -bool DMShellModule::shouldProcessIncomingFrame(const DMShellFrame &frame) +bool DMShellModule::shouldProcessIncomingFrame(const meshtastic_RemoteShell &frame) { if (frame.seq == 0) { return true; @@ -534,65 +543,53 @@ bool DMShellModule::shouldProcessIncomingFrame(const DMShellFrame &frame) return true; } -void DMShellModule::sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_RemoteShell_OpCode op, uint32_t sessionId, - uint32_t seq, const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, - uint32_t ackSeq, uint32_t flags, bool remember) +void DMShellModule::sendFrameToPeer(NodeNum peer, meshtastic_RemoteShell frame, bool remember) { - meshtastic_MeshPacket *packet = buildFramePacket(op, sessionId, seq, payload, payloadLen, cols, rows, ackSeq, flags); + meshtastic_MeshPacket *packet = allocDataPacket(); if (!packet) { return; } + LOG_WARN("DMShell: building packet op=%u session=0x%x seq=%u payloadLen=%zu", frame.op, frame.session_id, frame.seq, + frame.payload.size); + const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes), + meshtastic_RemoteShell_fields, &frame); + if (encoded == 0) { + return; + } + packet->decoded.payload.size = encoded; if (remember) { - rememberSentFrame(op, sessionId, seq, payload, payloadLen, cols, rows, ackSeq, flags); + rememberSentFrame(frame); } packet->to = peer; - packet->channel = channel; + packet->channel = 0; packet->want_ack = false; packet->pki_encrypted = true; packet->priority = meshtastic_MeshPacket_Priority_RELIABLE; service->sendToMesh(packet); } -void DMShellModule::sendError(const char *message) +void DMShellModule::sendError(const char *message, NodeNum peer) { const size_t len = strnlen(message, MAX_MESSAGE_SIZE); - sendFrameToPeer(session.peer, session.channel, meshtastic_RemoteShell_OpCode_ERROR, session.sessionId, session.nextTxSeq++, - reinterpret_cast(message), len, 0, 0, session.lastAckedRxSeq); -} - -meshtastic_MeshPacket *DMShellModule::buildFramePacket(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, - const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, - uint32_t ackSeq, uint32_t flags) -{ - meshtastic_RemoteShell frame = meshtastic_RemoteShell_init_zero; - frame.op = op; - frame.session_id = sessionId; - frame.seq = seq; - frame.ack_seq = ackSeq; - frame.cols = cols; - frame.rows = rows; - frame.flags = flags; - - if (payload && payloadLen > 0) { - assert(payloadLen <= sizeof(frame.payload.bytes)); - memcpy(frame.payload.bytes, payload, payloadLen); - frame.payload.size = payloadLen; - } - - meshtastic_MeshPacket *packet = allocDataPacket(); - if (!packet) { - return nullptr; - } - LOG_WARN("DMShell: building packet op=%u session=0x%x seq=%u payloadLen=%zu", op, sessionId, seq, payloadLen); - const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes), - meshtastic_RemoteShell_fields, &frame); - if (encoded == 0) { - return nullptr; - } - packet->decoded.payload.size = encoded; - return packet; + meshtastic_RemoteShell frame = { + .op = meshtastic_RemoteShell_OpCode_ERROR, + .session_id = session.sessionId, + .seq = session.nextTxSeq++, + .ack_seq = session.lastAckedRxSeq, + .cols = 0, + .rows = 0, + .flags = 0, + }; + if (message && len > 0) { + assert(len <= sizeof(frame.payload.bytes)); + memcpy(frame.payload.bytes, message, len); + frame.payload.size = len; + } + if (peer == 0) { + peer = session.peer; + } + sendFrameToPeer(peer, frame, true); } - #endif \ No newline at end of file diff --git a/src/modules/DMShell.h b/src/modules/DMShell.h index d03a836d2c6..51e4e710edd 100644 --- a/src/modules/DMShell.h +++ b/src/modules/DMShell.h @@ -12,18 +12,6 @@ #if defined(ARCH_PORTDUINO) -struct DMShellFrame { - meshtastic_RemoteShell_OpCode op = meshtastic_RemoteShell_OpCode_ERROR; - uint32_t sessionId = 0; - uint32_t seq = 0; - uint32_t ackSeq = 0; - uint32_t cols = 0; - uint32_t rows = 0; - uint32_t flags = 0; - uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; - size_t payloadLen = 0; -}; - struct DMShellSession { bool active = false; uint32_t sessionId = 0; @@ -68,28 +56,20 @@ class DMShellModule : private concurrency::OSThread, public SinglePortModule DMShellSession session; pid_t pendingChildPid = -1; - bool parseFrame(const meshtastic_MeshPacket &mp, DMShellFrame &outFrame); + bool parseFrame(const meshtastic_MeshPacket &mp, meshtastic_RemoteShell &outFrame); bool isAuthorizedPacket(const meshtastic_MeshPacket &mp) const; - bool openSession(const meshtastic_MeshPacket &mp, const DMShellFrame &frame); - bool handleAckFrame(const DMShellFrame &frame); - bool shouldProcessIncomingFrame(const DMShellFrame &frame); - bool writeSessionInput(const DMShellFrame &frame); + bool openSession(const meshtastic_MeshPacket &mp, const meshtastic_RemoteShell &frame); + bool shouldProcessIncomingFrame(const meshtastic_RemoteShell &frame); + bool writeSessionInput(const meshtastic_RemoteShell &frame); void closeSession(const char *reason, bool notifyPeer); void reapChildIfExited(); void processPendingChildReap(); - void rememberSentFrame(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload, - size_t payloadLen, uint32_t cols, uint32_t rows, uint32_t ackSeq, uint32_t flags); - void pruneSentFrames(uint32_t ackSeq); + void rememberSentFrame(meshtastic_RemoteShell frame); void resendFramesFrom(uint32_t startSeq); void sendAck(uint32_t replayFromSeq = 0); - void sendFrameToPeer(NodeNum peer, uint8_t channel, meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, - const uint8_t *payload, size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0, uint32_t ackSeq = 0, - uint32_t flags = 0, bool remember = true); - void sendError(const char *message); - meshtastic_MeshPacket *buildFramePacket(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, - const uint8_t *payload, size_t payloadLen, uint32_t cols, uint32_t rows, - uint32_t ackSeq, uint32_t flags); + void sendFrameToPeer(NodeNum peer, meshtastic_RemoteShell frame, bool remember = true); + void sendError(const char *message, NodeNum peer = 0); }; extern DMShellModule *dmShellModule; From dc3947117e8b181dadf6e498570f8d93a63e12e3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 14 Apr 2026 18:47:53 -0500 Subject: [PATCH 21/22] Make new protobuf value consistent --- bin/dmshell_client.py | 4 ++-- src/modules/DMShell.cpp | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/dmshell_client.py b/bin/dmshell_client.py index c955a83edbb..816a60b0937 100644 --- a/bin/dmshell_client.py +++ b/bin/dmshell_client.py @@ -556,7 +556,7 @@ def send_ack_frame(transport, state: SessionState, replay_from: Optional[int] = state, state.pb2.mesh.RemoteShell.ACK, seq=0, - last_rx_seq=0 if replay_from is None else replay_from, + last_rx_seq=0 if replay_from is None else replay_from - 1, remember=False, ) @@ -675,7 +675,7 @@ def handle_in_order_shell(shell) -> bool: #state.prune_sent_frames(shell.ack_seq) if shell.op == state.pb2.mesh.RemoteShell.ACK: #state.event_queue.put("peer requested replay") - replay_from = shell.last_rx_seq if shell.last_rx_seq > 0 else None + replay_from = shell.last_rx_seq + 1 if shell.last_rx_seq > 0 else None if replay_from is not None: #state.event_queue.put(f"peer requested replay from seq={replay_from}") replay_frames_from(transport, state, replay_from) diff --git a/src/modules/DMShell.cpp b/src/modules/DMShell.cpp index 48154f370a1..b2cf6c0d933 100644 --- a/src/modules/DMShell.cpp +++ b/src/modules/DMShell.cpp @@ -52,7 +52,7 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) if (frame.op == meshtastic_RemoteShell_OpCode_ACK) { if (session.active && frame.session_id == session.sessionId && getFrom(&mp) == session.peer && frame.last_rx_seq > 0) { - resendFramesFrom(frame.last_rx_seq); + resendFramesFrom(frame.last_rx_seq + 1); } return ProcessMessage::CONTINUE; } @@ -77,6 +77,13 @@ ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp) } if (!session.active || frame.session_id != session.sessionId || getFrom(&mp) != session.peer) { + if (!session.active) { + LOG_WARN("DMShell: no active session, rejecting op %d from 0x%x", frame.op, mp.from); + } else { + LOG_WARN("DMShell: session ID mismatch (got 0x%x expected 0x%x) or peer mismatch (got 0x%x expected 0x%x), rejecting " + "op %d", + frame.session_id, session.sessionId, mp.from, session.peer, frame.op); + } sendError("invalid_session", getFrom(&mp)); return ProcessMessage::STOP; } @@ -501,7 +508,7 @@ void DMShellModule::sendAck(uint32_t replayFromSeq) .cols = 0, .rows = 0, .flags = 0, - .last_rx_seq = replayFromSeq, + .last_rx_seq = replayFromSeq - 1, }; frame.payload.size = 0; sendFrameToPeer(session.peer, frame, false); From 3c83e01d0e04ce30ac4ff99b1e5d0843d1ecca06 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:28:53 -0500 Subject: [PATCH 22/22] Update protobufs (#10188) Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com> --- src/mesh/generated/meshtastic/atak.pb.cpp | 51 - src/mesh/generated/meshtastic/atak.pb.h | 928 +------------------ src/mesh/generated/meshtastic/mesh.pb.cpp | 5 - src/mesh/generated/meshtastic/mesh.pb.h | 88 +- src/mesh/generated/meshtastic/portnums.pb.h | 2 - src/mesh/generated/meshtastic/telemetry.pb.h | 21 +- 6 files changed, 24 insertions(+), 1071 deletions(-) diff --git a/src/mesh/generated/meshtastic/atak.pb.cpp b/src/mesh/generated/meshtastic/atak.pb.cpp index 3f1adea5389..bbafa33e2c7 100644 --- a/src/mesh/generated/meshtastic/atak.pb.cpp +++ b/src/mesh/generated/meshtastic/atak.pb.cpp @@ -27,33 +27,6 @@ PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO) PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO) -PB_BIND(meshtastic_CotGeoPoint, meshtastic_CotGeoPoint, AUTO) - - -PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, 2) - - -PB_BIND(meshtastic_Marker, meshtastic_Marker, AUTO) - - -PB_BIND(meshtastic_RangeAndBearing, meshtastic_RangeAndBearing, AUTO) - - -PB_BIND(meshtastic_Route, meshtastic_Route, 2) - - -PB_BIND(meshtastic_Route_Link, meshtastic_Route_Link, AUTO) - - -PB_BIND(meshtastic_CasevacReport, meshtastic_CasevacReport, AUTO) - - -PB_BIND(meshtastic_EmergencyAlert, meshtastic_EmergencyAlert, AUTO) - - -PB_BIND(meshtastic_TaskRequest, meshtastic_TaskRequest, AUTO) - - PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2) @@ -68,27 +41,3 @@ PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2) - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h index d25fa65434b..c12b042f444 100644 --- a/src/mesh/generated/meshtastic/atak.pb.h +++ b/src/mesh/generated/meshtastic/atak.pb.h @@ -241,91 +241,7 @@ typedef enum _meshtastic_CotType { /* b-f-t-r: File transfer request */ meshtastic_CotType_CotType_b_f_t_r = 74, /* b-f-t-a: File transfer acknowledgment */ - meshtastic_CotType_CotType_b_f_t_a = 75, - /* u-d-f-m: Freehand telestration / annotation. Anchor at event point, - geometry carried via DrawnShape.vertices. May be truncated to - MAX_VERTICES by the sender. */ - meshtastic_CotType_CotType_u_d_f_m = 76, - /* u-d-p: Closed polygon. Geometry carried via DrawnShape.vertices, - implicitly closed (receiver duplicates first vertex as needed). */ - meshtastic_CotType_CotType_u_d_p = 77, - /* b-m-p-s-m: Spot map marker (colored dot at a point of interest). */ - meshtastic_CotType_CotType_b_m_p_s_m = 78, - /* b-m-p-c: Checkpoint (intermediate route control point). */ - meshtastic_CotType_CotType_b_m_p_c = 79, - /* u-r-b-c-c: Ranging circle (range rings centered on the event point). */ - meshtastic_CotType_CotType_u_r_b_c_c = 80, - /* u-r-b-bullseye: Bullseye with configurable range rings and bearing - reference (magnetic / true / grid). */ - meshtastic_CotType_CotType_u_r_b_bullseye = 81, - /* a-f-G-E-V-A: Friendly armored vehicle, user-selectable self PLI. */ - meshtastic_CotType_CotType_a_f_G_E_V_A = 82, - /* a-n-A: Neutral aircraft (friendly/hostile/unknown already present). */ - meshtastic_CotType_CotType_a_n_A = 83, - /* --- 2525 quick-drop: artillery (4) ---------------------------------- */ - meshtastic_CotType_CotType_a_u_G_U_C_F = 84, - meshtastic_CotType_CotType_a_n_G_U_C_F = 85, - meshtastic_CotType_CotType_a_h_G_U_C_F = 86, - meshtastic_CotType_CotType_a_f_G_U_C_F = 87, - /* --- 2525 quick-drop: building (4) ----------------------------------- */ - meshtastic_CotType_CotType_a_u_G_I = 88, - meshtastic_CotType_CotType_a_n_G_I = 89, - meshtastic_CotType_CotType_a_h_G_I = 90, - meshtastic_CotType_CotType_a_f_G_I = 91, - /* --- 2525 quick-drop: mine (4) --------------------------------------- */ - meshtastic_CotType_CotType_a_u_G_E_X_M = 92, - meshtastic_CotType_CotType_a_n_G_E_X_M = 93, - meshtastic_CotType_CotType_a_h_G_E_X_M = 94, - meshtastic_CotType_CotType_a_f_G_E_X_M = 95, - /* --- 2525 quick-drop: ship (3; a-f-S already at 17) ------------------ */ - meshtastic_CotType_CotType_a_u_S = 96, - meshtastic_CotType_CotType_a_n_S = 97, - meshtastic_CotType_CotType_a_h_S = 98, - /* --- 2525 quick-drop: sniper (4) ------------------------------------- */ - meshtastic_CotType_CotType_a_u_G_U_C_I_d = 99, - meshtastic_CotType_CotType_a_n_G_U_C_I_d = 100, - meshtastic_CotType_CotType_a_h_G_U_C_I_d = 101, - meshtastic_CotType_CotType_a_f_G_U_C_I_d = 102, - /* --- 2525 quick-drop: tank (4) --------------------------------------- */ - meshtastic_CotType_CotType_a_u_G_E_V_A_T = 103, - meshtastic_CotType_CotType_a_n_G_E_V_A_T = 104, - meshtastic_CotType_CotType_a_h_G_E_V_A_T = 105, - meshtastic_CotType_CotType_a_f_G_E_V_A_T = 106, - /* --- 2525 quick-drop: troops (3; a-f-G-U-C-I already at 2) ----------- */ - meshtastic_CotType_CotType_a_u_G_U_C_I = 107, - meshtastic_CotType_CotType_a_n_G_U_C_I = 108, - meshtastic_CotType_CotType_a_h_G_U_C_I = 109, - /* --- 2525 quick-drop: generic vehicle (3; a-u-G-E-V already at 69) --- */ - meshtastic_CotType_CotType_a_n_G_E_V = 110, - meshtastic_CotType_CotType_a_h_G_E_V = 111, - meshtastic_CotType_CotType_a_f_G_E_V = 112, - /* b-m-p-w-GOTO: Go To / bloodhound navigation target. */ - meshtastic_CotType_CotType_b_m_p_w_GOTO = 113, - /* b-m-p-c-ip: Initial point (mission planning). */ - meshtastic_CotType_CotType_b_m_p_c_ip = 114, - /* b-m-p-c-cp: Contact point (mission planning). */ - meshtastic_CotType_CotType_b_m_p_c_cp = 115, - /* b-m-p-s-p-op: Observation post. */ - meshtastic_CotType_CotType_b_m_p_s_p_op = 116, - /* u-d-v: 2D vehicle outline drawn on the map. */ - meshtastic_CotType_CotType_u_d_v = 117, - /* u-d-v-m: 3D vehicle model reference. */ - meshtastic_CotType_CotType_u_d_v_m = 118, - /* u-d-c-e: Non-circular ellipse (circle with distinct major/minor axes). */ - meshtastic_CotType_CotType_u_d_c_e = 119, - /* b-i-x-i: Quick Pic geotagged image marker. The image itself does not - ride on LoRa; this event references the image via iconset metadata. */ - meshtastic_CotType_CotType_b_i_x_i = 120, - /* b-t-f-d: GeoChat delivered receipt. Carried on the existing `chat` - payload_variant via GeoChat.receipt_for_uid + receipt_type. */ - meshtastic_CotType_CotType_b_t_f_d = 121, - /* b-t-f-r: GeoChat read receipt. Same wire slot as b-t-f-d. */ - meshtastic_CotType_CotType_b_t_f_r = 122, - /* b-a-o-c: Custom / generic emergency beacon. */ - meshtastic_CotType_CotType_b_a_o_c = 123, - /* t-s: Task / engage request. Structured payload carried via the new - TaskRequest typed variant. */ - meshtastic_CotType_CotType_t_s = 124 + meshtastic_CotType_CotType_b_f_t_a = 75 } meshtastic_CotType; /* Geopoint and altitude source */ @@ -340,191 +256,10 @@ typedef enum _meshtastic_GeoPointSource { meshtastic_GeoPointSource_GeoPointSource_NETWORK = 3 } meshtastic_GeoPointSource; -/* Receipt discriminator. Set alongside cot_type_id = b-t-f-d (delivered) - or b-t-f-r (read). ReceiptType_None is the default for a normal chat - message (cot_type_id = b-t-f). - - Receivers can detect a receipt by checking receipt_type != ReceiptType_None - without re-parsing the envelope cot_type_id. */ -typedef enum _meshtastic_GeoChat_ReceiptType { - meshtastic_GeoChat_ReceiptType_ReceiptType_None = 0, /* normal chat message */ - meshtastic_GeoChat_ReceiptType_ReceiptType_Delivered = 1, /* b-t-f-d delivered receipt */ - meshtastic_GeoChat_ReceiptType_ReceiptType_Read = 2 /* b-t-f-r read receipt */ -} meshtastic_GeoChat_ReceiptType; - -/* Shape kind discriminator. Drives receiver rendering and also controls - which optional fields below are meaningful. */ -typedef enum _meshtastic_DrawnShape_Kind { - /* Unspecified (do not use on the wire) */ - meshtastic_DrawnShape_Kind_Kind_Unspecified = 0, - /* u-d-c-c: User-drawn circle (uses major/minor/angle, anchor = event point) */ - meshtastic_DrawnShape_Kind_Kind_Circle = 1, - /* u-d-r: User-drawn rectangle (uses vertices = 4 corners) */ - meshtastic_DrawnShape_Kind_Kind_Rectangle = 2, - /* u-d-f: User-drawn polyline (uses vertices, not closed) */ - meshtastic_DrawnShape_Kind_Kind_Freeform = 3, - /* u-d-f-m: Freehand telestration / annotation (uses vertices, may be truncated) */ - meshtastic_DrawnShape_Kind_Kind_Telestration = 4, - /* u-d-p: Closed polygon (uses vertices, implicitly closed) */ - meshtastic_DrawnShape_Kind_Kind_Polygon = 5, - /* u-r-b-c-c: Ranging circle (major/minor/angle, stroke + optional fill) */ - meshtastic_DrawnShape_Kind_Kind_RangingCircle = 6, - /* u-r-b-bullseye: Bullseye ring with range rings and bearing reference */ - meshtastic_DrawnShape_Kind_Kind_Bullseye = 7, - /* u-d-c-e: Ellipse with distinct major/minor axes (same storage as - Kind_Circle — uses major_cm/minor_cm/angle_deg — but receivers - render it as a non-circular ellipse rather than a round circle). */ - meshtastic_DrawnShape_Kind_Kind_Ellipse = 8, - /* u-d-v: 2D vehicle outline drawn on the map. Vertices carry the - outline polygon; receivers draw it as a filled polygon. */ - meshtastic_DrawnShape_Kind_Kind_Vehicle2D = 9, - /* u-d-v-m: 3D vehicle model reference. Same vertex polygon as - Kind_Vehicle2D; receivers that support 3D rendering extrude it. */ - meshtastic_DrawnShape_Kind_Kind_Vehicle3D = 10 -} meshtastic_DrawnShape_Kind; - -/* Explicit stroke/fill/both discriminator. - - ATAK's source XML distinguishes "stroke-only polyline" from "closed shape - with both stroke and fill" by the presence of the element. - Both states can hash to all-zero color fields, so we carry the signal - explicitly. Parser sets this from (sawStrokeColor, sawFillColor) at the - end of parse; builder uses it to decide which of / - to emit in the reconstructed XML. */ -typedef enum _meshtastic_DrawnShape_StyleMode { - /* Unspecified — receiver infers from which color fields are non-zero. */ - meshtastic_DrawnShape_StyleMode_StyleMode_Unspecified = 0, - /* Stroke only. No in the source XML. Used for polylines, - ranging lines, bullseye rings. */ - meshtastic_DrawnShape_StyleMode_StyleMode_StrokeOnly = 1, - /* Fill only. No in the source XML. Rare but valid in - ATAK (solid region with no outline). */ - meshtastic_DrawnShape_StyleMode_StyleMode_FillOnly = 2, - /* Both stroke and fill present. Closed shapes: circle, rectangle, - polygon, ranging circle. */ - meshtastic_DrawnShape_StyleMode_StyleMode_StrokeAndFill = 3 -} meshtastic_DrawnShape_StyleMode; - -/* Marker kind. Used to pick sensible receiver defaults when the CoT type - alone is ambiguous (e.g. a-u-G could be a 2525 symbol or a custom icon - depending on the iconset path). */ -typedef enum _meshtastic_Marker_Kind { - /* Unspecified — fall back to TAKPacketV2.cot_type_id */ - meshtastic_Marker_Kind_Kind_Unspecified = 0, - /* b-m-p-s-m: Spot map marker */ - meshtastic_Marker_Kind_Kind_Spot = 1, - /* b-m-p-w: Route waypoint */ - meshtastic_Marker_Kind_Kind_Waypoint = 2, - /* b-m-p-c: Checkpoint */ - meshtastic_Marker_Kind_Kind_Checkpoint = 3, - /* b-m-p-s-p-i / b-m-p-s-p-loc: Self-position marker */ - meshtastic_Marker_Kind_Kind_SelfPosition = 4, - /* 2525B/C military symbol (iconsetpath = COT_MAPPING_2525B/...) */ - meshtastic_Marker_Kind_Kind_Symbol2525 = 5, - /* COT_MAPPING_SPOTMAP icon (e.g. colored dot) */ - meshtastic_Marker_Kind_Kind_SpotMap = 6, - /* Custom icon set (UUID/GroupName/filename.png) */ - meshtastic_Marker_Kind_Kind_CustomIcon = 7, - /* b-m-p-w-GOTO: Go To / bloodhound navigation waypoint. */ - meshtastic_Marker_Kind_Kind_GoToPoint = 8, - /* b-m-p-c-ip: Initial point (mission planning control point). */ - meshtastic_Marker_Kind_Kind_InitialPoint = 9, - /* b-m-p-c-cp: Contact point (mission planning control point). */ - meshtastic_Marker_Kind_Kind_ContactPoint = 10, - /* b-m-p-s-p-op: Observation post. */ - meshtastic_Marker_Kind_Kind_ObservationPost = 11, - /* b-i-x-i: Quick Pic geotagged image marker. iconset carries the - image reference (local filename or remote URL); the image itself - does not ride on the LoRa wire. */ - meshtastic_Marker_Kind_Kind_ImageMarker = 12 -} meshtastic_Marker_Kind; - -/* Travel method for the route. */ -typedef enum _meshtastic_Route_Method { - /* Unspecified / unknown */ - meshtastic_Route_Method_Method_Unspecified = 0, - /* Driving / vehicle */ - meshtastic_Route_Method_Method_Driving = 1, - /* Walking / foot */ - meshtastic_Route_Method_Method_Walking = 2, - /* Flying */ - meshtastic_Route_Method_Method_Flying = 3, - /* Swimming (individual) */ - meshtastic_Route_Method_Method_Swimming = 4, - /* Watercraft (boat) */ - meshtastic_Route_Method_Method_Watercraft = 5 -} meshtastic_Route_Method; - -/* Route direction (infil = ingress, exfil = egress). */ -typedef enum _meshtastic_Route_Direction { - /* Unspecified */ - meshtastic_Route_Direction_Direction_Unspecified = 0, - /* Infiltration (ingress) */ - meshtastic_Route_Direction_Direction_Infil = 1, - /* Exfiltration (egress) */ - meshtastic_Route_Direction_Direction_Exfil = 2 -} meshtastic_Route_Direction; - -/* Line 3: precedence / urgency. */ -typedef enum _meshtastic_CasevacReport_Precedence { - meshtastic_CasevacReport_Precedence_Precedence_Unspecified = 0, - meshtastic_CasevacReport_Precedence_Precedence_Urgent = 1, /* A - immediate, life-threatening */ - meshtastic_CasevacReport_Precedence_Precedence_UrgentSurgical = 2, /* B - needs surgery */ - meshtastic_CasevacReport_Precedence_Precedence_Priority = 3, /* C - within 4 hours */ - meshtastic_CasevacReport_Precedence_Precedence_Routine = 4, /* D - within 24 hours */ - meshtastic_CasevacReport_Precedence_Precedence_Convenience = 5 /* E - convenience */ -} meshtastic_CasevacReport_Precedence; - -/* Line 7: HLZ marking method. */ -typedef enum _meshtastic_CasevacReport_HlzMarking { - meshtastic_CasevacReport_HlzMarking_HlzMarking_Unspecified = 0, - meshtastic_CasevacReport_HlzMarking_HlzMarking_Panels = 1, - meshtastic_CasevacReport_HlzMarking_HlzMarking_PyroSignal = 2, - meshtastic_CasevacReport_HlzMarking_HlzMarking_Smoke = 3, - meshtastic_CasevacReport_HlzMarking_HlzMarking_None = 4, - meshtastic_CasevacReport_HlzMarking_HlzMarking_Other = 5 -} meshtastic_CasevacReport_HlzMarking; - -/* Line 6: security situation at the pickup zone. */ -typedef enum _meshtastic_CasevacReport_Security { - meshtastic_CasevacReport_Security_Security_Unspecified = 0, - meshtastic_CasevacReport_Security_Security_NoEnemy = 1, /* N - no enemy activity */ - meshtastic_CasevacReport_Security_Security_PossibleEnemy = 2, /* P - possible enemy */ - meshtastic_CasevacReport_Security_Security_EnemyInArea = 3, /* E - enemy, approach with caution */ - meshtastic_CasevacReport_Security_Security_EnemyInArmedContact = 4 /* X - armed escort required */ -} meshtastic_CasevacReport_Security; - -typedef enum _meshtastic_EmergencyAlert_Type { - meshtastic_EmergencyAlert_Type_Type_Unspecified = 0, - meshtastic_EmergencyAlert_Type_Type_Alert911 = 1, /* b-a-o-tbl */ - meshtastic_EmergencyAlert_Type_Type_RingTheBell = 2, /* b-a-o-pan */ - meshtastic_EmergencyAlert_Type_Type_InContact = 3, /* b-a-o-opn */ - meshtastic_EmergencyAlert_Type_Type_GeoFenceBreached = 4, /* b-a-g */ - meshtastic_EmergencyAlert_Type_Type_Custom = 5, /* b-a-o-c */ - meshtastic_EmergencyAlert_Type_Type_Cancel = 6 /* b-a-o-can */ -} meshtastic_EmergencyAlert_Type; - -typedef enum _meshtastic_TaskRequest_Priority { - meshtastic_TaskRequest_Priority_Priority_Unspecified = 0, - meshtastic_TaskRequest_Priority_Priority_Low = 1, - meshtastic_TaskRequest_Priority_Priority_Normal = 2, - meshtastic_TaskRequest_Priority_Priority_High = 3, - meshtastic_TaskRequest_Priority_Priority_Critical = 4 -} meshtastic_TaskRequest_Priority; - -typedef enum _meshtastic_TaskRequest_Status { - meshtastic_TaskRequest_Status_Status_Unspecified = 0, - meshtastic_TaskRequest_Status_Status_Pending = 1, /* assigned, not yet acknowledged */ - meshtastic_TaskRequest_Status_Status_Acknowledged = 2, /* assignee has seen it */ - meshtastic_TaskRequest_Status_Status_InProgress = 3, /* assignee is working it */ - meshtastic_TaskRequest_Status_Status_Completed = 4, /* task done */ - meshtastic_TaskRequest_Status_Status_Cancelled = 5 /* cancelled before completion */ -} meshtastic_TaskRequest_Status; - /* Struct definitions */ /* ATAK GeoChat message */ typedef struct _meshtastic_GeoChat { - /* The text message. Empty for receipts. */ + /* The text message */ char message[200]; /* Uid recipient of the message */ bool has_to; @@ -532,14 +267,6 @@ typedef struct _meshtastic_GeoChat { /* Callsign of the recipient for the message */ bool has_to_callsign; char to_callsign[120]; - /* UID of the chat message this event is acknowledging. Empty for a - normal chat message; set for delivered / read receipts. Paired with - receipt_type so receivers can match the ack back to the original - outbound GeoChat by its event uid. */ - char receipt_for_uid[48]; - /* Receipt kind discriminator. See ReceiptType doc. Default ReceiptType_None - means this is a regular chat message, not a receipt. */ - meshtastic_GeoChat_ReceiptType receipt_type; } meshtastic_GeoChat; /* ATAK Group @@ -633,284 +360,6 @@ typedef struct _meshtastic_AircraftTrack { char cot_host_id[64]; } meshtastic_AircraftTrack; -/* Compact geographic vertex used by repeated vertex lists in TAK geometry - payloads. Named with a `Cot` prefix to avoid a namespace collision with - `meshtastic.GeoPoint` in `device_ui.proto`, which is an unrelated zoom/ - latitude/longitude type used by the on-device map UI. - - Encoded as a signed DELTA from TAKPacketV2.latitude_i / longitude_i (the - enclosing event's anchor point). The absolute coordinate is recovered by - the receiver as `event.latitude_i + vertex.lat_delta_i` (and likewise for - longitude). - - Why deltas: a 32-vertex telestration with vertices clustered within a few - hundred meters of the anchor has per-vertex deltas in the ±10^4 range. - Under sint32+zigzag those encode as 2 bytes each (tag+varint), versus the - 4 bytes that sfixed32 would always require. At 32 vertices that is ~128 - bytes of savings — the difference between fitting under the LoRa MTU or - not. Absolute coordinates (values ~10^9) would cost sint32 varint 5 bytes - per field, which is why TAKPacketV2's top-level latitude_i / longitude_i - stay sfixed32 — only small values win with sint32. */ -typedef struct _meshtastic_CotGeoPoint { - /* Latitude delta from TAKPacketV2.latitude_i, in 1e-7 degree units. - Add to the enclosing event's latitude_i to recover the absolute latitude. */ - int32_t lat_delta_i; - /* Longitude delta from TAKPacketV2.longitude_i, in 1e-7 degree units. */ - int32_t lon_delta_i; -} meshtastic_CotGeoPoint; - -/* User-drawn tactical graphic: circle, rectangle, polygon, polyline, freehand - telestration, ranging circle, or bullseye. - - Covers CoT types u-d-c-c, u-d-r, u-d-f, u-d-f-m, u-d-p, u-r-b-c-c, - u-r-b-bullseye. The shape's anchor position is carried on - TAKPacketV2.latitude_i/longitude_i; polyline/polygon vertices are in the - `vertices` repeated field as `CotGeoPoint` deltas from that anchor. - - Colors use the Team enum as a 14-color palette (see color encoding below) - with a fixed32 exact-ARGB fallback for custom user-picked colors that - don't map to a palette entry. */ -typedef struct _meshtastic_DrawnShape { - /* Shape kind (circle, rectangle, freeform, etc.) */ - meshtastic_DrawnShape_Kind kind; - /* Explicit stroke/fill/both discriminator. See StyleMode doc. */ - meshtastic_DrawnShape_StyleMode style; - /* Ellipse major radius in centimeters. 0 for non-ellipse kinds. */ - uint32_t major_cm; - /* Ellipse minor radius in centimeters. 0 for non-ellipse kinds. */ - uint32_t minor_cm; - /* Ellipse rotation angle in degrees. Valid values are 0..360 inclusive; - 0 and 360 are equivalent rotations. In proto3, an unset uint32 reads - as 0, so senders should emit 0 when the angle is unspecified. */ - uint16_t angle_deg; - /* Stroke color as a named palette entry from the Team enum. If - Unspecifed_Color, the exact ARGB is carried in stroke_argb. - Valid only when style is StrokeOnly or StrokeAndFill. */ - meshtastic_Team stroke_color; - /* Stroke color as an exact 32-bit ARGB bit pattern. Always populated - on the wire; readers MUST use this value when stroke_color == - Unspecifed_Color and MAY use it to recover the exact original bytes - even when a palette entry is set. */ - uint32_t stroke_argb; - /* Stroke weight in tenths of a unit (e.g. 30 = 3.0). Typical ATAK - range 10..60. */ - uint16_t stroke_weight_x10; - /* Fill color as a named palette entry. See stroke_color docs. - Valid only when style is FillOnly or StrokeAndFill. */ - meshtastic_Team fill_color; - /* Fill color exact ARGB fallback. See stroke_argb docs. */ - uint32_t fill_argb; - /* Whether labels are rendered on this shape. */ - bool labels_on; - /* Vertex list for polyline/polygon/rectangle shapes. Capped at 32 by - the nanopb pool; senders MUST truncate longer inputs and set - `truncated = true`. */ - pb_size_t vertices_count; - meshtastic_CotGeoPoint vertices[32]; - /* True if the sender truncated `vertices` to fit the pool. */ - bool truncated; /* --- Bullseye-only fields. All ignored unless kind == Kind_Bullseye. --- */ - /* Bullseye distance in meters * 10 (e.g. 3285 = 328.5 m). 0 = unset. */ - uint32_t bullseye_distance_dm; - /* Bullseye bearing reference: 0 unset, 1 Magnetic, 2 True, 3 Grid. */ - uint8_t bullseye_bearing_ref; - /* Bullseye attribute bit flags: - bit 0: rangeRingVisible - bit 1: hasRangeRings - bit 2: edgeToCenter - bit 3: mils */ - uint8_t bullseye_flags; - /* Bullseye reference UID (anchor marker). Empty = anchor is self. */ - char bullseye_uid_ref[48]; -} meshtastic_DrawnShape; - -/* Fixed point of interest: spot marker, waypoint, checkpoint, 2525 symbol, - or custom icon. - - Covers CoT types b-m-p-s-m, b-m-p-w, b-m-p-c, b-m-p-s-p-i, b-m-p-s-p-loc, - plus a-u-G / a-f-G / a-h-G / a-n-G with iconset paths. The marker position - is carried on TAKPacketV2.latitude_i/longitude_i; fields below carry only - the marker-specific metadata. */ -typedef struct _meshtastic_Marker { - /* Marker kind */ - meshtastic_Marker_Kind kind; - /* Marker color as a named palette entry. If Unspecifed_Color, the exact - ARGB is in color_argb. */ - meshtastic_Team color; - /* Marker color exact ARGB bit pattern. Always populated on the wire. */ - uint32_t color_argb; - /* Status readiness flag (ATAK ). */ - bool readiness; - /* Parent link UID (ATAK ). Empty = no parent. - For spot/waypoint markers this is typically the producing TAK user's UID. */ - char parent_uid[48]; - /* Parent CoT type (e.g. "a-f-G-U-C"). Usually the parent TAK user's type. */ - char parent_type[24]; - /* Parent callsign (e.g. "HOPE"). */ - char parent_callsign[24]; - /* Iconset path stored verbatim. ATAK emits three flavors: - Kind_Symbol2525 -> "COT_MAPPING_2525B//" - Kind_SpotMap -> "COT_MAPPING_SPOTMAP//" - Kind_CustomIcon -> "//.png" - Stored end-to-end without prefix stripping; the ~19 bytes saved by - stripping well-known prefixes are not worth the builder-side bug - surface, and the dict compresses the repetition effectively. */ - char iconset[80]; -} meshtastic_Marker; - -/* Range and bearing measurement line from the event anchor to a target point. - - Covers CoT type u-rb-a. The anchor position is on - TAKPacketV2.latitude_i/longitude_i; the target endpoint is carried as a - CotGeoPoint — same delta-from-anchor encoding used by DrawnShape.vertices - so a self-anchored RAB (common case) encodes in zero bytes. */ -typedef struct _meshtastic_RangeAndBearing { - /* Target/anchor endpoint (delta-encoded from TAKPacketV2.latitude_i/longitude_i). */ - bool has_anchor; - meshtastic_CotGeoPoint anchor; - /* Anchor UID (from ). Empty = free-standing. */ - char anchor_uid[48]; - /* Range in centimeters (value * 100). Range 0..4294 km. */ - uint32_t range_cm; - /* Bearing in degrees * 100 (0..36000). */ - uint16_t bearing_cdeg; - /* Stroke color as a Team palette entry. See DrawnShape.stroke_color doc. */ - meshtastic_Team stroke_color; - /* Stroke color exact ARGB fallback. */ - uint32_t stroke_argb; - /* Stroke weight * 10 (e.g. 30 = 3.0). */ - uint16_t stroke_weight_x10; -} meshtastic_RangeAndBearing; - -/* Route waypoint or control point. Each link corresponds to one ATAK - entry inside the b-m-r event. */ -typedef struct _meshtastic_Route_Link { - /* Waypoint position (delta-encoded from TAKPacketV2.latitude_i/longitude_i). */ - bool has_point; - meshtastic_CotGeoPoint point; - /* Optional UID (empty = receiver derives). */ - char uid[48]; - /* Optional display callsign (e.g. "CP1"). Empty for unnamed control points. */ - char callsign[16]; - /* Link role: 0 = waypoint (b-m-p-w), 1 = checkpoint (b-m-p-c). */ - uint8_t link_type; -} meshtastic_Route_Link; - -/* Named route consisting of ordered waypoints and control points. - - Covers CoT type b-m-r. The first waypoint's position is on - TAKPacketV2.latitude_i/longitude_i; subsequent waypoints and checkpoints - are in `links`. Link count is capped at 16 by the nanopb pool; senders - MUST truncate longer routes and set `truncated = true`. */ -typedef struct _meshtastic_Route { - /* Travel method */ - meshtastic_Route_Method method; - /* Direction (infil/exfil) */ - meshtastic_Route_Direction direction; - /* Waypoint name prefix (e.g. "CP"). */ - char prefix[8]; - /* Stroke weight * 10 (e.g. 30 = 3.0). 0 = default. */ - uint16_t stroke_weight_x10; - /* Ordered list of route control points. Capped at 16. */ - pb_size_t links_count; - meshtastic_Route_Link links[16]; - /* True if the sender truncated `links` to fit the pool. */ - bool truncated; -} meshtastic_Route; - -/* 9-line MEDEVAC request (CoT type b-r-f-h-c). - - Mirrors the ATAK MedLine tool's <_medevac_> detail element. Every field - is optional (proto3 default); senders omit lines they don't have. The - envelope (TAKPacketV2.uid, cot_type_id=b-r-f-h-c, latitude_i/longitude_i, - altitude, callsign) carries Line 1 (location) and Line 2 (callsign). - - All numeric fields are tight varints so a complete 9-line request fits - in well under 100 bytes of proto on the wire. */ -typedef struct _meshtastic_CasevacReport { - /* Line 3: precedence / urgency. */ - meshtastic_CasevacReport_Precedence precedence; - /* Line 4: special equipment required, as a bitfield. - bit 0: none - bit 1: hoist - bit 2: extraction equipment - bit 3: ventilator - bit 4: blood */ - uint8_t equipment_flags; - /* Line 5: number of litter (stretcher-bound) patients. */ - uint8_t litter_patients; - /* Line 5: number of ambulatory (walking-wounded) patients. */ - uint8_t ambulatory_patients; - /* Line 6: security situation at the PZ. */ - meshtastic_CasevacReport_Security security; - /* Line 7: HLZ marking method. */ - meshtastic_CasevacReport_HlzMarking hlz_marking; - /* Line 7 supplementary: short free-text describing the zone marker - (e.g. "Green smoke", "VS-17 panel west"). Capped tight in options. */ - char zone_marker[16]; - /* --- Line 8: patient nationality counts --- */ - uint8_t us_military; - uint8_t us_civilian; - uint8_t non_us_military; - uint8_t non_us_civilian; - uint8_t epw; /* enemy prisoner of war */ - uint8_t child; - /* Line 9: terrain and obstacles at the PZ, as a bitfield. - bit 0: slope - bit 1: rough - bit 2: loose - bit 3: trees - bit 4: wires - bit 5: other */ - uint8_t terrain_flags; - /* Line 2: radio frequency / callsign metadata (e.g. "38.90 Mhz" or - "Victor 6"). Capped tight in options. */ - char frequency[16]; -} meshtastic_CasevacReport; - -/* Emergency alert / 911 beacon (CoT types b-a-o-tbl, b-a-o-pan, b-a-o-opn, - b-a-o-can, b-a-o-c, b-a-g). - - Small, high-priority structured record. The CoT type string is still set - on cot_type_id so receivers that ignore payload_variant can still display - the alert from the enum alone; the typed fields let modern receivers show - the authoring unit and handle cancel-referencing without XML parsing. */ -typedef struct _meshtastic_EmergencyAlert { - /* Alert discriminator. */ - meshtastic_EmergencyAlert_Type type; - /* UID of the unit that raised the alert. Often the same as - TAKPacketV2.uid but can be a parent device uid when a tracker raises - an alert on behalf of a dismount. */ - char authoring_uid[48]; - /* For Type_Cancel: the uid of the alert being cancelled. Empty for - non-cancel alert types. */ - char cancel_reference_uid[48]; -} meshtastic_EmergencyAlert; - -/* Task / engage request (CoT type t-s). - - Mirrors ATAK's TaskCotReceiver / CotTaskBuilder workflow. The envelope - carries the task's originating uid (implicit requester), position, and - creation time; the fields below carry structured metadata the raw-detail - fallback currently loses. - - Fields are deliberately lean — this variant is closer to the MTU ceiling - than the others, so every string is capped in options. */ -typedef struct _meshtastic_TaskRequest { - /* Short tag for the task category (e.g. "engage", "observe", "recon", - "rescue"). Free text on the wire so ATAK-specific task taxonomies - don't need proto coordination; capped tight in options. */ - char task_type[12]; - /* UID of the target / map item being tasked. */ - char target_uid[32]; - /* UID of the assigned unit. Empty = unassigned / broadcast task. */ - char assignee_uid[32]; - meshtastic_TaskRequest_Priority priority; - meshtastic_TaskRequest_Status status; - /* Optional short note (reason, constraints, grid reference). Capped - tight in options to keep the worst-case under the LoRa MTU. */ - char note[48]; -} meshtastic_TaskRequest; - typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacketV2_raw_detail_t; /* ATAK v2 packet with expanded CoT field support and zstd dictionary compression. Sent on ATAK_PLUGIN_V2 port. The wire payload is: @@ -964,12 +413,6 @@ typedef struct _meshtastic_TAKPacketV2 { char phone[20]; /* CoT event type string, only populated when cot_type_id is CotType_Other */ char cot_type_str[32]; - /* Optional remarks / free-text annotation from the element. - Populated for non-GeoChat payload types (shapes, markers, routes, etc.) - when the original CoT event carried non-empty remarks text. - GeoChat messages carry their text in GeoChat.message instead. - Empty string (proto3 default) means no remarks were present. */ - pb_callback_t remarks; pb_size_t which_payload_variant; union { /* Position report (true = PLI, no extra fields beyond the common ones above) */ @@ -978,26 +421,8 @@ typedef struct _meshtastic_TAKPacketV2 { meshtastic_GeoChat chat; /* Aircraft track data (ADS-B, military air) */ meshtastic_AircraftTrack aircraft; - /* Generic CoT detail XML for unmapped types. Kept as a fallback for CoT - types not yet promoted to a typed variant; drawings, markers, ranging - tools, and routes have dedicated variants below and should not land here. */ + /* Generic CoT detail XML for unmapped types */ meshtastic_TAKPacketV2_raw_detail_t raw_detail; - /* User-drawn tactical graphic: circle, rectangle, polygon, polyline, - telestration, ranging circle, or bullseye. See DrawnShape. */ - meshtastic_DrawnShape shape; - /* Fixed point of interest: spot marker, waypoint, checkpoint, 2525 - symbol, or custom icon. See Marker. */ - meshtastic_Marker marker; - /* Range and bearing measurement line. See RangeAndBearing. */ - meshtastic_RangeAndBearing rab; - /* Named route with ordered waypoints and control points. See Route. */ - meshtastic_Route route; - /* 9-line MEDEVAC request. See CasevacReport. */ - meshtastic_CasevacReport casevac; - /* Emergency beacon / 911 alert. See EmergencyAlert. */ - meshtastic_EmergencyAlert emergency; - /* Task / engage request. See TaskRequest. */ - meshtastic_TaskRequest task; } payload_variant; } meshtastic_TAKPacketV2; @@ -1020,63 +445,14 @@ extern "C" { #define _meshtastic_CotHow_ARRAYSIZE ((meshtastic_CotHow)(meshtastic_CotHow_CotHow_m_s+1)) #define _meshtastic_CotType_MIN meshtastic_CotType_CotType_Other -#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_t_s -#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_t_s+1)) +#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_b_f_t_a +#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_b_f_t_a+1)) #define _meshtastic_GeoPointSource_MIN meshtastic_GeoPointSource_GeoPointSource_Unspecified #define _meshtastic_GeoPointSource_MAX meshtastic_GeoPointSource_GeoPointSource_NETWORK #define _meshtastic_GeoPointSource_ARRAYSIZE ((meshtastic_GeoPointSource)(meshtastic_GeoPointSource_GeoPointSource_NETWORK+1)) -#define _meshtastic_GeoChat_ReceiptType_MIN meshtastic_GeoChat_ReceiptType_ReceiptType_None -#define _meshtastic_GeoChat_ReceiptType_MAX meshtastic_GeoChat_ReceiptType_ReceiptType_Read -#define _meshtastic_GeoChat_ReceiptType_ARRAYSIZE ((meshtastic_GeoChat_ReceiptType)(meshtastic_GeoChat_ReceiptType_ReceiptType_Read+1)) - -#define _meshtastic_DrawnShape_Kind_MIN meshtastic_DrawnShape_Kind_Kind_Unspecified -#define _meshtastic_DrawnShape_Kind_MAX meshtastic_DrawnShape_Kind_Kind_Vehicle3D -#define _meshtastic_DrawnShape_Kind_ARRAYSIZE ((meshtastic_DrawnShape_Kind)(meshtastic_DrawnShape_Kind_Kind_Vehicle3D+1)) - -#define _meshtastic_DrawnShape_StyleMode_MIN meshtastic_DrawnShape_StyleMode_StyleMode_Unspecified -#define _meshtastic_DrawnShape_StyleMode_MAX meshtastic_DrawnShape_StyleMode_StyleMode_StrokeAndFill -#define _meshtastic_DrawnShape_StyleMode_ARRAYSIZE ((meshtastic_DrawnShape_StyleMode)(meshtastic_DrawnShape_StyleMode_StyleMode_StrokeAndFill+1)) - -#define _meshtastic_Marker_Kind_MIN meshtastic_Marker_Kind_Kind_Unspecified -#define _meshtastic_Marker_Kind_MAX meshtastic_Marker_Kind_Kind_ImageMarker -#define _meshtastic_Marker_Kind_ARRAYSIZE ((meshtastic_Marker_Kind)(meshtastic_Marker_Kind_Kind_ImageMarker+1)) - -#define _meshtastic_Route_Method_MIN meshtastic_Route_Method_Method_Unspecified -#define _meshtastic_Route_Method_MAX meshtastic_Route_Method_Method_Watercraft -#define _meshtastic_Route_Method_ARRAYSIZE ((meshtastic_Route_Method)(meshtastic_Route_Method_Method_Watercraft+1)) - -#define _meshtastic_Route_Direction_MIN meshtastic_Route_Direction_Direction_Unspecified -#define _meshtastic_Route_Direction_MAX meshtastic_Route_Direction_Direction_Exfil -#define _meshtastic_Route_Direction_ARRAYSIZE ((meshtastic_Route_Direction)(meshtastic_Route_Direction_Direction_Exfil+1)) - -#define _meshtastic_CasevacReport_Precedence_MIN meshtastic_CasevacReport_Precedence_Precedence_Unspecified -#define _meshtastic_CasevacReport_Precedence_MAX meshtastic_CasevacReport_Precedence_Precedence_Convenience -#define _meshtastic_CasevacReport_Precedence_ARRAYSIZE ((meshtastic_CasevacReport_Precedence)(meshtastic_CasevacReport_Precedence_Precedence_Convenience+1)) -#define _meshtastic_CasevacReport_HlzMarking_MIN meshtastic_CasevacReport_HlzMarking_HlzMarking_Unspecified -#define _meshtastic_CasevacReport_HlzMarking_MAX meshtastic_CasevacReport_HlzMarking_HlzMarking_Other -#define _meshtastic_CasevacReport_HlzMarking_ARRAYSIZE ((meshtastic_CasevacReport_HlzMarking)(meshtastic_CasevacReport_HlzMarking_HlzMarking_Other+1)) - -#define _meshtastic_CasevacReport_Security_MIN meshtastic_CasevacReport_Security_Security_Unspecified -#define _meshtastic_CasevacReport_Security_MAX meshtastic_CasevacReport_Security_Security_EnemyInArmedContact -#define _meshtastic_CasevacReport_Security_ARRAYSIZE ((meshtastic_CasevacReport_Security)(meshtastic_CasevacReport_Security_Security_EnemyInArmedContact+1)) - -#define _meshtastic_EmergencyAlert_Type_MIN meshtastic_EmergencyAlert_Type_Type_Unspecified -#define _meshtastic_EmergencyAlert_Type_MAX meshtastic_EmergencyAlert_Type_Type_Cancel -#define _meshtastic_EmergencyAlert_Type_ARRAYSIZE ((meshtastic_EmergencyAlert_Type)(meshtastic_EmergencyAlert_Type_Type_Cancel+1)) - -#define _meshtastic_TaskRequest_Priority_MIN meshtastic_TaskRequest_Priority_Priority_Unspecified -#define _meshtastic_TaskRequest_Priority_MAX meshtastic_TaskRequest_Priority_Priority_Critical -#define _meshtastic_TaskRequest_Priority_ARRAYSIZE ((meshtastic_TaskRequest_Priority)(meshtastic_TaskRequest_Priority_Priority_Critical+1)) - -#define _meshtastic_TaskRequest_Status_MIN meshtastic_TaskRequest_Status_Status_Unspecified -#define _meshtastic_TaskRequest_Status_MAX meshtastic_TaskRequest_Status_Status_Cancelled -#define _meshtastic_TaskRequest_Status_ARRAYSIZE ((meshtastic_TaskRequest_Status)(meshtastic_TaskRequest_Status_Status_Cancelled+1)) - - -#define meshtastic_GeoChat_receipt_type_ENUMTYPE meshtastic_GeoChat_ReceiptType #define meshtastic_Group_role_ENUMTYPE meshtastic_MemberRole #define meshtastic_Group_team_ENUMTYPE meshtastic_Team @@ -1085,30 +461,6 @@ extern "C" { - -#define meshtastic_DrawnShape_kind_ENUMTYPE meshtastic_DrawnShape_Kind -#define meshtastic_DrawnShape_style_ENUMTYPE meshtastic_DrawnShape_StyleMode -#define meshtastic_DrawnShape_stroke_color_ENUMTYPE meshtastic_Team -#define meshtastic_DrawnShape_fill_color_ENUMTYPE meshtastic_Team - -#define meshtastic_Marker_kind_ENUMTYPE meshtastic_Marker_Kind -#define meshtastic_Marker_color_ENUMTYPE meshtastic_Team - -#define meshtastic_RangeAndBearing_stroke_color_ENUMTYPE meshtastic_Team - -#define meshtastic_Route_method_ENUMTYPE meshtastic_Route_Method -#define meshtastic_Route_direction_ENUMTYPE meshtastic_Route_Direction - - -#define meshtastic_CasevacReport_precedence_ENUMTYPE meshtastic_CasevacReport_Precedence -#define meshtastic_CasevacReport_security_ENUMTYPE meshtastic_CasevacReport_Security -#define meshtastic_CasevacReport_hlz_marking_ENUMTYPE meshtastic_CasevacReport_HlzMarking - -#define meshtastic_EmergencyAlert_type_ENUMTYPE meshtastic_EmergencyAlert_Type - -#define meshtastic_TaskRequest_priority_ENUMTYPE meshtastic_TaskRequest_Priority -#define meshtastic_TaskRequest_status_ENUMTYPE meshtastic_TaskRequest_Status - #define meshtastic_TAKPacketV2_cot_type_id_ENUMTYPE meshtastic_CotType #define meshtastic_TAKPacketV2_how_ENUMTYPE meshtastic_CotHow #define meshtastic_TAKPacketV2_team_ENUMTYPE meshtastic_Team @@ -1119,46 +471,26 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}} -#define meshtastic_GeoChat_init_default {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN} +#define meshtastic_GeoChat_init_default {"", false, "", false, ""} #define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_default {0} #define meshtastic_Contact_init_default {"", ""} #define meshtastic_PLI_init_default {0, 0, 0, 0, 0} #define meshtastic_AircraftTrack_init_default {"", "", "", "", 0, "", 0, 0, ""} -#define meshtastic_CotGeoPoint_init_default {0, 0} -#define meshtastic_DrawnShape_init_default {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default}, 0, 0, 0, 0, ""} -#define meshtastic_Marker_init_default {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""} -#define meshtastic_RangeAndBearing_init_default {false, meshtastic_CotGeoPoint_init_default, "", 0, 0, _meshtastic_Team_MIN, 0, 0} -#define meshtastic_Route_init_default {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default}, 0} -#define meshtastic_Route_Link_init_default {false, meshtastic_CotGeoPoint_init_default, "", "", 0} -#define meshtastic_CasevacReport_init_default {_meshtastic_CasevacReport_Precedence_MIN, 0, 0, 0, _meshtastic_CasevacReport_Security_MIN, _meshtastic_CasevacReport_HlzMarking_MIN, "", 0, 0, 0, 0, 0, 0, 0, ""} -#define meshtastic_EmergencyAlert_init_default {_meshtastic_EmergencyAlert_Type_MIN, "", ""} -#define meshtastic_TaskRequest_init_default {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""} -#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, 0, {0}} +#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", 0, {0}} #define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}} -#define meshtastic_GeoChat_init_zero {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN} +#define meshtastic_GeoChat_init_zero {"", false, "", false, ""} #define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN} #define meshtastic_Status_init_zero {0} #define meshtastic_Contact_init_zero {"", ""} #define meshtastic_PLI_init_zero {0, 0, 0, 0, 0} #define meshtastic_AircraftTrack_init_zero {"", "", "", "", 0, "", 0, 0, ""} -#define meshtastic_CotGeoPoint_init_zero {0, 0} -#define meshtastic_DrawnShape_init_zero {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero}, 0, 0, 0, 0, ""} -#define meshtastic_Marker_init_zero {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""} -#define meshtastic_RangeAndBearing_init_zero {false, meshtastic_CotGeoPoint_init_zero, "", 0, 0, _meshtastic_Team_MIN, 0, 0} -#define meshtastic_Route_init_zero {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero}, 0} -#define meshtastic_Route_Link_init_zero {false, meshtastic_CotGeoPoint_init_zero, "", "", 0} -#define meshtastic_CasevacReport_init_zero {_meshtastic_CasevacReport_Precedence_MIN, 0, 0, 0, _meshtastic_CasevacReport_Security_MIN, _meshtastic_CasevacReport_HlzMarking_MIN, "", 0, 0, 0, 0, 0, 0, 0, ""} -#define meshtastic_EmergencyAlert_init_zero {_meshtastic_EmergencyAlert_Type_MIN, "", ""} -#define meshtastic_TaskRequest_init_zero {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""} -#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, 0, {0}} +#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", 0, {0}} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_GeoChat_message_tag 1 #define meshtastic_GeoChat_to_tag 2 #define meshtastic_GeoChat_to_callsign_tag 3 -#define meshtastic_GeoChat_receipt_for_uid_tag 4 -#define meshtastic_GeoChat_receipt_type_tag 5 #define meshtastic_Group_role_tag 1 #define meshtastic_Group_team_tag 2 #define meshtastic_Status_battery_tag 1 @@ -1185,74 +517,6 @@ extern "C" { #define meshtastic_AircraftTrack_rssi_x10_tag 7 #define meshtastic_AircraftTrack_gps_tag 8 #define meshtastic_AircraftTrack_cot_host_id_tag 9 -#define meshtastic_CotGeoPoint_lat_delta_i_tag 1 -#define meshtastic_CotGeoPoint_lon_delta_i_tag 2 -#define meshtastic_DrawnShape_kind_tag 1 -#define meshtastic_DrawnShape_style_tag 2 -#define meshtastic_DrawnShape_major_cm_tag 3 -#define meshtastic_DrawnShape_minor_cm_tag 4 -#define meshtastic_DrawnShape_angle_deg_tag 5 -#define meshtastic_DrawnShape_stroke_color_tag 6 -#define meshtastic_DrawnShape_stroke_argb_tag 7 -#define meshtastic_DrawnShape_stroke_weight_x10_tag 8 -#define meshtastic_DrawnShape_fill_color_tag 9 -#define meshtastic_DrawnShape_fill_argb_tag 10 -#define meshtastic_DrawnShape_labels_on_tag 11 -#define meshtastic_DrawnShape_vertices_tag 12 -#define meshtastic_DrawnShape_truncated_tag 13 -#define meshtastic_DrawnShape_bullseye_distance_dm_tag 14 -#define meshtastic_DrawnShape_bullseye_bearing_ref_tag 15 -#define meshtastic_DrawnShape_bullseye_flags_tag 16 -#define meshtastic_DrawnShape_bullseye_uid_ref_tag 17 -#define meshtastic_Marker_kind_tag 1 -#define meshtastic_Marker_color_tag 2 -#define meshtastic_Marker_color_argb_tag 3 -#define meshtastic_Marker_readiness_tag 4 -#define meshtastic_Marker_parent_uid_tag 5 -#define meshtastic_Marker_parent_type_tag 6 -#define meshtastic_Marker_parent_callsign_tag 7 -#define meshtastic_Marker_iconset_tag 8 -#define meshtastic_RangeAndBearing_anchor_tag 1 -#define meshtastic_RangeAndBearing_anchor_uid_tag 2 -#define meshtastic_RangeAndBearing_range_cm_tag 3 -#define meshtastic_RangeAndBearing_bearing_cdeg_tag 4 -#define meshtastic_RangeAndBearing_stroke_color_tag 5 -#define meshtastic_RangeAndBearing_stroke_argb_tag 6 -#define meshtastic_RangeAndBearing_stroke_weight_x10_tag 7 -#define meshtastic_Route_Link_point_tag 1 -#define meshtastic_Route_Link_uid_tag 2 -#define meshtastic_Route_Link_callsign_tag 3 -#define meshtastic_Route_Link_link_type_tag 4 -#define meshtastic_Route_method_tag 1 -#define meshtastic_Route_direction_tag 2 -#define meshtastic_Route_prefix_tag 3 -#define meshtastic_Route_stroke_weight_x10_tag 4 -#define meshtastic_Route_links_tag 5 -#define meshtastic_Route_truncated_tag 6 -#define meshtastic_CasevacReport_precedence_tag 1 -#define meshtastic_CasevacReport_equipment_flags_tag 2 -#define meshtastic_CasevacReport_litter_patients_tag 3 -#define meshtastic_CasevacReport_ambulatory_patients_tag 4 -#define meshtastic_CasevacReport_security_tag 5 -#define meshtastic_CasevacReport_hlz_marking_tag 6 -#define meshtastic_CasevacReport_zone_marker_tag 7 -#define meshtastic_CasevacReport_us_military_tag 8 -#define meshtastic_CasevacReport_us_civilian_tag 9 -#define meshtastic_CasevacReport_non_us_military_tag 10 -#define meshtastic_CasevacReport_non_us_civilian_tag 11 -#define meshtastic_CasevacReport_epw_tag 12 -#define meshtastic_CasevacReport_child_tag 13 -#define meshtastic_CasevacReport_terrain_flags_tag 14 -#define meshtastic_CasevacReport_frequency_tag 15 -#define meshtastic_EmergencyAlert_type_tag 1 -#define meshtastic_EmergencyAlert_authoring_uid_tag 2 -#define meshtastic_EmergencyAlert_cancel_reference_uid_tag 3 -#define meshtastic_TaskRequest_task_type_tag 1 -#define meshtastic_TaskRequest_target_uid_tag 2 -#define meshtastic_TaskRequest_assignee_uid_tag 3 -#define meshtastic_TaskRequest_priority_tag 4 -#define meshtastic_TaskRequest_status_tag 5 -#define meshtastic_TaskRequest_note_tag 6 #define meshtastic_TAKPacketV2_cot_type_id_tag 1 #define meshtastic_TAKPacketV2_how_tag 2 #define meshtastic_TAKPacketV2_callsign_tag 3 @@ -1276,18 +540,10 @@ extern "C" { #define meshtastic_TAKPacketV2_endpoint_tag 21 #define meshtastic_TAKPacketV2_phone_tag 22 #define meshtastic_TAKPacketV2_cot_type_str_tag 23 -#define meshtastic_TAKPacketV2_remarks_tag 24 #define meshtastic_TAKPacketV2_pli_tag 30 #define meshtastic_TAKPacketV2_chat_tag 31 #define meshtastic_TAKPacketV2_aircraft_tag 32 #define meshtastic_TAKPacketV2_raw_detail_tag 33 -#define meshtastic_TAKPacketV2_shape_tag 34 -#define meshtastic_TAKPacketV2_marker_tag 35 -#define meshtastic_TAKPacketV2_rab_tag 36 -#define meshtastic_TAKPacketV2_route_tag 37 -#define meshtastic_TAKPacketV2_casevac_tag 38 -#define meshtastic_TAKPacketV2_emergency_tag 39 -#define meshtastic_TAKPacketV2_task_tag 40 /* Struct field encoding specification for nanopb */ #define meshtastic_TAKPacket_FIELDLIST(X, a) \ @@ -1309,9 +565,7 @@ X(a, STATIC, ONEOF, BYTES, (payload_variant,detail,payload_variant.detai #define meshtastic_GeoChat_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, message, 1) \ X(a, STATIC, OPTIONAL, STRING, to, 2) \ -X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) \ -X(a, STATIC, SINGULAR, STRING, receipt_for_uid, 4) \ -X(a, STATIC, SINGULAR, UENUM, receipt_type, 5) +X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) #define meshtastic_GeoChat_CALLBACK NULL #define meshtastic_GeoChat_DEFAULT NULL @@ -1354,114 +608,6 @@ X(a, STATIC, SINGULAR, STRING, cot_host_id, 9) #define meshtastic_AircraftTrack_CALLBACK NULL #define meshtastic_AircraftTrack_DEFAULT NULL -#define meshtastic_CotGeoPoint_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, SINT32, lat_delta_i, 1) \ -X(a, STATIC, SINGULAR, SINT32, lon_delta_i, 2) -#define meshtastic_CotGeoPoint_CALLBACK NULL -#define meshtastic_CotGeoPoint_DEFAULT NULL - -#define meshtastic_DrawnShape_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, kind, 1) \ -X(a, STATIC, SINGULAR, UENUM, style, 2) \ -X(a, STATIC, SINGULAR, UINT32, major_cm, 3) \ -X(a, STATIC, SINGULAR, UINT32, minor_cm, 4) \ -X(a, STATIC, SINGULAR, UINT32, angle_deg, 5) \ -X(a, STATIC, SINGULAR, UENUM, stroke_color, 6) \ -X(a, STATIC, SINGULAR, FIXED32, stroke_argb, 7) \ -X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 8) \ -X(a, STATIC, SINGULAR, UENUM, fill_color, 9) \ -X(a, STATIC, SINGULAR, FIXED32, fill_argb, 10) \ -X(a, STATIC, SINGULAR, BOOL, labels_on, 11) \ -X(a, STATIC, REPEATED, MESSAGE, vertices, 12) \ -X(a, STATIC, SINGULAR, BOOL, truncated, 13) \ -X(a, STATIC, SINGULAR, UINT32, bullseye_distance_dm, 14) \ -X(a, STATIC, SINGULAR, UINT32, bullseye_bearing_ref, 15) \ -X(a, STATIC, SINGULAR, UINT32, bullseye_flags, 16) \ -X(a, STATIC, SINGULAR, STRING, bullseye_uid_ref, 17) -#define meshtastic_DrawnShape_CALLBACK NULL -#define meshtastic_DrawnShape_DEFAULT NULL -#define meshtastic_DrawnShape_vertices_MSGTYPE meshtastic_CotGeoPoint - -#define meshtastic_Marker_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, kind, 1) \ -X(a, STATIC, SINGULAR, UENUM, color, 2) \ -X(a, STATIC, SINGULAR, FIXED32, color_argb, 3) \ -X(a, STATIC, SINGULAR, BOOL, readiness, 4) \ -X(a, STATIC, SINGULAR, STRING, parent_uid, 5) \ -X(a, STATIC, SINGULAR, STRING, parent_type, 6) \ -X(a, STATIC, SINGULAR, STRING, parent_callsign, 7) \ -X(a, STATIC, SINGULAR, STRING, iconset, 8) -#define meshtastic_Marker_CALLBACK NULL -#define meshtastic_Marker_DEFAULT NULL - -#define meshtastic_RangeAndBearing_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, anchor, 1) \ -X(a, STATIC, SINGULAR, STRING, anchor_uid, 2) \ -X(a, STATIC, SINGULAR, UINT32, range_cm, 3) \ -X(a, STATIC, SINGULAR, UINT32, bearing_cdeg, 4) \ -X(a, STATIC, SINGULAR, UENUM, stroke_color, 5) \ -X(a, STATIC, SINGULAR, FIXED32, stroke_argb, 6) \ -X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 7) -#define meshtastic_RangeAndBearing_CALLBACK NULL -#define meshtastic_RangeAndBearing_DEFAULT NULL -#define meshtastic_RangeAndBearing_anchor_MSGTYPE meshtastic_CotGeoPoint - -#define meshtastic_Route_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, method, 1) \ -X(a, STATIC, SINGULAR, UENUM, direction, 2) \ -X(a, STATIC, SINGULAR, STRING, prefix, 3) \ -X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 4) \ -X(a, STATIC, REPEATED, MESSAGE, links, 5) \ -X(a, STATIC, SINGULAR, BOOL, truncated, 6) -#define meshtastic_Route_CALLBACK NULL -#define meshtastic_Route_DEFAULT NULL -#define meshtastic_Route_links_MSGTYPE meshtastic_Route_Link - -#define meshtastic_Route_Link_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, point, 1) \ -X(a, STATIC, SINGULAR, STRING, uid, 2) \ -X(a, STATIC, SINGULAR, STRING, callsign, 3) \ -X(a, STATIC, SINGULAR, UINT32, link_type, 4) -#define meshtastic_Route_Link_CALLBACK NULL -#define meshtastic_Route_Link_DEFAULT NULL -#define meshtastic_Route_Link_point_MSGTYPE meshtastic_CotGeoPoint - -#define meshtastic_CasevacReport_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, precedence, 1) \ -X(a, STATIC, SINGULAR, UINT32, equipment_flags, 2) \ -X(a, STATIC, SINGULAR, UINT32, litter_patients, 3) \ -X(a, STATIC, SINGULAR, UINT32, ambulatory_patients, 4) \ -X(a, STATIC, SINGULAR, UENUM, security, 5) \ -X(a, STATIC, SINGULAR, UENUM, hlz_marking, 6) \ -X(a, STATIC, SINGULAR, STRING, zone_marker, 7) \ -X(a, STATIC, SINGULAR, UINT32, us_military, 8) \ -X(a, STATIC, SINGULAR, UINT32, us_civilian, 9) \ -X(a, STATIC, SINGULAR, UINT32, non_us_military, 10) \ -X(a, STATIC, SINGULAR, UINT32, non_us_civilian, 11) \ -X(a, STATIC, SINGULAR, UINT32, epw, 12) \ -X(a, STATIC, SINGULAR, UINT32, child, 13) \ -X(a, STATIC, SINGULAR, UINT32, terrain_flags, 14) \ -X(a, STATIC, SINGULAR, STRING, frequency, 15) -#define meshtastic_CasevacReport_CALLBACK NULL -#define meshtastic_CasevacReport_DEFAULT NULL - -#define meshtastic_EmergencyAlert_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, type, 1) \ -X(a, STATIC, SINGULAR, STRING, authoring_uid, 2) \ -X(a, STATIC, SINGULAR, STRING, cancel_reference_uid, 3) -#define meshtastic_EmergencyAlert_CALLBACK NULL -#define meshtastic_EmergencyAlert_DEFAULT NULL - -#define meshtastic_TaskRequest_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, STRING, task_type, 1) \ -X(a, STATIC, SINGULAR, STRING, target_uid, 2) \ -X(a, STATIC, SINGULAR, STRING, assignee_uid, 3) \ -X(a, STATIC, SINGULAR, UENUM, priority, 4) \ -X(a, STATIC, SINGULAR, UENUM, status, 5) \ -X(a, STATIC, SINGULAR, STRING, note, 6) -#define meshtastic_TaskRequest_CALLBACK NULL -#define meshtastic_TaskRequest_DEFAULT NULL - #define meshtastic_TAKPacketV2_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, cot_type_id, 1) \ X(a, STATIC, SINGULAR, UENUM, how, 2) \ @@ -1486,29 +632,14 @@ X(a, STATIC, SINGULAR, STRING, tak_os, 20) \ X(a, STATIC, SINGULAR, STRING, endpoint, 21) \ X(a, STATIC, SINGULAR, STRING, phone, 22) \ X(a, STATIC, SINGULAR, STRING, cot_type_str, 23) \ -X(a, CALLBACK, SINGULAR, STRING, remarks, 24) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,pli,payload_variant.pli), 30) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 31) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,aircraft,payload_variant.aircraft), 32) \ -X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,shape,payload_variant.shape), 34) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,marker,payload_variant.marker), 35) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,rab,payload_variant.rab), 36) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,route,payload_variant.route), 37) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,casevac,payload_variant.casevac), 38) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,emergency,payload_variant.emergency), 39) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task), 40) -#define meshtastic_TAKPacketV2_CALLBACK pb_default_field_callback +X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33) +#define meshtastic_TAKPacketV2_CALLBACK NULL #define meshtastic_TAKPacketV2_DEFAULT NULL #define meshtastic_TAKPacketV2_payload_variant_chat_MSGTYPE meshtastic_GeoChat #define meshtastic_TAKPacketV2_payload_variant_aircraft_MSGTYPE meshtastic_AircraftTrack -#define meshtastic_TAKPacketV2_payload_variant_shape_MSGTYPE meshtastic_DrawnShape -#define meshtastic_TAKPacketV2_payload_variant_marker_MSGTYPE meshtastic_Marker -#define meshtastic_TAKPacketV2_payload_variant_rab_MSGTYPE meshtastic_RangeAndBearing -#define meshtastic_TAKPacketV2_payload_variant_route_MSGTYPE meshtastic_Route -#define meshtastic_TAKPacketV2_payload_variant_casevac_MSGTYPE meshtastic_CasevacReport -#define meshtastic_TAKPacketV2_payload_variant_emergency_MSGTYPE meshtastic_EmergencyAlert -#define meshtastic_TAKPacketV2_payload_variant_task_MSGTYPE meshtastic_TaskRequest extern const pb_msgdesc_t meshtastic_TAKPacket_msg; extern const pb_msgdesc_t meshtastic_GeoChat_msg; @@ -1517,15 +648,6 @@ extern const pb_msgdesc_t meshtastic_Status_msg; extern const pb_msgdesc_t meshtastic_Contact_msg; extern const pb_msgdesc_t meshtastic_PLI_msg; extern const pb_msgdesc_t meshtastic_AircraftTrack_msg; -extern const pb_msgdesc_t meshtastic_CotGeoPoint_msg; -extern const pb_msgdesc_t meshtastic_DrawnShape_msg; -extern const pb_msgdesc_t meshtastic_Marker_msg; -extern const pb_msgdesc_t meshtastic_RangeAndBearing_msg; -extern const pb_msgdesc_t meshtastic_Route_msg; -extern const pb_msgdesc_t meshtastic_Route_Link_msg; -extern const pb_msgdesc_t meshtastic_CasevacReport_msg; -extern const pb_msgdesc_t meshtastic_EmergencyAlert_msg; -extern const pb_msgdesc_t meshtastic_TaskRequest_msg; extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -1536,36 +658,18 @@ extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg; #define meshtastic_Contact_fields &meshtastic_Contact_msg #define meshtastic_PLI_fields &meshtastic_PLI_msg #define meshtastic_AircraftTrack_fields &meshtastic_AircraftTrack_msg -#define meshtastic_CotGeoPoint_fields &meshtastic_CotGeoPoint_msg -#define meshtastic_DrawnShape_fields &meshtastic_DrawnShape_msg -#define meshtastic_Marker_fields &meshtastic_Marker_msg -#define meshtastic_RangeAndBearing_fields &meshtastic_RangeAndBearing_msg -#define meshtastic_Route_fields &meshtastic_Route_msg -#define meshtastic_Route_Link_fields &meshtastic_Route_Link_msg -#define meshtastic_CasevacReport_fields &meshtastic_CasevacReport_msg -#define meshtastic_EmergencyAlert_fields &meshtastic_EmergencyAlert_msg -#define meshtastic_TaskRequest_fields &meshtastic_TaskRequest_msg #define meshtastic_TAKPacketV2_fields &meshtastic_TAKPacketV2_msg /* Maximum encoded size of messages (where known) */ -/* meshtastic_TAKPacketV2_size depends on runtime parameters */ -#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_Route_size +#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacketV2_size #define meshtastic_AircraftTrack_size 134 -#define meshtastic_CasevacReport_size 70 #define meshtastic_Contact_size 242 -#define meshtastic_CotGeoPoint_size 12 -#define meshtastic_DrawnShape_size 553 -#define meshtastic_EmergencyAlert_size 100 -#define meshtastic_GeoChat_size 495 +#define meshtastic_GeoChat_size 444 #define meshtastic_Group_size 4 -#define meshtastic_Marker_size 191 #define meshtastic_PLI_size 31 -#define meshtastic_RangeAndBearing_size 84 -#define meshtastic_Route_Link_size 83 -#define meshtastic_Route_size 1379 #define meshtastic_Status_size 3 -#define meshtastic_TAKPacket_size 756 -#define meshtastic_TaskRequest_size 132 +#define meshtastic_TAKPacketV2_size 1027 +#define meshtastic_TAKPacket_size 705 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 3648d88502a..7f1a738c652 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -27,9 +27,6 @@ PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO) PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) -PB_BIND(meshtastic_RemoteShell, meshtastic_RemoteShell, AUTO) - - PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) @@ -132,8 +129,6 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU - - diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 11f3d9f01c2..fc6931d7346 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -308,13 +308,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126, /* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */ meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127, - /* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, - GPS, button, buzzer, and sensors. */ + /* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, GPS, button, buzzer, and sensors. */ meshtastic_HardwareModel_TRACKER_T1000_E_PRO = 128, - /* Elecrow ThinkNode M7, M8 and M9 */ - meshtastic_HardwareModel_THINKNODE_M7 = 129, - meshtastic_HardwareModel_THINKNODE_M8 = 130, - meshtastic_HardwareModel_THINKNODE_M9 = 131, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -518,27 +513,6 @@ typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type { meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF = 6 } meshtastic_StoreForwardPlusPlus_SFPP_message_type; -/* Frame op code for PTY session control and stream transport. - - Values 1-63 are client->server requests. - Values 64-127 are server->client responses/events. */ -typedef enum _meshtastic_RemoteShell_OpCode { - meshtastic_RemoteShell_OpCode_OP_UNSET = 0, - /* Client -> server */ - meshtastic_RemoteShell_OpCode_OPEN = 1, - meshtastic_RemoteShell_OpCode_INPUT = 2, - meshtastic_RemoteShell_OpCode_RESIZE = 3, - meshtastic_RemoteShell_OpCode_CLOSE = 4, - meshtastic_RemoteShell_OpCode_PING = 5, - meshtastic_RemoteShell_OpCode_ACK = 6, - /* Server -> client */ - meshtastic_RemoteShell_OpCode_OPEN_OK = 64, - meshtastic_RemoteShell_OpCode_OUTPUT = 65, - meshtastic_RemoteShell_OpCode_CLOSED = 66, - meshtastic_RemoteShell_OpCode_ERROR = 67, - meshtastic_RemoteShell_OpCode_PONG = 68 -} meshtastic_RemoteShell_OpCode; - /* The priority of this message for sending. Higher priorities are sent first (when managing the transmit queue). This field is never sent over the air, it is only used internally inside of a local device node. @@ -871,31 +845,6 @@ typedef struct _meshtastic_StoreForwardPlusPlus { uint32_t chain_count; } meshtastic_StoreForwardPlusPlus; -typedef PB_BYTES_ARRAY_T(200) meshtastic_RemoteShell_payload_t; -/* The actual over-the-mesh message doing RemoteShell */ -typedef struct _meshtastic_RemoteShell { - /* Structured frame operation. */ - meshtastic_RemoteShell_OpCode op; - /* Logical PTY session identifier. */ - uint32_t session_id; - /* Monotonic sequence number for this frame. */ - uint32_t seq; - /* Cumulative ack sequence number. */ - uint32_t ack_seq; - /* Opaque bytes payload for INPUT/OUTPUT/ERROR and other frame bodies. */ - meshtastic_RemoteShell_payload_t payload; - /* Terminal size columns used for OPEN/RESIZE signaling. */ - uint32_t cols; - /* Terminal size rows used for OPEN/RESIZE signaling. */ - uint32_t rows; - /* Bit flags for protocol extensions. */ - uint32_t flags; - /* Terminal size rows used for OPEN/RESIZE signaling. */ - uint32_t last_tx_seq; - /* Bit flags for protocol extensions. */ - uint32_t last_rx_seq; -} meshtastic_RemoteShell; - /* Waypoint message, used to share arbitrary locations across the mesh */ typedef struct _meshtastic_Waypoint { /* Id of the waypoint */ @@ -1436,10 +1385,6 @@ extern "C" { #define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF #define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1)) -#define _meshtastic_RemoteShell_OpCode_MIN meshtastic_RemoteShell_OpCode_OP_UNSET -#define _meshtastic_RemoteShell_OpCode_MAX meshtastic_RemoteShell_OpCode_PONG -#define _meshtastic_RemoteShell_OpCode_ARRAYSIZE ((meshtastic_RemoteShell_OpCode)(meshtastic_RemoteShell_OpCode_PONG+1)) - #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX #define _meshtastic_MeshPacket_Priority_ARRAYSIZE ((meshtastic_MeshPacket_Priority)(meshtastic_MeshPacket_Priority_MAX+1)) @@ -1470,8 +1415,6 @@ extern "C" { #define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type -#define meshtastic_RemoteShell_op_ENUMTYPE meshtastic_RemoteShell_OpCode - @@ -1516,7 +1459,6 @@ extern "C" { #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} -#define meshtastic_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} @@ -1550,7 +1492,6 @@ extern "C" { #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} -#define meshtastic_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 0, 0, 0, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} @@ -1640,16 +1581,6 @@ extern "C" { #define meshtastic_StoreForwardPlusPlus_encapsulated_from_tag 8 #define meshtastic_StoreForwardPlusPlus_encapsulated_rxtime_tag 9 #define meshtastic_StoreForwardPlusPlus_chain_count_tag 10 -#define meshtastic_RemoteShell_op_tag 1 -#define meshtastic_RemoteShell_session_id_tag 2 -#define meshtastic_RemoteShell_seq_tag 3 -#define meshtastic_RemoteShell_ack_seq_tag 4 -#define meshtastic_RemoteShell_payload_tag 5 -#define meshtastic_RemoteShell_cols_tag 6 -#define meshtastic_RemoteShell_rows_tag 7 -#define meshtastic_RemoteShell_flags_tag 8 -#define meshtastic_RemoteShell_last_tx_seq_tag 9 -#define meshtastic_RemoteShell_last_rx_seq_tag 10 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1882,20 +1813,6 @@ X(a, STATIC, SINGULAR, UINT32, chain_count, 10) #define meshtastic_StoreForwardPlusPlus_CALLBACK NULL #define meshtastic_StoreForwardPlusPlus_DEFAULT NULL -#define meshtastic_RemoteShell_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, op, 1) \ -X(a, STATIC, SINGULAR, UINT32, session_id, 2) \ -X(a, STATIC, SINGULAR, UINT32, seq, 3) \ -X(a, STATIC, SINGULAR, UINT32, ack_seq, 4) \ -X(a, STATIC, SINGULAR, BYTES, payload, 5) \ -X(a, STATIC, SINGULAR, UINT32, cols, 6) \ -X(a, STATIC, SINGULAR, UINT32, rows, 7) \ -X(a, STATIC, SINGULAR, UINT32, flags, 8) \ -X(a, STATIC, SINGULAR, UINT32, last_tx_seq, 9) \ -X(a, STATIC, SINGULAR, UINT32, last_rx_seq, 10) -#define meshtastic_RemoteShell_CALLBACK NULL -#define meshtastic_RemoteShell_DEFAULT NULL - #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \ @@ -2178,7 +2095,6 @@ extern const pb_msgdesc_t meshtastic_Routing_msg; extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; -extern const pb_msgdesc_t meshtastic_RemoteShell_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; extern const pb_msgdesc_t meshtastic_StatusMessage_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; @@ -2214,7 +2130,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Data_fields &meshtastic_Data_msg #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg #define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg -#define meshtastic_RemoteShell_fields &meshtastic_RemoteShell_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg #define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg @@ -2270,7 +2185,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_NodeRemoteHardwarePin_size 29 #define meshtastic_Position_size 144 #define meshtastic_QueueStatus_size 23 -#define meshtastic_RemoteShell_size 253 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 #define meshtastic_StatusMessage_size 81 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 494ef4a5497..a474e5b9227 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -76,8 +76,6 @@ typedef enum _meshtastic_PortNum { meshtastic_PortNum_ALERT_APP = 11, /* Module/port for handling key verification requests. */ meshtastic_PortNum_KEY_VERIFICATION_APP = 12, - /* Module/port for handling primitive remote shell access. */ - meshtastic_PortNum_REMOTE_SHELL_APP = 13, /* Provides a 'ping' service that replies to any packet it receives. Also serves as a small example module. ENCODING: ASCII Plaintext */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 8c0fdd56314..f48d946a4ae 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -113,9 +113,7 @@ typedef enum _meshtastic_TelemetrySensorType { /* SCD30 CO2, humidity, temperature sensor */ meshtastic_TelemetrySensorType_SCD30 = 49, /* SHT family of sensors for temperature and humidity */ - meshtastic_TelemetrySensorType_SHTXX = 50, - /* DS248X Bridge for one-wire temperature sensors */ - meshtastic_TelemetrySensorType_DS248X = 51 + meshtastic_TelemetrySensorType_SHTXX = 50 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -208,9 +206,6 @@ typedef struct _meshtastic_EnvironmentMetrics { /* Soil temperature measured (*C) */ bool has_soil_temperature; float soil_temperature; - /* One-wire temperature (*C) */ - pb_size_t one_wire_temperature_count; - float one_wire_temperature[8]; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -496,8 +491,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DS248X -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DS248X+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHTXX +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHTXX+1)) @@ -513,7 +508,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -524,7 +519,7 @@ extern "C" { #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -563,7 +558,6 @@ extern "C" { #define meshtastic_EnvironmentMetrics_rainfall_24h_tag 20 #define meshtastic_EnvironmentMetrics_soil_moisture_tag 21 #define meshtastic_EnvironmentMetrics_soil_temperature_tag 22 -#define meshtastic_EnvironmentMetrics_one_wire_temperature_tag 23 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -689,8 +683,7 @@ X(a, STATIC, OPTIONAL, FLOAT, radiation, 18) \ X(a, STATIC, OPTIONAL, FLOAT, rainfall_1h, 19) \ X(a, STATIC, OPTIONAL, FLOAT, rainfall_24h, 20) \ X(a, STATIC, OPTIONAL, UINT32, soil_moisture, 21) \ -X(a, STATIC, OPTIONAL, FLOAT, soil_temperature, 22) \ -X(a, STATIC, REPEATED, FLOAT, one_wire_temperature, 23) +X(a, STATIC, OPTIONAL, FLOAT, soil_temperature, 22) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -859,7 +852,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 150 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 161 +#define meshtastic_EnvironmentMetrics_size 113 #define meshtastic_HealthMetrics_size 11 #define meshtastic_HostMetrics_size 264 #define meshtastic_LocalStats_size 87