Bouke / HAP

Swift implementation of the Homekit Accessory Protocol
https://boukehaarsma.nl/HAP/
MIT License
366 stars 50 forks source link

SecuritySystem - Show as Single Tile #125

Closed kentnz closed 3 years ago

kentnz commented 3 years ago

I'm setting up a 'SecuritySystem' with 6 zones in it.

When I use the Apple HomeKit Accessory Simulator for the six zones, they appear as a single 'Tile' in the 'Home' app and there is an toggle to 'Show as Single Tile' or 'Show as Separate Tiles'.

I cannot figure out how to recreate this setup using HAP. They always appear as separate tiles and there is no option to 'Show as Single Tile'.

Does anyone know how to create it so that they can be a single 'Tile' ?

Note: I've tried using the same 'serialNumber', etc.

Thanks Kent.


let zone1Info = Service.Info(name: "Name1", serialNumber: "A1801", manufacturer: "Linux", model: "A", firmwareRevision: "1") let zone2Info = Service.Info(name: "Name2", serialNumber: "A1802", manufacturer: "Linux", model: "A", firmwareRevision: "1") let zone3Info = Service.Info(name: "Name3", serialNumber: "A1803", manufacturer: "Linux", model: "A", firmwareRevision: "1") let zone4Info = Service.Info(name: "Name4", serialNumber: "A1804", manufacturer: "Linux", model: "A", firmwareRevision: "1") let zone5Info = Service.Info(name: "Name5", serialNumber: "A1805", manufacturer: "Linux", model: "A", firmwareRevision: "1") let zone6Info = Service.Info(name: "Name6", serialNumber: "A1806", manufacturer: "Linux", model: "A", firmwareRevision: "1") let zone1 = OfficeAlarmSystem(info: zone1Info, name: "Name1" ) let zone2 = OfficeAlarmSystem(info: zone2Info, name: "Name2" ) let zone3 = OfficeAlarmSystem(info: zone3Info, name: "Name3" ) let zone4 = OfficeAlarmSystem(info: zone4Info, name: "Name4" ) let zone5 = OfficeAlarmSystem(info: zone5Info, name: "Name5" ) let zone6 = OfficeAlarmSystem(info: zone6Info, name: "Name6" )

let device = Device( bridgeInfo: bridgeInfo, setupCode: setupCode, storage: storage, accessories: [ zone1, zone2, zone3, zone4, zone5, zone6 ]

Bouke commented 3 years ago

In the simulator I suppose you've created a single accessory with multiple services, which can be done using this package as well:

let securitySystem = Accessory(info: .init(name: "Multi-Zone", serialNumber: "A1803"),
                               type: .securitySystem,
                               services: [
                                Service.SecuritySystemBase(characteristics: [.name("Zone A")]),
                                Service.SecuritySystemBase(characteristics: [.name("Zone B")])
                               ])
let device = Device(
    bridgeInfo: Service.Info(name: "Bridge", serialNumber: "00001"),
    setupCode: "123-44-321",
    storage: storage,
    accessories: [
        securitySystem
    ])

To include the optional characteristics "status fault" and "status tampered", you have to explicitly add those during initialization:

Service.SecuritySystemBase(characteristics: [.name("Zone A"), .statusFault(), .statusTampered()])
kentnz commented 3 years ago

Hi,

Thanks - that has achieved exactly what I was trying to achieve in the Apple Home interface.

1) How do I now add the logic to the code to handle the actions, etc.

2) I assume I'll know which item (name) is being called

3) How do I change the Tampered() status - will it appear similar to the non optional settings ?

Thanks Kent.

eg. Before I had set up as six separate accessories as you had worked out:

let officeAlarmEntranceInfo = Service.Info(name: "Entrance", serialNumber: "A1801", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001") let officeAlarmMeetingInfo = Service.Info(name: "Meeting", serialNumber: "A1802", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001") let officeAlarmSupportInfo = Service.Info(name: "Support", serialNumber: "A1803", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001") let officeAlarmDevsInfo = Service.Info(name: "Devs", serialNumber: "A1804", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001") let officeAlarmOfficeInfo = Service.Info(name: "Office", serialNumber: "A1805", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001") let officeAlarmServerInfo = Service.Info(name: "Server Room", serialNumber: "A1806", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001") let officeAlarmEntrance = OfficeAlarmSystem(info: officeAlarmEntranceInfo, name: "entrance", address: alarmaddress, password: alarmpswd ) let officeAlarmMeeting = OfficeAlarmSystem(info: officeAlarmMeetingInfo, name: "meeting", address: alarmaddress, password: alarmpswd ) let officeAlarmSupport = OfficeAlarmSystem(info: officeAlarmSupportInfo, name: "support", address: alarmaddress, password: alarmpswd ) let officeAlarmDevs = OfficeAlarmSystem(info: officeAlarmDevsInfo, name: "devs", address: alarmaddress, password: alarmpswd ) let officeAlarmOffice = OfficeAlarmSystem(info: officeAlarmOfficeInfo, name: "office", address: alarmaddress, password: alarmpswd ) let officeAlarmServer = OfficeAlarmSystem(info: officeAlarmServerInfo, name: "server", address: alarmaddress, password: alarmpswd )

And then I have a OfficeAlarmSystem.swift() file which has the logic, etc.

// // AlarmSystem.swift // HAP // // Created by Kent on 3/1/2021. //

import Foundation import Logging import HAP

if os(Linux)

import Dispatch

endif

fileprivate let logger = Logger(label: "hap.office.officealarmsystem")

class OfficeAlarmSystem: HAP.Accessory.SecuritySystem {

var sensorid = ""
var serveraddress = ""
var serverpassword = ""

init(info: Service.Info, name: String, address: String, password: String) {
    super.init(info:info)

    logger.info("[Office Alarm System] Init: \(name)")

    sensorid = name
    serveraddress = address
    serverpassword = password

    self.securitySystem.securitySystemCurrentState.value = HAP.Enums.SecuritySystemCurrentState.disarm
    self.securitySystem.securitySystemTargetState.value = HAP.Enums.SecuritySystemTargetState.disarm

}

On 5/01/2021, at 12:05 PM, Bouke Haarsma notifications@github.com wrote:

In the simulator I suppose you've created a single accessory with multiple services, which can be done using this package as well:

let securitySystem = Accessory(info: .init(name: "Multi-Zone", serialNumber: "A1803"), type: .securitySystem, services: [ Service.SecuritySystemBase(characteristics: [.name("Zone A")]), Service.SecuritySystemBase(characteristics: [.name("Zone B")]) ]) let device = Device( bridgeInfo: Service.Info(name: "Bridge", serialNumber: "00001"), setupCode: "123-44-321", storage: storage, accessories: [ securitySystem ]) To include the optional characteristics "status fault" and "status tampered", you have to explicitly add those during initialization:

Service.SecuritySystemBase(characteristics: [.name("Zone A"), .statusFault(), .statusTampered()]) — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Bouke/HAP/issues/125#issuecomment-754275665, or unsubscribe https://github.com/notifications/unsubscribe-auth/ANLIE6GXEJ2IA6BX25AYYJDSYJCULANCNFSM4VSGYHKA.

Bouke commented 3 years ago

statusFault and statusTempered are available on the SecuritySystemBase : Service: https://github.com/Bouke/HAP/blob/b1038b5dac7598419f7c58f54d47952385d6ecaa/Sources/HAP/Base/Generated.swift#L1459-L1460.

The default accessory HAP.Accessory.SecuritySystem assumes a single SecuritySystem service: https://github.com/Bouke/HAP/blob/8fbd1a76373fbdb5a39ad67691b4ddf22543e5f2/Sources/HAP/Accessories/SecuritySystem.swift#L3-L7

In your situation, you're better off not extending SecuritySystem(|Base), but directly from Accessory instead. That gives you the freedom to add as many services as you like. See my example where I create such an accessory in a functional style. The same is possible in a declarative style through inheritance, something like this (untested):

class OfficeAlarmSystem : HAP.Accessory {
    var zones: Service.SecuritySystemBase[]

    init(info: Service.Info) {
        zones = [
            Service.SecuritySystemBase(characteristics: [.name("Zone A"), .statusFault(), .statusTampered()]),
            Service.SecuritySystemBase(characteristics: [.name("Zone B")])
        ]
        super.init(info:info,
                   type: .securitySystem,
                   services: zones)

        zones[0].statusTampered.value = 1
    }
}
kentnz commented 3 years ago

I've created the class (with a couple of modifications)

1) var zones: [Service.SecuritySystemBase] <-- XCode tells me [ ]'s should be around the item 2) zones[0].statusTampered?.value = 0 <-- XCode tells me I need the ?

class OfficeAlarmSystem : HAP.Accessory {
    var zones: [Service.SecuritySystemBase]

    init(info: Service.Info) {
        zones = [
            Service.SecuritySystemBase(characteristics: [.name("Zone 1"), .statusTampered()] ),
            Service.SecuritySystemBase(characteristics: [.name("Zone 2"), .statusTampered()] ),
            Service.SecuritySystemBase(characteristics: [.name("Zone 3"), .statusTampered()] ),
            Service.SecuritySystemBase(characteristics: [.name("Zone 4"), .statusTampered()] ),
            Service.SecuritySystemBase(characteristics: [.name("Zone 5"), .statusTampered()] ),
            Service.SecuritySystemBase(characteristics: [.name("Zone 6"), .statusTampered()] )
        ]
        super.init(info:info,
                   type: .securitySystem,
                   services: zones)

        zones[0].statusTampered?.value = 0
        zones[1].statusTampered?.value = 1
        zones[2].statusTampered?.value = 0
        zones[3].statusTampered?.value = 0
        zones[4].statusTampered?.value = 0
        zones[5].statusTampered?.value = 0
    }

    // ----------------------------------------------------------------------------------------------------------
    override func characteristic<T>(_ characteristic: GenericCharacteristic<T>, ofService service: Service, didChangeValue newValue: T?)
    {
        logger.info("[Office Alarm] Characteristic \(characteristic) did change: \(String(describing: newValue))")

    }

}

Then in 'Main.swift' I've created the accessory.

let officeAlarmInfo = Service.Info(name: "Office Alarm",    serialNumber: "A1804", manufacturer: "Linux", model: "Touch", firmwareRevision: "10001")
let officeAlarm = OfficeAlarmSystem(info: officeAlarmInfo )

This appears to be working - so now to make it actually do something 👍

Kent.