-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AccessoryDelegate to handle HAP controller Characteristic get/sets by…
… Accessory implementations Separate functions to obtain the value of a characteristic for a HAP server and to just get a jason formatted value. Notify device delegate when an accessory changes it's value Add an example OpenWeatherThermostat accessory, which uses the AccessoryDelegate protocol.
- Loading branch information
Showing
8 changed files
with
271 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// | ||
// OpenWeather.swift | ||
// hap-server | ||
// | ||
|
||
import Foundation | ||
import func Evergreen.getLogger | ||
|
||
fileprivate let logger = getLogger("openweather") | ||
|
||
public class OpenWeather { | ||
|
||
public var temperature: Float { | ||
update() | ||
return _temperature | ||
} | ||
|
||
public var humidity: Int { | ||
update() | ||
return _humidity | ||
} | ||
|
||
public enum Units: String { | ||
case imperial | ||
case metric | ||
} | ||
|
||
// swiftlint:disable identifier_name | ||
public struct Measurement: Decodable { | ||
let temp: Float | ||
let pressure: Int | ||
let humidity: Int | ||
let temp_min: Int? | ||
let temp_max: Int? | ||
} | ||
public struct OpenWeatherResponse: Decodable { | ||
let main: Measurement | ||
let name: String | ||
} | ||
|
||
let appid: String | ||
let name: String | ||
let lat: String | ||
let lon: String | ||
let units: Units | ||
|
||
private var _temperature: Float = 0.0 | ||
private var _humidity: Int = 50 | ||
|
||
private let decoder = JSONDecoder() | ||
|
||
private let limit: TimeInterval = 900 // 15 Minutes | ||
private var lastExecutedAt: Date? | ||
private let updateQueue = DispatchQueue(label: "openweather", attributes: []) | ||
|
||
private var observers = [(OpenWeather) -> Void]() | ||
|
||
public init(name: String, lat: Double, lon: Double, appid: String, units: Units = .metric) { | ||
precondition((lat >= -90.0) && (lat <= 90.0), "Latitude \(lat) is out of range") | ||
precondition((lon >= -180.0) && (lon <= 180.0), "Longitude \(lon) is out of range") | ||
|
||
self.name = name | ||
self.appid = appid | ||
self.lat = "\(lat)" | ||
self.lon = "\(lon)" | ||
self.units = units | ||
|
||
self.update() | ||
} | ||
|
||
public func whenUpdated(closure: @escaping (OpenWeather) -> Void) { | ||
observers.append(closure) | ||
} | ||
|
||
func update() { | ||
updateQueue.async { | ||
let now = Date() | ||
|
||
// Lookup last executed | ||
let timeInterval = now.timeIntervalSince(self.lastExecutedAt ?? .distantPast) | ||
|
||
// Only refresh the values if the last request was older than 'limit' | ||
if timeInterval > self.limit { | ||
// Record execution | ||
self.lastExecutedAt = now | ||
|
||
self.updateNow() | ||
} | ||
} | ||
} | ||
|
||
func updateNow() { | ||
|
||
var urlQuery = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")! | ||
urlQuery.queryItems = [ | ||
URLQueryItem(name: "lat", value: lat), | ||
URLQueryItem(name: "lon", value: lon), | ||
URLQueryItem(name: "APPID", value: appid), | ||
URLQueryItem(name: "units", value: units.rawValue)] | ||
|
||
let url = urlQuery.url! | ||
logger.debug("URL: \(url)") | ||
|
||
let task = URLSession.shared.dataTask(with: url) { data, response, error in | ||
if let error = error { | ||
logger.debug("OpenWeather connection error \(error)") | ||
return | ||
} | ||
guard let httpResponse = response as? HTTPURLResponse, | ||
(200...299).contains(httpResponse.statusCode) else { | ||
logger.debug("OpenWeather Server error \(response)") | ||
return | ||
} | ||
|
||
if let mimeType = httpResponse.mimeType, mimeType == "application/json", | ||
let data = data, | ||
let weatherReport = try? self.decoder.decode(OpenWeatherResponse.self, from: data) { | ||
|
||
DispatchQueue.main.sync { | ||
self._temperature = weatherReport.main.temp | ||
self._humidity = weatherReport.main.humidity | ||
for observer in self.observers { | ||
observer(self) | ||
} | ||
} | ||
|
||
} | ||
} | ||
task.resume() | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// | ||
// OpenWeatherThermometer.swift | ||
// hap-server | ||
// | ||
// Created by Guy Brooker on 03/10/2018. | ||
// | ||
|
||
import Foundation | ||
import HAP | ||
import func Evergreen.getLogger | ||
|
||
fileprivate let logger = getLogger("openweather") | ||
|
||
extension Accessory { | ||
|
||
open class OpenWeatherThermometer: Thermometer { | ||
|
||
let weather: OpenWeather | ||
|
||
public let humiditySensor = Service.HumiditySensor() | ||
|
||
public init(_ openWeatherLocation: OpenWeather) { | ||
weather = openWeatherLocation | ||
|
||
super.init(info: .init(name:openWeatherLocation.name, | ||
serialNumber:openWeatherLocation.name, | ||
manufacturer:"Open Weather", | ||
model:"API", | ||
firmwareRevision: "1.0"), | ||
additionalServices: [humiditySensor] | ||
) | ||
|
||
delegate = self | ||
|
||
getLogger("openweather").logLevel = .debug | ||
|
||
weather.whenUpdated(closure: { weatherLocation in | ||
self.temperatureSensor.currentTemperature.value = weatherLocation.temperature | ||
self.humiditySensor.currentRelativeHumidity.value = Float(weatherLocation.humidity) | ||
}) | ||
updateState() | ||
} | ||
|
||
func updateState() { | ||
didGetCurrentTemperature(self.weather.temperature) | ||
} | ||
|
||
func didGetCurrentTemperature(_ currentTemp: Float?) { | ||
weather.update() | ||
} | ||
} | ||
} | ||
|
||
extension Accessory.OpenWeatherThermometer: AccessoryDelegate { | ||
|
||
/// Characteristic's value was changed by controller. Used for notifying | ||
public func characteristic<T>( | ||
_ characteristic: GenericCharacteristic<T>, | ||
ofService: Service, | ||
didChangeValue: T?) {} | ||
|
||
/// Characteristic's value was observed by controller. Used for lazy updating | ||
public func characteristic<T>( | ||
_ characteristic: GenericCharacteristic<T>, | ||
ofService: Service, | ||
didGetValue value: T?) { | ||
switch characteristic.type { | ||
case .currentTemperature: | ||
// swiftlint:disable:next force_cast | ||
didGetCurrentTemperature(value as! Float?) | ||
default: | ||
break | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.