Merge pull request #162 from meshtastic/text_size_cleanup

Assorted Layout Cleanup, MQTT Config
This commit is contained in:
Garth Vander Houwen 2022-09-04 22:40:02 -07:00 committed by GitHub
commit e89b134827
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 605 additions and 58 deletions

View file

@ -16,6 +16,7 @@
DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */; };
DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */; };
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; };
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; };
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
@ -121,6 +122,8 @@
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = "<group>"; };
DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = storeforward.pb.swift; sourceTree = "<group>"; };
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = "<group>"; };
DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 10.xcdatamodel"; sourceTree = "<group>"; };
DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = "<group>"; };
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = "<group>"; };
DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = "<group>"; };
@ -260,6 +263,13 @@
path = Custom;
sourceTree = "<group>";
};
DD2160AC28C5019400C17253 /* Messages */ = {
isa = PBXGroup;
children = (
);
path = Messages;
sourceTree = "<group>";
};
DD47E3CA26F0E50300029299 /* Nodes */ = {
isa = PBXGroup;
children = (
@ -318,6 +328,7 @@
DD41582928585C32009B0E59 /* RangeTestConfig.swift */,
DD6193782863875F00E59241 /* SerialConfig.swift */,
DD415827285859C4009B0E59 /* TelemetryConfig.swift */,
DD2160AE28C5552500C17253 /* MQTTConfig.swift */,
);
path = Module;
sourceTree = "<group>";
@ -488,6 +499,7 @@
DDC2E18D26CE25CB0042C5E4 /* Helpers */ = {
isa = PBXGroup;
children = (
DD2160AC28C5019400C17253 /* Messages */,
DD47E3D526F17ED900029299 /* CircleText.swift */,
DD47E3D826F3093800029299 /* MessageBubble.swift */,
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */,
@ -705,6 +717,7 @@
DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */,
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */,
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */,
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */,
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */,
@ -936,7 +949,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.39;
MARKETING_VERSION = 1.3.40;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -968,7 +981,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.39;
MARKETING_VERSION = 1.3.40;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1141,6 +1154,7 @@
DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */,
DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */,
DD4033C328B405A60096A444 /* MeshtasticDataModel v 8.xcdatamodel */,
DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */,
@ -1151,7 +1165,7 @@
DD45C77427BD4EF80011784F /* MeshtasticDataModel v2.xcdatamodel */,
DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */,
);
currentVersion = DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */;
currentVersion = DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -1418,6 +1418,35 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
return 0
}
public func saveMQTTConfig(config: ModuleConfig.MQTTConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 {
var adminPacket = AdminMessage()
adminPacket.setModuleConfig.mqtt = 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 = true
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "Unknown")"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
return Int64(meshPacket.id)
}
return 0
}
public func saveRangeTestModuleConfig(config: ModuleConfig.RangeTestConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 {
var adminPacket = AdminMessage()

View file

@ -497,7 +497,6 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
newWiFiConfig.password = config.wifi.psk
newWiFiConfig.mode = Int32(config.wifi.mode.rawValue)
}
newWiFiConfig.num = fetchedNode[0].num
fetchedNode[0].wiFiConfig = newWiFiConfig
} else {
@ -596,8 +595,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj
newCannedMessageConfig.inputbrokerEventCcw = Int32(config.cannedMessage.inputbrokerEventCcw.rawValue)
newCannedMessageConfig.inputbrokerEventPress = Int32(config.cannedMessage.inputbrokerEventPress.rawValue)
}
newCannedMessageConfig.num = nodeNum
fetchedNode[0].cannedMessageConfig = newCannedMessageConfig
} else {
@ -628,7 +625,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj
fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.cannedMessage.inputbrokerEventCcw.rawValue)
fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.cannedMessage.inputbrokerEventPress.rawValue)
}
fetchedNode[0].cannedMessageConfig?.num = nodeNum
}
do {
@ -703,8 +699,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj
newExternalNotificationConfig.output = Int32(config.externalNotification.output)
newExternalNotificationConfig.outputMilliseconds = Int32(config.externalNotification.outputMs)
}
newExternalNotificationConfig.num = nodeNum
fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig
} else {
@ -727,7 +721,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj
fetchedNode[0].externalNotificationConfig?.output = Int32(config.externalNotification.output)
fetchedNode[0].externalNotificationConfig?.outputMilliseconds = Int32(config.externalNotification.outputMs)
}
fetchedNode[0].externalNotificationConfig?.num = nodeNum
}
do {
@ -789,7 +782,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj
newRangeTestConfig.enabled = config.rangeTest.enabled
newRangeTestConfig.save = config.rangeTest.save
}
newRangeTestConfig.num = nodeNum
fetchedNode[0].rangeTestConfig = newRangeTestConfig
} else {

View file

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

View file

@ -0,0 +1,233 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21G83" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<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="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="txPower" 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="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<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="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
<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="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
</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="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<relationship name="wiFiConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="WiFiConfigEntity" inverseName="wiFiConfigNode" inverseEntity="WiFiConfigEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
</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="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" 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="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="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="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<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="adminMessages" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(toUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
</fetchedProperty>
<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 AND admin = false"/>
</fetchedProperty>
</entity>
<entity name="WiFiConfigEntity" representedClassName="WiFiConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="password" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
<attribute name="ssid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
<relationship name="wiFiConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="wiFiConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<elements>
<element name="BluetoothConfigEntity" positionX="54" positionY="153" width="128" height="104"/>
<element name="CannedMessageConfigEntity" positionX="45" positionY="144" width="128" height="194"/>
<element name="DeviceConfigEntity" positionX="45" positionY="144" width="128" height="89"/>
<element name="DisplayConfigEntity" positionX="54" positionY="153" width="128" height="104"/>
<element name="ExternalNotificationConfigEntity" positionX="63" positionY="162" width="128" height="134"/>
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="245"/>
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="209"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="344"/>
<element name="PositionConfigEntity" positionX="63" positionY="162" width="128" height="149"/>
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="RangeTestConfigEntity" positionX="72" positionY="171" width="128" height="89"/>
<element name="SerialConfigEntity" positionX="54" positionY="153" width="128" height="149"/>
<element name="TelemetryConfigEntity" positionX="72" positionY="171" width="128" height="119"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="209"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="230"/>
<element name="WiFiConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="MQTTConfigEntity" positionX="45" positionY="144" width="128" height="134"/>
</elements>
</model>

View file

@ -732,8 +732,8 @@ struct Config {
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
/// Maximum number of hops. This can't be greater than 7.
/// Default of 3
var hopLimit: UInt32 = 0
///

View file

@ -205,6 +205,10 @@ struct ModuleConfig {
/// Decrypted packets may be useful for external systems that want to consume meshtastic packets
var encryptionEnabled: Bool = false
///
/// Whether to send / consume json packets on MQTT
var jsonEnabled: Bool = false
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
@ -850,6 +854,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
3: .same(proto: "username"),
4: .same(proto: "password"),
5: .standard(proto: "encryption_enabled"),
6: .standard(proto: "json_enabled"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -863,6 +868,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
case 3: try { try decoder.decodeSingularStringField(value: &self.username) }()
case 4: try { try decoder.decodeSingularStringField(value: &self.password) }()
case 5: try { try decoder.decodeSingularBoolField(value: &self.encryptionEnabled) }()
case 6: try { try decoder.decodeSingularBoolField(value: &self.jsonEnabled) }()
default: break
}
}
@ -884,6 +890,9 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
if self.encryptionEnabled != false {
try visitor.visitSingularBoolField(value: self.encryptionEnabled, fieldNumber: 5)
}
if self.jsonEnabled != false {
try visitor.visitSingularBoolField(value: self.jsonEnabled, fieldNumber: 6)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -893,6 +902,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
if lhs.username != rhs.username {return false}
if lhs.password != rhs.password {return false}
if lhs.encryptionEnabled != rhs.encryptionEnabled {return false}
if lhs.jsonEnabled != rhs.jsonEnabled {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -20,7 +20,7 @@ struct Connect: View {
@State var isPreferredRadio: Bool = false
@State var firmwareVersion = "0.0.0"
@State var minimumVersion = "1.3.39"
@State var minimumVersion = "1.3.40"
@State var invalidVersion = false
var body: some View {

View file

@ -23,6 +23,11 @@ struct Contacts: View {
var body: some View {
NavigationView {
// Display Contact for Primary Channel
// Display Contacts for DM's on the Primary Channel
// Display Contacts for the rest of the non admin channels
List(users) { (user: UserEntity) in

View file

@ -64,17 +64,21 @@ struct NodeDetail: View {
)
}
.ignoresSafeArea(.all, edges: [.leading, .trailing])
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.75)
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.6)
}
}
Text("Sats: \(mostRecent.satsInView)").offset( y:-40)
} else {
Image(node.user?.hwModel ?? "UNSET")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
.frame(width: bounds.size.width, height: bounds.size.height / 2)
HStack {
Image(node.user?.hwModel ?? "UNSET")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
.frame(width: bounds.size.width, height: bounds.size.height / 2.3)
.padding([.top], 40)
}
.offset( y:-40)
}
ScrollView {
@ -168,6 +172,7 @@ struct NodeDetail: View {
Image(hwModelString)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 90, height: 90)
.cornerRadius(5)
@ -429,16 +434,16 @@ struct NodeDetail: View {
}
}
.listStyle(GroupedListStyle())
.frame(minHeight:170)
.frame(minHeight: 170)
.padding(0)
}
.offset( y: (node.myInfo!.hasGps ? 0 : -40))
.offset( y: (node.myInfo?.hasGps ?? false ? 0 : -40))
}
.edgesIgnoringSafeArea([.leading, .trailing])
}
}
.navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown")
.navigationBarTitleDisplayMode(.automatic)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {

View file

@ -123,6 +123,7 @@ struct NodeList: View {
}
.padding([.leading, .top, .bottom])
}
.isDetailLink(false)
}
}
}

View file

@ -128,7 +128,7 @@ struct CannedMessagesConfig: View {
/// Generate input event on Press of this kind.
@State var inputbrokerEventPress = 0
@State var messagesPart1 = ""
@State var messages = ""
var body: some View {
@ -160,10 +160,34 @@ struct CannedMessagesConfig: View {
.padding(.bottom, 10)
}
Section(header: Text("Messages")) {
TextEditor(text: $messagesPart1)
HStack {
Label("Messages", systemImage: "message.fill")
TextField("Messages seperate with |", text: $messages)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: messages, perform: { value in
let totalBytes = messages.utf8.count
// Only mess with the value if it is too big
if totalBytes > 198 {
let firstNBytes = Data(messages.utf8.prefix(198))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
messages = maxBytesString
}
}
hasMessagesChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
Section(header: Text("Control Type")) {
@ -331,7 +355,7 @@ struct CannedMessagesConfig: View {
if hasMessagesChanges {
let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messagesPart1, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
@ -465,13 +489,6 @@ struct CannedMessagesConfig: View {
if newKeyPress != node!.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true }
}
}
.onChange(of: messagesPart1) { newMessagesChanges in
if node != nil && node!.cannedMessageConfig != nil {
hasMessagesChanges = true
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -0,0 +1,233 @@
//
// MQTT.swift
// Meshtastic
//
// Copyright (c) Garth Vander Houwen 9/4/22.
//
import SwiftUI
struct MQTTConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@State var hasChanges: Bool = false
@State var enabled = false
@State var address = ""
@State var username = ""
@State var password = ""
@State var encryptionEnabled = false
@State var jsonEnabled = false
var body: some View {
VStack {
Form {
Text("WiFi must also be enabled for MQTT to work. You can set uplink and downlink for each channel.")
.font(.title3)
Section(header: Text("Options")) {
Toggle(isOn: $enabled) {
Label("Enabled", systemImage: "dot.radiowaves.right")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $encryptionEnabled) {
Label("Encryption Enabled", systemImage: "lock.icloud")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $jsonEnabled) {
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("Custom Server")) {
HStack {
Label("Address", systemImage: "server.rack")
TextField("Server Address", text: $username)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: address, perform: { value in
let totalBytes = address.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
let firstNBytes = Data(username.utf8.prefix(30))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
address = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
HStack {
Label("Username", systemImage: "person.text.rectangle")
TextField("Server Username", text: $username)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: username, perform: { value in
let totalBytes = username.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
let firstNBytes = Data(username.utf8.prefix(30))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
username = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
HStack {
Label("Password", systemImage: "wallet.pass")
TextField("Server Password", text: $password)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: password, perform: { value in
let totalBytes = password.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
let firstNBytes = Data(password.utf8.prefix(30))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
password = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
}
}
.disabled(!(node != nil && node!.myInfo?.hasWifi ?? false))
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 WiFI Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
var mqtt = ModuleConfig.MQTTConfig()
mqtt.enabled = self.enabled
mqtt.address = self.address
mqtt.username = self.username
mqtt.password = self.password
mqtt.encryptionEnabled = self.encryptionEnabled
mqtt.jsonEnabled = self.jsonEnabled
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: node!.user!, toUser: node!.user!)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
// for now just disable the button after a successful save
self.hasChanges = false
} else {
}
}
}
}
.navigationTitle("MQTT Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
.onAppear {
if self.initialLoad{
self.bleManager.context = context
self.enabled = (node!.mqttConfig?.enabled ?? false)
self.address = node!.mqttConfig?.address ?? ""
self.username = node!.mqttConfig?.username ?? ""
self.password = node!.mqttConfig?.password ?? ""
self.encryptionEnabled = (node!.mqttConfig?.encryptionEnabled ?? false)
self.jsonEnabled = (node!.mqttConfig?.jsonEnabled ?? false)
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: enabled) { newEnabled in
if node != nil && node!.wiFiConfig != nil {
if newEnabled != node!.wiFiConfig!.enabled { hasChanges = true }
}
}
.onChange(of: encryptionEnabled) { newEncryptionEnabled in
if node != nil && node!.wiFiConfig != nil {
if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true }
}
}
.onChange(of: jsonEnabled) { newJsonEnabled in
if node != nil && node!.wiFiConfig != nil {
if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true }
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -72,6 +72,7 @@ struct WiFiConfig: View {
ssid = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
@ -99,6 +100,7 @@ struct WiFiConfig: View {
password = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
@ -176,20 +178,6 @@ struct WiFiConfig: View {
if newEnabled != node!.wiFiConfig!.enabled { hasChanges = true }
}
}
.onChange(of: ssid) { newSsid in
if node != nil && node!.wiFiConfig != nil {
if newSsid != node!.wiFiConfig!.ssid { hasChanges = true }
}
}
.onChange(of: password) { newPassword in
if node != nil && node!.wiFiConfig != nil {
if newPassword != node!.wiFiConfig!.password { hasChanges = true }
}
}
.onChange(of: mode) { newMode in
if node != nil && node!.wiFiConfig != nil {

View file

@ -151,6 +151,17 @@ struct Settings: View {
}
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
MQTTConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "dot.radiowaves.right")
.symbolRenderingMode(.hierarchical)
Text("MQTT (ESP32 Only)")
}
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {

View file

@ -62,16 +62,25 @@ struct ShareChannel: View {
.resizable()
.scaledToFit()
.frame(
minWidth: smallest * 0.9,
maxWidth: smallest * 0.9,
minHeight: smallest * 0.9,
maxHeight: smallest * 0.9,
minWidth: smallest * 0.8,
maxWidth: smallest * 0.8,
minHeight: smallest * 0.8,
maxHeight: smallest * 0.8,
alignment: .center
)
Spacer()
Text("Channel Name (Long/Slow)").font(.title)
Text(String(node!.myInfo!.maxChannels))
Spacer()
if node?.loRaConfig != nil {
HStack {
let preset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset))
Text("Modem Preset \(preset!.description)").font(.title3)
}
}
HStack {
Text("Number of Channels: \(node!.myInfo!.maxChannels)").font(.title2)
}
}
.frame(width: bounds.size.width, height: bounds.size.height)
}