Merge pull request #361 from meshtastic/2.1.11_Working_Changes

2.1.11 working changes
This commit is contained in:
Garth Vander Houwen 2023-05-26 21:25:18 -07:00 committed by GitHub
commit 2fc01479d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 769 additions and 101 deletions

3
.gitignore vendored
View file

@ -5,6 +5,9 @@
## User settings
xcuserdata/
## Generated Protobufs
protobufs/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

View file

@ -104,6 +104,8 @@
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A152A0594AD006ED576 /* TileOverlay.swift */; };
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; };
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */; };
DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */; };
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */; };
DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; };
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; };
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; };
@ -294,6 +296,8 @@
DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; sourceTree = "<group>"; };
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.swift; sourceTree = "<group>"; };
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV13.xcdatamodel; sourceTree = "<group>"; };
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrength.swift; sourceTree = "<group>"; };
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevelCompact.swift; sourceTree = "<group>"; };
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = "<group>"; };
DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; };
DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = "<group>"; };
@ -694,6 +698,8 @@
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */,
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -1008,6 +1014,7 @@
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */,
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */,
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */,
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */,
DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */,
@ -1060,6 +1067,7 @@
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */,
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */,
DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */,
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */,
DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */,
DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */,
@ -1298,7 +1306,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.1.10;
MARKETING_VERSION = 2.1.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1332,7 +1340,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.1.10;
MARKETING_VERSION = 2.1.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1451,7 +1459,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.10;
MARKETING_VERSION = 2.1.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1482,7 +1490,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.1.10;
MARKETING_VERSION = 2.1.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View file

@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"filename" : "heltecwsl 2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"filename" : "heltecwsl 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"filename" : "heltecwsl.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"filename" : "tbeam_supreme 2.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"filename" : "tbeam_supreme 1.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"filename" : "tbeam_supreme.jpg",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

View file

@ -269,6 +269,12 @@ enum MapTileServer: String, CaseIterable, Identifiable {
}
}
enum OverlayType: String, CaseIterable, Equatable {
case tileServer
case geoJson
var localized: String { self.rawValue.localized }
}
enum MapOverlayServer: String, CaseIterable, Identifiable {
case baseReReflectivityCurrent
@ -282,6 +288,29 @@ enum MapOverlayServer: String, CaseIterable, Identifiable {
case mrmsHybridScanReflectivityComposite
var id: String { self.rawValue }
var overlayType: OverlayType {
switch self {
case .baseReReflectivityCurrent:
return .tileServer
case .baseReReflectivityOneHourAgo:
return .tileServer
case .echoTopsEetCurrent:
return .tileServer
case .echoTopsEetOneHourAgo:
return .tileServer
case .q2OneHourPrecipitation:
return .tileServer
case .q2TwentyFourHourPrecipitation:
return .tileServer
case .q2FortyEightHourPrecipitation:
return .tileServer
case .q2SeventyTwoHourPrecipitation:
return .tileServer
case .mrmsHybridScanReflectivityComposite:
return .tileServer
}
}
var attribution: String {
return "NEXRAD Weather tiles from Iowa State University Environmental Mesonet [OGC Web Services](https://mesonet.agron.iastate.edu/ogc/)."
}

View file

@ -204,6 +204,26 @@ struct AdminMessage {
set {payloadVariant = .setHamMode(newValue)}
}
///
/// Get the mesh's nodes with their available gpio pins for RemoteHardware module use
var getNodeRemoteHardwarePinsRequest: Bool {
get {
if case .getNodeRemoteHardwarePinsRequest(let v)? = payloadVariant {return v}
return false
}
set {payloadVariant = .getNodeRemoteHardwarePinsRequest(newValue)}
}
///
/// Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use
var getNodeRemoteHardwarePinsResponse: NodeRemoteHardwarePinsResponse {
get {
if case .getNodeRemoteHardwarePinsResponse(let v)? = payloadVariant {return v}
return NodeRemoteHardwarePinsResponse()
}
set {payloadVariant = .getNodeRemoteHardwarePinsResponse(newValue)}
}
///
/// Set the owner for this node
var setOwner: User {
@ -409,6 +429,12 @@ struct AdminMessage {
/// Setup a node for licensed amateur (ham) radio operation
case setHamMode(HamParameters)
///
/// Get the mesh's nodes with their available gpio pins for RemoteHardware module use
case getNodeRemoteHardwarePinsRequest(Bool)
///
/// Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use
case getNodeRemoteHardwarePinsResponse(NodeRemoteHardwarePinsResponse)
///
/// Set the owner for this node
case setOwner(User)
///
@ -532,6 +558,14 @@ struct AdminMessage {
guard case .setHamMode(let l) = lhs, case .setHamMode(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getNodeRemoteHardwarePinsRequest, .getNodeRemoteHardwarePinsRequest): return {
guard case .getNodeRemoteHardwarePinsRequest(let l) = lhs, case .getNodeRemoteHardwarePinsRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getNodeRemoteHardwarePinsResponse, .getNodeRemoteHardwarePinsResponse): return {
guard case .getNodeRemoteHardwarePinsResponse(let l) = lhs, case .getNodeRemoteHardwarePinsResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setOwner, .setOwner): return {
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
return l == r
@ -803,12 +837,29 @@ struct HamParameters {
init() {}
}
///
/// Response envelope for node_remote_hardware_pins
struct NodeRemoteHardwarePinsResponse {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// Nodes and their respective remote hardware GPIO pins
var nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension AdminMessage: @unchecked Sendable {}
extension AdminMessage.OneOf_PayloadVariant: @unchecked Sendable {}
extension AdminMessage.ConfigType: @unchecked Sendable {}
extension AdminMessage.ModuleConfigType: @unchecked Sendable {}
extension HamParameters: @unchecked Sendable {}
extension NodeRemoteHardwarePinsResponse: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
@ -835,6 +886,8 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
16: .standard(proto: "get_device_connection_status_request"),
17: .standard(proto: "get_device_connection_status_response"),
18: .standard(proto: "set_ham_mode"),
19: .standard(proto: "get_node_remote_hardware_pins_request"),
20: .standard(proto: "get_node_remote_hardware_pins_response"),
32: .standard(proto: "set_owner"),
33: .standard(proto: "set_channel"),
34: .standard(proto: "set_config"),
@ -1028,6 +1081,27 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
self.payloadVariant = .setHamMode(v)
}
}()
case 19: try {
var v: Bool?
try decoder.decodeSingularBoolField(value: &v)
if let v = v {
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
self.payloadVariant = .getNodeRemoteHardwarePinsRequest(v)
}
}()
case 20: try {
var v: NodeRemoteHardwarePinsResponse?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .getNodeRemoteHardwarePinsResponse(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .getNodeRemoteHardwarePinsResponse(v)
}
}()
case 32: try {
var v: User?
var hadOneofValue = false
@ -1239,6 +1313,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
guard case .setHamMode(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 18)
}()
case .getNodeRemoteHardwarePinsRequest?: try {
guard case .getNodeRemoteHardwarePinsRequest(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 19)
}()
case .getNodeRemoteHardwarePinsResponse?: try {
guard case .getNodeRemoteHardwarePinsResponse(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 20)
}()
case .setOwner?: try {
guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 32)
@ -1382,3 +1464,35 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
return true
}
}
extension NodeRemoteHardwarePinsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".NodeRemoteHardwarePinsResponse"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "node_remote_hardware_pins"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.nodeRemoteHardwarePins) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.nodeRemoteHardwarePins.isEmpty {
try visitor.visitRepeatedMessageField(value: self.nodeRemoteHardwarePins, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: NodeRemoteHardwarePinsResponse, rhs: NodeRemoteHardwarePinsResponse) -> Bool {
if lhs.nodeRemoteHardwarePins != rhs.nodeRemoteHardwarePins {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -204,7 +204,7 @@ struct Config {
///
/// Router device role.
/// Mesh packets will prefer to be routed over this node. This node will not be used by client apps.
/// 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.
/// This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh.
case router // = 2
@ -217,7 +217,7 @@ struct Config {
///
/// Repeater device role
/// Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
/// or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
/// or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
case repeater // = 4
///
@ -508,7 +508,7 @@ struct Config {
///
/// Mesh Super Deep Sleep Timeout Seconds
/// While in Light Sleep if this value is exceeded we will lower into super deep sleep
/// While in Light Sleep if this value is exceeded we will lower into super deep sleep
/// for sds_secs (default 1 year) or a button press
/// 0 for default of two hours, MAXUINT for disabled
var meshSdsTimeoutSecs: UInt32 = 0
@ -674,7 +674,7 @@ struct Config {
var autoScreenCarouselSecs: UInt32 = 0
///
/// If this is set, the displayed compass will always point north. if unset, the old behaviour
/// If this is set, the displayed compass will always point north. if unset, the old behaviour
/// (top of display is heading direction) is used.
var compassNorthTop: Bool = false
@ -978,9 +978,9 @@ struct Config {
var channelNum: UInt32 = 0
///
/// If true, duty cycle limits will be exceeded and thus you're possibly not following
/// If true, duty cycle limits will be exceeded and thus you're possibly not following
/// the local regulations if you're not a HAM.
/// Has no effect if the duty cycle of the used region is 100%.
/// Has no effect if the duty cycle of the used region is 100%.
var overrideDutyCycle: Bool = false
///

View file

@ -98,7 +98,7 @@ struct WifiConnectionStatus {
mutating func clearStatus() {self._status = nil}
///
/// WiFi access point SSID
/// WiFi access point SSID
var ssid: String = String()
///

View file

@ -172,6 +172,13 @@ struct DeviceState {
/// Clears the value of `rxWaypoint`. Subsequent reads from it will return its default value.
mutating func clearRxWaypoint() {_uniqueStorage()._rxWaypoint = nil}
///
/// The mesh's nodes with their available gpio pins for RemoteHardware module
var nodeRemoteHardwarePins: [NodeRemoteHardwarePin] {
get {return _storage._nodeRemoteHardwarePins}
set {_uniqueStorage()._nodeRemoteHardwarePins = newValue}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
@ -263,11 +270,41 @@ struct OEMStore {
fileprivate var _oemLocalModuleConfig: LocalModuleConfig? = nil
}
///
/// RemoteHardwarePins associated with a node
struct NodeRemoteHardwarePin {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The node_num exposing the available gpio pin
var nodeNum: UInt32 = 0
///
/// The the available gpio pin for usage with RemoteHardware module
var pin: RemoteHardwarePin {
get {return _pin ?? RemoteHardwarePin()}
set {_pin = newValue}
}
/// Returns true if `pin` has been explicitly set.
var hasPin: Bool {return self._pin != nil}
/// Clears the value of `pin`. Subsequent reads from it will return its default value.
mutating func clearPin() {self._pin = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _pin: RemoteHardwarePin? = nil
}
#if swift(>=5.5) && canImport(_Concurrency)
extension ScreenFonts: @unchecked Sendable {}
extension DeviceState: @unchecked Sendable {}
extension ChannelFile: @unchecked Sendable {}
extension OEMStore: @unchecked Sendable {}
extension NodeRemoteHardwarePin: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
@ -294,6 +331,7 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
9: .standard(proto: "no_save"),
11: .standard(proto: "did_gps_reset"),
12: .standard(proto: "rx_waypoint"),
13: .standard(proto: "node_remote_hardware_pins"),
]
fileprivate class _StorageClass {
@ -306,6 +344,7 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
var _noSave: Bool = false
var _didGpsReset: Bool = false
var _rxWaypoint: MeshPacket? = nil
var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = []
static let defaultInstance = _StorageClass()
@ -321,6 +360,7 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
_noSave = source._noSave
_didGpsReset = source._didGpsReset
_rxWaypoint = source._rxWaypoint
_nodeRemoteHardwarePins = source._nodeRemoteHardwarePins
}
}
@ -348,6 +388,7 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
case 9: try { try decoder.decodeSingularBoolField(value: &_storage._noSave) }()
case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }()
case 12: try { try decoder.decodeSingularMessageField(value: &_storage._rxWaypoint) }()
case 13: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeRemoteHardwarePins) }()
default: break
}
}
@ -387,6 +428,9 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
try { if let v = _storage._rxWaypoint {
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
} }()
if !_storage._nodeRemoteHardwarePins.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._nodeRemoteHardwarePins, fieldNumber: 13)
}
}
try unknownFields.traverse(visitor: &visitor)
}
@ -405,6 +449,7 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
if _storage._noSave != rhs_storage._noSave {return false}
if _storage._didGpsReset != rhs_storage._didGpsReset {return false}
if _storage._rxWaypoint != rhs_storage._rxWaypoint {return false}
if _storage._nodeRemoteHardwarePins != rhs_storage._nodeRemoteHardwarePins {return false}
return true
}
if !storagesAreEqual {return false}
@ -529,3 +574,45 @@ extension OEMStore: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
return true
}
}
extension NodeRemoteHardwarePin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".NodeRemoteHardwarePin"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "node_num"),
2: .same(proto: "pin"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeNum) }()
case 2: try { try decoder.decodeSingularMessageField(value: &self._pin) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.nodeNum != 0 {
try visitor.visitSingularUInt32Field(value: self.nodeNum, fieldNumber: 1)
}
try { if let v = self._pin {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
} }()
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: NodeRemoteHardwarePin, rhs: NodeRemoteHardwarePin) -> Bool {
if lhs.nodeNum != rhs.nodeNum {return false}
if lhs._pin != rhs._pin {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -2318,6 +2318,10 @@ struct DeviceMetadata {
/// Device hardware model
var hwModel: HardwareModel = .unset
///
/// Has Remote Hardware enabled
var hasRemoteHardware_p: Bool = false
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
@ -4036,6 +4040,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
7: .same(proto: "role"),
8: .standard(proto: "position_flags"),
9: .standard(proto: "hw_model"),
10: .same(proto: "hasRemoteHardware"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -4053,6 +4058,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
case 7: try { try decoder.decodeSingularEnumField(value: &self.role) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }()
case 9: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }()
case 10: try { try decoder.decodeSingularBoolField(value: &self.hasRemoteHardware_p) }()
default: break
}
}
@ -4086,6 +4092,9 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
if self.hwModel != .unset {
try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 9)
}
if self.hasRemoteHardware_p != false {
try visitor.visitSingularBoolField(value: self.hasRemoteHardware_p, fieldNumber: 10)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -4099,6 +4108,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
if lhs.role != rhs.role {return false}
if lhs.positionFlags != rhs.positionFlags {return false}
if lhs.hwModel != rhs.hwModel {return false}
if lhs.hasRemoteHardware_p != rhs.hasRemoteHardware_p {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -20,6 +20,59 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
enum RemoteHardwarePinType: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// Unset/unused
case unknown // = 0
///
/// GPIO pin can be read (if it is high / low)
case digitalRead // = 1
///
/// GPIO pin can be written to (high / low)
case digitalWrite // = 2
case UNRECOGNIZED(Int)
init() {
self = .unknown
}
init?(rawValue: Int) {
switch rawValue {
case 0: self = .unknown
case 1: self = .digitalRead
case 2: self = .digitalWrite
default: self = .UNRECOGNIZED(rawValue)
}
}
var rawValue: Int {
switch self {
case .unknown: return 0
case .digitalRead: return 1
case .digitalWrite: return 2
case .UNRECOGNIZED(let i): return i
}
}
}
#if swift(>=4.2)
extension RemoteHardwarePinType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [RemoteHardwarePinType] = [
.unknown,
.digitalRead,
.digitalWrite,
]
}
#endif // swift(>=4.2)
///
/// Module Config
struct ModuleConfig {
@ -267,6 +320,14 @@ struct ModuleConfig {
/// Whether the Module is enabled
var enabled: Bool = false
///
/// Whether the Module allows consumers to read / write to pins not defined in available_pins
var allowUndefinedPinAccess: Bool = false
///
/// Exposes the available pins to the mesh for reading and writing
var availablePins: [RemoteHardwarePin] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
@ -641,7 +702,7 @@ struct ModuleConfig {
var sender: UInt32 = 0
///
/// Bool value indicating that this node should save a RangeTest.csv file.
/// Bool value indicating that this node should save a RangeTest.csv file.
/// ESP32 Only
var save: Bool = false
@ -891,7 +952,32 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable {
#endif // swift(>=4.2)
///
/// A GPIO pin definition for remote hardware module
struct RemoteHardwarePin {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// GPIO Pin number (must match Arduino)
var gpioPin: UInt32 = 0
///
/// Name for the GPIO pin (i.e. Front gate, mailbox, etc)
var name: String = String()
///
/// Type of GPIO access available to consumers on the mesh
var type: RemoteHardwarePinType = .unknown
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension RemoteHardwarePinType: @unchecked Sendable {}
extension ModuleConfig: @unchecked Sendable {}
extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {}
extension ModuleConfig.MQTTConfig: @unchecked Sendable {}
@ -907,12 +993,21 @@ extension ModuleConfig.RangeTestConfig: @unchecked Sendable {}
extension ModuleConfig.TelemetryConfig: @unchecked Sendable {}
extension ModuleConfig.CannedMessageConfig: @unchecked Sendable {}
extension ModuleConfig.CannedMessageConfig.InputEventChar: @unchecked Sendable {}
extension RemoteHardwarePin: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
extension RemoteHardwarePinType: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "UNKNOWN"),
1: .same(proto: "DIGITAL_READ"),
2: .same(proto: "DIGITAL_WRITE"),
]
}
extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ModuleConfig"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -1187,6 +1282,8 @@ extension ModuleConfig.RemoteHardwareConfig: SwiftProtobuf.Message, SwiftProtobu
static let protoMessageName: String = ModuleConfig.protoMessageName + ".RemoteHardwareConfig"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "enabled"),
2: .standard(proto: "allow_undefined_pin_access"),
3: .standard(proto: "available_pins"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1196,6 +1293,8 @@ extension ModuleConfig.RemoteHardwareConfig: SwiftProtobuf.Message, SwiftProtobu
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularBoolField(value: &self.enabled) }()
case 2: try { try decoder.decodeSingularBoolField(value: &self.allowUndefinedPinAccess) }()
case 3: try { try decoder.decodeRepeatedMessageField(value: &self.availablePins) }()
default: break
}
}
@ -1205,11 +1304,19 @@ extension ModuleConfig.RemoteHardwareConfig: SwiftProtobuf.Message, SwiftProtobu
if self.enabled != false {
try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1)
}
if self.allowUndefinedPinAccess != false {
try visitor.visitSingularBoolField(value: self.allowUndefinedPinAccess, fieldNumber: 2)
}
if !self.availablePins.isEmpty {
try visitor.visitRepeatedMessageField(value: self.availablePins, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: ModuleConfig.RemoteHardwareConfig, rhs: ModuleConfig.RemoteHardwareConfig) -> Bool {
if lhs.enabled != rhs.enabled {return false}
if lhs.allowUndefinedPinAccess != rhs.allowUndefinedPinAccess {return false}
if lhs.availablePins != rhs.availablePins {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -1778,3 +1885,47 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: SwiftProtobuf._ProtoN
27: .same(proto: "BACK"),
]
}
extension RemoteHardwarePin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".RemoteHardwarePin"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "gpio_pin"),
2: .same(proto: "name"),
3: .same(proto: "type"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.gpioPin) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.name) }()
case 3: try { try decoder.decodeSingularEnumField(value: &self.type) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.gpioPin != 0 {
try visitor.visitSingularUInt32Field(value: self.gpioPin, fieldNumber: 1)
}
if !self.name.isEmpty {
try visitor.visitSingularStringField(value: self.name, fieldNumber: 2)
}
if self.type != .unknown {
try visitor.visitSingularEnumField(value: self.type, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: RemoteHardwarePin, rhs: RemoteHardwarePin) -> Bool {
if lhs.gpioPin != rhs.gpioPin {return false}
if lhs.name != rhs.name {return false}
if lhs.type != rhs.type {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -124,10 +124,10 @@ enum PortNum: SwiftProtobuf.Enum {
case zpsApp // = 68
///
/// Used to let multiple instances of Linux native applications communicate
/// Used to let multiple instances of Linux native applications communicate
/// as if they did using their LoRa chip.
/// Maintained by GitHub user GUVWAF.
/// Project files at https://github.com/GUVWAF/Meshtasticator
/// Maintained by GitHub user GUVWAF.
/// Project files at https://github.com/GUVWAF/Meshtasticator
case simulatorApp // = 69
///

View file

@ -0,0 +1,88 @@
//
// BatteryIcon.swift
// Meshtastic
//
// Copyright Garth Vander Houwen 3/24/23.
//
import SwiftUI
struct BatteryLevelCompact: View {
var batteryLevel: Int32?
var font: Font
var iconFont: Font
var color: Color
var body: some View {
HStack (alignment: .center, spacing: 0) {
if batteryLevel == 100 {
Image(systemName: "battery.100.bolt")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel! < 100 && batteryLevel! > 74 {
Image(systemName: "battery.75")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel! < 75 && batteryLevel! > 49 {
Image(systemName: "battery.50")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel! < 50 && batteryLevel! > 14 {
Image(systemName: "battery.25")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel! < 15 && batteryLevel! > 0 {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel! == 0 {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(.red)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel! > 100 {
Image(systemName: "powerplug")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
}
if batteryLevel ?? 0 > 100 {
Text("PWD")
.font(font)
} else if batteryLevel == 100 {
Text("CHG")
.font(font)
} else {
Text("\(batteryLevel ?? 0)%")
.font(font)
}
}
}
}
struct BatteryLevelCompact_Previews: PreviewProvider {
static var previews: some View {
VStack {
BatteryLevelCompact(batteryLevel: 111, font: .footnote, iconFont: .callout, color: Color.accentColor)
BatteryLevelCompact(batteryLevel: 100, font: .footnote, iconFont: .callout, color: Color.accentColor)
BatteryLevelCompact(batteryLevel: 99, font: .footnote, iconFont: .callout, color: Color.accentColor)
BatteryLevelCompact(batteryLevel: 74, font: .footnote, iconFont: .callout, color: Color.accentColor)
BatteryLevelCompact(batteryLevel: 49, font: .footnote, iconFont: .callout, color: Color.accentColor)
BatteryLevelCompact(batteryLevel: 14, font: .footnote, iconFont: .callout, color: Color.accentColor)
}
}
}

View file

@ -0,0 +1,96 @@
//
// LoRaSignalStrength.swift
// Meshtastic
//
// Created by Garth Vander Houwen on 5/15/23.
//
import Foundation
import SwiftUI
struct LoRaSignalStrengthMeter: View {
var snr: Float
var rssi: Int32
var preset: ModemPresets
var compact: Bool
var body: some View {
let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset)
let gradient = Gradient(colors: [.red, .orange, .yellow, .green])
if !compact {
VStack {
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.footnote)
Text("SNR \(String(format: "%.2f", snr))dB")
.foregroundColor(getSnrColor(snr: snr, preset: ModemPresets.longFast))
.font(.caption2)
Text("RSSI \(rssi)dB")
.foregroundColor(getRssiColor(rssi: rssi))
.font(.caption2)
}
} else {
Gauge(value: Double(signalStrength.rawValue), in: 0...3) {
} currentValueLabel: {
Image(systemName: "dot.radiowaves.left.and.right")
.font(.caption)
Text("Signal \(signalStrength.description)")
.font(.caption)
}
.gaugeStyle(.accessoryLinear)
.tint(gradient)
.font(.caption)
}
}
}
struct LoRaSignalStrengthMeter_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
VStack {
HStack {
// Good
LoRaSignalStrengthMeter(snr: -10, rssi: -100, preset: ModemPresets.longFast, compact: false)
LoRaSignalStrengthMeter(snr: -17, rssi: -100, preset: ModemPresets.longFast, compact: false)
}
HStack {
// Fair
LoRaSignalStrengthMeter(snr: -9.5, rssi: -119, preset: ModemPresets.longFast, compact: false)
LoRaSignalStrengthMeter(snr: -17.5, rssi: -100, preset: ModemPresets.longFast, compact: false)
}
HStack {
// Bad
LoRaSignalStrengthMeter(snr: -11.25, rssi: -120, preset: ModemPresets.longFast, compact: false)
LoRaSignalStrengthMeter(snr: -12.75, rssi: -139, preset: ModemPresets.longFast, compact: false)
}
HStack {
LoRaSignalStrengthMeter(snr: -20.25, rssi: -128, preset: ModemPresets.longFast, compact: false)
LoRaSignalStrengthMeter(snr: -30, rssi: -120, preset: ModemPresets.longFast, compact: false)
}
HStack {
LoRaSignalStrengthMeter(snr: -15, rssi: -124, preset: ModemPresets.longFast, compact: false)
LoRaSignalStrengthMeter(snr: -17.25, rssi: -126, preset: ModemPresets.longFast, compact: false)
LoRaSignalStrengthMeter(snr: -19.5, rssi: -128, preset: ModemPresets.longFast, compact: false)
}
HStack {
// None
LoRaSignalStrengthMeter(snr: -26.0, rssi: -129, preset: ModemPresets.longFast, compact: false)
}
}
}
VStack {
// Good
LoRaSignalStrengthMeter(snr: -10, rssi: -100, preset: ModemPresets.longFast, compact: true)
// Fair
LoRaSignalStrengthMeter(snr: -9.5, rssi: -119, preset: ModemPresets.longFast, compact: true)
// Bad
LoRaSignalStrengthMeter(snr: -12.75, rssi: -139, preset: ModemPresets.longFast, compact: true)
// None
LoRaSignalStrengthMeter(snr: -26.0, rssi: -128, preset: ModemPresets.longFast, compact: true)
}
}
}

View file

@ -5,8 +5,6 @@
// Copyright Garth Vander Houwen 5/9/23.
//
import Foundation
import Foundation
import SwiftUI
@ -18,22 +16,25 @@ struct LoRaSignalStrengthIndicator: View {
ForEach(0..<3) { bar in
RoundedRectangle(cornerRadius: 3)
.divided(amount: (CGFloat(bar) + 1) / CGFloat(3))
.fill(getColor().opacity(bar <= signalStrength.rawValue ? 1 : 0.3))
.fill(getColor(signalStrength: signalStrength).opacity(bar <= signalStrength.rawValue ? 1 : 0.3))
.frame(width: 8, height: 40)
}
}
}
}
private func getColor() -> Color {
switch signalStrength {
case .none:
return Color.red
case .bad:
return Color.orange
case .fair:
return Color.yellow
case .good:
return Color.green
struct LoRaSignalStrengthIndicator_Previews: PreviewProvider {
static var previews: some View {
VStack {
let signalStrength = getLoRaSignalStrength(snr: -12.75, rssi: -139, preset: ModemPresets.longFast)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.footnote)
Text("SNR \(String(format: "%.2f", -12.75))dB")
.foregroundColor(getSnrColor(snr: -12.75, preset: ModemPresets.longFast))
.font(.caption2)
Text("RSSI \(-139)dB")
.foregroundColor(getRssiColor(rssi: -139))
.font(.caption2)
}
}
}
@ -57,13 +58,26 @@ enum LoRaSignalStrength: Int {
}
}
func getLoRaSignalStrength(snr: Float, rssi: Int32) -> LoRaSignalStrength {
private func getColor(signalStrength: LoRaSignalStrength) -> Color {
switch signalStrength {
case .none:
return Color.red
case .bad:
return Color.orange
case .fair:
return Color.yellow
case .good:
return Color.green
}
}
func getLoRaSignalStrength(snr: Float, rssi: Int32, preset: ModemPresets) -> LoRaSignalStrength {
if rssi > -115 && snr > -7 {
if rssi > -115 && snr > (preset.snrLimit()) {
return .good
} else if rssi < -126 && snr < -15 {
} else if rssi < -126 && snr < (preset.snrLimit() - 7.5) {
return .none
} else if rssi <= -120 || snr <= -13 {
} else if rssi <= -120 || snr <= (preset.snrLimit() - 5.5) {
return .bad
} else {
return .fair
@ -86,14 +100,14 @@ func getRssiColor(rssi: Int32) -> Color {
}
}
func getSnrColor(snr: Float) -> Color {
if snr > -7 {
func getSnrColor(snr: Float, preset: ModemPresets) -> Color {
if snr > preset.snrLimit() {
/// Good
return .green
} else if snr < -7 && snr > -13 {
} else if snr < preset.snrLimit() && snr > (preset.snrLimit() - 5.5) {
/// Fair
return .yellow
} else if snr >= -14 {
} else if snr >= (preset.snrLimit() - 7.5) {
/// Bad
return .orange
} else {

View file

@ -47,11 +47,11 @@ struct NodeInfoView: View {
Divider()
if node.snr != 0 {
VStack(alignment: .center) {
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi)
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.title)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(getSnrColor(snr: node.snr))
.foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
.font(.title3)
Text("RSSI \(node.rssi)dB")
.foregroundColor(getRssiColor(rssi: node.rssi))
@ -156,11 +156,11 @@ struct NodeInfoView: View {
if node.snr != 0 {
Divider()
VStack(alignment: .center) {
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi)
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.footnote)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(getSnrColor(snr: node.snr))
.foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
.font(.caption2)
Text("RSSI \(node.rssi)dB")
.foregroundColor(getRssiColor(rssi: node.rssi))

View file

@ -4,9 +4,18 @@
//
// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22.
import Foundation
import SwiftUI
import MapKit
struct PolygonInfo: Codable {
let stroke: String?
let strokeWidth, strokeOpacity: Int?
let fill: String?
let fillOpacity: Double?
let title, subtitle: String?
}
func degreesToRadians(_ number: Double) -> Double {
return number * .pi / 180
}
@ -29,7 +38,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
let showRouteLines: Bool
let mapViewType: MKMapType = MKMapType.standard
// Offline Map Tiles
@AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
@State private var loadedLastUpdatedLocalMapFile = 0
@ -65,6 +74,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
// Other MKMapView Settings
mapView.preferredConfiguration.elevationStyle = .realistic// .flat
mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
mapView.isPitchEnabled = true
mapView.isRotateEnabled = true
mapView.isScrollEnabled = true
@ -134,14 +144,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
func makeUIView(context: Context) -> MKMapView {
currentMapLayer = nil
mapView.delegate = context.coordinator
self.configureMap(mapView: mapView)
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
private func setMbtilesOverlay(mapView: MKMapView) {
// MBTiles Offline
if UserDefaults.enableOfflineMaps && UserDefaults.enableOfflineMapsMBTiles {
@ -149,7 +152,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile {
mapView.removeOverlays(mapView.overlays)
if self.customMapOverlay != nil {
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path
@ -169,15 +172,69 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
}
}
private func setGeoJsonOverlay(mapView: MKMapView) {
guard let geoJsonFileUrl = URL(string: "https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json"), // Bundle.main.url(forResource: "location", withExtension: "geojson"),
//guard let geoJsonFileUrl = URL(string: "https://hrbrmstr.github.io/noaa-alerts-sp-to-geojson/current-all.geojson"),
let geoJsonData = try? Data.init(contentsOf: geoJsonFileUrl) else {
fatalError("Failure to fetch the file.")
}
guard let objs = try? MKGeoJSONDecoder().decode(geoJsonData) as? [MKGeoJSONFeature] else {
fatalError("Wrong format")
}
// Parse the objects
objs.forEach { (feature) in
guard let geometry = feature.geometry.first,
let propData = feature.properties else {
return;
}
// Check if it is MKPolygon
if let polygon = geometry as? MKPolygon {
let polygonInfo = try? JSONDecoder.init().decode(PolygonInfo.self, from: propData)
mapView.addOverlay(polygon)
//self.view?.render(overlay: polygon, info: polygonInfo)
}
// Check if it is MKPolyline
if let polyline = geometry as? MKPolyline {
mapView.addOverlay(polyline, level: .aboveLabels)
//let polylineInfo = try? JSONDecoder.init().decode(PolylineInfo.self, from: propData)
//self.view?.render(overlay: polyline, info: polylineInfo)
}
// Check if it is MKPointAnnotation
// if let annotation = geometry as? MKPointAnnotation {
// let info = try? JSONDecoder.init().decode(Info.self, from: propData)
// let storeAnnotation = StoreAnnotation.init(title: info?.name,
// subtitle: info?.subTitle,
// website: info?.website,
// coordinate: annotation.coordinate)
// self.view?.setAnnotations(annotations: [storeAnnotation])
// }
}
}
func makeUIView(context: Context) -> MKMapView {
currentMapLayer = nil
mapView.delegate = context.coordinator
self.configureMap(mapView: mapView)
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
// Set MBTiles overlay layer
setMbtilesOverlay(mapView: mapView)
// Set selected map base layer
setMapBaseLayer(mapView: mapView)
// Set map overlay layer
// Set map tile server and weather overlay layers
setMapOverlays(mapView: mapView)
let latest = positions
.filter { $0.latest == true }
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
// Node Route Lines
if showRouteLines {
// Remove all existing PolyLine Overlays
@ -437,7 +494,14 @@ struct MapViewSwiftUI: UIViewRepresentable {
renderer.lineWidth = 8
return renderer
}
return MKOverlayRenderer()
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
renderer.strokeColor = .purple.withAlphaComponent(0.7)
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
}

View file

@ -54,7 +54,7 @@ struct NodeDetail: View {
var body: some View {
let hwModelString = node.user?.hwModel ?? "UNSET"
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
NavigationStack {
GeometryReader { bounds in
VStack {
@ -84,19 +84,21 @@ struct NodeDetail: View {
VStack(alignment: .leading) {
Spacer()
HStack(alignment: .bottom, spacing: 1) {
// Picker("Map Type", selection: $mapType) {
// ForEach(MeshMapTypes.allCases) { map in
// Text(map.description)
// .tag(map.MKMapTypeValue())
// }
// }
// .onChange(of: (mapType)) { newMapType in
// UserDefaults.mapType = Int(newMapType.rawValue)
// }
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
// .pickerStyle(.menu)
// .padding(5)
Picker("Map Type", selection: $selectedMapLayer) {
ForEach(MapLayer.allCases, id: \.self) { layer in
if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
Text(layer.localized)
} else if layer != MapLayer.offline {
Text(layer.localized)
}
}
}
.onChange(of: (selectedMapLayer)) { newMapLayer in
UserDefaults.mapLayer = newMapLayer
}
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.pickerStyle(.menu)
.padding(5)
VStack {
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
.font(.caption)
@ -151,7 +153,7 @@ struct NodeDetail: View {
if self.bleManager.connectedPeripheral != nil && node.metadata != nil {
HStack {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
if node.metadata?.canShutdown ?? false {
Button(action: {

View file

@ -27,22 +27,31 @@ struct NodeList: View {
var body: some View {
NavigationSplitView {
let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
List(nodes, id: \.self, selection: $selection) { node in
if nodes.count == 0 {
Text("no.nodes").font(.title)
} else {
NavigationLink(value: node) {
let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num)
VStack(alignment: .leading) {
LazyVStack(alignment: .leading) {
HStack {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : 22, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
.padding(.trailing, 5)
VStack(alignment: .leading) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : 22, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
.padding(.trailing, 5)
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor)
}
}
VStack(alignment: .leading) {
Text(node.user?.longName ?? "unknown".localized)
.fontWeight(.medium)
.font(.callout)
if connected {
HStack {
HStack(alignment: .bottom) {
Image(systemName: "repeat.circle.fill")
.font(.callout)
.symbolRenderingMode(.hierarchical)
@ -80,6 +89,11 @@ struct NodeList: View {
LastHeardText(lastHeard: node.lastHeard)
.font(.caption)
}
if !connected {
HStack(alignment: .bottom) { let preset = ModemPresets(rawValue: Int(connectedNode?.loRaConfig?.modemPreset ?? 0))
LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}

View file

@ -178,14 +178,14 @@ struct NodeMap: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.onChange(of: (enableOfflineMaps)) { newEnableOfflineMaps in
UserDefaults.enableOfflineMaps = newEnableOfflineMaps
if !newEnableOfflineMaps {
UserDefaults.enableOfflineMaps = enableOfflineMaps
if !enableOfflineMaps {
if self.selectedMapLayer == .offline {
self.selectedMapLayer = .standard
}
}
}
if UserDefaults.enableOfflineMaps {
if enableOfflineMaps {
VStack (alignment: .leading) {
if !enableOfflineMapsMBTiles {

View file

@ -262,54 +262,42 @@ struct DeviceConfig: View {
}
}
.onChange(of: deviceRole) { newRole in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newRole != node!.deviceConfig!.role { hasChanges = true }
}
}
.onChange(of: serialEnabled) { newSerial in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true }
}
}
.onChange(of: debugLogEnabled) { newDebugLog in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true }
}
}
.onChange(of: buttonGPIO) { newButtonGPIO in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newButtonGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true }
}
}
.onChange(of: buzzerGPIO) { newBuzzerGPIO in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newBuzzerGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true }
}
}
.onChange(of: rebroadcastMode) { newRebroadcastMode in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newRebroadcastMode != node!.deviceConfig!.rebroadcastMode { hasChanges = true }
}
}
.onChange(of: doubleTapAsButtonPress) { newDoubleTapAsButtonPress in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newDoubleTapAsButtonPress != node!.deviceConfig!.doubleTapAsButtonPress { hasChanges = true }
}
}
.onChange(of: isManaged) { newIsManaged in
if node != nil && node!.deviceConfig != nil {
if node != nil && node?.deviceConfig != nil {
if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true }
}
}