diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index fadb81fe..fc1539f2 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -277,6 +277,7 @@ 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricTableColumn.swift; sourceTree = ""; }; 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = ""; }; 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnDetail.swift; sourceTree = ""; }; + 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 50.xcdatamodel"; sourceTree = ""; }; 2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAttributePropertyWrapper.swift; sourceTree = ""; }; 2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataClass.swift"; sourceTree = ""; }; 2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = ""; }; @@ -2011,6 +2012,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */, 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */, DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */, DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */, @@ -2061,7 +2063,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */; + currentVersion = 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift b/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift index 3b123207..5c5fc71f 100644 --- a/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift +++ b/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift @@ -29,6 +29,8 @@ public struct ManagedAttribute { converter = { $0.int32Value as? Value } } else if Value.self == Int64.self { converter = { $0.int64Value as? Value } + } else if Value.self == UInt32.self { + converter = { $0.uint32Value as? Value } } else { fatalError("Unsupported type: \(Value.self)") } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 093066ef..b6c8db17 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -723,10 +723,20 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage telemetry.current = telemetryMessage.environmentMetrics.hasCurrent.then(telemetryMessage.environmentMetrics.current) telemetry.voltage = telemetryMessage.environmentMetrics.hasVoltage.then(telemetryMessage.environmentMetrics.voltage) telemetry.weight = telemetryMessage.environmentMetrics.hasWeight.then(telemetryMessage.environmentMetrics.weight) + telemetry.distance = telemetryMessage.environmentMetrics.hasDistance.then(telemetryMessage.environmentMetrics.distance) telemetry.windSpeed = telemetryMessage.environmentMetrics.hasWindSpeed.then(telemetryMessage.environmentMetrics.windSpeed) telemetry.windGust = telemetryMessage.environmentMetrics.hasWindGust.then(telemetryMessage.environmentMetrics.windGust) telemetry.windLull = telemetryMessage.environmentMetrics.hasWindLull.then(telemetryMessage.environmentMetrics.windLull) telemetry.windDirection = telemetryMessage.environmentMetrics.hasWindDirection.then(Int32(truncatingIfNeeded: telemetryMessage.environmentMetrics.windDirection)) + telemetry.irLux = telemetryMessage.environmentMetrics.hasIrLux.then(telemetryMessage.environmentMetrics.irLux) + telemetry.lux = telemetryMessage.environmentMetrics.hasLux.then(telemetryMessage.environmentMetrics.lux) + telemetry.whiteLux = telemetryMessage.environmentMetrics.hasWhiteLux.then(telemetryMessage.environmentMetrics.whiteLux) + telemetry.uvLux = telemetryMessage.environmentMetrics.hasUvLux.then(telemetryMessage.environmentMetrics.uvLux) + telemetry.radiation = telemetryMessage.environmentMetrics.hasRadiation.then(telemetryMessage.environmentMetrics.radiation) + telemetry.rainfall1H = telemetryMessage.environmentMetrics.hasRainfall1H.then(telemetryMessage.environmentMetrics.rainfall1H) + telemetry.rainfall24H = telemetryMessage.environmentMetrics.hasRainfall24H.then(telemetryMessage.environmentMetrics.rainfall24H) + telemetry.soilTemperature = telemetryMessage.environmentMetrics.hasSoilTemperature.then(telemetryMessage.environmentMetrics.soilTemperature) + telemetry.soilMoisture = telemetryMessage.environmentMetrics.hasSoilMoisture.then(telemetryMessage.environmentMetrics.soilMoisture) telemetry.metricsType = 1 } else if telemetryMessage.variant == Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) { // Local Stats for Live activity diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 0b4b8e13..cd530ab2 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 49.xcdatamodel + MeshtasticDataModelV 50.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 50.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 50.xcdatamodel/contents new file mode 100644 index 00000000..52f1a59f --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 50.xcdatamodel/contents @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift b/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift index bcbaf4b9..48f8263b 100644 --- a/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift +++ b/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift @@ -42,5 +42,14 @@ public class TelemetryEntity: NSManagedObject, Identifiable { @ManagedAttribute(attributeName: "windGust") public var windGust: Float? @ManagedAttribute(attributeName: "windLull") public var windLull: Float? @ManagedAttribute(attributeName: "windSpeed") public var windSpeed: Float? + @ManagedAttribute(attributeName: "irLux") public var irLux: Float? + @ManagedAttribute(attributeName: "lux") public var lux: Float? + @ManagedAttribute(attributeName: "uvLux") public var uvLux: Float? + @ManagedAttribute(attributeName: "whiteLux") public var whiteLux: Float? + @ManagedAttribute(attributeName: "radiation") public var radiation: Float? + @ManagedAttribute(attributeName: "rainfall1H") public var rainfall1H: Float? + @ManagedAttribute(attributeName: "rainfall24H") public var rainfall24H: Float? + @ManagedAttribute(attributeName: "soilTemperature") public var soilTemperature: Float? + @ManagedAttribute(attributeName: "soilMoisture") public var soilMoisture: UInt32? } diff --git a/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift b/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift deleted file mode 100644 index fe166e48..00000000 --- a/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// CurrentConditionsCompact.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 2/5/23. -// -import SwiftUI - -struct CurrentConditionsCompact: View { - var temp: Float - var condition: WeatherConditions - - var body: some View { - Label("\(String(temp.formattedTemperature()))", systemImage: condition.symbolName) - .font(.caption) - .foregroundColor(.gray) - .symbolRenderingMode(.multicolor) - } -} -struct CurrentConditionsCompact_Previews: PreviewProvider { - static var previews: some View { - - VStack { - CurrentConditionsCompact(temp: 22, condition: WeatherConditions.clear) - CurrentConditionsCompact(temp: 17, condition: WeatherConditions.cloudy) - CurrentConditionsCompact(temp: -5, condition: WeatherConditions.frigid) - CurrentConditionsCompact(temp: 38, condition: WeatherConditions.hot) - CurrentConditionsCompact(temp: 10, condition: WeatherConditions.rain) - CurrentConditionsCompact(temp: 30, condition: WeatherConditions.smoky) - CurrentConditionsCompact(temp: -2, condition: WeatherConditions.snow) - } - } -} diff --git a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift index b4e5336c..c2879aec 100644 --- a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift +++ b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift @@ -89,113 +89,6 @@ struct LocalWeatherConditions: View { } } -struct WeatherConditionsCompactWidget: View { - let temperature: String - let symbolName: String - let description: String - var body: some View { - VStack(alignment: .leading) { - HStack(spacing: 5.0) { - Image(systemName: symbolName) - .foregroundColor(.accentColor) - .font(.callout) - Text(description) - .lineLimit(2) - .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) - .fixedSize(horizontal: false, vertical: true) - .font(.caption) - } - Text(temperature) - .font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) ) - } - .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) - .padding() - .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) - } -} - -struct HumidityCompactWidget: View { - let humidity: Int - let dewPoint: String? - var body: some View { - VStack(alignment: .leading) { - HStack(spacing: 5.0) { - Image(systemName: "humidity") - .foregroundColor(.accentColor) - .font(.callout) - Text("Humidity") - .textCase(.uppercase) - .font(.caption) - } - Text("\(humidity)%") - .font(.largeTitle) - .padding(.bottom, 5) - if let dewPoint { - Text("The dew point is \(dewPoint) right now.") - .lineLimit(3) - .allowsTightening(true) - .fixedSize(horizontal: false, vertical: true) - .font(.caption2) - } - } - .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) - .padding() - .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) - } -} - -struct PressureCompactWidget: View { - let pressure: String - let unit: String - let low: Bool - var body: some View { - VStack(alignment: .leading) { - HStack(spacing: 5.0) { - Image(systemName: "gauge") - .foregroundColor(.accentColor) - .font(.callout) - Text("Pressure") - .textCase(.uppercase) - .font(.caption) - } - Text(pressure) - .font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) ) - Text(low ? "LOW" : "HIGH") - .padding(.bottom, 10) - Text(unit) - } - .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) - .padding() - .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) - } -} - -struct WindCompactWidget: View { - let speed: String - let gust: String? - let direction: String? - - var body: some View { - let hasGust = ((gust ?? "").isEmpty == false) - VStack(alignment: .leading) { - Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) } - if let direction { - Text("\(direction)") - .font(!hasGust ? .callout : .caption) - .padding(.bottom, 10) - } - Text(speed) - .font(!hasGust ? .system(size: 45) : .system(size: 35)) - if let gust, !gust.isEmpty { - Text("Gusts \(gust)") - } - } - .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) - .padding() - .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) - } -} - /// Magnus Formula func calculateDewPoint(temp: Float, relativeHumidity: Float) -> Double { let a: Float = 17.27 diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 485bbdd6..958f4bfc 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -212,7 +212,7 @@ struct NodeDetail: View { // to use with WeatherKit, or has actual data in the most recent EnvironmentMetrics entity // that will be rendered in this section. if node.hasPositions && UserDefaults.environmentEnableWeatherKit - || node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed"]) { + || node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed", "radiation", "weight", "distance", "soilTemperature", "soilMoisture"]) { Section("Environment") { if !node.hasEnvironmentMetrics { LocalWeatherConditions(location: node.latestPosition?.nodeLocation) @@ -245,6 +245,24 @@ struct NodeDetail: View { WindCompactWidget(speed: windSpeedMeasurement.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))), gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction) } + if let radiation = node.latestEnvironmentMetrics?.radiation { + RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(2))), unit: "µR/hr") + } + if let weight = node.latestEnvironmentMetrics?.weight { + WeightCompactWidget(weight: weight.formatted(.number.precision(.fractionLength(1))), unit: "kg") + } + if let distance = node.latestEnvironmentMetrics?.distance { + DistanceCompactWidget(distance: distance.formatted(.number.precision(.fractionLength(0))), unit: "mm") + } + if let soilTemperature = node.latestEnvironmentMetrics?.soilTemperature { + let locale = NSLocale.current as NSLocale + let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey")) + let unit = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? "°F" : "°C" + SoilTemperatureCompactWidget(temperature: soilTemperature.localeTemperature().formatted(.number.precision(.fractionLength(0))), unit: unit) + } + if let soilMoisture = node.latestEnvironmentMetrics?.soilMoisture { + SoilMoistureCompactWidget(moisture: soilMoisture.formatted(.number.precision(.fractionLength(0))), unit: "%") + } } .padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical) }