|
1 | 1 | # SwiftyWeatherKit
|
2 | 2 |
|
3 |
| -A description of this package. |
| 3 | +[](https://travis-ci.org/MikeManzo/SwiftyWeatherKit) |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +A swift weather package to support multiple weather APIs. |
| 10 | + |
| 11 | +# Supported APIs |
| 12 | +| Weather API | Documentation | Supported Format | API Support | Notes |
| 13 | +| :----: | :----- | :---- | :----: | :---- | |
| 14 | +| [Ambient Weather](https://github.com/ambient-weather/api-docs) | * [API Docs](https://ambientweather.docs.apiary.io/)<br>* [API Wiki](https://github.com/ambient-weather/api-docs/wiki)<br>* [Device Specifications](https://github.com/ambient-weather/api-docs/wiki/Device-Data-Specs) | - [x] JSON<br> - [ ] Real Time | - [x] All Paramaters | Supports error conditions and<br>rate limits as deffined in the API |
| 15 | +| |
| 16 | + |
| 17 | +# Installation |
| 18 | + |
| 19 | +## Swift Package Manager |
| 20 | +For [Package Manager](https://swift.org/package-manager/) add the following package to your Package.swift file. Swift 4 & 5 are supported for macOS 10.12, iOS 12 and tvOS 12. |
| 21 | + |
| 22 | +``` Swift |
| 23 | +.package(url: "https://github.com/MikeManzo/SwiftyWeatherKit", .branch("master")), |
| 24 | +``` |
| 25 | + |
| 26 | +# Getting Started |
| 27 | +SwiftyWeatherKit uses a factory pattern to create individual weather services. For example: |
| 28 | +```swift |
| 29 | +/// Weather Factory |
| 30 | +public class SwiftyWeatherKit { |
| 31 | + public static var WeatherFactory = SwiftyWeatherKit() |
| 32 | + |
| 33 | + open class func shared() -> SwiftyWeatherKit { |
| 34 | + return WeatherFactory |
| 35 | + } |
| 36 | + |
| 37 | + /// Initialize the desired service using a platform model |
| 38 | + /// - Parameters: |
| 39 | + /// - weatherType: desired weather service |
| 40 | + /// - apiKeys: key(s) that are required to initialize the service; it's up to the resulting service to handle the order and # of keys. |
| 41 | + /// - Returns: a fully formed weather platform |
| 42 | + public func getService(WeatherServiceType weatherType: WeatherServiceType, WeatherAPI api: [String]) -> WeatherPlatform? { |
| 43 | + switch weatherType { |
| 44 | + case .Ambient: |
| 45 | + return AmbientWeather(apiKeys: api) |
| 46 | + case .Undefined: |
| 47 | + return nil |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + ``` |
| 52 | +To initialize the service, all you you need to do is pass it the required API key(s). Some APIs require a single key; some require multiple keys. For now, the only supported service is AmbientWeather.net. AmbientWeather requires two keys. To initialze the service, you need call only one function. For example: |
| 53 | +```swift |
| 54 | +guard let service = SwiftyWeatherKit.shared().getService(WeatherServiceType: .Ambient, |
| 55 | + WeatherAPI: ["*** YOUR API Key ***", |
| 56 | + "*** YOUR Application Key***"]) else { return } |
| 57 | +``` |
| 58 | +Once you have successfully initialized the service, it needs to be setup for one of two possible use cases. |
| 59 | +## Use Case #1: Latest Data Reported |
| 60 | +Retrieve the last good data reported by the weather station. For example: |
| 61 | +```swift |
| 62 | +service.setupService(completionHandler: { stationStatus in |
| 63 | + switch stationStatus { |
| 64 | + case .NotReporting: |
| 65 | + print("According to the weather service, you do not have any devices reporting data") |
| 66 | + break |
| 67 | + case .Reporting: |
| 68 | + sleep(1) // <-- DO NOT DO THIS!! This is for the test stub ONLY: It just prevents the API from throwing us out w/ back-to-back calls within 1 second (e.g, rate exceeded) |
| 69 | + for (_, device) in service.reportingDevices { |
| 70 | + switch device { |
| 71 | + case is AWDevice: |
| 72 | + print(device.prettyString) |
| 73 | + service.getLastMeasurement(uniqueID: device.deviceID, completionHandler: { stationData in |
| 74 | + guard let data = stationData else { return } |
| 75 | + print(data.prettyString) |
| 76 | + }) |
| 77 | + default: |
| 78 | + print("Unknown device type detected") |
| 79 | + break |
| 80 | + } |
| 81 | + } |
| 82 | + case .Error: |
| 83 | + print("There was an error retrieving weather information from the weather service") |
| 84 | + break |
| 85 | + } |
| 86 | +}) |
| 87 | +``` |
| 88 | +For this use case we focus on *getLastMeasurement*: |
| 89 | +```swift |
| 90 | +service.getLastMeasurement(uniqueID: device.deviceID, completionHandler: { stationData in |
| 91 | + guard let data = stationData else { return } |
| 92 | + print(data.prettyString) |
| 93 | + }) |
| 94 | +``` |
| 95 | +The code snippet above produces the following output for a weather station connected to AmbientWeather.net. Each sensor can be interrogated for its intrinsic JSON value. |
| 96 | +``` |
| 97 | +MAC Address: A1:B2:C3:D4:E5:F6 |
| 98 | + Info: |
| 99 | + Name: My Station |
| 100 | + Location: My Roof |
| 101 | + GeoLocation: |
| 102 | + Location: Somewhere |
| 103 | + Address: Some Address |
| 104 | + Elevation: Some Elevation |
| 105 | + Coordiates: |
| 106 | + Type: Point |
| 107 | + Latitude: YOUR LATITUDE |
| 108 | + Longitude: YOUR LONGITUDE |
| 109 | +
|
| 110 | +Outdoor Battery Status: 0 |
| 111 | +Date: 1590605700000 ms |
| 112 | +Relative Pressure: 29.62 inHg |
| 113 | +Absolute Pressure: 29.58 inHg |
| 114 | +Hourly Rain: 0.0 in/hr |
| 115 | +Rain Today: 0.0 in |
| 116 | +Monthly Rain: 1.54 in |
| 117 | +Yearly Rain: 13.85 in |
| 118 | +Last Time it Rained: 2020-05-23T08:32:00.000Z |
| 119 | +Indoor Temperature: 90.7 ºF |
| 120 | +Outdoor Temperature: 77.2 ºF |
| 121 | +Outdoor Dew Point: 63.61 ºF |
| 122 | +Outdoor Temperature Feels Like: 77.58 ºF |
| 123 | +Wind Direction: 116 º |
| 124 | +Wind Speed: 6.5 MPH |
| 125 | +Wind Gust: 15.0 MPH |
| 126 | +Max Wind Gust Today: 17.4 MPH |
| 127 | +Wind Gust Direction: 190 º |
| 128 | +2 Minute Wind Speed Avg: 5.2 MPH |
| 129 | +10 Minute Wind Speed Avg: 5.2 MPH |
| 130 | +10 Minute Wind Direction Avg: 133 º |
| 131 | +UV Index: 5 |
| 132 | +Solar Radiation: 358.0 W/m^2 |
| 133 | +Outdoor Humidity: 63 % |
| 134 | +Indoor Humidity: 30 % |
| 135 | +``` |
| 136 | +## Use Case #2: Retrieve "n" Historical Measurements |
| 137 | +The second use case is to retrieve historical data. By changing *count* in the function below, you can retrieve mulitple historical measurements for the desired station. For example: |
| 138 | +```swift |
| 139 | + service.setupService(completionHandler: { stationStatus in |
| 140 | + switch stationStatus { |
| 141 | + case .NotReporting: |
| 142 | + print("According to the selected service, you do not have any devices reporting weather data") |
| 143 | + break |
| 144 | + case .Reporting: |
| 145 | + sleep(1) // <-- Test Sub: Just to prevent the API from throwing us out w/ back-to-back calls |
| 146 | + for (_, device) in service.reportingDevices { |
| 147 | + switch device { |
| 148 | + case is AWDevice: |
| 149 | + print(device.prettyString) |
| 150 | + service.getHistoricalMeasurements(uniqueID: device.deviceID, count: 288, completionHandler: { stationData in |
| 151 | + guard let historcalData = stationData else { return } |
| 152 | + print("Successfully returned:\(historcalData.count) Measurements from AmbientWeather") |
| 153 | + }) |
| 154 | + default: |
| 155 | + print("Unknown device type detected") |
| 156 | + break |
| 157 | + } |
| 158 | + } |
| 159 | + case .Error: |
| 160 | + print("There was an error retrieving weather information from the selected service") |
| 161 | + break |
| 162 | + } |
| 163 | + }) |
| 164 | +``` |
| 165 | +For this use case we focus on *getHistoricalMeasurements*: |
| 166 | +```swift |
| 167 | +service.getHistoricalMeasurements(uniqueID: device.deviceID, count: 288, completionHandler: { stationData in |
| 168 | + guard let historcalData = stationData else { return } |
| 169 | + print("Successfully returned\(historcalData.count) Measurements") |
| 170 | + }) |
| 171 | +``` |
| 172 | +The code snippet above produces the following output for a weather station connected to AmbientWeather.net. As above, each sensor can be interrogated for its intrinsic JSON value. |
| 173 | +``` |
| 174 | +MAC Address: A1:B2:C3:D4:E5:F6 |
| 175 | + Info: |
| 176 | + Name: My Station |
| 177 | + Location: My Roof |
| 178 | + GeoLocation: |
| 179 | + Location: Somewhere |
| 180 | + Address: Some Address |
| 181 | + Elevation: Some Elevation |
| 182 | + Coordiates: |
| 183 | + Type: Point |
| 184 | + Latitude: YOUR LATITUDE |
| 185 | + Longitude: YOUR LONGITUDE |
| 186 | +
|
| 187 | +Successfully returned 288 Measurements |
| 188 | +``` |
| 189 | +## Access to Device (station) Data |
| 190 | +Each weather service consists of a Device |
| 191 | +```swift |
| 192 | +public protocol SWKDevice { |
| 193 | + var prettyString: String { get } |
| 194 | + var deviceID: String? { get } |
| 195 | +} |
| 196 | +``` |
| 197 | +that contains data: |
| 198 | +```swift |
| 199 | +public protocol SWKDeviceData { |
| 200 | + var prettyString: String { get } |
| 201 | + var availableSensors: [SWKSensor] { get } |
| 202 | +} |
| 203 | +``` |
| 204 | +where SWKSensor is defined as: |
| 205 | +```swift |
| 206 | +/// Supported Service Types |
| 207 | +public enum SWKSensorType { |
| 208 | + case Temperature |
| 209 | + case AirQuality |
| 210 | + case Pressure |
| 211 | + case Battery |
| 212 | + case Humidity |
| 213 | + case General |
| 214 | + case Rain |
| 215 | + case Wind |
| 216 | +} |
| 217 | + |
| 218 | +/// |
| 219 | +/// Base sensor descriptor for SwiftyWeatherKit |
| 220 | +/// Generic descriptors: |
| 221 | +/// - _description: What the sensor does |
| 222 | +/// - _sensorID: Uniqe Identifier of of the sensor |
| 223 | +/// - _unit: What the measurements are in (e.g., in, W, F, etc.) |
| 224 | +/// - _name: What do you want to call the sensor |
| 225 | +/// - _value: Current measurement for the sensor |
| 226 | +/// |
| 227 | +open class SWKSensor { |
| 228 | + private let _type: SWKSensorType |
| 229 | + private let _description: String |
| 230 | + private let _sensorID: String |
| 231 | + private let _unit: String |
| 232 | + private let _name: String |
| 233 | + private var _value: Any |
| 234 | + |
| 235 | + . |
| 236 | + . |
| 237 | + . |
| 238 | +} |
| 239 | +``` |
| 240 | +Once you have successfully called either *getLastMeasurement* or *getHistoricalMeasurements* you will have access to every piece of data that the station is reporting back to the API service. Have a look in the package for details on exposed Properties to use in your application. |
| 241 | + |
| 242 | +## To Do |
| 243 | +- [ ] Add support for [Cocoapods](https://cocoapods.org) |
| 244 | +- [ ] Add Real time Ambient Weather API support |
| 245 | +- [ ] Add support for another weather API - suggestions are welcome |
| 246 | +- [ ] Extensive testing for multiple reporting devices per service |
| 247 | +- [ ] Localization - I will need help with this |
| 248 | + |
| 249 | +### License |
| 250 | +Copyright 2020 Mike Manzo. Licensed under the Apache License, Version 2.0 |
0 commit comments