Skip to content

Commit c6284f9

Browse files
thebenternclaude
andcommitted
feat: 2.8 protobuf sync + LoRa region→preset compatibility map
Sync the vendored protobufs to 2.8 and implement the LoRa region→preset compatibility feature (FromRadio.region_presets), gating the new 2.8 regions and presets behind connected-firmware support. Protobuf sync holes: - Regenerate the vendored .pb.swift from the 2.8 protobufs submodule. - Handle the new meshBeaconApp PortNum (37) in the packet switch. - Realign ModuleConfig.TrafficManagementConfig to its redesigned schema (the boolean/precision/hop-management fields were dropped in favor of five implicit-enable integers): update the proto<->entity mapping and the config view, removing the defunct precision-bits and hop-management UI. The SwiftData entity is left intact, so no migration is required. LoRa region->preset map (per the firmware client spec): - Decode FromRadio.region_presets into a per-region lookup (LoRaRegionPresetMap.decoded -> RegionPresetInfo), store it on AccessoryManager, and reset it on disconnect. - LoRaConfig constrains the preset picker to the selected region's legal set, switches to the region's default when the current preset is illegal, and warns on licensed (ham) bands. Falls back to the full list when the map or region is absent, and only consults the map on 2.8 firmware. Firmware gating: - New 2.8 regions (ham / EU-narrow) and presets (lite / narrow / tiny) are hidden on radios reporting firmware < 2.8.0 via the RegionCodes / ModemPresets selectable(supports2_8:) helpers. Docs: - Add a developer page for the region-preset map, update the LoRa and Traffic Management user docs, add what's-new entries, and regenerate the in-app documentation bundle. Tests: LoRaRegionPresetMap decoding and firmware-gating suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bad8024 commit c6284f9

44 files changed

Lines changed: 3689 additions & 1150 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Localizable.xcstrings

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14062,9 +14062,6 @@
1406214062
},
1406314063
"Bitrate" : {
1406414064

14065-
},
14066-
"Bits" : {
14067-
1406814065
},
1406914066
"BLE" : {
1407014067
"localizations" : {
@@ -34069,12 +34066,6 @@
3406934066
}
3407034067
}
3407134068
}
34072-
},
34073-
"Exhaust Hop Position" : {
34074-
34075-
},
34076-
"Exhaust Hop Telemetry" : {
34077-
3407834069
},
3407934070
"Expanded" : {
3408034071

@@ -40992,9 +40983,6 @@
4099240983
}
4099340984
}
4099440985
}
40995-
},
40996-
"Hop Management" : {
40997-
4099840986
},
4099940987
"Hops" : {
4100040988

@@ -47160,6 +47148,9 @@
4716047148
}
4716147149
}
4716247150
}
47151+
},
47152+
"Licensed band" : {
47153+
4716347154
},
4716447155
"Licensed Operator" : {
4716547156
"localizations" : {
@@ -50472,9 +50463,15 @@
5047250463
},
5047350464
"Max Transmit Power" : {
5047450465

50466+
},
50467+
"Maximum hop distance from the requestor at which direct NodeInfo responses are served from the local cache." : {
50468+
5047550469
},
5047650470
"Maximum packets allowed per node within the rate limit window." : {
5047750471

50472+
},
50473+
"Maximum unknown/undecryptable packets per rate window before the source is dropped." : {
50474+
5047850475
},
5047950476
"Mesh activity update" : {
5048050477
"localizations" : {
@@ -52348,9 +52345,6 @@
5234852345
}
5234952346
}
5235052347
}
52351-
},
52352-
"Minimum hop distance from requestor before responding to NodeInfo requests." : {
52353-
5235452348
},
5235552349
"Minimum interval in seconds between position updates from the same node." : {
5235652350

@@ -57511,9 +57505,6 @@
5751157505
},
5751257506
"NTP Server" : {
5751357507

57514-
},
57515-
"Number of bits of precision for position deduplication (0-32)." : {
57516-
5751757508
},
5751857509
"Number of hops" : {
5751957510
"localizations" : {
@@ -57760,9 +57751,6 @@
5776057751
}
5776157752
}
5776257753
}
57763-
},
57764-
"Number of unknown packets before dropping from a node." : {
57765-
5776657754
},
5776757755
"Offline Node" : {
5776857756
"comment" : "A description of an offline node.",
@@ -64412,12 +64400,6 @@
6441264400
}
6441364401
}
6441464402
}
64415-
},
64416-
"Precision Bits" : {
64417-
64418-
},
64419-
"Preserve hop_limit for router-to-router traffic." : {
64420-
6442164403
},
6442264404
"Presets" : {
6442364405
"localizations" : {
@@ -71837,9 +71819,6 @@
7183771819
}
7183871820
}
7183971821
}
71840-
},
71841-
"Router Preserve Hops" : {
71842-
7184371822
},
7184471823
"Routes" : {
7184571824
"localizations" : {
@@ -78375,12 +78354,6 @@
7837578354
}
7837678355
}
7837778356
}
78378-
},
78379-
"Set hop_limit to 0 for relayed position broadcasts (own packets unaffected)." : {
78380-
78381-
},
78382-
"Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected)." : {
78383-
7838478357
},
7838578358
"Set LoRa Region" : {
7838678359
"localizations" : {

Meshtastic/Accessory/Accessory Manager/AccessoryManager+FromRadio.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,17 @@ extension AccessoryManager {
240240
}
241241
}
242242

243+
/// Decode the region → legal-preset map the radio advertises during the
244+
/// want_config handshake (2.8+). Stored on the AccessoryManager so the LoRa
245+
/// config screen can constrain its preset picker to the selected region's
246+
/// legal set. Older firmware never sends this; the map simply stays empty and
247+
/// the UI falls back to its unconstrained behavior.
248+
func handleRegionPresets(_ regionPresets: LoRaRegionPresetMap) {
249+
let decoded = regionPresets.decoded()
250+
loRaRegionPresets = decoded
251+
Logger.services.info("✅ [handleRegionPresets] decoded \(decoded.count, privacy: .public) region(s) from \(regionPresets.groups.count, privacy: .public) preset group(s)")
252+
}
253+
243254
func handleDeviceMetadata(_ metadata: DeviceMetadata) async {
244255
// Note: moved firmware version check to be inline with connection process
245256
guard let device = activeConnection?.device, let deviceNum = device.num else {

Meshtastic/Accessory/Accessory Manager/AccessoryManager.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
183183
@Published var isInBackground: Bool = false
184184
@Published var firmwareEdition: FirmwareEditions = .vanilla
185185

186+
/// Region → legal-preset lookup advertised by the connected radio during the
187+
/// want_config handshake (FromRadio.region_presets, 2.8+). Empty when the
188+
/// firmware predates the feature or hasn't sent it yet — callers must treat an
189+
/// absent region (or an empty map) as "no constraint". Reset on disconnect.
190+
@Published var loRaRegionPresets: [Config.LoRaConfig.RegionCode: RegionPresetInfo] = [:]
191+
186192
var activeConnection: (device: Device, connection: any Connection)?
187193

188194
/// Reference to the active discovery scan engine, if any
@@ -461,6 +467,7 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
461467
self.isConnected = false
462468
self.isConnecting = false
463469
self.firmwareEdition = .vanilla
470+
self.loRaRegionPresets = [:]
464471
case .connecting, .communicating, .retrying:
465472
self.isConnected = false
466473
self.isConnecting = true
@@ -762,6 +769,8 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
762769
await MeshPackets.shared.paxCounterPacket(packet: decodedInfo.packet)
763770
case .mapReportApp:
764771
Logger.mesh.info("[Map Report] packet received from \(packet.from.toHex(), privacy: .public)")
772+
case .meshBeaconApp:
773+
Logger.mesh.info("[Mesh Beacon] packet received from \(packet.from.toHex(), privacy: .public)")
765774
case .UNRECOGNIZED:
766775
Logger.mesh.info("[Unrecognized] packet received from \(packet.from.toHex(), privacy: .public)")
767776
case .max:
@@ -811,6 +820,9 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
811820
case .metadata(let metadata):
812821
await handleDeviceMetadata(metadata)
813822

823+
case .regionPresets(let regionPresets):
824+
handleRegionPresets(regionPresets)
825+
814826
case .deviceuiConfig:
815827
#if DEBUG
816828
Logger.admin.info("🕸️ MESH PACKET received for deviceUIConfig UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")

0 commit comments

Comments
 (0)