From 637acb11b06031f8a216a8bfcd2099b366995a49 Mon Sep 17 00:00:00 2001 From: Daryll Drexler Date: Tue, 7 Sep 2021 15:47:30 -0700 Subject: [PATCH] Adding option to show raw PurpleAir values in the widget - iOS client changes --- .../Widget/LocationSelection.intentdefinition | 52 +++++++++++++++++-- client/ios/Widget/Widget.swift | 12 +++-- client/swift/AQI/Download.swift | 5 +- server/template.yaml | 4 ++ server/update_data/app.py | 9 ++-- 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/client/ios/Widget/LocationSelection.intentdefinition b/client/ios/Widget/LocationSelection.intentdefinition index 748e916..0aac055 100644 --- a/client/ios/Widget/LocationSelection.intentdefinition +++ b/client/ios/Widget/LocationSelection.intentdefinition @@ -9,11 +9,11 @@ INIntentDefinitionNamespace 88xZPY INIntentDefinitionSystemVersion - 19H2 + 20G95 INIntentDefinitionToolsBuildVersion - 12A7300 + 12E507 INIntentDefinitionToolsVersion - 12.0.1 + 12.5.1 INIntents @@ -30,10 +30,10 @@ INIntentIneligibleForSuggestions INIntentLastParameterTag - 4 + 6 INIntentManagedParameterCombinations - location + location,display_purpleair INIntentParameterCombinationSupportsBackgroundExecution @@ -81,6 +81,48 @@ INIntentParameterType Placemark + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Show Raw PurpleAir values + INIntentParameterDisplayNameID + gyzQoM + INIntentParameterDisplayPriority + 2 + INIntentParameterMetadata + + INIntentParameterMetadataFalseDisplayName + false + INIntentParameterMetadataFalseDisplayNameID + C9JREb + INIntentParameterMetadataTrueDisplayName + true + INIntentParameterMetadataTrueDisplayNameID + Pp5b6V + + INIntentParameterName + display_purpleair + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterTag + 6 + INIntentParameterType + Boolean + INIntentResponse diff --git a/client/ios/Widget/Widget.swift b/client/ios/Widget/Widget.swift index 6d902e0..ded5bb5 100644 --- a/client/ios/Widget/Widget.swift +++ b/client/ios/Widget/Widget.swift @@ -26,6 +26,7 @@ struct MapProvider : IntentTimelineProvider { func getSnapshot(for intent: LocationSelectionIntent, in context: Context, completion: @escaping (MapEntry) -> Void) { var location = self._location(for: intent) + let readingType = self._readingType(for: intent) if location == nil { let locationManager = CLLocationManager() locationManager.desiredAccuracy = kCLLocationAccuracyKilometer @@ -48,7 +49,7 @@ struct MapProvider : IntentTimelineProvider { } } } - self._mapImage(location: location, size: context.displaySize) { (mapImage) in + self._mapImage(location: location, size: context.displaySize, readingType: readingType) { (mapImage) in completion(MapEntry(date: Date(), mapImage: mapImage ?? UIImage(named: "MapPlaceholder")!)) } } @@ -65,7 +66,12 @@ struct MapProvider : IntentTimelineProvider { return intent.location?.location } - private func _mapImage(location: CLLocation?, size: CGSize, callback: @escaping (UIImage?) -> Void) { + private func _readingType(for intent: LocationSelectionIntent) -> AQI.ReadingType { + let displayPurpleAir = intent.display_purpleair?.boolValue ?? false + return displayPurpleAir ? .rawPurpleAir : .epaCorrected + } + + private func _mapImage(location: CLLocation?, size: CGSize, readingType: AQI.ReadingType, callback: @escaping (UIImage?) -> Void) { let coordinate: CLLocationCoordinate2D if let location = location { coordinate = location.coordinate @@ -81,7 +87,7 @@ struct MapProvider : IntentTimelineProvider { callback(nil) return } - AQI.downloadCompactReadings { (readings, error) in + AQI.downloadCompactReadings(type: readingType) { (readings, error) in if let readings = readings { var region = options.region if size.width > size.height { diff --git a/client/swift/AQI/Download.swift b/client/swift/AQI/Download.swift index d604fe5..2cdfac8 100644 --- a/client/swift/AQI/Download.swift +++ b/client/swift/AQI/Download.swift @@ -6,6 +6,7 @@ import GameKit private let aqiDataURL = URL(string: "https://dfddnmlutocpt.cloudfront.net/sensors.pb")! private let particulateMatterDataURL = URL(string: "https://dfddnmlutocpt.cloudfront.net/sensors.raw.pb")! private let compactDataURL = URL(string: "https://dfddnmlutocpt.cloudfront.net/sensors.compact.pb")! +private let compactParticulateMatterDataURL = URL(string: "https://dfddnmlutocpt.cloudfront.net/sensors.raw.compact.pb")! /// The type of air quality measurement public enum ReadingType { @@ -37,11 +38,11 @@ public func downloadReadings(type: ReadingType, onProgess: @escaping (Float) -> /// - onResponse: The callback to which we report the parsed download response @available(iOS 13.0, *) @available(macOS 10.12, *) -public func downloadCompactReadings(onResponse: @escaping (GKRTree?, Error?) -> Void) { +public func downloadCompactReadings(type: ReadingType, onResponse: @escaping (GKRTree?, Error?) -> Void) { let client = DownloadClient(onProgess: { (percentage) in }, onResponse: onResponse) let session = URLSession(configuration: URLSessionConfiguration.default, delegate: client, delegateQueue: OperationQueue.main) - session.downloadTask(with: compactDataURL).resume() + session.downloadTask(with: type == .epaCorrected ? compactDataURL : compactParticulateMatterDataURL).resume() } @available(iOS 13.0, *) diff --git a/server/template.yaml b/server/template.yaml index da86429..7d5f195 100644 --- a/server/template.yaml +++ b/server/template.yaml @@ -71,6 +71,7 @@ Resources: AWS_S3_OBJECT: !Ref S3Key AWS_S3_OBJECT_RAW: !Ref S3KeyRaw AWS_S3_OBJECT_COMPACT: !Ref S3KeyCompact + AWS_S3_OBJECT_RAW_COMPACT: !Ref S3KeyRawCompact Policies: - Statement: - Sid: UpdateDataPolicy @@ -127,3 +128,6 @@ Parameters: S3KeyCompact: Type: String Default: sensors.compact.pb + S3KeyRawCompact: + Type: String + Default: sensors.raw.compact.pb diff --git a/server/update_data/app.py b/server/update_data/app.py index 0c1746e..38b46d7 100644 --- a/server/update_data/app.py +++ b/server/update_data/app.py @@ -17,10 +17,11 @@ def update_sensor_data( We download JSON from PurpleAir and convert to our proprietary Protocol Buffer format, which is used by all clients. - We upload three versions of the protocol buffer data: + We upload four versions of the protocol buffer data: (1) The corrected EPA AQI readings (2) Raw PurpleAir PM2.5 AQI readings (3) A compact data, with just a single AQI reading, used by the widget + (4) A raw compact data, to represent raw single AQI readings in the widget """ start_time = time.time() raw = urllib.request.urlopen(purpleair.api_url(purpleair_api_key)).read() @@ -28,11 +29,12 @@ def update_sensor_data( data = purpleair.parse_api(raw, epa_correction=True) raw_data = purpleair.parse_api(raw, epa_correction=False) compact_data = purpleair.compact_sensor_data(data) + raw_compact_data = purpleair.compact_sensor_data(raw_data) parse_time = time.time() s3 = boto3.client("s3") update(s3, s3_bucket, data=data, object_name=s3_object) update(s3, s3_bucket, data=compact_data, object_name=compact_s3_object) - update(s3, s3_bucket, data=raw_data, object_name=raw_s3_object) + update(s3, s3_bucket, data=raw_data, object_name=raw_compact_s3_object) s3_time = time.time() total_time = time.time() - start_time logging.info("Processed PurpleAir in %.1fs (download: %.1fs, parse: %.1fs, " @@ -55,4 +57,5 @@ def lambda_handler(event, context): s3_bucket=os.environ["AWS_S3_BUCKET"], s3_object=os.environ["AWS_S3_OBJECT"], raw_s3_object=os.environ["AWS_S3_OBJECT_RAW"], - compact_s3_object=os.environ["AWS_S3_OBJECT_COMPACT"]) + compact_s3_object=os.environ["AWS_S3_OBJECT_COMPACT"], + raw_compact_s3_object=os.environ["AWS_S3_OBJECT_RAW_COMPACT"])