Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adaptive lighting (WIP) #160

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions Sources/HAP/Base/Accessory.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import Foundation

// TODO: SwiftFoundation in Swift 4.0 cannot encode/decode UInt64,
// 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
// HAP Specification 2.6.1: Instance IDs
//
// instance IDs are numbers with a range of [1, 18446744073709551615] for IP
// accessories (see ”7.4.4.2 Instance IDs” (page 122) for BLE accessories).
// These numbers are used to uniquely identify HAP accessory objects within an
// HAP accessory server, or uniquely identify services, and characteristics
// within an HAP accessory object. The instance ID for each object must be
// unique for the lifetime of the server/client pairing.
public typealias InstanceID = UInt64

// HAP Specification 2.6.1.1: Accessory Instance IDs
//
Expand All @@ -25,7 +29,7 @@ struct AIDGenerator: Sequence, IteratorProtocol, Codable {
}
}

open class Accessory: Hashable, JSONSerializable {
open class Accessory: Hashable, JSONSerializable, CustomDebugStringConvertible {
public weak var device: Device?
internal var aid: InstanceID = 0
public let type: AccessoryType
Expand Down Expand Up @@ -127,4 +131,8 @@ open class Accessory: Hashable, JSONSerializable {
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}

public var debugDescription: String {
"#\(aid) \(type) \(info.name.value ?? "Unnamed accessory")"
}
}
8 changes: 6 additions & 2 deletions Sources/HAP/Base/Characteristic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ extension Characteristic {
}
}

public class GenericCharacteristic<T: CharacteristicValueType>: Characteristic, JSONSerializable, Hashable, Equatable {
public class GenericCharacteristic<T: CharacteristicValueType>: Characteristic, JSONSerializable, Hashable, Equatable, CustomDebugStringConvertible {
enum Error: Swift.Error {
case valueTypeException
}

weak var service: Service?

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

internal var _value: T?
Expand Down Expand Up @@ -179,4 +179,8 @@ public class GenericCharacteristic<T: CharacteristicValueType>: Characteristic,
internal var device: Device? {
service?.accessory?.device
}

public var debugDescription: String {
"characteristic #\(iid) \(description ?? type.description) of service \(service?.debugDescription ?? "no service")"
}
}
8 changes: 6 additions & 2 deletions Sources/HAP/Base/CharacteristicValueType.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Logging

public protocol CharacteristicValueType: Equatable, JSONValueTypeConvertible {
init?(value: Any)
Expand Down Expand Up @@ -171,11 +172,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?")
return self.base64EncodedString()
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/HAP/Base/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public enum CharacteristicPermission: String, Codable {
// This characteristic is hidden from the user
case hidden = "hd"

// This characteristic supports write response
case writeResponse = "wr"

// Short-hand for "all" permissions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
public extension AnyCharacteristic {
static func characteristicValueTransitionControl(
_ value: Data = Data(),
permissions: [CharacteristicPermission] = [.read, .write],
permissions: [CharacteristicPermission] = [.read, .write, .writeResponse],
description: String? = "Characteristic Value Transition Control",
format: CharacteristicFormat? = .tlv8,
unit: CharacteristicUnit? = nil,
Expand Down Expand Up @@ -33,7 +33,7 @@ public extension AnyCharacteristic {
public extension PredefinedCharacteristic {
static func characteristicValueTransitionControl(
_ value: Data = Data(),
permissions: [CharacteristicPermission] = [.read, .write],
permissions: [CharacteristicPermission] = [.read, .write, .writeResponse],
description: String? = "Characteristic Value Transition Control",
format: CharacteristicFormat? = .tlv8,
unit: CharacteristicUnit? = nil,
Expand Down
4 changes: 4 additions & 0 deletions Sources/HAP/Base/Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ open class Service: NSObject, JSONSerializable {
}
return json
}

open override var debugDescription: String {
"#\(iid) \(type.description) of accessory \(accessory?.debugDescription ?? "no accessory")"
}
}

func getOrCreateAppend<T>(type: CharacteristicType,
Expand Down
5 changes: 5 additions & 0 deletions Sources/HAP/Endpoints/characteristics().swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ func characteristics(device: Device, channel: Channel, request: HTTPRequest) ->
status: .resourceDoesNotExist))
continue
}

logger.trace("reading value of \(characteristic)")

guard characteristic.permissions.contains(.read) else {
logger.info("\(characteristic) has no read permission")
responses.append(Protocol.Characteristic(aid: path.aid, iid: path.iid, status: .writeOnly))
Expand Down Expand Up @@ -91,6 +94,8 @@ func characteristics(device: Device, channel: Channel, request: HTTPRequest) ->
response.ev = characteristic.permissions.contains(.events)
}
responses.append(response)

logger.trace("read value: \(value)")
}

/* HAP Specification 5.7.3.2
Expand Down
10 changes: 5 additions & 5 deletions Sources/HAP/Server/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,17 @@ 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
hash ^= 17 &* UInt64(accessory.aid)
for service in accessory.services {
hash ^= 19 &* service.iid
hash ^= 19 &* UInt64(service.iid)
for characteristic in service.characteristics {
hash ^= 23 &* characteristic.iid
hash ^= 23 &* UInt64(characteristic.iid)
}
}
}
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) }
}
}
28 changes: 26 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,30 @@ 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(Data([TLV8.delimiter.rawValue, 0]))
}
first = false
result.append(encode([(type, item)]))
}
if first {
// Zero-length array
result.append(Data([type.rawValue, 0]))
}
}
return result
}

enum TLV8: UInt8 {
case delimiter = 0
}

// Pair Setup State
enum PairSetupStep: UInt8 {
case waiting = 0
Expand Down
2 changes: 2 additions & 0 deletions Sources/HAPDemo/createLogHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ func createLogHandler(label: String) -> LogHandler {
switch label {
case "hap.encryption":
handler.logLevel = .info
case "hap.endpoints.characteristics":
handler.logLevel = .trace
case "hap",
_ where label.starts(with: "hap."):
handler.logLevel = .debug
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.littleEndianBytes),
(SupportedValueTransitionConfigurationTypes.TransitionType, Data([TransitionType.Brightness.rawValue])),
]),
encode([
(SupportedValueTransitionConfigurationTypes.CharacteristicIid, colorTemperature.iid.littleEndianBytes),
(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
12 changes: 10 additions & 2 deletions Sources/HAPInspector/Inspector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ let defaultTypes: [DefaultType] = [

]

let additionalPermissions = [
"characteristic-value-transition-control": CharacteristicInfoPermission.writeResponse
]

struct FileHandlerOutputStream: TextOutputStream {
private let fileHandle: FileHandle
let encoding: String.Encoding
Expand Down Expand Up @@ -404,7 +408,7 @@ func inspect(source plistPath: URL, target outputPath: String) throws {
format: format,
maxValue: dict["MaxValue"] as? NSNumber,
minValue: dict["MinValue"] as? NSNumber,
permissions: CharacteristicInfoPermission(rawValue: dict["Properties"] as! Int),
permissions: CharacteristicInfoPermission(rawValue: dict["Properties"] as! UInt32).union(additionalPermissions[name] ?? []),
stepValue: dict["StepValue"] as? NSNumber,
units: dict["Units"] as? String))
characteristicFormats.insert(format)
Expand Down Expand Up @@ -874,17 +878,21 @@ struct ServiceInfo {
}

struct CharacteristicInfoPermission: OptionSet, CustomStringConvertible {
let rawValue: Int
let rawValue: UInt32

static let read = CharacteristicInfoPermission(rawValue: 2)
static let write = CharacteristicInfoPermission(rawValue: 4)
static let events = CharacteristicInfoPermission(rawValue: 1)

// Custom values to support additional permissions.
static let writeResponse = CharacteristicInfoPermission(rawValue: 1 << 31)

var description: String {
var permissions: [String] = []
if contains(.read) { permissions += [".read"] }
if contains(.write) { permissions += [".write"] }
if contains(.events) { permissions += [".events"] }
if contains(.writeResponse) { permissions += [".writeResponse"] }
return "[" + permissions.joined(separator: ", ") + "]"
}
}
Loading