Skip to content

MikeManzo/SwiftyWeatherKit

Repository files navigation

SwiftyWeatherKit

Swift GitHub code size in bytes GitHub last commit GitHub All Releases Swift

A swift weather package to support multiple weather APIs.

Supported APIs

Weather API Documentation Supported Format API Support Notes
Ambient Weather * API Docs
* API Wiki
* Device Specifications
- [x] JSON
- [ ] Real Time
- [x] All Paramaters Supports error conditions and
rate limits as deffined in the API

Installation

Swift 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.

.package(url: "https://github.com/MikeManzo/SwiftyWeatherKit", .branch("master")),

Getting Started

SwiftyWeatherKit uses a factory pattern to create individual weather services. For example:

/// Weather Factory
public class SwiftyWeatherKit {
    public static var WeatherFactory = SwiftyWeatherKit()
    
    open class func shared() -> SwiftyWeatherKit {
        return WeatherFactory
    }
    
    /// Initialize the desired service using a platform model
    /// - Parameters:
    ///   - weatherType: desired weather service
    ///   - apiKeys: key(s) that are required to initialize the service; it's up to the resulting service to handle the order and # of keys.
    /// - Returns: a fully formed weather platform
    public func getService(WeatherServiceType weatherType: WeatherServiceType, WeatherAPI api: [String]) -> WeatherPlatform? {
        switch weatherType {
            case .Ambient:
                return AmbientWeather(apiKeys: api)
            case .Undefined:
                return nil
        }
    }
}

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:

guard let service = SwiftyWeatherKit.shared().getService(WeatherServiceType: .Ambient,
                                                     WeatherAPI: ["*** YOUR API Key ***",
                                                                  "*** YOUR Application Key***"]) else { return }

Once you have successfully initialized the service, it needs to be setup for one of two possible use cases.

Use Case #1: Latest Data Reported

Retrieve the last good data reported by the weather station. For example:

service.setupService(completionHandler: { stationStatus in
    switch stationStatus {
    case .NotReporting:
        print("According to the weather service, you do not have any devices reporting data")
        break
    case .Reporting:
        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)
        for (_, device) in service.reportingDevices {
            switch device {
            case is AWDevice:
                print(device.prettyString)
                service.getLastMeasurement(uniqueID: device.deviceID, completionHandler: { stationData in
                    guard let data = stationData else { return }
                    print(data.prettyString)
                })
            default:
                print("Unknown device type detected")
                break
            }
        }
    case .Error:
        print("There was an error retrieving weather information from the weather service")
        break
    }
})

For this use case we focus on getLastMeasurement:

service.getLastMeasurement(uniqueID: device.deviceID, completionHandler: { stationData in
                            guard let data = stationData else { return }
                            print(data.prettyString)
                            })

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.

MAC Address: A1:B2:C3:D4:E5:F6
  Info:
      Name: My Station
      Location: My Roof
      GeoLocation:
		Location: Somewhere
		Address: Some Address
		Elevation: Some Elevation
	  Coordiates:
 		Type: Point
 		Latitude: YOUR LATITUDE
 		Longitude: YOUR LONGITUDE

Outdoor Battery Status: 0
Date: 1590605700000 ms
Relative Pressure: 29.62 inHg
Absolute Pressure: 29.58 inHg
Hourly Rain: 0.0 in/hr
Rain Today: 0.0 in
Monthly Rain: 1.54 in
Yearly Rain: 13.85 in
Last Time it Rained: 2020-05-23T08:32:00.000Z
Indoor Temperature: 90.7 ºF
Outdoor Temperature: 77.2 ºF
Outdoor Dew Point: 63.61 ºF
Outdoor Temperature Feels Like: 77.58 ºF
Wind Direction: 116 º
Wind Speed: 6.5 MPH
Wind Gust: 15.0 MPH
Max Wind Gust Today: 17.4 MPH
Wind Gust Direction: 190 º
2 Minute Wind Speed Avg: 5.2 MPH
10 Minute Wind Speed Avg: 5.2 MPH
10 Minute Wind Direction Avg: 133 º
UV Index: 5
Solar Radiation: 358.0 W/m^2
Outdoor Humidity: 63 %
Indoor Humidity: 30 %

Use Case #2: Retrieve "n" Historical Measurements

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:

        service.setupService(completionHandler: { stationStatus in
            switch stationStatus {
            case .NotReporting:
                print("According to the selected service, you do not have any devices reporting weather data")
                break
            case .Reporting:
                sleep(1) // <-- Test Sub: Just to prevent the API from throwing us out w/ back-to-back calls
                for (_, device) in service.reportingDevices {
                    switch device {
                    case is AWDevice:
                        print(device.prettyString)
                        service.getHistoricalMeasurements(uniqueID: device.deviceID, count: 288, completionHandler: { stationData in
                            guard let historcalData = stationData else { return }
                             print("Successfully returned:\(historcalData.count) Measurements from AmbientWeather")
                       })
                    default:
                        print("Unknown device type detected")
                        break
                    }
                }
            case .Error:
                print("There was an error retrieving weather information from the selected service")
                break
            }
        })

For this use case we focus on getHistoricalMeasurements:

service.getHistoricalMeasurements(uniqueID: device.deviceID, count: 288, completionHandler: { stationData in
                                guard let historcalData = stationData else { return }
                                print("Successfully returned\(historcalData.count) Measurements")
                                })

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.

MAC Address: A1:B2:C3:D4:E5:F6
  Info:
      Name: My Station
      Location: My Roof
      GeoLocation:
		Location: Somewhere
		Address: Some Address
		Elevation: Some Elevation
	  Coordiates:
 		Type: Point
 		Latitude: YOUR LATITUDE
 		Longitude: YOUR LONGITUDE

Successfully returned 288 Measurements

Access to Device (station) Data

Each weather service consists of a Device

public protocol SWKDevice {
    var prettyString: String { get }
    var deviceID: String? { get }
}

that contains data:

public protocol SWKDeviceData {
    var prettyString: String { get }
    var availableSensors: [SWKSensor] { get }
}

where SWKSensor is defined as:

/// Supported Service Types
public enum SWKSensorType {
    case WindDirection
    case Temperature
    case AirQuality
    case WindSpeed
    case Radiation
    case RainRate
    case Humidity
    case RainDate
    case Pressure
    case Battery
    case General
    case Rain
}

/// 
/// Base sensor descriptor for SwiftyWeatherKit
/// Generic descriptors:
/// - _description: What the sensor does
/// - _sensorID: Uniqe Identifier of of the sensor
/// - _unit: What the measurements are in (e.g., in, W, F, etc.)
/// - _name: What do you want to call the sensor
/// - _value: Current measurement for the sensor
///
open class SWKSensor {
    internal var _type: SWKSensorType
    internal var _description: String
    internal var _sensorID: String
    internal var _unit: String
    internal var _name: String
    internal var _value: Any

    .
    .
    .
}

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.

To Do

  • Add support for Cocoapods
  • Add Real time Ambient Weather API support
  • Add support for another weather API - suggestions are welcome
  • Extensive testing for multiple reporting devices per service
  • Localization - I will need help with this

Copyright & License Information

Copyright © 2020 Mike Manzo. Released under the MIT License.

About

A swift package to support multiple weather APIs

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages