Configure protobufs, set up each type of data being streamed over BLE to be saved

This commit is contained in:
Garth Vander Houwen 2021-09-12 11:37:19 -07:00
parent 693aec1fd3
commit da9e01b4e7
21 changed files with 6347 additions and 81 deletions

View file

@ -3,12 +3,23 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; };
DDAF8C5526EBA0530058C060 /* Bluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5426EBA0530058C060 /* Bluetooth.swift */; };
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */; };
DDAF8C5B26ED08D30058C060 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DDAF8C5A26ED08D30058C060 /* SwiftProtobuf */; };
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5C26ED09490058C060 /* portnums.pb.swift */; };
DDAF8C5F26ED09B50058C060 /* radioconfig.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5E26ED09B50058C060 /* radioconfig.pb.swift */; };
DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6026ED0A230058C060 /* mqtt.pb.swift */; };
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6126ED0A230058C060 /* admin.pb.swift */; };
DDAF8C6526ED0A490058C060 /* channel.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6426ED0A490058C060 /* channel.pb.swift */; };
DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6626ED0C8C0058C060 /* remote_hardware.pb.swift */; };
DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6826ED0D070058C060 /* deviceonly.pb.swift */; };
DDAF8C6B26ED0DD80058C060 /* environmental_measurement.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6A26ED0DD80058C060 /* environmental_measurement.pb.swift */; };
DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6D26ED19040058C060 /* Extensions.swift */; };
DDAF8C7026ED1DD20058C060 /* Device2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6F26ED1DD20058C060 /* Device2.swift */; };
DDC2E15826CE248E0042C5E4 /* MeshtasticClientApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticClientApp.swift */; };
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; };
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; };
@ -48,7 +59,18 @@
/* Begin PBXFileReference section */
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = "<group>"; };
DDAF8C5426EBA0530058C060 /* Bluetooth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bluetooth.swift; sourceTree = "<group>"; };
DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = "<group>"; };
DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = "<group>"; };
DDAF8C5E26ED09B50058C060 /* radioconfig.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = radioconfig.pb.swift; sourceTree = "<group>"; };
DDAF8C6026ED0A230058C060 /* mqtt.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mqtt.pb.swift; sourceTree = "<group>"; };
DDAF8C6126ED0A230058C060 /* admin.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = admin.pb.swift; sourceTree = "<group>"; };
DDAF8C6426ED0A490058C060 /* channel.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = channel.pb.swift; sourceTree = "<group>"; };
DDAF8C6626ED0C8C0058C060 /* remote_hardware.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = remote_hardware.pb.swift; sourceTree = "<group>"; };
DDAF8C6826ED0D070058C060 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = "<group>"; };
DDAF8C6A26ED0DD80058C060 /* environmental_measurement.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = environmental_measurement.pb.swift; sourceTree = "<group>"; };
DDAF8C6D26ED19040058C060 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
DDAF8C6F26ED1DD20058C060 /* Device2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Device2.swift; sourceTree = "<group>"; };
DDAF8C7126ED2AD80058C060 /* nodeInfo.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = nodeInfo.json; sourceTree = "<group>"; };
DDC2E15426CE248E0042C5E4 /* MeshtasticClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeshtasticClient.app; sourceTree = BUILT_PRODUCTS_DIR; };
DDC2E15726CE248E0042C5E4 /* MeshtasticClientApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticClientApp.swift; sourceTree = "<group>"; };
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -80,6 +102,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DDAF8C5B26ED08D30058C060 /* SwiftProtobuf in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -100,6 +123,22 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
DDAF8C5626ED07740058C060 /* Protobufs */ = {
isa = PBXGroup;
children = (
DDAF8C5E26ED09B50058C060 /* radioconfig.pb.swift */,
DDAF8C6426ED0A490058C060 /* channel.pb.swift */,
DDAF8C6826ED0D070058C060 /* deviceonly.pb.swift */,
DDAF8C5C26ED09490058C060 /* portnums.pb.swift */,
DDAF8C6626ED0C8C0058C060 /* remote_hardware.pb.swift */,
DDAF8C6A26ED0DD80058C060 /* environmental_measurement.pb.swift */,
DDAF8C6126ED0A230058C060 /* admin.pb.swift */,
DDAF8C6026ED0A230058C060 /* mqtt.pb.swift */,
DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */,
);
path = Protobufs;
sourceTree = "<group>";
};
DDC2E14B26CE248E0042C5E4 = {
isa = PBXGroup;
children = (
@ -123,6 +162,7 @@
DDC2E15626CE248E0042C5E4 /* MeshtasticClient */ = {
isa = PBXGroup;
children = (
DDAF8C5626ED07740058C060 /* Protobufs */,
DDC2E1A526CEB32B0042C5E4 /* Helpers */,
DDC2E18726CE24E40042C5E4 /* Views */,
DDC2E18826CE24EE0042C5E4 /* Model */,
@ -177,6 +217,7 @@
children = (
DDC2E19C26CE27580042C5E4 /* ModelData.swift */,
DDC2E19E26CE27630042C5E4 /* Device.swift */,
DDAF8C6F26ED1DD20058C060 /* Device2.swift */,
);
path = Model;
sourceTree = "<group>";
@ -186,6 +227,7 @@
children = (
DDC2E18A26CE25690042C5E4 /* deviceData.json */,
DDC2E1AA26DD89EC0042C5E4 /* packets.json */,
DDAF8C7126ED2AD80058C060 /* nodeInfo.json */,
);
path = Resources;
sourceTree = "<group>";
@ -206,7 +248,6 @@
DDC2E19626CE26840042C5E4 /* DeviceRow.swift */,
DDC2E19826CE26940042C5E4 /* DeviceMap.swift */,
DDC2E1A326CE2F940042C5E4 /* DeviceBLE.swift */,
DDAF8C5426EBA0530058C060 /* Bluetooth.swift */,
);
path = Devices;
sourceTree = "<group>";
@ -215,7 +256,6 @@
isa = PBXGroup;
children = (
DDC2E19A26CE27150042C5E4 /* CircleImage.swift */,
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -223,7 +263,9 @@
DDC2E1A526CEB32B0042C5E4 /* Helpers */ = {
isa = PBXGroup;
children = (
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */,
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */,
DDAF8C6D26ED19040058C060 /* Extensions.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -244,6 +286,9 @@
dependencies = (
);
name = MeshtasticClient;
packageProductDependencies = (
DDAF8C5A26ED08D30058C060 /* SwiftProtobuf */,
);
productName = MeshtasticClient;
productReference = DDC2E15426CE248E0042C5E4 /* MeshtasticClient.app */;
productType = "com.apple.product-type.application";
@ -315,6 +360,9 @@
Base,
);
mainGroup = DDC2E14B26CE248E0042C5E4;
packageReferences = (
DDAF8C5926ED08D30058C060 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
);
productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -359,19 +407,29 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */,
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */,
DDAF8C7026ED1DD20058C060 /* Device2.swift in Sources */,
DDC2E19F26CE27630042C5E4 /* Device.swift in Sources */,
DDC2E19926CE26940042C5E4 /* DeviceMap.swift in Sources */,
DDC2E19526CE26760042C5E4 /* DeviceDetail.swift in Sources */,
DDAF8C5F26ED09B50058C060 /* radioconfig.pb.swift in Sources */,
DDC2E19726CE26840042C5E4 /* DeviceRow.swift in Sources */,
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
DDAF8C5526EBA0530058C060 /* Bluetooth.swift in Sources */,
DDC2E19126CE26290042C5E4 /* Messages.swift in Sources */,
DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */,
DDC2E19D26CE27580042C5E4 /* ModelData.swift in Sources */,
DDAF8C6B26ED0DD80058C060 /* environmental_measurement.pb.swift in Sources */,
DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */,
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */,
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,
DDC2E19326CE266B0042C5E4 /* DeviceHome.swift in Sources */,
DDC2E19B26CE27150042C5E4 /* CircleImage.swift in Sources */,
DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */,
DDC2E1A426CE2F940042C5E4 /* DeviceBLE.swift in Sources */,
DDAF8C6526ED0A490058C060 /* channel.pb.swift in Sources */,
DDC2E15826CE248E0042C5E4 /* MeshtasticClientApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -692,6 +750,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
DDAF8C5926ED08D30058C060 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-protobuf.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.17.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
DDAF8C5A26ED08D30058C060 /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = DDAF8C5926ED08D30058C060 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = DDC2E14C26CE248E0042C5E4 /* Project object */;
}

View file

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:/Users/garthvanderhouwen/Documents/source/MeshtasticClient/Meshtastic Client.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View file

@ -63,10 +63,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
// Start Scanning
print("Start Scanning")
centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID])
// scanningLabel.text = "Scanning..." }
//Timer.scheduledTimer(withTimeInterval: 15, repeats: false) {_ in
// self.stopScanning()
//}
}
//---------------------------------------------------------------------------------------
@ -74,7 +70,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
//---------------------------------------------------------------------------------------
func stopScanning() {
print("Stop Scanning")
centralManager.stopScan()
self.centralManager.stopScan()
}
//---------------------------------------------------------------------------------------
@ -82,7 +78,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
//---------------------------------------------------------------------------------------
func connectToDevice(id: String) {
connectedPeripheral = peripheralArray.filter({ $0.identifier.uuidString == id }).first
centralManager?.connect(connectedPeripheral!)
self.centralManager?.connect(connectedPeripheral!)
}
//---------------------------------------------------------------------------------------
@ -90,7 +86,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
//---------------------------------------------------------------------------------------
func disconnectDevice(){
if connectedPeripheral != nil {
centralManager?.cancelPeripheralConnection(connectedPeripheral!)
self.centralManager?.cancelPeripheralConnection(connectedPeripheral!)
}
}
@ -169,7 +165,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
//---------------------------------------------------------------------------------------
// Discover Characteristics Event
//---------------------------------------------------------------------------------------
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
@ -179,10 +176,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
case TORADIO_UUID:
print("TORADIO characteristic OK")
TORADIO_characteristic = characteristic
// var toRadio: ToRadio = ToRadio()
// toRadio.wantConfigID = 32168
// let binaryData: Data = try! toRadio.serializedData()
// peripheral.writeValue(binaryData, for: characteristic, type: .withResponse)
var toRadio: ToRadio = ToRadio()
toRadio.wantConfigID = 32168
let binaryData: Data = try! toRadio.serializedData()
peripheral.writeValue(binaryData, for: characteristic, type: .withResponse)
break
case FROMRADIO_UUID:
@ -205,15 +202,68 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
//---------------------------------------------------------------------------------------
// Update Characteristic Event
// Data Read / Update Characteristic Event
//---------------------------------------------------------------------------------------
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
switch characteristic.uuid {
case FROMRADIO_UUID:
print(characteristic.value ?? "no value")
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
{
switch characteristic.uuid
{
case FROMNUM_UUID:
peripheral.readValue(for: FROMRADIO_characteristic)
case FROMRADIO_UUID:
if (characteristic.value == nil || characteristic.value!.isEmpty)
{
return
}
//print(characteristic.value ?? "no value")
//let byteArray = [UInt8](characteristic.value!)
//print(characteristic.value?.hexDescription ?? "no value")
var decodedInfo = FromRadio()
decodedInfo = try! FromRadio(serializedData: characteristic.value!)
//print(decodedInfo.myInfo.myNodeNum)
if decodedInfo.myInfo.myNodeNum != 0
{
print("Save a myInfo")
do {
print(try decodedInfo.myInfo.jsonString())
} catch {
fatalError("Failed to decode json")
}
}
if decodedInfo.nodeInfo.num != 0
{
print("Save a nodeInfo")
do {
print(try decodedInfo.nodeInfo.jsonString())
} catch {
fatalError("Failed to decode json")
}
}
if decodedInfo.packet.from != 0
{
print("Save a packet")
do {
print(try decodedInfo.packet.jsonString())
} catch {
fatalError("Failed to decode json")
}
}
if decodedInfo.configCompleteID != 0 {
print(decodedInfo)
}
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
peripheral.readValue(for: FROMRADIO_characteristic)
}
}

View file

@ -0,0 +1,46 @@
import Foundation
extension Data
{
var hexDescription: String
{
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
extension String
{
/// Create `Data` from hexadecimal string representation
///
/// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
///
/// - returns: Data represented by this hexadecimal string.
var hexadecimal: Data?
{
var data = Data(capacity: count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
let byteString = (self as NSString).substring(with: match!.range)
let num = UInt8(byteString, radix: 16)!
data.append(num)
}
guard data.count > 0 else { return nil }
return data
}
}
extension Date
{
static var currentTimeStamp: Int64
{
return Int64(Date().timeIntervalSince1970 * 1000)
}
}

View file

@ -1,10 +1,3 @@
//
// LocationHelper.swift
// RChat
//
// Created by Andrew Morgan on 24/11/2020.
//
import CoreLocation
struct MyAnnotationItem: Identifiable {

View file

@ -0,0 +1,45 @@
/*
See LICENSE folder for this apps licensing information.
Abstract:
A representation of a single device.
*/
import Foundation
import SwiftUI
import CoreLocation
struct Device2: Hashable, Codable, Identifiable {
var id: String
var num: Int32
struct MyInfo: Hashable, Codable {
var hasGps: Bool
var numBands: Int32
var maxChannels: Int32
var firmwareVersion: String
var rebootCount: Int32
var messageTimeoutMsec: Int32
var minAppVersion: Int32
}
struct User: Hashable, Codable {
var id: String
var longName: String
var shortName: String
var macaddr: String
var hwModel: String
}
struct Position: Hashable, Codable {
var latitude: Int32
var longitude: Int32
var altitude: Int32
var batteryLevel: Int
}
var lastHeard: Int32
}

View file

@ -0,0 +1,447 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: admin.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
///
/// This message is handled by the Admin plugin and is responsible for all settings/channel read/write operations.
/// This message is used to do settings operations to both remote AND local nodes.
/// (Prior to 1.2 these operations were done via special ToRadio operations)
struct AdminMessage {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var variant: AdminMessage.OneOf_Variant? = nil
///
/// set the radio provisioning for this node
var setRadio: RadioConfig {
get {
if case .setRadio(let v)? = variant {return v}
return RadioConfig()
}
set {variant = .setRadio(newValue)}
}
///
/// Set the owner for this node
var setOwner: User {
get {
if case .setOwner(let v)? = variant {return v}
return User()
}
set {variant = .setOwner(newValue)}
}
///
/// Set channels (using the new API).
/// A special channel is the "primary channel".
/// The other records are secondary channels.
/// Note: only one channel can be marked as primary.
/// If the client sets a particular channel to be primary, the previous channel will be set to SECONDARY automatically.
var setChannel: Channel {
get {
if case .setChannel(let v)? = variant {return v}
return Channel()
}
set {variant = .setChannel(newValue)}
}
///
/// Send the current RadioConfig in the response for this message.
var getRadioRequest: Bool {
get {
if case .getRadioRequest(let v)? = variant {return v}
return false
}
set {variant = .getRadioRequest(newValue)}
}
var getRadioResponse: RadioConfig {
get {
if case .getRadioResponse(let v)? = variant {return v}
return RadioConfig()
}
set {variant = .getRadioResponse(newValue)}
}
///
/// Send the specified channel in the response for this message
/// NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
var getChannelRequest: UInt32 {
get {
if case .getChannelRequest(let v)? = variant {return v}
return 0
}
set {variant = .getChannelRequest(newValue)}
}
var getChannelResponse: Channel {
get {
if case .getChannelResponse(let v)? = variant {return v}
return Channel()
}
set {variant = .getChannelResponse(newValue)}
}
///
/// Setting channels/radio config remotely carries the risk that you might send an invalid config and the radio never talks to your mesh again.
/// Therefore if setting either of these properties remotely, you must send a confirm_xxx message within 10 minutes.
/// If you fail to do so, the radio will assume loss of comms and revert your changes.
/// These messages are optional when changing the local node.
var confirmSetChannel: Bool {
get {
if case .confirmSetChannel(let v)? = variant {return v}
return false
}
set {variant = .confirmSetChannel(newValue)}
}
var confirmSetRadio: Bool {
get {
if case .confirmSetRadio(let v)? = variant {return v}
return false
}
set {variant = .confirmSetRadio(newValue)}
}
///
/// This message is only supported for the simulator porduino build.
/// If received the simulator will exit successfully.
var exitSimulator: Bool {
get {
if case .exitSimulator(let v)? = variant {return v}
return false
}
set {variant = .exitSimulator(newValue)}
}
///
/// Tell the node to reboot in this many seconds (or <0 to cancel reboot)
var rebootSeconds: Int32 {
get {
if case .rebootSeconds(let v)? = variant {return v}
return 0
}
set {variant = .rebootSeconds(newValue)}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
enum OneOf_Variant: Equatable {
///
/// set the radio provisioning for this node
case setRadio(RadioConfig)
///
/// Set the owner for this node
case setOwner(User)
///
/// Set channels (using the new API).
/// A special channel is the "primary channel".
/// The other records are secondary channels.
/// Note: only one channel can be marked as primary.
/// If the client sets a particular channel to be primary, the previous channel will be set to SECONDARY automatically.
case setChannel(Channel)
///
/// Send the current RadioConfig in the response for this message.
case getRadioRequest(Bool)
case getRadioResponse(RadioConfig)
///
/// Send the specified channel in the response for this message
/// NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
case getChannelRequest(UInt32)
case getChannelResponse(Channel)
///
/// Setting channels/radio config remotely carries the risk that you might send an invalid config and the radio never talks to your mesh again.
/// Therefore if setting either of these properties remotely, you must send a confirm_xxx message within 10 minutes.
/// If you fail to do so, the radio will assume loss of comms and revert your changes.
/// These messages are optional when changing the local node.
case confirmSetChannel(Bool)
case confirmSetRadio(Bool)
///
/// This message is only supported for the simulator porduino build.
/// If received the simulator will exit successfully.
case exitSimulator(Bool)
///
/// Tell the node to reboot in this many seconds (or <0 to cancel reboot)
case rebootSeconds(Int32)
#if !swift(>=4.1)
static func ==(lhs: AdminMessage.OneOf_Variant, rhs: AdminMessage.OneOf_Variant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.setRadio, .setRadio): return {
guard case .setRadio(let l) = lhs, case .setRadio(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setOwner, .setOwner): return {
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setChannel, .setChannel): return {
guard case .setChannel(let l) = lhs, case .setChannel(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getRadioRequest, .getRadioRequest): return {
guard case .getRadioRequest(let l) = lhs, case .getRadioRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getRadioResponse, .getRadioResponse): return {
guard case .getRadioResponse(let l) = lhs, case .getRadioResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getChannelRequest, .getChannelRequest): return {
guard case .getChannelRequest(let l) = lhs, case .getChannelRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getChannelResponse, .getChannelResponse): return {
guard case .getChannelResponse(let l) = lhs, case .getChannelResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.confirmSetChannel, .confirmSetChannel): return {
guard case .confirmSetChannel(let l) = lhs, case .confirmSetChannel(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.confirmSetRadio, .confirmSetRadio): return {
guard case .confirmSetRadio(let l) = lhs, case .confirmSetRadio(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.exitSimulator, .exitSimulator): return {
guard case .exitSimulator(let l) = lhs, case .exitSimulator(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.rebootSeconds, .rebootSeconds): return {
guard case .rebootSeconds(let l) = lhs, case .rebootSeconds(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "AdminMessage"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "set_radio"),
2: .standard(proto: "set_owner"),
3: .standard(proto: "set_channel"),
4: .standard(proto: "get_radio_request"),
5: .standard(proto: "get_radio_response"),
6: .standard(proto: "get_channel_request"),
7: .standard(proto: "get_channel_response"),
32: .standard(proto: "confirm_set_channel"),
33: .standard(proto: "confirm_set_radio"),
34: .standard(proto: "exit_simulator"),
35: .standard(proto: "reboot_seconds"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try {
var v: RadioConfig?
var hadOneofValue = false
if let current = self.variant {
hadOneofValue = true
if case .setRadio(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.variant = .setRadio(v)
}
}()
case 2: try {
var v: User?
var hadOneofValue = false
if let current = self.variant {
hadOneofValue = true
if case .setOwner(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.variant = .setOwner(v)
}
}()
case 3: try {
var v: Channel?
var hadOneofValue = false
if let current = self.variant {
hadOneofValue = true
if case .setChannel(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.variant = .setChannel(v)
}
}()
case 4: try {
var v: Bool?
try decoder.decodeSingularBoolField(value: &v)
if let v = v {
if self.variant != nil {try decoder.handleConflictingOneOf()}
self.variant = .getRadioRequest(v)
}
}()
case 5: try {
var v: RadioConfig?
var hadOneofValue = false
if let current = self.variant {
hadOneofValue = true
if case .getRadioResponse(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.variant = .getRadioResponse(v)
}
}()
case 6: try {
var v: UInt32?
try decoder.decodeSingularUInt32Field(value: &v)
if let v = v {
if self.variant != nil {try decoder.handleConflictingOneOf()}
self.variant = .getChannelRequest(v)
}
}()
case 7: try {
var v: Channel?
var hadOneofValue = false
if let current = self.variant {
hadOneofValue = true
if case .getChannelResponse(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.variant = .getChannelResponse(v)
}
}()
case 32: try {
var v: Bool?
try decoder.decodeSingularBoolField(value: &v)
if let v = v {
if self.variant != nil {try decoder.handleConflictingOneOf()}
self.variant = .confirmSetChannel(v)
}
}()
case 33: try {
var v: Bool?
try decoder.decodeSingularBoolField(value: &v)
if let v = v {
if self.variant != nil {try decoder.handleConflictingOneOf()}
self.variant = .confirmSetRadio(v)
}
}()
case 34: try {
var v: Bool?
try decoder.decodeSingularBoolField(value: &v)
if let v = v {
if self.variant != nil {try decoder.handleConflictingOneOf()}
self.variant = .exitSimulator(v)
}
}()
case 35: try {
var v: Int32?
try decoder.decodeSingularInt32Field(value: &v)
if let v = v {
if self.variant != nil {try decoder.handleConflictingOneOf()}
self.variant = .rebootSeconds(v)
}
}()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch self.variant {
case .setRadio?: try {
guard case .setRadio(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}()
case .setOwner?: try {
guard case .setOwner(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}()
case .setChannel?: try {
guard case .setChannel(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}()
case .getRadioRequest?: try {
guard case .getRadioRequest(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 4)
}()
case .getRadioResponse?: try {
guard case .getRadioResponse(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
}()
case .getChannelRequest?: try {
guard case .getChannelRequest(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6)
}()
case .getChannelResponse?: try {
guard case .getChannelResponse(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
}()
case .confirmSetChannel?: try {
guard case .confirmSetChannel(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 32)
}()
case .confirmSetRadio?: try {
guard case .confirmSetRadio(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 33)
}()
case .exitSimulator?: try {
guard case .exitSimulator(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 34)
}()
case .rebootSeconds?: try {
guard case .rebootSeconds(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularInt32Field(value: v, fieldNumber: 35)
}()
case nil: break
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: AdminMessage, rhs: AdminMessage) -> Bool {
if lhs.variant != rhs.variant {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -0,0 +1,491 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: channel.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
///
/// Meshtastic protobufs
///
/// For more information on protobufs (and tools to use them with the language of your choice) see
/// https://developers.google.com/protocol-buffers/docs/proto3
///
/// We are not placing any of these defs inside a package, because if you do the
/// resulting nanopb version is super verbose package mesh.
///
/// Protobuf build instructions:
///
/// To build java classes for reading writing:
/// protoc -I=. --java_out /tmp mesh.proto
///
/// To generate Nanopb c code:
/// /home/kevinh/packages/nanopb-0.4.0-linux-x86/generator-bin/protoc --nanopb_out=/tmp -I=app/src/main/proto mesh.proto
///
/// Nanopb binaries available here: https://jpa.kapsi.fi/nanopb/download/ use nanopb 0.4.0
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
///
/// Full settings (center freq, spread factor, pre-shared secret key etc...)
/// needed to configure a radio for speaking on a particular channel This
/// information can be encoded as a QRcode/url so that other users can configure
/// their radio to join the same channel.
/// A note about how channel names are shown to users: channelname-Xy
/// poundsymbol is a prefix used to indicate this is a channel name (idea from @professr).
/// Where X is a letter from A-Z (base 26) representing a hash of the PSK for this
/// channel - so that if the user changes anything about the channel (which does
/// force a new PSK) this letter will also change. Thus preventing user confusion if
/// two friends try to type in a channel name of "BobsChan" and then can't talk
/// because their PSKs will be different.
/// The PSK is hashed into this letter by "0x41 + [xor all bytes of the psk ] modulo 26"
/// This also allows the option of someday if people have the PSK off (zero), the
/// users COULD type in a channel name and be able to talk.
/// Y is a lower case letter from a-z that represents the channel 'speed' settings
/// (for some future definition of speed)
///
/// FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
/// FIXME: explain how apps use channels for security.
/// explain how remote settings and remote gpio are managed as an example
struct ChannelSettings {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// If zero then, use default max legal continuous power (ie. something that won't
/// burn out the radio hardware)
/// In most cases you should use zero here.
/// Units are in dBm.
var txPower: Int32 = 0
///
/// Note: This is the 'old' mechanism for specifying channel parameters.
/// Either modem_config or bandwidth/spreading/coding will be specified - NOT BOTH.
/// As a heuristic: If bandwidth is specified, do not use modem_config.
/// Because protobufs take ZERO space when the value is zero this works out nicely.
/// This value is replaced by bandwidth/spread_factor/coding_rate.
/// If you'd like to experiment with other options add them to MeshRadio.cpp in the device code.
var modemConfig: ChannelSettings.ModemConfig = .bw125Cr45Sf128
///
/// Bandwidth in MHz
/// Certain bandwidth numbers are 'special' and will be converted to the
/// appropriate floating point value: 31 -> 31.25MHz
var bandwidth: UInt32 = 0
///
/// A number from 7 to 12.
/// Indicates number of chirps per symbol as 1<<spread_factor.
var spreadFactor: UInt32 = 0
///
/// The denominator of the coding rate.
/// ie for 4/8, the value is 8. 5/8 the value is 5.
var codingRate: UInt32 = 0
///
/// NOTE: this field is _independent_ and unrelated to the concepts in channel.proto.
/// this is controlling the actual hardware frequency the radio is transmitting on.
/// In a perfect world we would have called it something else (band?) but I forgot to make this change during the big 1.2 renaming.
/// Most users should never need to be exposed to this field/concept.
/// A channel number between 1 and 13 (or whatever the max is in the current
/// region). If ZERO then the rule is "use the old channel name hash based
/// algorithm to derive the channel number")
/// If using the hash algorithm the channel number will be: hash(channel_name) %
/// NUM_CHANNELS (Where num channels depends on the regulatory region).
/// NUM_CHANNELS_US is 13, for other values see MeshRadio.h in the device code.
/// hash a string into an integer - djb2 by Dan Bernstein. -
/// http://www.cse.yorku.ca/~oz/hash.html
/// unsigned long hash(char *str) {
/// unsigned long hash = 5381; int c;
/// while ((c = *str++) != 0)
/// hash = ((hash << 5) + hash) + (unsigned char) c;
/// return hash;
/// }
var channelNum: UInt32 = 0
///
/// A simple pre-shared key for now for crypto.
/// Must be either 0 bytes (no crypto), 16 bytes (AES128), or 32 bytes (AES256).
/// A special shorthand is used for 1 byte long psks.
/// These psks should be treated as only minimally secure,
/// because they are listed in this source code.
/// Those bytes are mapped using the following scheme:
/// `0` = No crypto
/// `1` = The special "default" channel key: {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf}
/// `2` through 10 = The default channel key, except with 1 through 9 added to the last byte.
/// Shown to user as simple1 through 10
var psk: Data = Data()
///
/// A SHORT name that will be packed into the URL.
/// Less than 12 bytes.
/// Something for end users to call the channel
/// If this is the empty string it is assumed that this channel
/// is the special (minimally secure) "Default"channel.
/// In user interfaces it should be rendered as a local language translation of "X".
/// For channel_num hashing empty string will be treated as "X".
/// Where "X" is selected based on the English words listed above for ModemConfig
var name: String = String()
///
/// Used to construct a globally unique channel ID.
/// The full globally unique ID will be: "name.id" where ID is shown as base36.
/// Assuming that the number of meshtastic users is below 20K (true for a long time)
/// the chance of this 64 bit random number colliding with anyone else is super low.
/// And the penalty for collision is low as well, it just means that anyone trying to decrypt channel messages might need to
/// try multiple candidate channels.
/// Any time a non wire compatible change is made to a channel, this field should be regenerated.
/// There are a small number of 'special' globally known (and fairly) insecure standard channels.
/// Those channels do not have a numeric id included in the settings, but instead it is pulled from
/// a table of well known IDs.
/// (see Well Known Channels FIXME)
var id: UInt32 = 0
///
/// If true, messages on the mesh will be sent to the *public* internet by any gateway ndoe
var uplinkEnabled: Bool = false
///
/// If true, messages seen on the internet will be forwarded to the local mesh.
var downlinkEnabled: Bool = false
var unknownFields = SwiftProtobuf.UnknownStorage()
///
/// Standard predefined channel settings
/// Note: these mappings must match ModemConfigChoice in the device code.
enum ModemConfig: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// < Bw = 125 kHz, Cr = 4/5, Sf(7) = 128chips/symbol, CRC
/// < on. Default medium range (5.469 kbps)
case bw125Cr45Sf128 // = 0
///
/// < Bw = 500 kHz, Cr = 4/5, Sf(7) = 128chips/symbol, CRC
/// < on. Fast+short range (21.875 kbps)
case bw500Cr45Sf128 // = 1
///
/// < Bw = 31.25 kHz, Cr = 4/8, Sf(9) = 512chips/symbol,
/// < CRC on. Slow+long range (275 bps)
case bw3125Cr48Sf512 // = 2
///
/// < Bw = 125 kHz, Cr = 4/8, Sf(12) = 4096chips/symbol, CRC
/// < on. Slow+long range (183 bps)
case bw125Cr48Sf4096 // = 3
case UNRECOGNIZED(Int)
init() {
self = .bw125Cr45Sf128
}
init?(rawValue: Int) {
switch rawValue {
case 0: self = .bw125Cr45Sf128
case 1: self = .bw500Cr45Sf128
case 2: self = .bw3125Cr48Sf512
case 3: self = .bw125Cr48Sf4096
default: self = .UNRECOGNIZED(rawValue)
}
}
var rawValue: Int {
switch self {
case .bw125Cr45Sf128: return 0
case .bw500Cr45Sf128: return 1
case .bw3125Cr48Sf512: return 2
case .bw125Cr48Sf4096: return 3
case .UNRECOGNIZED(let i): return i
}
}
}
init() {}
}
#if swift(>=4.2)
extension ChannelSettings.ModemConfig: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [ChannelSettings.ModemConfig] = [
.bw125Cr45Sf128,
.bw500Cr45Sf128,
.bw3125Cr48Sf512,
.bw125Cr48Sf4096,
]
}
#endif // swift(>=4.2)
///
/// A pair of a channel number, mode and the (sharable) settings for that channel
struct Channel {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The index of this channel in the channel table (from 0 to MAX_NUM_CHANNELS-1)
/// (Someday - not currently implemented) An index of -1 could be used to mean "set by name",
/// in which case the target node will find and set the channel by settings.name.
var index: Int32 = 0
///
/// The new settings, or NULL to disable that channel
var settings: ChannelSettings {
get {return _settings ?? ChannelSettings()}
set {_settings = newValue}
}
/// Returns true if `settings` has been explicitly set.
var hasSettings: Bool {return self._settings != nil}
/// Clears the value of `settings`. Subsequent reads from it will return its default value.
mutating func clearSettings() {self._settings = nil}
var role: Channel.Role = .disabled
var unknownFields = SwiftProtobuf.UnknownStorage()
///
/// How this channel is being used (or not).
///
/// Note: this field is an enum to give us options for the future.
/// In particular, someday we might make a 'SCANNING' option.
/// SCANNING channels could have different frequencies and the radio would
/// occasionally check that freq to see if anything is being transmitted.
///
/// For devices that have multiple physical radios attached, we could keep multiple PRIMARY/SCANNING channels active at once to allow
/// cross band routing as needed.
/// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
/// (but any number of SECONDARY channels can't be sent received on that common frequency)
enum Role: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// This channel is not in use right now
case disabled // = 0
///
/// This channel is used to set the frequency for the radio - all other enabled channels must be SECONDARY
case primary // = 1
///
/// Secondary channels are only used for encryption/decryption/authentication purposes.
/// Their radio settings (freq etc) are ignored, only psk is used.
case secondary // = 2
case UNRECOGNIZED(Int)
init() {
self = .disabled
}
init?(rawValue: Int) {
switch rawValue {
case 0: self = .disabled
case 1: self = .primary
case 2: self = .secondary
default: self = .UNRECOGNIZED(rawValue)
}
}
var rawValue: Int {
switch self {
case .disabled: return 0
case .primary: return 1
case .secondary: return 2
case .UNRECOGNIZED(let i): return i
}
}
}
init() {}
fileprivate var _settings: ChannelSettings? = nil
}
#if swift(>=4.2)
extension Channel.Role: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [Channel.Role] = [
.disabled,
.primary,
.secondary,
]
}
#endif // swift(>=4.2)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension ChannelSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "ChannelSettings"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "tx_power"),
3: .standard(proto: "modem_config"),
6: .same(proto: "bandwidth"),
7: .standard(proto: "spread_factor"),
8: .standard(proto: "coding_rate"),
9: .standard(proto: "channel_num"),
4: .same(proto: "psk"),
5: .same(proto: "name"),
10: .same(proto: "id"),
16: .standard(proto: "uplink_enabled"),
17: .standard(proto: "downlink_enabled"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.txPower) }()
case 3: try { try decoder.decodeSingularEnumField(value: &self.modemConfig) }()
case 4: try { try decoder.decodeSingularBytesField(value: &self.psk) }()
case 5: try { try decoder.decodeSingularStringField(value: &self.name) }()
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.bandwidth) }()
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.spreadFactor) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.codingRate) }()
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.channelNum) }()
case 10: try { try decoder.decodeSingularFixed32Field(value: &self.id) }()
case 16: try { try decoder.decodeSingularBoolField(value: &self.uplinkEnabled) }()
case 17: try { try decoder.decodeSingularBoolField(value: &self.downlinkEnabled) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.txPower != 0 {
try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 1)
}
if self.modemConfig != .bw125Cr45Sf128 {
try visitor.visitSingularEnumField(value: self.modemConfig, fieldNumber: 3)
}
if !self.psk.isEmpty {
try visitor.visitSingularBytesField(value: self.psk, fieldNumber: 4)
}
if !self.name.isEmpty {
try visitor.visitSingularStringField(value: self.name, fieldNumber: 5)
}
if self.bandwidth != 0 {
try visitor.visitSingularUInt32Field(value: self.bandwidth, fieldNumber: 6)
}
if self.spreadFactor != 0 {
try visitor.visitSingularUInt32Field(value: self.spreadFactor, fieldNumber: 7)
}
if self.codingRate != 0 {
try visitor.visitSingularUInt32Field(value: self.codingRate, fieldNumber: 8)
}
if self.channelNum != 0 {
try visitor.visitSingularUInt32Field(value: self.channelNum, fieldNumber: 9)
}
if self.id != 0 {
try visitor.visitSingularFixed32Field(value: self.id, fieldNumber: 10)
}
if self.uplinkEnabled != false {
try visitor.visitSingularBoolField(value: self.uplinkEnabled, fieldNumber: 16)
}
if self.downlinkEnabled != false {
try visitor.visitSingularBoolField(value: self.downlinkEnabled, fieldNumber: 17)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: ChannelSettings, rhs: ChannelSettings) -> Bool {
if lhs.txPower != rhs.txPower {return false}
if lhs.modemConfig != rhs.modemConfig {return false}
if lhs.bandwidth != rhs.bandwidth {return false}
if lhs.spreadFactor != rhs.spreadFactor {return false}
if lhs.codingRate != rhs.codingRate {return false}
if lhs.channelNum != rhs.channelNum {return false}
if lhs.psk != rhs.psk {return false}
if lhs.name != rhs.name {return false}
if lhs.id != rhs.id {return false}
if lhs.uplinkEnabled != rhs.uplinkEnabled {return false}
if lhs.downlinkEnabled != rhs.downlinkEnabled {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension ChannelSettings.ModemConfig: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "Bw125Cr45Sf128"),
1: .same(proto: "Bw500Cr45Sf128"),
2: .same(proto: "Bw31_25Cr48Sf512"),
3: .same(proto: "Bw125Cr48Sf4096"),
]
}
extension Channel: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "Channel"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "index"),
2: .same(proto: "settings"),
3: .same(proto: "role"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularInt32Field(value: &self.index) }()
case 2: try { try decoder.decodeSingularMessageField(value: &self._settings) }()
case 3: try { try decoder.decodeSingularEnumField(value: &self.role) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.index != 0 {
try visitor.visitSingularInt32Field(value: self.index, fieldNumber: 1)
}
if let v = self._settings {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}
if self.role != .disabled {
try visitor.visitSingularEnumField(value: self.role, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: Channel, rhs: Channel) -> Bool {
if lhs.index != rhs.index {return false}
if lhs._settings != rhs._settings {return false}
if lhs.role != rhs.role {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Channel.Role: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "DISABLED"),
1: .same(proto: "PRIMARY"),
2: .same(proto: "SECONDARY"),
]
}

View file

@ -0,0 +1,402 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: deviceonly.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
///
/// This is a stub version of the old 1.1 representation of RadioConfig.
/// But only keeping the region info.
/// The device firmware uses this stub while migrating old nodes to the new preferences system.
struct LegacyRadioConfig {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var preferences: LegacyRadioConfig.LegacyPreferences {
get {return _preferences ?? LegacyRadioConfig.LegacyPreferences()}
set {_preferences = newValue}
}
/// Returns true if `preferences` has been explicitly set.
var hasPreferences: Bool {return self._preferences != nil}
/// Clears the value of `preferences`. Subsequent reads from it will return its default value.
mutating func clearPreferences() {self._preferences = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
struct LegacyPreferences {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The region code for my radio (US, CN, EU433, etc...)
var region: RegionCode = .unset
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
init() {}
fileprivate var _preferences: LegacyRadioConfig.LegacyPreferences? = nil
}
///
/// This message is never sent over the wire, but it is used for serializing DB
/// state to flash in the device code
/// FIXME, since we write this each time we enter deep sleep (and have infinite
/// flash) it would be better to use some sort of append only data structure for
/// the receive queue and use the preferences store for the other stuff
struct DeviceState {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// Moved to its own file, but we keep this here so we can automatically migrate old radio.region settings
var legacyRadio: LegacyRadioConfig {
get {return _storage._legacyRadio ?? LegacyRadioConfig()}
set {_uniqueStorage()._legacyRadio = newValue}
}
/// Returns true if `legacyRadio` has been explicitly set.
var hasLegacyRadio: Bool {return _storage._legacyRadio != nil}
/// Clears the value of `legacyRadio`. Subsequent reads from it will return its default value.
mutating func clearLegacyRadio() {_uniqueStorage()._legacyRadio = nil}
///
/// Read only settings/info about this node
var myNode: MyNodeInfo {
get {return _storage._myNode ?? MyNodeInfo()}
set {_uniqueStorage()._myNode = newValue}
}
/// Returns true if `myNode` has been explicitly set.
var hasMyNode: Bool {return _storage._myNode != nil}
/// Clears the value of `myNode`. Subsequent reads from it will return its default value.
mutating func clearMyNode() {_uniqueStorage()._myNode = nil}
///
/// My owner info
var owner: User {
get {return _storage._owner ?? User()}
set {_uniqueStorage()._owner = newValue}
}
/// Returns true if `owner` has been explicitly set.
var hasOwner: Bool {return _storage._owner != nil}
/// Clears the value of `owner`. Subsequent reads from it will return its default value.
mutating func clearOwner() {_uniqueStorage()._owner = nil}
var nodeDb: [NodeInfo] {
get {return _storage._nodeDb}
set {_uniqueStorage()._nodeDb = newValue}
}
///
/// Received packets saved for delivery to the phone
var receiveQueue: [MeshPacket] {
get {return _storage._receiveQueue}
set {_uniqueStorage()._receiveQueue = newValue}
}
///
/// A version integer used to invalidate old save files when we make
/// incompatible changes This integer is set at build time and is private to
/// NodeDB.cpp in the device code.
var version: UInt32 {
get {return _storage._version}
set {_uniqueStorage()._version = newValue}
}
///
/// We keep the last received text message (only) stored in the device flash,
/// so we can show it on the screen.
/// Might be null
var rxTextMessage: MeshPacket {
get {return _storage._rxTextMessage ?? MeshPacket()}
set {_uniqueStorage()._rxTextMessage = newValue}
}
/// Returns true if `rxTextMessage` has been explicitly set.
var hasRxTextMessage: Bool {return _storage._rxTextMessage != nil}
/// Clears the value of `rxTextMessage`. Subsequent reads from it will return its default value.
mutating func clearRxTextMessage() {_uniqueStorage()._rxTextMessage = nil}
///
/// Used only during development.
/// Indicates developer is testing and changes should never be saved to flash.
var noSave: Bool {
get {return _storage._noSave}
set {_uniqueStorage()._noSave = newValue}
}
///
/// Some GPSes seem to have bogus settings from the factory, so we always do one factory reset.
var didGpsReset: Bool {
get {return _storage._didGpsReset}
set {_uniqueStorage()._didGpsReset = newValue}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _storage = _StorageClass.defaultInstance
}
///
/// The on-disk saved channels
struct ChannelFile {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The channels our node knows about
var channels: [Channel] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension LegacyRadioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "LegacyRadioConfig"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "preferences"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularMessageField(value: &self._preferences) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._preferences {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: LegacyRadioConfig, rhs: LegacyRadioConfig) -> Bool {
if lhs._preferences != rhs._preferences {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension LegacyRadioConfig.LegacyPreferences: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = LegacyRadioConfig.protoMessageName + ".LegacyPreferences"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
15: .same(proto: "region"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 15: try { try decoder.decodeSingularEnumField(value: &self.region) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.region != .unset {
try visitor.visitSingularEnumField(value: self.region, fieldNumber: 15)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: LegacyRadioConfig.LegacyPreferences, rhs: LegacyRadioConfig.LegacyPreferences) -> Bool {
if lhs.region != rhs.region {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "DeviceState"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "legacyRadio"),
2: .standard(proto: "my_node"),
3: .same(proto: "owner"),
4: .standard(proto: "node_db"),
5: .standard(proto: "receive_queue"),
8: .same(proto: "version"),
7: .standard(proto: "rx_text_message"),
9: .standard(proto: "no_save"),
11: .standard(proto: "did_gps_reset"),
]
fileprivate class _StorageClass {
var _legacyRadio: LegacyRadioConfig? = nil
var _myNode: MyNodeInfo? = nil
var _owner: User? = nil
var _nodeDb: [NodeInfo] = []
var _receiveQueue: [MeshPacket] = []
var _version: UInt32 = 0
var _rxTextMessage: MeshPacket? = nil
var _noSave: Bool = false
var _didGpsReset: Bool = false
static let defaultInstance = _StorageClass()
private init() {}
init(copying source: _StorageClass) {
_legacyRadio = source._legacyRadio
_myNode = source._myNode
_owner = source._owner
_nodeDb = source._nodeDb
_receiveQueue = source._receiveQueue
_version = source._version
_rxTextMessage = source._rxTextMessage
_noSave = source._noSave
_didGpsReset = source._didGpsReset
}
}
fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularMessageField(value: &_storage._legacyRadio) }()
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }()
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }()
case 4: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDb) }()
case 5: try { try decoder.decodeRepeatedMessageField(value: &_storage._receiveQueue) }()
case 7: try { try decoder.decodeSingularMessageField(value: &_storage._rxTextMessage) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }()
case 9: try { try decoder.decodeSingularBoolField(value: &_storage._noSave) }()
case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }()
default: break
}
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._legacyRadio {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
if let v = _storage._myNode {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}
if let v = _storage._owner {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
if !_storage._nodeDb.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._nodeDb, fieldNumber: 4)
}
if !_storage._receiveQueue.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5)
}
if let v = _storage._rxTextMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
}
if _storage._version != 0 {
try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 8)
}
if _storage._noSave != false {
try visitor.visitSingularBoolField(value: _storage._noSave, fieldNumber: 9)
}
if _storage._didGpsReset != false {
try visitor.visitSingularBoolField(value: _storage._didGpsReset, fieldNumber: 11)
}
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: DeviceState, rhs: DeviceState) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._legacyRadio != rhs_storage._legacyRadio {return false}
if _storage._myNode != rhs_storage._myNode {return false}
if _storage._owner != rhs_storage._owner {return false}
if _storage._nodeDb != rhs_storage._nodeDb {return false}
if _storage._receiveQueue != rhs_storage._receiveQueue {return false}
if _storage._version != rhs_storage._version {return false}
if _storage._rxTextMessage != rhs_storage._rxTextMessage {return false}
if _storage._noSave != rhs_storage._noSave {return false}
if _storage._didGpsReset != rhs_storage._didGpsReset {return false}
return true
}
if !storagesAreEqual {return false}
}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension ChannelFile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "ChannelFile"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "channels"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.channels) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.channels.isEmpty {
try visitor.visitRepeatedMessageField(value: self.channels, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: ChannelFile, rhs: ChannelFile) -> Bool {
if lhs.channels != rhs.channels {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -0,0 +1,83 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: environmental_measurement.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
struct EnvironmentalMeasurement {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var temperature: Float = 0
var relativeHumidity: Float = 0
var barometricPressure: Float = 0
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension EnvironmentalMeasurement: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "EnvironmentalMeasurement"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "temperature"),
2: .standard(proto: "relative_humidity"),
3: .standard(proto: "barometric_pressure"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularFloatField(value: &self.temperature) }()
case 2: try { try decoder.decodeSingularFloatField(value: &self.relativeHumidity) }()
case 3: try { try decoder.decodeSingularFloatField(value: &self.barometricPressure) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.temperature != 0 {
try visitor.visitSingularFloatField(value: self.temperature, fieldNumber: 1)
}
if self.relativeHumidity != 0 {
try visitor.visitSingularFloatField(value: self.relativeHumidity, fieldNumber: 2)
}
if self.barometricPressure != 0 {
try visitor.visitSingularFloatField(value: self.barometricPressure, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: EnvironmentalMeasurement, rhs: EnvironmentalMeasurement) -> Bool {
if lhs.temperature != rhs.temperature {return false}
if lhs.relativeHumidity != rhs.relativeHumidity {return false}
if lhs.barometricPressure != rhs.barometricPressure {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,144 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: mqtt.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
///
/// This message wraps a MeshPacket with extra metadata about the sender and how it arrived.
struct ServiceEnvelope {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The (probably encrypted) packet
var packet: MeshPacket {
get {return _storage._packet ?? MeshPacket()}
set {_uniqueStorage()._packet = newValue}
}
/// Returns true if `packet` has been explicitly set.
var hasPacket: Bool {return _storage._packet != nil}
/// Clears the value of `packet`. Subsequent reads from it will return its default value.
mutating func clearPacket() {_uniqueStorage()._packet = nil}
///
/// The global channel ID it was sent on
var channelID: String {
get {return _storage._channelID}
set {_uniqueStorage()._channelID = newValue}
}
///
/// The sending gateway node ID. Can we use this to authenticate/prevent fake
/// nodeid impersonation for senders? - i.e. use gateway/mesh id (which is authenticated) + local node id as
/// the globally trusted nodenum
var gatewayID: String {
get {return _storage._gatewayID}
set {_uniqueStorage()._gatewayID = newValue}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _storage = _StorageClass.defaultInstance
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension ServiceEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "ServiceEnvelope"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "packet"),
2: .standard(proto: "channel_id"),
3: .standard(proto: "gateway_id"),
]
fileprivate class _StorageClass {
var _packet: MeshPacket? = nil
var _channelID: String = String()
var _gatewayID: String = String()
static let defaultInstance = _StorageClass()
private init() {}
init(copying source: _StorageClass) {
_packet = source._packet
_channelID = source._channelID
_gatewayID = source._gatewayID
}
}
fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularMessageField(value: &_storage._packet) }()
case 2: try { try decoder.decodeSingularStringField(value: &_storage._channelID) }()
case 3: try { try decoder.decodeSingularStringField(value: &_storage._gatewayID) }()
default: break
}
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._packet {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
if !_storage._channelID.isEmpty {
try visitor.visitSingularStringField(value: _storage._channelID, fieldNumber: 2)
}
if !_storage._gatewayID.isEmpty {
try visitor.visitSingularStringField(value: _storage._gatewayID, fieldNumber: 3)
}
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: ServiceEnvelope, rhs: ServiceEnvelope) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._packet != rhs_storage._packet {return false}
if _storage._channelID != rhs_storage._channelID {return false}
if _storage._gatewayID != rhs_storage._gatewayID {return false}
return true
}
if !storagesAreEqual {return false}
}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -0,0 +1,227 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: portnums.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
///
/// For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a
/// unique 'portnum' for their application.
///
/// If you are making a new app using meshtastic, please send in a pull request to add your 'portnum' to this
/// master table.
/// PortNums should be assigned in the following range:
///
/// 0-63 Core Meshtastic use, do not use for third party apps
/// 64-127 Registered 3rd party apps, send in a pull request that adds a new entry to portnums.proto to register your application
/// 256-511 Use one of these portnums for your private applications that you don't want to register publically
///
/// All other values are reserved.
///
/// Note: This was formerly a Type enum named 'typ' with the same id #
///
/// We have change to this 'portnum' based scheme for specifying app handlers for particular payloads.
/// This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically.
enum PortNum: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// Deprecated: do not use in new code (formerly called OPAQUE)
/// A message sent from a device outside of the mesh, in a form the mesh does not understand
/// NOTE: This must be 0, because it is documented in IMeshService.aidl to be so
case unknownApp // = 0
///
/// A simple UTF-8 text message, which even the little micros in the mesh
/// can understand and show on their screen eventually in some circumstances
/// even signal might send messages in this form (see below)
/// Formerly called CLEAR_TEXT
case textMessageApp // = 1
///
/// Reserved for built-in GPIO/example app.
/// See remote_hardware.proto/HardwareMessage for details on the message sent/received to this port number
case remoteHardwareApp // = 2
///
/// The built-in position messaging app.
/// Payload is a [Position](/developers/protobufs/api.md#position) message
case positionApp // = 3
///
/// The built-in user info app.
/// Payload is a [User](/developers/protobufs/api.md#user) message
case nodeinfoApp // = 4
///
/// Protocol control packets for mesh protocol use.
/// Payload is a [Routing](/developers/protobufs/api.md#routing) message
case routingApp // = 5
///
/// Admin control packets.
/// Payload is a [AdminMessage](/developers/protobufs/api.md#adminmessage) message
case adminApp // = 6
///
/// Provides a 'ping' service that replies to any packet it receives.
/// Also serves as a small example plugin.
case replyApp // = 32
///
/// Used for the python IP tunnel feature
case ipTunnelApp // = 33
///
/// Provides a hardware serial interface to send and receive from the Meshtastic network.
/// Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
/// network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
/// Maximum packet size of 240 bytes.
/// Plugin is disabled by default can be turned on by setting SERIALPLUGIN_ENABLED = 1 in SerialPlugh.cpp.
/// Maintained by Jm Casler (MC Hamster) : jm@casler.org
case serialApp // = 64
///
/// STORE_FORWARD_APP (Work in Progress)
/// Maintained by Jm Casler (MC Hamster) : jm@casler.org
case storeForwardApp // = 65
///
/// STORE_FORWARD_APP (Work in Progress)
/// Maintained by Jm Casler (MC Hamster) : jm@casler.org
case rangeTestApp // = 66
///
/// Provides a format to send and receive environmental data from the Meshtastic network.
/// Maintained by Charles Crossan (crossan007) : crossan007@gmail.com
case environmentalMeasurementApp // = 67
///
/// Private applications should use portnums >= 256.
/// To simplify initial development and testing you can use "PRIVATE_APP"
/// in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/Meshtastic-device/blob/master/bin/regen-protos.sh))
case privateApp // = 256
///
/// ATAK Forwarder Plugin https://github.com/paulmandal/atak-forwarder
case atakForwarder // = 257
///
/// Currently we limit port nums to no higher than this value
case max // = 511
case UNRECOGNIZED(Int)
init() {
self = .unknownApp
}
init?(rawValue: Int) {
switch rawValue {
case 0: self = .unknownApp
case 1: self = .textMessageApp
case 2: self = .remoteHardwareApp
case 3: self = .positionApp
case 4: self = .nodeinfoApp
case 5: self = .routingApp
case 6: self = .adminApp
case 32: self = .replyApp
case 33: self = .ipTunnelApp
case 64: self = .serialApp
case 65: self = .storeForwardApp
case 66: self = .rangeTestApp
case 67: self = .environmentalMeasurementApp
case 256: self = .privateApp
case 257: self = .atakForwarder
case 511: self = .max
default: self = .UNRECOGNIZED(rawValue)
}
}
var rawValue: Int {
switch self {
case .unknownApp: return 0
case .textMessageApp: return 1
case .remoteHardwareApp: return 2
case .positionApp: return 3
case .nodeinfoApp: return 4
case .routingApp: return 5
case .adminApp: return 6
case .replyApp: return 32
case .ipTunnelApp: return 33
case .serialApp: return 64
case .storeForwardApp: return 65
case .rangeTestApp: return 66
case .environmentalMeasurementApp: return 67
case .privateApp: return 256
case .atakForwarder: return 257
case .max: return 511
case .UNRECOGNIZED(let i): return i
}
}
}
#if swift(>=4.2)
extension PortNum: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [PortNum] = [
.unknownApp,
.textMessageApp,
.remoteHardwareApp,
.positionApp,
.nodeinfoApp,
.routingApp,
.adminApp,
.replyApp,
.ipTunnelApp,
.serialApp,
.storeForwardApp,
.rangeTestApp,
.environmentalMeasurementApp,
.privateApp,
.atakForwarder,
.max,
]
}
#endif // swift(>=4.2)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension PortNum: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "UNKNOWN_APP"),
1: .same(proto: "TEXT_MESSAGE_APP"),
2: .same(proto: "REMOTE_HARDWARE_APP"),
3: .same(proto: "POSITION_APP"),
4: .same(proto: "NODEINFO_APP"),
5: .same(proto: "ROUTING_APP"),
6: .same(proto: "ADMIN_APP"),
32: .same(proto: "REPLY_APP"),
33: .same(proto: "IP_TUNNEL_APP"),
64: .same(proto: "SERIAL_APP"),
65: .same(proto: "STORE_FORWARD_APP"),
66: .same(proto: "RANGE_TEST_APP"),
67: .same(proto: "ENVIRONMENTAL_MEASUREMENT_APP"),
256: .same(proto: "PRIVATE_APP"),
257: .same(proto: "ATAK_FORWARDER"),
511: .same(proto: "MAX"),
]
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,190 @@
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: remote_hardware.proto
//
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
///
/// An example app to show off the plugin system. This message is used for
/// REMOTE_HARDWARE_APP PortNums.
///
/// Also provides easy remote access to any GPIO.
///
/// In the future other remote hardware operations can be added based on user interest
/// (i.e. serial output, spi/i2c input/output).
///
/// FIXME - currently this feature is turned on by default which is dangerous
/// because no security yet (beyond the channel mechanism).
/// It should be off by default and then protected based on some TBD mechanism
/// (a special channel once multichannel support is included?)
struct HardwareMessage {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// What type of HardwareMessage is this?
var typ: HardwareMessage.TypeEnum = .unset
///
/// What gpios are we changing. Not used for all MessageTypes, see MessageType for details
var gpioMask: UInt64 = 0
///
/// For gpios that were listed in gpio_mask as valid, what are the signal levels for those gpios.
/// Not used for all MessageTypes, see MessageType for details
var gpioValue: UInt64 = 0
var unknownFields = SwiftProtobuf.UnknownStorage()
enum TypeEnum: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// Unset/unused
case unset // = 0
///
/// Set gpio gpios based on gpio_mask/gpio_value
case writeGpios // = 1
///
/// We are now interested in watching the gpio_mask gpios.
/// If the selected gpios change, please broadcast GPIOS_CHANGED.
/// Will implicitly change the gpios requested to be INPUT gpios.
case watchGpios // = 2
///
/// The gpios listed in gpio_mask have changed, the new values are listed in gpio_value
case gpiosChanged // = 3
///
/// Read the gpios specified in gpio_mask, send back a READ_GPIOS_REPLY reply with gpio_value populated
case readGpios // = 4
///
/// A reply to READ_GPIOS. gpio_mask and gpio_value will be populated
case readGpiosReply // = 5
case UNRECOGNIZED(Int)
init() {
self = .unset
}
init?(rawValue: Int) {
switch rawValue {
case 0: self = .unset
case 1: self = .writeGpios
case 2: self = .watchGpios
case 3: self = .gpiosChanged
case 4: self = .readGpios
case 5: self = .readGpiosReply
default: self = .UNRECOGNIZED(rawValue)
}
}
var rawValue: Int {
switch self {
case .unset: return 0
case .writeGpios: return 1
case .watchGpios: return 2
case .gpiosChanged: return 3
case .readGpios: return 4
case .readGpiosReply: return 5
case .UNRECOGNIZED(let i): return i
}
}
}
init() {}
}
#if swift(>=4.2)
extension HardwareMessage.TypeEnum: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [HardwareMessage.TypeEnum] = [
.unset,
.writeGpios,
.watchGpios,
.gpiosChanged,
.readGpios,
.readGpiosReply,
]
}
#endif // swift(>=4.2)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension HardwareMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = "HardwareMessage"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "typ"),
2: .standard(proto: "gpio_mask"),
3: .standard(proto: "gpio_value"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self.typ) }()
case 2: try { try decoder.decodeSingularUInt64Field(value: &self.gpioMask) }()
case 3: try { try decoder.decodeSingularUInt64Field(value: &self.gpioValue) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.typ != .unset {
try visitor.visitSingularEnumField(value: self.typ, fieldNumber: 1)
}
if self.gpioMask != 0 {
try visitor.visitSingularUInt64Field(value: self.gpioMask, fieldNumber: 2)
}
if self.gpioValue != 0 {
try visitor.visitSingularUInt64Field(value: self.gpioValue, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: HardwareMessage, rhs: HardwareMessage) -> Bool {
if lhs.typ != rhs.typ {return false}
if lhs.gpioMask != rhs.gpioMask {return false}
if lhs.gpioValue != rhs.gpioValue {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension HardwareMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "UNSET"),
1: .same(proto: "WRITE_GPIOS"),
2: .same(proto: "WATCH_GPIOS"),
3: .same(proto: "GPIOS_CHANGED"),
4: .same(proto: "READ_GPIOS"),
5: .same(proto: "READ_GPIOS_REPLY"),
]
}

View file

@ -0,0 +1,38 @@
[{
"nodeInfo": {
"num":3186677820,
"user":{
"id":"!bdf0d83c",
"longName":"Garth Vander Houwen",
"shortName":"GVH",
"macaddr":"fJ698Ng8",
"hwModel":"TBEAM"
},
"position":{
"latitudeI":476040257,
"longitudeI":-1221545412,
"altitude":11,
"batteryLevel":100,
"time":1631384279
},
"lastHeard":1631384279
},
{
"nodeInfo":{
"num":2792101487,
"user":{
"id":"!a66c166f",
"longName":"RAK Solar 2",
"shortName":"RS2",
"macaddr":"8eambBZv",
"hwModel":"RAK4631"
},
"position":{
"batteryLevel":88,
"time":1631297585
},
"lastHeard":1631383493,
"snr":12.0
}
}
}]

View file

@ -1,4 +0,0 @@
import SwiftUI
import CoreBluetooth

View file

@ -5,6 +5,9 @@
// Created by Garth Vander Houwen on 8/18/21.
//
// Abstract:
// A view allowing you to interact with nearby meshtastic nodes
import SwiftUI
import MapKit
import CoreLocation
@ -19,65 +22,68 @@ struct DeviceBLE: View {
modelData.devices
}
var body: some View {
NavigationView {
VStack {
List {
Section(header: Text("Connected Device")) {
Section(header: Text("Connected Device").font(.title)) {
if(bleManager.connectedPeripheral != nil){
HStack{
Image(systemName: "dot.radiowaves.left.and.right").imageScale(.medium).foregroundColor(.green)
Text(bleManager.connectedPeripheral.name!)
Spacer()
// print(bleManager.meshtasticPeripheral)
Image(systemName: "dot.radiowaves.left.and.right").imageScale(.large).foregroundColor(.green)
Text(bleManager.connectedPeripheral.name!).font(.title2)
}
}
else {
Text("No device connected").font(.title2)
}
}.textCase(nil)
Section(header: Text("Other Meshtastic Devices")) {
Section(header: Text("Other Meshtastic Devices").font(.title)) {
ForEach(bleManager.peripherals.sorted(by: { $0.rssi > $1.rssi })) { peripheral in
HStack {
Image(systemName: "circle.fill").imageScale(.medium).foregroundColor(.gray)
Image(systemName: "circle.fill").imageScale(.large).foregroundColor(.gray)
Button(action: {
self.bleManager.stopScanning()
self.bleManager.disconnectDevice()
self.bleManager.connectToDevice(id: peripheral.id)
}) {
Text(peripheral.name)
Text(peripheral.name).font(.title2)
}
Spacer()
Text(String(peripheral.rssi) + " dB")
Text(String(peripheral.rssi) + " dB").font(.title3)
}
}
}.textCase(nil)
}
// Image(systemName: "dot.radiowaves.left.and.right").imageScale(.medium).foregroundColor(.green)//.rotationEffect(Angle(degrees: 90))
Spacer()
HStack {
VStack (spacing: 10) {
Button(action: {
self.bleManager.startScanning()
}) {
Text("Start Scanning")
}
Button(action: {
self.bleManager.stopScanning()
}) {
Text("Stop Scanning")
}
}.padding()
Spacer()
}
HStack (spacing: 15) {
Button(action: {
self.bleManager.startScanning()
}) {
Image(systemName: "play.fill").imageScale(.large).foregroundColor(.gray)
Text("Start Scanning").font(.caption)
.font(.caption)
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
Spacer(minLength: 10)
Button(action: {
self.bleManager.stopScanning()
}) {
Image(systemName: "stop.fill").imageScale(.large).foregroundColor(.gray)
Text("Stop Scanning")
.font(.caption)
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
}.padding()
Spacer()
}
.navigationTitle("Nearby Devices")
.navigationTitle("Nearby BLE Devices")
.navigationBarItems(leading:
HStack {
Button(action: {
@ -99,7 +105,5 @@ struct DeviceBLE: View {
}
)
}.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -1,5 +1,6 @@
//
// DeviceHome.swift
// MeshtasticClient
//
// Created by Garth Vander Houwen on 8/7/21.
//

View file

@ -1,6 +1,6 @@
//
// DeviceMap.swift
// Landmarks
// MeshtasticClient
//
// Created by Garth Vander Houwen on 8/7/21.
// Copyright © 2021 Apple. All rights reserved.

View file

@ -1,9 +1,11 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
A single row to be displayed in a list of landmarks.
*/
//
// DeviceMap.swift
// MeshtasticClient
//
// Created by Garth Vander Houwen on 8/7/21.
//
// Abstract:
// A single row to be displayed in a list of landmarks.
import SwiftUI