From 052a9fa4bffda16ba5a1ca625b829c75fac03151 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 15 Apr 2026 11:16:31 -0500 Subject: [PATCH 1/6] Add v2.x medline extensions and ZMistEntry message to CasevacReport --- meshtastic/atak.proto | 135 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/meshtastic/atak.proto b/meshtastic/atak.proto index 7e94696..b9dbaae 100644 --- a/meshtastic/atak.proto +++ b/meshtastic/atak.proto @@ -1498,6 +1498,141 @@ message CasevacReport { * "Victor 6"). Capped tight in options. */ string frequency = 15; + + // --- v2.x medline extensions (tags 16–33) -------------------------------- + // + // Fields 16+ cost a 2-byte tag instead of 1 byte, but they're usually + // sparse so the on-wire delta is modest when most stay unset. A fully + // populated CASEVAC with 13 free-text fields + 2 ZMIST entries can run + // 200-400 bytes compressed, i.e. potentially over the 237 B LoRa MTU. + // Callers that hit the MTU on the `compressWithRemarksFallback` path + // SHOULD strip the tier-2 situational fields (tags 28-32 + terrain_other_detail) + // before dropping the packet entirely. See README "CASEVAC tier-2 stripping". + + /* + * Short title / MEDEVAC identifier (e.g. "EAGLE.15.181230"). Usually the + * same as the envelope callsign but ATAK sometimes carries a distinct + * ops-number here. + */ + string title = 16; + /* + * Primary medline free-text — the single most clinically important line + * on a MEDLINE form (e.g. "2 urgent litter patients, smoke on approach"). + * MUST be preserved under MTU pressure as long as any casevac is sent. + */ + string medline_remarks = 17; + + /* + * Line 3 (newer ATAK format): patient counts by precedence level. + * Coexists with the enum-style `precedence` field (tag 1) — older ATAK + * emits a single enum, newer ATAK emits these counts, and both can be + * set simultaneously. Senders populate whichever style(s) the source + * XML had; receivers prefer counts when non-zero. + */ + uint32 urgent_count = 18; + uint32 urgent_surgical_count = 19; + uint32 priority_count = 20; + uint32 routine_count = 21; + uint32 convenience_count = 22; + + /* + * Line 4 supplementary: free-text description of non-standard equipment + * (e.g. "Blood warmer"). Pairs with the `equipment_flags` bitfield. + */ + string equipment_detail = 23; + /* + * Line 1 override: MGRS grid when distinct from the event anchor point + * (e.g. "34T CQ 12345 67890"). Event lat/lon/hae still carries the + * numeric location; this field preserves the exact MGRS string the + * medic entered. + */ + string zone_protected_coord = 24; + /* + * Line 9 supplementary: slope direction (e.g. "N", "NE", "SSW") when + * `terrain_flags` bit 0 (slope) is set. + */ + string terrain_slope_dir = 25; + /* + * Line 9 supplementary: free-text description of "other" terrain hazards + * (e.g. "Loose debris on west edge") when `terrain_flags` bit 5 (other) + * is set. Tier-2 strippable under MTU pressure. + */ + string terrain_other_detail = 26; + /* + * Line 7 supplementary: how the zone is being marked right now + * (e.g. "Orange smoke", "VS-17 panel"). Complements the structured + * `hlz_marking` enum with a specific human-readable description. + */ + string marked_by = 27; + + // --- Tier-2 situational awareness (stripped first under MTU pressure) --- + // These fields are free-text context that helps the receiver plan the + // approach but aren't strictly required to evacuate the patient. + + /* + * Nearby obstacles on the approach (e.g. "Power lines north of HLZ"). + */ + string obstacles = 28; + /* + * Wind direction and speed (e.g. "270 at 12 kts"). + */ + string winds_are_from = 29; + /* + * Friendly forces posture near the pickup zone + * (e.g. "Squad east of HLZ"). + */ + string friendlies = 30; + /* + * Known or suspected enemy positions near the pickup zone + * (e.g. "Possible enemy on south ridge"). + */ + string enemy = 31; + /* + * Free-text description of the HLZ itself + * (e.g. "Primary HLZ is soccer field"). + */ + string hlz_remarks = 32; + + /* + * Per-patient clinical records. Each entry is one patient's ZMIST card + * (Zap number / Mechanism / Injuries / Signs / Treatment). Repeatable — + * a mass-casualty event can carry 1-6 entries in practice, limited by + * the 237 B LoRa MTU. + */ + repeated ZMistEntry zmist = 33; +} + +/* + * Per-patient clinical summary record — one entry per patient in a CASEVAC. + * Maps directly to ATAK's child element inside . + * All fields are optional free-text; senders populate what they have. + */ +message ZMistEntry { + /* + * Patient identifier / sequence label (e.g. "ZMIST-1", "ZMIST-2"). + */ + string title = 1; + /* + * Zap number — unique patient tracking ID (often a terse code like + * "Gunshot" or a serial). + */ + string z = 2; + /* + * Mechanism of injury (e.g. "Penetrating trauma", "Blast injury"). + */ + string m = 3; + /* + * Injuries observed (e.g. "Left thigh", "Concussion"). + */ + string i = 4; + /* + * Signs / vital stats (e.g. "Stable", "Priority", "BP 110/70"). + */ + string s = 5; + /* + * Treatment given (e.g. "Tourniquet 1810Z", "O2 administered"). + */ + string t = 6; } /* From da33d0ea72678e430e034275f724c754728235c8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 15 Apr 2026 15:20:28 -0500 Subject: [PATCH 2/6] Add Environment and SensorFov annotations to TAKPacketV2 Two new top-level optional fields at tags 25 and 26 that attach to any payload_variant. Environment carries weather readings from CoT detail elements (temperature, wind direction, wind speed). SensorFov carries the 8 geometry attributes of ATAK-CIV's element (type, azimuth, range, horizontal and vertical FOV, elevation, roll, model), dropping the 9 display-only attributes (fovAlpha/RGB, strokeColor/Weight, hideFov, fovLabels, displayMagneticReference, rangeLines) that are receiver-side render hints. Units are chosen for compact varint encoding and parity with existing fields: temperature in deci-degrees Celsius (sint32), wind speed in cm/s to match TAKPacketV2.speed, all angles as whole degrees. Tags 27-29 reserved for future annotations before the payload_variant oneof resumes at tag 30. --- meshtastic/atak.proto | 119 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/meshtastic/atak.proto b/meshtastic/atak.proto index b9dbaae..b8a4fe5 100644 --- a/meshtastic/atak.proto +++ b/meshtastic/atak.proto @@ -1722,6 +1722,105 @@ message TaskRequest { string note = 6; } +/* + * Weather annotation from CoT detail element. + * + * Attaches to any TAKPacketV2 regardless of payload_variant — an Aircraft, + * PLI, or Marker can all carry observed conditions at the emitting station. + * ATAK-CIV ships an XSD for but no dedicated handler, so the + * element round-trips through the generic detail pipeline; this message + * promotes it to a first-class structured field. + * + * Target wire cost: ~6-8 bytes compressed with a fully populated instance. + */ +message Environment { + /* + * Temperature in deci-degrees Celsius. 225 = 22.5°C. + * Range covers -50°C to +50°C (-500 to +500) which spans every realistic + * outdoor TAK deployment. sint32 because negative temps are common in + * cold-weather ops. + */ + sint32 temperature_c_x10 = 1; + /* + * Wind direction in whole degrees, 0-359. "Direction FROM" per + * meteorological convention (matches CoT / ATAK). + */ + uint32 wind_direction_deg = 2; + /* + * Wind speed in cm/s. Matches the unit of TAKPacketV2.speed for + * consistency. 1200 = 12.00 m/s = ~27 mph. + */ + uint32 wind_speed_cm_s = 3; +} + +/* + * Sensor field-of-view cone from CoT detail element. + * + * Encodes the 8 geometry attributes that ATAK-CIV's SensorDetailHandler + * reads from the wire; drops the 9 visual-styling attributes that are + * receiver-side render hints (fovAlpha, fovRed/Green/Blue, strokeColor, + * strokeWeight, displayMagneticReference, hideFov, fovLabels, rangeLines). + * The receiving ATAK client restores those from its own defaults, same as + * every other CoT carried over Meshtastic today. + * + * Attaches to any TAKPacketV2 — a PLI with a sensor on the operator's head, + * an Aircraft with a FLIR turret, a Marker dropped on a UAV. + * Target wire cost: ~7-14 bytes compressed (dominated by model string). + */ +message SensorFov { + /* + * Coarse sensor category, inferred from `model` on parse when the source + * XML doesn't label it. Receivers that render differently per sensor + * class (thermal overlay vs daylight cone) use this. + */ + enum SensorType { + SensorType_Unspecified = 0; + SensorType_Camera = 1; // daylight / general optical + SensorType_Thermal = 2; // FLIR, thermal imager + SensorType_Laser = 3; // rangefinder, LRF, designator + SensorType_Nvg = 4; // night vision goggles + SensorType_Rf = 5; // radio/radar direction-finding + SensorType_Other = 6; + } + + SensorType type = 1; + /* + * Azimuth in whole degrees, 0-359. "Pointing direction" of the cone axis, + * measured clockwise from true north. Whole degrees match ATAK-CIV's + * SensorDetailHandler default (270°) and save varint bytes over centi-deg. + */ + uint32 azimuth_deg = 2; + /* + * Maximum range of the cone in meters. ATAK-CIV default is 100m. + */ + uint32 range_m = 3; + /* + * Horizontal field of view in whole degrees (cone's angular width). + * ATAK-CIV default is 45°. + */ + uint32 fov_horizontal_deg = 4; + /* + * Vertical field of view in whole degrees. ATAK-CIV default is 45°. + * Optional — a value of 0 means "not set / use horizontal FOV". + */ + uint32 fov_vertical_deg = 5; + /* + * Elevation angle in whole degrees. Positive = up, negative = down. + * Range -90 to +90. sint32 for varint efficiency on small negatives. + */ + sint32 elevation_deg = 6; + /* + * Roll (camera tilt) in whole degrees, -180 to +180. + * Optional — use 0 if the sensor doesn't track roll. + */ + sint32 roll_deg = 7; + /* + * Free-form device model identifier, e.g. "FLIR-Boson-640", "SEEK". + * Optional — empty string means "unknown model" (ATAK-CIV default). + */ + string model = 8; +} + /* * ATAK v2 packet with expanded CoT field support and zstd dictionary compression. * Sent on ATAK_PLUGIN_V2 port. The wire payload is: @@ -1830,6 +1929,26 @@ message TAKPacketV2 { * Empty string (proto3 default) means no remarks were present. */ string remarks = 24; + + // --- Sensor / environment annotations ---------------------------------- + // + // Both fields are OPTIONAL and attach to any payload_variant. They + // describe observed conditions at the emitting station — a PLI with + // environment data, an Aircraft with a sensor cone, a Marker with both. + // Absent by default; presence is signaled by the message being non-null. + + /* + * Observed weather conditions (temperature, wind). From . + */ + optional Environment environment = 25; + /* + * Sensor field-of-view cone (camera, FLIR, laser, etc.). From . + */ + optional SensorFov sensor_fov = 26; + + // Tags 27, 28, 29 reserved for future top-level annotations before the + // payload_variant oneof resumes at 30. + /* * The payload of the packet */ From 3ce7c51068cc45281f3a5fa353a8f4df6f7eca27 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 15 Apr 2026 16:29:03 -0500 Subject: [PATCH 3/6] Rename Environment -> TAKEnvironment to avoid SwiftUI collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bare `Environment` message name clashes with SwiftUI's `@Environment` property wrapper, which is used by every SwiftUI view in an iOS consumer app. Importing the generated proto module (e.g. `MeshtasticProtobufs`) made `Environment` ambiguous in all 78 files of Meshtastic-Apple that reference `@Environment(\.scenePhase)` or similar, breaking the build. Field name `environment` on TAKPacketV2 stays unchanged — only the message type is renamed. Wire format is unaffected (proto3 encodes by tag number, not type name), but all generated code and source-level references must be regenerated. The `TAK` prefix matches the convention used by the outer TAKPacketV2 wrapper and is unambiguous across Swift (no SwiftUI/WeatherKit clash), Kotlin, Python, TypeScript, and C# targets. --- meshtastic/atak.proto | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/meshtastic/atak.proto b/meshtastic/atak.proto index b8a4fe5..7223ee1 100644 --- a/meshtastic/atak.proto +++ b/meshtastic/atak.proto @@ -1732,8 +1732,16 @@ message TaskRequest { * promotes it to a first-class structured field. * * Target wire cost: ~6-8 bytes compressed with a fully populated instance. + * + * Named `TAKEnvironment` (not just `Environment`) because the bare name + * collides with `SwiftUI.Environment` — every SwiftUI view in a consuming + * iOS app uses the `@Environment` property wrapper, and importing the + * generated proto module would make `Environment` ambiguous in every one + * of those files. The `TAK` prefix matches the convention used by the + * outer `TAKPacketV2` wrapper and is unambiguous across all target + * languages (Swift, Kotlin, Python, TypeScript, C#). */ -message Environment { +message TAKEnvironment { /* * Temperature in deci-degrees Celsius. 225 = 22.5°C. * Range covers -50°C to +50°C (-500 to +500) which spans every realistic @@ -1939,8 +1947,10 @@ message TAKPacketV2 { /* * Observed weather conditions (temperature, wind). From . + * Type is `TAKEnvironment`, not `Environment`, to avoid colliding with + * SwiftUI's `@Environment` property wrapper in iOS consumers. */ - optional Environment environment = 25; + optional TAKEnvironment environment = 25; /* * Sensor field-of-view cone (camera, FLIR, laser, etc.). From . */ From 3a398acbff52b7844b48965cd78b408d27a17ce2 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 16 Apr 2026 19:51:52 -0500 Subject: [PATCH 4/6] Update atak.proto Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- meshtastic/atak.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshtastic/atak.proto b/meshtastic/atak.proto index 7223ee1..ec0c596 100644 --- a/meshtastic/atak.proto +++ b/meshtastic/atak.proto @@ -1799,9 +1799,10 @@ message SensorFov { */ uint32 azimuth_deg = 2; /* - * Maximum range of the cone in meters. ATAK-CIV default is 100m. + * Maximum range of the cone in meters. + * Optional — if unset, receivers should use the ATAK-CIV default of 100m. */ - uint32 range_m = 3; + optional uint32 range_m = 3; /* * Horizontal field of view in whole degrees (cone's angular width). * ATAK-CIV default is 45°. From e5b07ae0b0d5901b3e9cf586c68f30f38589c42b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 16 Apr 2026 19:52:05 -0500 Subject: [PATCH 5/6] Update atak.proto Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- meshtastic/atak.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/meshtastic/atak.proto b/meshtastic/atak.proto index ec0c596..0325d92 100644 --- a/meshtastic/atak.proto +++ b/meshtastic/atak.proto @@ -1957,6 +1957,7 @@ message TAKPacketV2 { */ optional SensorFov sensor_fov = 26; + reserved 27, 28, 29; // Tags 27, 28, 29 reserved for future top-level annotations before the // payload_variant oneof resumes at 30. From 793e274f599c7f08e4d343dd56607ab36e5486b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 00:56:12 +0000 Subject: [PATCH 6/6] Run buf format on atak proto Agent-Logs-Url: https://github.com/meshtastic/protobufs/sessions/059c2e02-63e3-467d-b032-dc5c15883261 Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- meshtastic/atak.proto | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/meshtastic/atak.proto b/meshtastic/atak.proto index 0325d92..873e0b6 100644 --- a/meshtastic/atak.proto +++ b/meshtastic/atak.proto @@ -1783,11 +1783,11 @@ message SensorFov { */ enum SensorType { SensorType_Unspecified = 0; - SensorType_Camera = 1; // daylight / general optical - SensorType_Thermal = 2; // FLIR, thermal imager - SensorType_Laser = 3; // rangefinder, LRF, designator - SensorType_Nvg = 4; // night vision goggles - SensorType_Rf = 5; // radio/radar direction-finding + SensorType_Camera = 1; // daylight / general optical + SensorType_Thermal = 2; // FLIR, thermal imager + SensorType_Laser = 3; // rangefinder, LRF, designator + SensorType_Nvg = 4; // night vision goggles + SensorType_Rf = 5; // radio/radar direction-finding SensorType_Other = 6; }