Merge pull request #92 from meshtastic/feature/lora_config

Implement LoRa Config
This commit is contained in:
Garth Vander Houwen 2022-06-20 22:55:07 -07:00 committed by GitHub
commit 7202289ad1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 635 additions and 270 deletions

View file

@ -117,6 +117,7 @@
DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = cannedmessages.pb.swift; sourceTree = "<group>"; };
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = "<group>"; };
DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = "<group>"; };
DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 4.xcdatamodel"; sourceTree = "<group>"; };
DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
@ -809,7 +810,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.19;
MARKETING_VERSION = 1.3.20;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -840,7 +841,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.19;
MARKETING_VERSION = 1.3.20;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1013,11 +1014,12 @@
DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */,
DDB2CC6F27F3F0AC009C5FCC /* MeshtasticDataModel v 3.xcdatamodel */,
DD45C77427BD4EF80011784F /* MeshtasticDataModel v2.xcdatamodel */,
DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */,
);
currentVersion = DDB2CC6F27F3F0AC009C5FCC /* MeshtasticDataModel v 3.xcdatamodel */;
currentVersion = DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = MeshtasticApple/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -179,7 +179,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
peripheralName = name
}
let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: peripheralName, shortName: String(peripheralName.suffix(3)), longName: peripheralName, lastFourCode: last4Code, firmwareVersion: "Unknown", rssi: RSSI.intValue, bitrate: nil, channelUtilization: nil, airTime: nil, lastUpdate: Date(), subscribed: false, peripheral: peripheral)
let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: peripheralName, shortName: last4Code, longName: peripheralName, lastFourCode: last4Code, firmwareVersion: "Unknown", rssi: RSSI.intValue, bitrate: nil, channelUtilization: nil, airTime: nil, lastUpdate: Date(), subscribed: false, peripheral: peripheral)
let peripheralIndex = peripherals.firstIndex(where: { $0.id == newPeripheral.id })
if peripheralIndex != nil && newPeripheral.peripheral.state != CBPeripheralState.connected {
@ -409,6 +409,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
lastConnnectionVersion = myInfo?.firmwareVersion ?? myInfo!.firmwareVersion ?? "Unknown"
self.connectedPeripheral.firmwareVersion = myInfo!.firmwareVersion ?? "Unknown"
self.connectedPeripheral.name = myInfo!.bleName ?? "Unknown"
}
} else if decodedInfo.nodeInfo.num != 0 {
@ -425,17 +426,22 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if nodeInfo!.user != nil {
connectedPeripheral.name = nodeInfo!.user!.longName ?? "Unknown"
connectedPeripheral.shortName = nodeInfo!.user!.shortName ?? "?????"
connectedPeripheral.longName = nodeInfo!.user!.longName ?? "Unknown"
}
}
}
} else if decodedInfo.config.version == 13 {
} else if decodedInfo.config.isInitialized {
localConfig(config: decodedInfo.config, meshlogging: meshLoggingEnabled, context: context!, nodeLongName: self.connectedPeripheral.longName)
localConfig(config: decodedInfo.config, meshlogging: meshLoggingEnabled, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName)
} else {
if meshLoggingEnabled { MeshLogger.log(" MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
if decodedInfo.configCompleteID == 0 {
if meshLoggingEnabled { MeshLogger.log(" MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
}
}
case .textMessageApp:
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), meshLogging: meshLoggingEnabled, context: context!)
@ -805,26 +811,60 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
return false
}
public func getConfig(destNum: Int64, wantResponse: Bool) -> Bool {
public func sendFactoryReset(destNum: Int64, wantResponse: Bool) -> Bool {
var deviceConfig = Config.DeviceConfig()
deviceConfig.factoryReset = true
var adminPacket = AdminMessage()
adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig
adminPacket.variant = AdminMessage.OneOf_Variant.getConfigRequest(AdminMessage.ConfigType.loraConfig)
adminPacket.setConfig.device = deviceConfig
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)
meshPacket.from = UInt32(connectedPeripheral.num)
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = false
meshPacket.wantAck = wantResponse
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
let binaryData: Data = try! toRadio.serializedData()
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
}
return false
}
public func saveLoRaConfig(config: Config.LoRaConfig, destNum: Int64, wantResponse: Bool) -> Bool {
var adminPacket = AdminMessage()
adminPacket.setConfig.lora = config
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = wantResponse
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
dataMessage.wantResponse = true
meshPacket.decoded = dataMessage

View file

@ -9,46 +9,130 @@ import Foundation
import CoreData
import SwiftUI
func localConfig (config: LocalConfig, meshlogging: Bool, context:NSManagedObjectContext, nodeLongName: String) {
func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
// We don't care about any of the Power settings
// We don't want to manage wifi from the phone app and disconnect our device
if meshlogging { MeshLogger.log("⚙️ Local Config version \(config.version) received for \(nodeLongName)") }
//if meshlogging { MeshLogger.log(" Local Config version \(config.version) received for \(nodeLongName)") }
if (try! config.device.jsonString()) == "{}" {
// if (try! config.device.jsonString()) == "{}" {
//
// print("📟 Default Device config")
//
// } else {
//
// print("📟 Has Device config")
// }
//
// if (try! config.position.jsonString()) == "{}" {
//
// print("📍 Default Position config")
//
// } else {
//
// print("📍 Has Position config")
// }
//
// if (try! config.power.jsonString() == "{\"lsSecs\":300}") {
//
// print("📍 Default Power config")
// print(try! config.power.jsonString())
//
// } else {
//
// print("📍 Has Power config")
// print(try! config.power.jsonString())
// }
//
// if (try! config.display.jsonString()) == "{}" {
//
// print("🖥 Default Display config")
//
// } else {
//
// print("🖥 Has Display config")
// }
print("📟 Default Device config")
if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) {
} else {
var isDefault = false
print("📟 Has Device config")
}
if (try! config.display.jsonString()) == "{}" {
if (try! config.lora.jsonString()) == "{}" {
isDefault = true
}
print("🖥️ Default Display config")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
} else {
print("🖥️ Has Display config")
}
if (try! config.lora.jsonString()) == "{}" {
print("📡 Default LoRa config")
} else {
print("📡 Has LoRa config")
}
if (try! config.position.jsonString()) == "{}" {
print("📍 Default Position config")
} else {
print("📍 Has Position config")
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
// Found a node, save LoRa Config
if !fetchedNode.isEmpty {
if fetchedNode[0].loRaConfig == nil {
let newLoRaConfig = LoRaConfigEntity(context: context)
if isDefault {
// UNSET default protobuf value of 0
newLoRaConfig.regionCode = 0
// LongFast default protobuf value of 0
newLoRaConfig.modemPreset = 0
// 3 Hops default protobuf value of 0
newLoRaConfig.hopLimit = 0
} else {
// UNSET default protobuf value of 0
newLoRaConfig.regionCode = Int32(config.lora.region.rawValue)
// LongFast default protobuf value of 0
newLoRaConfig.modemPreset = Int32(config.lora.modemPreset.rawValue)
// 3 Hops default protobuf value of 0
newLoRaConfig.hopLimit = Int32(config.lora.hopLimit)
}
fetchedNode[0].loRaConfig = newLoRaConfig
} else {
if isDefault {
// UNSET default protobuf value of 0
fetchedNode[0].loRaConfig?.regionCode = 0
// LongFast default protobuf value of 0
fetchedNode[0].loRaConfig?.modemPreset = 0
// 3 Hops default protobuf value of 0
fetchedNode[0].loRaConfig?.hopLimit = 0
} else {
// UNSET default protobuf value of 0
fetchedNode[0].loRaConfig?.regionCode = Int32(config.lora.region.rawValue)
// LongFast default protobuf value of 0
fetchedNode[0].loRaConfig?.modemPreset = Int32(config.lora.modemPreset.rawValue)
// 3 Hops default protobuf value of 0
fetchedNode[0].loRaConfig?.hopLimit = Int32(config.lora.hopLimit)
}
}
do {
try context.save()
if meshlogging { MeshLogger.log("💾 Updated LoRaConfig for node number: \(String(nodeNum))") }
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data MyInfoEntity: \(nsError)")
}
}
} catch {
}
}
}

View file

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MeshtasticDataModel v 3.xcdatamodel</string>
<string>MeshtasticDataModel v 4.xcdatamodel</string>
</dict>
</plist>

View file

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="modemPreset" optional="YES" attributeType="String"/>
<attribute name="num" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="String"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
@ -104,6 +99,5 @@
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="194"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="200"/>
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="74"/>
</elements>
</model>

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="num" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="direction" attributeType="String"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" attributeType="String"/>
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="fromUser" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
<relationship name="toUser" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
<fetchedProperty name="tapbacks" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
</fetchedProperty>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="messageId"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="bitrate" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="errorCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" attributeType="String"/>
<attribute name="hasGps" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="maxChannels" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messageTimeoutMsec" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="myNodeNum"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="lastHeard" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
<attribute name="hwModel" attributeType="String"/>
<attribute name="longName" attributeType="String"/>
<attribute name="macaddr" optional="YES" attributeType="Binary"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shortName" attributeType="String"/>
<attribute name="team" optional="YES" attributeType="String"/>
<attribute name="userId" attributeType="String"/>
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
<fetchedProperty name="allMessages" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND isEmoji == false"/>
</fetchedProperty>
</entity>
<elements>
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="215"/>
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="209"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="179"/>
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="194"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="200"/>
</elements>
</model>

View file

@ -26,11 +26,9 @@ struct Config {
// methods supported on all messages.
///
/// TODO: REPLACE
/// Payload Variant
var payloadVariant: Config.OneOf_PayloadVariant? = nil
///
/// TODO: REPLACE
var device: Config.DeviceConfig {
get {
if case .device(let v)? = payloadVariant {return v}
@ -39,8 +37,6 @@ struct Config {
set {payloadVariant = .device(newValue)}
}
///
/// TODO: REPLACE
var position: Config.PositionConfig {
get {
if case .position(let v)? = payloadVariant {return v}
@ -49,8 +45,6 @@ struct Config {
set {payloadVariant = .position(newValue)}
}
///
/// TODO: REPLACE
var power: Config.PowerConfig {
get {
if case .power(let v)? = payloadVariant {return v}
@ -59,8 +53,6 @@ struct Config {
set {payloadVariant = .power(newValue)}
}
///
/// TODO: REPLACE
var wifi: Config.WiFiConfig {
get {
if case .wifi(let v)? = payloadVariant {return v}
@ -69,8 +61,6 @@ struct Config {
set {payloadVariant = .wifi(newValue)}
}
///
/// TODO: REPLACE
var display: Config.DisplayConfig {
get {
if case .display(let v)? = payloadVariant {return v}
@ -79,8 +69,6 @@ struct Config {
set {payloadVariant = .display(newValue)}
}
///
/// TODO: REPLACE
var lora: Config.LoRaConfig {
get {
if case .lora(let v)? = payloadVariant {return v}
@ -92,25 +80,13 @@ struct Config {
var unknownFields = SwiftProtobuf.UnknownStorage()
///
/// TODO: REPLACE
/// Payload Variant
enum OneOf_PayloadVariant: Equatable {
///
/// TODO: REPLACE
case device(Config.DeviceConfig)
///
/// TODO: REPLACE
case position(Config.PositionConfig)
///
/// TODO: REPLACE
case power(Config.PowerConfig)
///
/// TODO: REPLACE
case wifi(Config.WiFiConfig)
///
/// TODO: REPLACE
case display(Config.DisplayConfig)
///
/// TODO: REPLACE
case lora(Config.LoRaConfig)
#if !swift(>=4.1)
@ -166,7 +142,6 @@ struct Config {
///
/// This setting is never saved to disk, but if set, all device settings will be returned to factory defaults.
/// (Region, serial number etc... will be preserved)
var factoryReset: Bool = false
///
@ -182,7 +157,6 @@ struct Config {
///
/// Defines the device's role on the Mesh network
/// unset - 0
enum Role: SwiftProtobuf.Enum {
typealias RawValue = Int
@ -191,24 +165,19 @@ struct Config {
case client // = 0
///
/// ClientMute device role
/// This is like the client but packets will not hop over this node. Would be
/// useful if you want to save power by not contributing to the mesh.
/// Client Mute device role
/// Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh.
case clientMute // = 1
///
/// Router device role.
/// Uses an agressive algirithem for the flood networking so packets will
/// prefer to be routed over this node. Also assume that this will be
/// unattended and so will turn off the wifi/ble radio as well as the oled screen.
/// Mesh packets will prefer to be routed over this node. This node will not be used by client apps.
/// The wifi/ble radios and the oled screen will be put to sleep.
case router // = 2
///
/// RouterClient device role
/// Uses an agressive algirithem for the flood networking so packets will
/// prefer to be routed over this node. Similiar power management as a regular
/// client, so the RouterClient can be used as both a Router and a Client. Useful
/// as a well placed base station that you could also use to send messages.
/// Router Client device role
/// Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client.
case routerClient // = 3
case UNRECOGNIZED(Int)
@ -331,6 +300,18 @@ struct Config {
///
/// Include positional timestamp (from GPS solution)
case posTimestamp // = 128
///
/// Include positional heading
/// Intended for use with vehicle not walking speeds
/// walking speeds are likely to be error prone like the compass
case posHeading // = 256
///
/// Include positional speed
/// Intended for use with vehicle not walking speeds
/// walking speeds are likely to be error prone like the compass
case posSpeed // = 512
case UNRECOGNIZED(Int)
init() {
@ -348,6 +329,8 @@ struct Config {
case 32: self = .posSatinview
case 64: self = .posSeqNos
case 128: self = .posTimestamp
case 256: self = .posHeading
case 512: self = .posSpeed
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -363,6 +346,8 @@ struct Config {
case .posSatinview: return 32
case .posSeqNos: return 64
case .posTimestamp: return 128
case .posHeading: return 256
case .posSpeed: return 512
case .UNRECOGNIZED(let i): return i
}
}
@ -389,14 +374,9 @@ struct Config {
/// If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in
/// we should try to minimize power consumption as much as possible.
/// YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case).
/// CLI Only Option
/// Advanced Option
var isPowerSaving: Bool = false
///
/// Circumvents the logic block for determining whether the device is powered or not.
/// Useful for devices with finicky ADC issues on the battery sense pins.
var isAlwaysPowered: Bool = false
///
/// If non-zero, the device will fully power off this many seconds after external power is removed.
var onBatteryShutdownAfterSecs: UInt32 = 0
@ -404,11 +384,13 @@ struct Config {
///
/// Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k)
/// Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation.
/// Should be set to floating point value between 2 and 4
/// Fixes issues on Heltec v2
var adcMultiplierOverride: Float = 0
///
/// Wait Bluetooth Seconds
/// The number of seconds for to wait before turning of BLE in No Bluetooth states\
/// The number of seconds for to wait before turning off BLE in No Bluetooth states
/// 0 for default of 1 minute
var waitBluetoothSecs: UInt32 = 0
@ -430,7 +412,7 @@ struct Config {
/// Light Sleep Seconds
/// In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on
/// ESP32 Only
/// 0 for default of 3600
/// 0 for default of 300
var lsSecs: UInt32 = 0
///
@ -446,73 +428,22 @@ struct Config {
/// **TBEAM 1.1 Only**
enum ChargeCurrent: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// TODO: REPLACE
case maunset // = 0
///
/// TODO: REPLACE
case ma100 // = 1
///
/// TODO: REPLACE
case ma190 // = 2
///
/// TODO: REPLACE
case ma280 // = 3
///
/// TODO: REPLACE
case ma360 // = 4
///
/// TODO: REPLACE
case ma450 // = 5
///
/// TODO: REPLACE
case ma550 // = 6
///
/// TODO: REPLACE
case ma630 // = 7
///
/// TODO: REPLACE
case ma700 // = 8
///
/// TODO: REPLACE
case ma780 // = 9
///
/// TODO: REPLACE
case ma880 // = 10
///
/// TODO: REPLACE
case ma960 // = 11
///
/// TODO: REPLACE
case ma1000 // = 12
///
/// TODO: REPLACE
case ma1080 // = 13
///
/// TODO: REPLACE
case ma1160 // = 14
///
/// TODO: REPLACE
case ma1240 // = 15
///
/// TODO: REPLACE
case ma1320 // = 16
case UNRECOGNIZED(Int)
@ -572,7 +503,7 @@ struct Config {
}
///
/// TODO: REPLACE
/// WiFi Config
struct WiFiConfig {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@ -609,13 +540,12 @@ struct Config {
// methods supported on all messages.
///
/// Power management state machine option.
/// See [power management](/docs/software/other/power) for details.
/// 0 for default of one minute
/// Number of seconds the screen stays on after pressing the user button or receiving a message
/// 0 for default of one minute MAXUINT for always on
var screenOnSecs: UInt32 = 0
///
/// How the GPS coordinates are displayed on the OLED screen.
/// How the GPS coordinates are formatted on the OLED screen.
var gpsFormat: Config.DisplayConfig.GpsCoordinateFormat = .gpsFormatDec
///
@ -641,24 +571,24 @@ struct Config {
case gpsFormatDms // = 1
///
/// GPS coordinates are displayed in Universal Transverse Mercator format:
/// Universal Transverse Mercator format:
/// ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
case gpsFormatUtm // = 2
///
/// GPS coordinates are displayed in Military Grid Reference System format:
/// Military Grid Reference System format:
/// ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
/// E is easting, N is northing
case gpsFormatMgrs // = 3
///
/// GPS coordinates are displayed in Open Location Code (aka Plus Codes).
/// Open Location Code (aka Plus Codes).
case gpsFormatOlc // = 4
///
/// GPS coordinates are displayed in Ordnance Survey Grid Reference (the National Grid System of the UK).
/// Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square, E is the easting,
/// N is the northing
/// Ordnance Survey Grid Reference (the National Grid System of the UK).
/// Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
/// E is the easting, N is the northing
case gpsFormatOsgr // = 5
case UNRECOGNIZED(Int)
@ -696,7 +626,7 @@ struct Config {
}
///
/// TODO: REPLACE
/// Lora Config
struct LoRaConfig {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@ -710,7 +640,6 @@ struct Config {
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.
@ -741,11 +670,12 @@ struct Config {
var frequencyOffset: Float = 0
///
/// The region code for my radio (US, CN, EU433, etc...)
/// The region code for the radio (US, CN, EU433, etc...)
var region: Config.LoRaConfig.RegionCode = .unset
///
/// Overrides HOPS_RELIABLE and sets the maximum number of hops. This can't be greater than 7.
/// 0 for default of 3
var hopLimit: UInt32 = 0
///
@ -761,66 +691,59 @@ struct Config {
var unknownFields = SwiftProtobuf.UnknownStorage()
///
/// The frequency/regulatory region the user has selected.
/// Note: In 1.0 builds (which must still be supported by the android app for a
/// long time) this field will be unpopulated.
/// If firmware is ever upgraded from an old 1.0ish build, the old
/// MyNodeInfo.region string will be used to set UserPreferences.region and the
/// old value will be no longer set.
enum RegionCode: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// TODO: REPLACE
/// Region is not set
case unset // = 0
///
/// TODO: REPLACE
/// United States
case us // = 1
///
/// TODO: REPLACE
/// European Union 433mhz
case eu433 // = 2
///
/// TODO: REPLACE
/// European Union 433mhz
case eu868 // = 3
///
/// TODO: REPLACE
/// China
case cn // = 4
///
/// TODO: REPLACE
/// Japan
case jp // = 5
///
/// TODO: REPLACE
/// Australia / New Zealand
case anz // = 6
///
/// TODO: REPLACE
/// Korea
case kr // = 7
///
/// TODO: REPLACE
/// Taiwan
case tw // = 8
///
/// TODO: REPLACE
/// Russia
case ru // = 9
///
/// TODO: REPLACE
/// India
case `in` // = 10
///
/// TODO: REPLACE
/// New Zealand 865mhz
case nz865 // = 11
///
/// TODO: REPLACE
/// Thailand
case th // = 12
case UNRECOGNIZED(Int)
@ -875,31 +798,31 @@ struct Config {
typealias RawValue = Int
///
/// TODO: REPLACE
/// Long Range - Fast
case longFast // = 0
///
/// TODO: REPLACE
/// Long Range - Slow
case longSlow // = 1
///
/// TODO: REPLACE
/// Very Long Range - Slow
case vlongSlow // = 2
///
/// TODO: REPLACE
/// Medium Range - Slow
case midSlow // = 3
///
/// TODO: REPLACE
/// Medium Range - Fast
case midFast // = 4
///
/// TODO: REPLACE
/// Short Range - Slow
case shortSlow // = 5
///
/// TODO: REPLACE
/// Short Range - Fast
case shortFast // = 6
case UNRECOGNIZED(Int)
@ -965,6 +888,8 @@ extension Config.PositionConfig.PositionFlags: CaseIterable {
.posSatinview,
.posSeqNos,
.posTimestamp,
.posHeading,
.posSpeed,
]
}
@ -1342,6 +1267,8 @@ extension Config.PositionConfig.PositionFlags: SwiftProtobuf._ProtoNameProviding
32: .same(proto: "POS_SATINVIEW"),
64: .same(proto: "POS_SEQ_NOS"),
128: .same(proto: "POS_TIMESTAMP"),
256: .same(proto: "POS_HEADING"),
512: .same(proto: "POS_SPEED"),
]
}
@ -1350,7 +1277,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "charge_current"),
2: .standard(proto: "is_power_saving"),
3: .standard(proto: "is_always_powered"),
4: .standard(proto: "on_battery_shutdown_after_secs"),
6: .standard(proto: "adc_multiplier_override"),
7: .standard(proto: "wait_bluetooth_secs"),
@ -1368,7 +1294,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self.chargeCurrent) }()
case 2: try { try decoder.decodeSingularBoolField(value: &self.isPowerSaving) }()
case 3: try { try decoder.decodeSingularBoolField(value: &self.isAlwaysPowered) }()
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.onBatteryShutdownAfterSecs) }()
case 6: try { try decoder.decodeSingularFloatField(value: &self.adcMultiplierOverride) }()
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.waitBluetoothSecs) }()
@ -1388,9 +1313,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if self.isPowerSaving != false {
try visitor.visitSingularBoolField(value: self.isPowerSaving, fieldNumber: 2)
}
if self.isAlwaysPowered != false {
try visitor.visitSingularBoolField(value: self.isAlwaysPowered, fieldNumber: 3)
}
if self.onBatteryShutdownAfterSecs != 0 {
try visitor.visitSingularUInt32Field(value: self.onBatteryShutdownAfterSecs, fieldNumber: 4)
}
@ -1418,7 +1340,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
static func ==(lhs: Config.PowerConfig, rhs: Config.PowerConfig) -> Bool {
if lhs.chargeCurrent != rhs.chargeCurrent {return false}
if lhs.isPowerSaving != rhs.isPowerSaving {return false}
if lhs.isAlwaysPowered != rhs.isAlwaysPowered {return false}
if lhs.onBatteryShutdownAfterSecs != rhs.onBatteryShutdownAfterSecs {return false}
if lhs.adcMultiplierOverride != rhs.adcMultiplierOverride {return false}
if lhs.waitBluetoothSecs != rhs.waitBluetoothSecs {return false}

View file

@ -1810,11 +1810,11 @@ struct FromRadio {
}
///
/// Include the entire config (was: RadioConfig radio)
var config: LocalConfig {
/// Include a part of the config (was: RadioConfig radio)
var config: Config {
get {
if case .config(let v)? = _storage._payloadVariant {return v}
return LocalConfig()
return Config()
}
set {_uniqueStorage()._payloadVariant = .config(newValue)}
}
@ -1872,8 +1872,8 @@ struct FromRadio {
/// starts over with the first node in our DB
case nodeInfo(NodeInfo)
///
/// Include the entire config (was: RadioConfig radio)
case config(LocalConfig)
/// Include a part of the config (was: RadioConfig radio)
case config(Config)
///
/// Set to send debug console output over our protobuf stream
case logRecord(LogRecord)
@ -3356,7 +3356,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
}
}()
case 6: try {
var v: LocalConfig?
var v: Config?
var hadOneofValue = false
if let current = _storage._payloadVariant {
hadOneofValue = true

View file

@ -265,8 +265,8 @@ struct Connect: View {
ConnectedDevice(
bluetoothOn: self.bleManager.isSwitchedOn,
deviceConnected: self.bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? self.bleManager.connectedPeripheral.lastFourCode :
"????")
name: (bleManager.connectedPeripheral != nil) ? self.bleManager.connectedPeripheral.shortName :
"?????")
}
)
}

View file

@ -17,10 +17,10 @@ struct ConnectedDevice: View {
if bluetoothOn {
if deviceConnected {
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
.imageScale(.large)
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
Text(name!).font(.subheadline).foregroundColor(.gray)
Text(name!).font(.callout).foregroundColor(.gray)
} else {
Image(systemName: "antenna.radiowaves.left.and.right.slash")

View file

@ -457,7 +457,7 @@ struct UserMessageList: View {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
}
}
}

View file

@ -352,7 +352,7 @@ struct NodeDetail: View {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
}
)
.onAppear(perform: {

View file

@ -105,7 +105,8 @@ struct NodeList: View {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
if UIDevice.current.userInterfaceIdiom == .pad {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
if nodes.count > 0 {
selection = "0"
}

View file

@ -125,8 +125,8 @@ struct NodeMap: View {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode :
"????")
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName :
"?????")
})
.onAppear(perform: {

View file

@ -176,7 +176,7 @@ struct AppSettings: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {

View file

@ -22,7 +22,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
case .client:
return "Client (default)"
case .clientMute:
return "Client Mute - Packets will not hop over this node, does not contribute to routing packets for mesh."
return "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."
case .router:
return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."
case .routerClient:
@ -41,8 +41,10 @@ struct DeviceConfig: View {
@State var serialEnabled = true
@State var debugLogEnabled = false
@State private var isPresentingFactoryResetConfirm: Bool = false
var body: some View {
VStack {
Form {
@ -74,18 +76,42 @@ struct DeviceConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
}
.navigationTitle("Device Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
})
.onAppear {
self.bleManager.context = context
Button("Factory Reset", role: .destructive) {
isPresentingFactoryResetConfirm = true
}
.navigationViewStyle(StackNavigationViewStyle())
.disabled(bleManager.connectedPeripheral == nil)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
isPresented: $isPresentingFactoryResetConfirm
) {
Button("Erase all device settings?", role: .destructive) {
if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
print("Factory Reset Failed")
}
}
}
Spacer()
}
.navigationTitle("Device Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {
self.bleManager.context = context
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -163,7 +163,7 @@ struct DisplayConfig: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {

View file

@ -19,7 +19,7 @@ enum RegionCodes : Int, CaseIterable, Identifiable {
case kr = 7
case tw = 8
case ru = 9
//case in = 10
case `in` = 10
case nz865
case th
@ -28,7 +28,7 @@ enum RegionCodes : Int, CaseIterable, Identifiable {
get {
switch self {
case .unset:
return "UNSET - Please set a Region"
return "Please set a region"
case .us:
return "United States"
case .eu433:
@ -47,13 +47,48 @@ enum RegionCodes : Int, CaseIterable, Identifiable {
return "Taiwan"
case .ru:
return "Russia"
case .in:
return "India"
case .nz865:
return "New Zealand 865mhz"
case .th:
return "TH"
return "Thailand"
}
}
}
func protoEnumValue() -> Config.LoRaConfig.RegionCode {
switch self {
case .unset:
return Config.LoRaConfig.RegionCode.unset
case .us:
return Config.LoRaConfig.RegionCode.us
case .eu433:
return Config.LoRaConfig.RegionCode.eu433
case .eu868:
return Config.LoRaConfig.RegionCode.eu868
case .cn:
return Config.LoRaConfig.RegionCode.cn
case .jp:
return Config.LoRaConfig.RegionCode.jp
case .anz:
return Config.LoRaConfig.RegionCode.anz
case .kr:
return Config.LoRaConfig.RegionCode.kr
case .tw:
return Config.LoRaConfig.RegionCode.tw
case .ru:
return Config.LoRaConfig.RegionCode.ru
case .in:
return Config.LoRaConfig.RegionCode.in
case .nz865:
return Config.LoRaConfig.RegionCode.nz865
case .th:
return Config.LoRaConfig.RegionCode.th
}
}
}
enum ModemPresets : Int, CaseIterable, Identifiable {
@ -72,19 +107,74 @@ enum ModemPresets : Int, CaseIterable, Identifiable {
switch self {
case .LongFast:
return "Long Fast"
return "Long Range - Fast"
case .LongSlow:
return "Long Slow"
return "Long Range - Slow"
case .VLongSlow:
return "Very Long Slow"
return "Very Long Range - Slow"
case .MidSlow:
return "Mid Slow"
return "Medium Range - Slow"
case .MidFast:
return "Mid Fast"
return "Medium Range - Fast"
case .ShortSlow:
return "Short Slow"
return "Short Range - Slow"
case .ShortFast:
return "Short Fast"
return "Short Range - Fast"
}
}
}
func protoEnumValue() -> Config.LoRaConfig.ModemPreset {
switch self {
case .LongFast:
return Config.LoRaConfig.ModemPreset.longFast
case .LongSlow:
return Config.LoRaConfig.ModemPreset.longSlow
case .VLongSlow:
return Config.LoRaConfig.ModemPreset.vlongSlow
case .MidSlow:
return Config.LoRaConfig.ModemPreset.midSlow
case .MidFast:
return Config.LoRaConfig.ModemPreset.midFast
case .ShortSlow:
return Config.LoRaConfig.ModemPreset.shortSlow
case .ShortFast:
return Config.LoRaConfig.ModemPreset.shortFast
}
}
}
enum HopValues : Int, CaseIterable, Identifiable {
case oneHop = 1
case twoHops = 2
case threeHops = 0
case fourHops = 4
case fiveHops = 5
case sixHops = 6
case sevenHops = 7
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .oneHop:
return "One Hop"
case .twoHops:
return "Two Hops"
case .threeHops:
return "Three Hops"
case .fourHops:
return "Four Hops"
case .fiveHops:
return "Five Hops"
case .sixHops:
return "Six Hops"
case .sevenHops:
return "Seven Hops"
}
}
}
@ -95,9 +185,13 @@ struct LoRaConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State var region = 1
@State var modemPreset = 0
@State var numberOfHops = 0
var node: NodeInfoEntity
@State private var isPresentingSaveConfirm: Bool = false
@State var region = 0
@State var modemPreset = 0
@State var hopLimit = 0
@State var hasChanges = false
var body: some View {
@ -112,49 +206,106 @@ struct LoRaConfig: View {
}
}
.pickerStyle(DefaultPickerStyle())
Text("The region where you will be using your Meshtastic LoRa radios.")
Text("The region where you will be using your radios.")
.font(.caption)
.listRowSeparator(.visible)
.listRowSeparator(.visible)
}
Section(header: Text("Modem")) {
Picker("Presets", selection: $region ) {
Picker("Presets", selection: $modemPreset ) {
ForEach(ModemPresets.allCases) { m in
Text(m.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Available modem presets.")
Text("Available modem presets, default is Long Fast.")
.font(.caption)
.listRowSeparator(.visible)
.listRowSeparator(.visible)
}
Section(header: Text("Mesh Options")) {
Picker("Number of hops", selection: $numberOfHops) {
ForEach(0..<8) {
if $0 == 0 {
Text("Default")
} else {
Text("\($0) Hops")
}
Picker("Number of hops", selection: $hopLimit) {
ForEach(HopValues.allCases) { hop in
Text(hop.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Sets the maximum number of hops, default is 3.")
.font(.caption)
.listRowSeparator(.visible)
}
}
Button {
isPresentingSaveConfirm = true
} label: {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
isPresented: $isPresentingSaveConfirm
) {
Button("Save LoRa Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
var lc = Config.LoRaConfig()
lc.hopLimit = UInt32(hopLimit)
lc.region = RegionCodes(rawValue: region)!.protoEnumValue()
lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue()
if bleManager.saveLoRaConfig(config: lc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
// Should show a saved successfully alert once I know that to be true
// for now just disable the button after a successful save
hasChanges = false
} else {
}
}
}
}
.navigationTitle("LoRa Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {
self.bleManager.context = context
}
.task {
do {
print("got hops \(node.loRaConfig?.hopLimit ?? 0)")
self.hopLimit = Int(node.loRaConfig?.hopLimit ?? 0)
self.region = Int(node.loRaConfig?.regionCode ?? 0)
self.modemPreset = Int(node.loRaConfig?.modemPreset ?? 0)
self.hasChanges = false
} catch {
print("Failed to load node data")
}
}
.onChange(of: region) { newRegion in
hasChanges = true
}
.onChange(of: modemPreset) { newModemPreset in
hasChanges = true
}
.onChange(of: hopLimit) { newHopLimit in
hasChanges = true
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -118,8 +118,14 @@ struct PositionConfig: View {
@State var gpsAttemptTime = 0
@State var positionBroadcastSeconds = 0
@State var includeAltitude = false
@State var includeSatInView = false
@State var includePosAltitude = false
@State var includePosSatsinview = false
@State var includePosSeqNos = false
@State var includePosTimestamp = false
@State var includePosSpeed = false
@State var includePosHeading = false
var body: some View {
@ -203,30 +209,44 @@ struct PositionConfig: View {
.font(.caption)
.listRowSeparator(.visible)
Toggle(isOn: $includeAltitude) {
Toggle(isOn: $includePosAltitude) {
Label("Include Altitude", systemImage: "arrow.up")
Label("Altitude", systemImage: "arrow.up")
}
.toggleStyle(DefaultToggleStyle())
.listRowSeparator(.visible)
Toggle(isOn: $includeSatInView) {
Toggle(isOn: $includePosSatsinview) {
Label("Include number of satellites in view", systemImage: "skew")
Label("Number of satellites", systemImage: "skew")
}
.toggleStyle(DefaultToggleStyle())
.listRowSeparator(.visible)
Toggle(isOn: $includeSatInView) { //64
Toggle(isOn: $includePosSeqNos) { //64
Label("Include a sequence number incremented per packet", systemImage: "number")
Label("Sequence number", systemImage: "number")
}
.toggleStyle(DefaultToggleStyle())
.listRowSeparator(.visible)
Toggle(isOn: $includeSatInView) { //128
Toggle(isOn: $includePosTimestamp) { //128
Label("Include positional timestamp", systemImage: "clock")
Label("Timestamp", systemImage: "clock")
}
.toggleStyle(DefaultToggleStyle())
.listRowSeparator(.visible)
Toggle(isOn: $includePosHeading) { //128
Label("Vehicle heading", systemImage: "location.circle")
}
.toggleStyle(DefaultToggleStyle())
.listRowSeparator(.visible)
Toggle(isOn: $includePosSpeed) { //128
Label("Vehicle speed", systemImage: "speedometer")
}
.toggleStyle(DefaultToggleStyle())
.listRowSeparator(.visible)
@ -239,7 +259,7 @@ struct PositionConfig: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {

View file

@ -55,7 +55,7 @@ struct RangeTestConfig: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {

View file

@ -13,6 +13,14 @@ struct Settings: View {
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)],
animation: .default)
private var nodes: FetchedResults<NodeInfoEntity>
var body: some View {
NavigationView {
List {
@ -53,8 +61,12 @@ struct Settings: View {
.symbolRenderingMode(.hierarchical)
Text("Display (Device Screen)")
}
NavigationLink {
LoRaConfig()
let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0
NavigationLink() {
LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
} label: {
Image(systemName: "dot.radiowaves.left.and.right")
@ -62,6 +74,8 @@ struct Settings: View {
Text("LoRa")
}
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
PositionConfig()
} label: {

View file

@ -81,7 +81,7 @@ struct ShareChannel: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {

View file

@ -26,7 +26,7 @@ struct TelemetryConfig: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????")
})
.onAppear {