Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Bouke committed Dec 31, 2022
1 parent 51e3505 commit 485b658
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Sources/HAP/Base/Accessory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
// which is the data-type we wanted to use here. We can change it back
// to UInt64 once the following commit has made it into a release:
// https://github.com/apple/swift-corelibs-foundation/commit/64b67c91479390776c43a96bd31e4e85f106d5e1
typealias InstanceID = Int
public typealias InstanceID = UInt64

// HAP Specification 2.6.1.1: Accessory Instance IDs
//
Expand Down
2 changes: 1 addition & 1 deletion Sources/HAP/Base/Characteristic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class GenericCharacteristic<T: CharacteristicValueType>: Characteristic,

weak var service: Service?

internal var iid: InstanceID = 0
public var iid: InstanceID = 0
public let type: CharacteristicType

internal var _value: T?
Expand Down
7 changes: 5 additions & 2 deletions Sources/HAP/Base/CharacteristicValueType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,14 @@ extension Double: CharacteristicValueType {

extension Data: CharacteristicValueType, JSONValueTypeConvertible {
public init?(value: Any) {
fatalError("How does deserialization of Data work?")
switch value {
case let value as String: self = Data(base64Encoded: value)!
default: fatalError("don't now how to decode \(value)")
}
}
static public let format = CharacteristicFormat.data
public var jsonValueType: JSONValueType {
fatalError("How does serialization of Data work?")
self.base64EncodedString()
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/HAP/Server/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public class Device {
/// Generate uniqueness hash for device configuration, used to determine
/// if the configuration number should be updated.
func generateStableHash() -> Int {
var hash = 0
var hash: UInt64 = 0
for accessory in accessories {
hash ^= 17 &* accessory.aid
for service in accessory.services {
Expand All @@ -267,7 +267,7 @@ public class Device {
}
}
}
return hash
return Int(truncatingIfNeeded: hash)
}

/// Notify the server that the config record has changed
Expand Down
1 change: 1 addition & 0 deletions Sources/HAP/Server/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extension Int: JSONValueType { }
extension UInt8: JSONValueType { }
extension UInt16: JSONValueType { }
extension UInt32: JSONValueType { }
extension UInt64: JSONValueType { }
extension Float: JSONValueType { }
extension Double: JSONValueType { }
extension NSNull: JSONValueType { }
Expand Down
2 changes: 1 addition & 1 deletion Sources/HAP/Utils/Data+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension Data {
}

extension RandomAccessCollection where Iterator.Element == UInt8 {
var hex: String {
public var hex: String {
self.reduce("") { $0 + String(format: "%02x", $1) }
}
}
27 changes: 25 additions & 2 deletions Sources/HAP/Utils/TLV8.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ enum TLV8Error: Swift.Error {
case decodeError
}

func decode<Key>(_ data: Data) throws -> [(Key, Data)] where Key: RawRepresentable, Key.RawValue == UInt8 {
public func decode<Key>(_ data: Data) throws -> [(Key, Data)] where Key: RawRepresentable, Key.RawValue == UInt8 {
var result = [(Key, Data)]()
var index = data.startIndex
var currentType: Key?
Expand Down Expand Up @@ -84,7 +84,7 @@ func decode<Key>(_ data: Data) throws -> [(Key, Data)] where Key: RawRepresentab
return result
}

func encode<Key>(_ array: [(Key, Data)]) -> Data where Key: RawRepresentable, Key.RawValue == UInt8 {
public func encode<Key>(_ array: [(Key, Data)]) -> Data where Key: RawRepresentable, Key.RawValue == UInt8 {
var result = Data()
func append(type: UInt8, value: Data.SubSequence) {
result.append(Data([type, UInt8(value.count)] + value))
Expand All @@ -105,6 +105,29 @@ func encode<Key>(_ array: [(Key, Data)]) -> Data where Key: RawRepresentable, Ke
return result
}

// I think we can avoid duplication of encode methods by having the value be a `TLV8ValueProtocol` thingy, which both Data and [Data] adhere to.
public func encode<Key>(_ array: [(Key, [Data])]) -> Data where Key: RawRepresentable, Key.RawValue == UInt8 {
var result = Data()
for (type, value) in array {
var first = true
for (item) in value {
if !first {
result.append(encode([(TLV8.delimiter, Data([0]))]))
}
first = false
result.append(encode([(type, item)]))
}
if first {
result.append(encode([(type, Data([0]))]))
}
}
return result
}

enum TLV8: UInt8 {
case delimiter = 0
}

// Pair Setup State
enum PairSetupStep: UInt8 {
case waiting = 0
Expand Down
54 changes: 51 additions & 3 deletions Sources/HAPDemo/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,51 @@ if CommandLine.arguments.contains("--recreate") {
let livingRoomLightbulb = Accessory.Lightbulb(info: Service.Info(name: "Living Room", serialNumber: "00002"))
let bedroomNightStand = Accessory.Lightbulb(info: Service.Info(name: "Bedroom", serialNumber: "00003"))

let brightness = PredefinedCharacteristic.brightness()
let colorTemperature = PredefinedCharacteristic.colorTemperature(maxValue: 400, minValue: 50)
let supportedTransitions = PredefinedCharacteristic.supportedCharacteristicValueTransitionConfiguration()

let adaptive = Accessory(info: .init(name: "Adaptive", serialNumber: "0001"), type: .lightbulb, services: [
Service.Lightbulb(characteristics: [
AnyCharacteristic(brightness),
AnyCharacteristic(colorTemperature),
.characteristicValueTransitionControl(),
.characteristicValueActiveTransitionCount(),
AnyCharacteristic(supportedTransitions)
])
])

enum SupportedCharacteristicValueTransitionConfigurationsTypes : UInt8 {
case SupportedTransitionConfiguration = 0x01
}

enum SupportedValueTransitionConfigurationTypes : UInt8 {
case CharacteristicIid = 0x01
case TransitionType = 0x02
}

enum TransitionType : UInt8 {
case Brightness = 0x01
case ColorTemperature = 0x02
}

supportedTransitions.value = encode([
(SupportedCharacteristicValueTransitionConfigurationsTypes.SupportedTransitionConfiguration, [
encode([
(SupportedValueTransitionConfigurationTypes.CharacteristicIid, brightness.iid.bytes),
(SupportedValueTransitionConfigurationTypes.TransitionType, Data([TransitionType.Brightness.rawValue])),
]),
encode([
(SupportedValueTransitionConfigurationTypes.CharacteristicIid, colorTemperature.iid.bytes),
(SupportedValueTransitionConfigurationTypes.TransitionType, Data([TransitionType.ColorTemperature.rawValue])),
]),
])
]);

//print("Did encode as: \(supportedTransitions.value!.base64EncodedString())")
//print("Did encode as: \(supportedTransitions.value!.hex)")
//exit(0)

// And a security system with multiple zones and statuses fault and tampered.
let securitySystem = Accessory(
info: Service.Info(name: "Multi-Zone", serialNumber: "A1803"),
Expand All @@ -45,11 +90,14 @@ let device = Device(
setupCode: "123-44-321",
storage: storage,
accessories: [
livingRoomLightbulb,
bedroomNightStand,
securitySystem
adaptive
// livingRoomLightbulb,
// bedroomNightStand,
// securitySystem
])



// Attach a delegate that logs all activity.
var delegate = LoggingDeviceDelegate(logger: logger)
device.delegate = delegate
Expand Down
24 changes: 12 additions & 12 deletions Tests/HAPTests/EndpointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class EndpointTests: XCTestCase {
guard let accessory = jsonObject["accessories"]?.first else {
return XCTFail("No accessory")
}
XCTAssertEqual(accessory["aid"] as? Int, lamp.aid)
XCTAssertEqual(accessory["aid"] as? InstanceID, lamp.aid)
guard let services = accessory["services"] as? [[String: Any]] else {
return XCTFail("No services")
}
Expand Down Expand Up @@ -77,15 +77,15 @@ class EndpointTests: XCTestCase {
return XCTFail("No characteristics")
}

guard let nameCharacteristic = characteristics.first(where: { $0["iid"] as? Int == energy.info.name.iid }) else {
guard let nameCharacteristic = characteristics.first(where: { $0["iid"] as? InstanceID == energy.info.name.iid }) else {
return XCTFail("No name characteristic")
}
XCTAssertEqual(nameCharacteristic["value"] as? String, "Energy")
XCTAssertEqual(nameCharacteristic["perms"] as? [String] ?? [], ["pr"])
XCTAssertEqual(nameCharacteristic["type"] as? String, "23")
XCTAssertEqual(nameCharacteristic["ev"] as? Bool, false)

guard let wattCharacteristic = characteristics.first(where: { $0["iid"] as? Int == energy.service.watt.iid }) else {
guard let wattCharacteristic = characteristics.first(where: { $0["iid"] as? InstanceID == energy.service.watt.iid }) else {
return XCTFail("No identify characteristic")
}
XCTAssertEqual(wattCharacteristic["value"] as? Int, 42)
Expand All @@ -111,12 +111,12 @@ class EndpointTests: XCTestCase {
return XCTFail("No characteristics")
}

guard let manufacturerCharacteristic = characteristics.first(where: { $0["iid"] as? Int == lamp.info.manufacturer.iid }) else {
guard let manufacturerCharacteristic = characteristics.first(where: { $0["iid"] as? InstanceID == lamp.info.manufacturer.iid }) else {
return XCTFail("No manufacturer characteristic")
}
XCTAssertEqual(manufacturerCharacteristic["value"] as? String, "Bouke")

guard let nameCharacteristic = characteristics.first(where: { $0["iid"] as? Int == lamp.info.name.iid }) else {
guard let nameCharacteristic = characteristics.first(where: { $0["iid"] as? InstanceID == lamp.info.name.iid }) else {
return XCTFail("No name characteristic")
}
XCTAssertEqual(nameCharacteristic["value"] as? String, "Night stand left")
Expand All @@ -131,15 +131,15 @@ class EndpointTests: XCTestCase {
return XCTFail("No characteristics")
}

guard let nameCharacteristic = characteristics.first(where: { $0["iid"] as? Int == lamp.info.name.iid }) else {
guard let nameCharacteristic = characteristics.first(where: { $0["iid"] as? InstanceID == lamp.info.name.iid }) else {
return XCTFail("No name characteristic")
}
XCTAssertEqual(nameCharacteristic["value"] as? String, "Night stand left")
XCTAssertEqual(nameCharacteristic["perms"] as? [String] ?? [], ["pr"])
XCTAssertEqual(nameCharacteristic["type"] as? String, "23")
XCTAssertEqual(nameCharacteristic["ev"] as? Bool, false)

guard let brightnessCharacteristic = characteristics.first(where: { $0["iid"] as? Int == lamp.lightbulb.brightness!.iid }) else {
guard let brightnessCharacteristic = characteristics.first(where: { $0["iid"] as? InstanceID == lamp.lightbulb.brightness!.iid }) else {
return XCTFail("No identify characteristic")
}
XCTAssertEqual(brightnessCharacteristic["value"] as? Int, 100)
Expand Down Expand Up @@ -571,15 +571,15 @@ class EndpointTests: XCTestCase {
return XCTFail("No characteristics")
}

guard let light = characteristics.first(where: { $0["aid"] as? Int == lightsensor.aid }) else {
guard let light = characteristics.first(where: { $0["aid"] as? InstanceID == lightsensor.aid }) else {
return XCTFail("Could not get light aid")
}

guard let therm = characteristics.first(where: { $0["aid"] as? Int == thermostat.aid }) else {
guard let therm = characteristics.first(where: { $0["aid"] as? InstanceID == thermostat.aid }) else {
return XCTFail("Could not get therm aid")
}

guard let lampa = characteristics.first(where: { $0["aid"] as? Int == lamp.aid }) else {
guard let lampa = characteristics.first(where: { $0["aid"] as? InstanceID == lamp.aid }) else {
return XCTFail("Could not get lampa aid")
}

Expand Down Expand Up @@ -608,8 +608,8 @@ class EndpointTests: XCTestCase {
XCTAssertEqual(response.status, .multiStatus)
let json = try! JSONSerialization.jsonObject(with: response.body.data ?? Data()) as! [String: [[String: Any]]]
XCTAssertEqual(json["characteristics"]![0]["status"]! as! Int, HAPStatusCodes.writeOnly.rawValue)
XCTAssertEqual(json["characteristics"]![0]["aid"]! as! Int, aid)
XCTAssertEqual(json["characteristics"]![0]["iid"]! as! Int, iid)
XCTAssertEqual(json["characteristics"]![0]["aid"]! as! InstanceID, aid)
XCTAssertEqual(json["characteristics"]![0]["iid"]! as! InstanceID, iid)
}

// trying to read write only access and one with read access
Expand Down

0 comments on commit 485b658

Please sign in to comment.