From cefe47a190684d14f151013fa7876a2383f6c0d9 Mon Sep 17 00:00:00 2001 From: TheMisfit68 Date: Mon, 18 Dec 2023 16:17:26 +0100 Subject: [PATCH] Created function key as a a PLC-class en made it uniform with the other PLC-classes --- .../CirtcuitEnabler.swift | 4 +- .../DimmableLight.swift | 19 ++- .../DoorLock.swift | 4 +- .../FunctionKey.swift | 108 ++++++++---------- .../GarageDoor.swift | 2 +- .../ PLCClassAccessoryDelegates/Light.swift | 4 +- .../ PLCClassAccessoryDelegates/Outlet.swift | 4 +- .../SmartSprinkler.swift | 2 +- .../ToggleableOutlet.swift | 4 +- .../WindowCovering.swift | 2 +- .../MilightAccessoryDelegate.swift | 15 ++- HAPiNest.xcodeproj/project.pbxproj | 4 + HAPiNest/AccessoryNames.xcstrings | 2 + HAPiNest/Localizable.xcstrings | 10 ++ HAPiNest/ServiceNames.xcstrings | 10 ++ MainConfiguration.swift | 8 +- 16 files changed, 110 insertions(+), 92 deletions(-) diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/CirtcuitEnabler.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/CirtcuitEnabler.swift index 119bae77b..4cf1c6343 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/CirtcuitEnabler.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/CirtcuitEnabler.swift @@ -16,7 +16,7 @@ import JVCocoa // MARK: - PLC level class class CirtcuitEnabler:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulateable{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.Lightbulb var characteristicChanged:Bool = false @@ -49,7 +49,7 @@ class CirtcuitEnabler:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulateab outputSignal.logicalValue = puls } - // MARK: - Processing + // MARK: - PLC Processing public func runCycle(){ reevaluate(&powerState, characteristic:accessory.lightbulb.powerState, hardwareFeedback: hardwarePowerState) diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/DimmableLight.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/DimmableLight.swift index 434c2ab63..cb8c0f89c 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/DimmableLight.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/DimmableLight.swift @@ -14,11 +14,18 @@ import IOTypes import JVCocoa // MARK: - PLC level class -class DimmableLight:PLCClassAccessoryDelegate{ - - // Accessory binding +class DimmableLight:PLCClassAccessoryDelegate, DimmedLight{ + + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.Lightbulb var characteristicChanged: Bool = false + + var brightnessTimer: BrightnessTimer! + + override init(){ + super.init() + self.brightnessTimer = BrightnessTimer(dimmer: self) + } // MARK: - State var powerState:Bool? = nil @@ -41,14 +48,13 @@ class DimmableLight:PLCClassAccessoryDelegate{ var brightness:Int? = nil{ didSet{ if let brightness = brightness, brightness != oldValue{ - + if brightness > switchOffLevelDimmer{ previousOnLevel = brightness }else{ powerState?.reset() } - } } } @@ -77,10 +83,11 @@ class DimmableLight:PLCClassAccessoryDelegate{ outputSignal.scaledValue = Float(powerState == true ? (brightness ?? switchOffLevelDimmer+1) : 0) } - // MARK: - Processing + // MARK: - PLC Processing public func runCycle() { reevaluate(&powerState, initialValue: (hardwareBrightness ?? 0 > switchOffLevelDimmer), characteristic:accessory.lightbulb.powerState, hardwareFeedback: nil) + reevaluate(&brightness, characteristic:accessory.lightbulb.brightness, hardwareFeedback:hardwareBrightness) characteristicChanged.reset() diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/DoorLock.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/DoorLock.swift index fad0ed6d4..a04c976e3 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/DoorLock.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/DoorLock.swift @@ -16,7 +16,7 @@ import JVCocoa // MARK: - PLC level class class Doorlock:PLCClassAccessoryDelegate, PulsOperatedCircuit{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.LockMechanism var characteristicChanged:Bool = false @@ -43,9 +43,7 @@ class Doorlock:PLCClassAccessoryDelegate, PulsOperatedCircuit{ } public func assignOutputParameters(){ - outputSignal.logicalValue = puls - } // MARK: - PLC Processing diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/FunctionKey.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/FunctionKey.swift index d98be1845..62f68c5d7 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/FunctionKey.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/FunctionKey.swift @@ -16,79 +16,61 @@ import IOTypes import JVCocoa -/// A PLC-Class type object that is not an Accessory-Delegate -/// because it has no Accessory associated with it, -/// it only processes hardware-signals class FunctionKey:PLCClassAccessoryDelegate{ + // MARK: - Accessory binding + typealias AccessorySubclass = Accessory.StatelessProgrammableSwitch var characteristicChanged: Bool = false - var hardwareFeedbackChanged: Bool = false - - private var inputPuls:Bool = false - private var inputTriggered:EBool - - var clicked:Bool = false{ - didSet{ - if clicked{ -#warning("DEBUGPRINT") // TODO: - remove temp print statement - print("🐞\tClicked") - } - clicked.reset() - } + // MARK: - State + private enum SwitchEvent:UInt8{ + case singlePress = 0 + case doublePress = 1 + case longPress = 2 } - let doubleclikInterval:TimeInterval - let doubleClickTimer:DigitalTimer - var doubleClicked:Bool = false{ + private var clicksCounter:Int = 0 + private var switchEvent:SwitchEvent? = nil{ + didSet{ - if doubleClicked{ -#warning("DEBUGPRINT") // TODO: - remove temp print statement - print("🐞\tDoubleClicked") - } - doubleClicked.reset() + clicksCounter = 0 + longPressTimer.reset() + doubleClickTimer.reset() } } - let longPressTime:TimeInterval - let longPressTimer:DigitalTimer - var longPressed:Bool = false{ - didSet{ - if longPressed{ -#warning("DEBUGPRINT") // TODO: - remove temp print statement - print("🐞\tPressedlong") - } - longPressed.reset() - } - } + private var inputPuls:Bool = false + private var inputTriggered:EBool + + private let doubleclikInterval:TimeInterval = 10.0 + private let doubleClickTimer:DigitalTimer + + private let longPressTime:TimeInterval = 20.0 + private let longPressTimer:DigitalTimer - override init(){ + // Hardware feedback state + // Function keys only have inputs, no controlable outputs and therefore also no associated hardwarefeedback + var hardwareFeedbackChanged:Bool = false + + override init(){ - self.inputTriggered = EBool(&inputPuls) - - self.doubleclikInterval = 1.0 + inputTriggered = EBool(&inputPuls) self.doubleClickTimer = DigitalTimer.OffDelay(time: doubleclikInterval) - - self.longPressTime = 2.0 self.longPressTimer = DigitalTimer.OnDelay(time: longPressTime) super.init() } + // MARK: - IO-Signal assignment var inputSignal:DigitalInputSignal{ - let ioSymbol:SoftPLC.IOSymbol = .functionKey(circuit:String(localized: "\(instanceName)", table:"AccessoryNames")) + let ioSymbol:SoftPLC.IOSymbol = .functionKey(circuit:String(localized: "\(instanceName)")) return plc.signal(ioSymbol:ioSymbol) as! DigitalInputSignal } + // MARK: - PLC Parameter assignment public func assignInputParameters(){ - - if let hardwarePuls = inputSignal.logicalValue{ - inputPuls = hardwarePuls - }else{ - inputPuls = false - } - + inputPuls = inputSignal.logicalValue ?? false } public func assignOutputParameters(){ @@ -98,24 +80,24 @@ class FunctionKey:PLCClassAccessoryDelegate{ // MARK: - PLC Processing public func runCycle() { - let risingEdge = inputTriggered.🔼 + doubleClickTimer.input = inputPuls + longPressTimer.input = inputPuls - if risingEdge && !doubleClickTimer.output{ - clicked.set() - }else if risingEdge && doubleClickTimer.output{ - doubleClicked.set() - }else if longPressTimer.output{ - longPressed.set() + if longPressTimer.output{ + switchEvent = .longPress + }else if (clicksCounter >= 2) { + switchEvent = .doublePress + }else if (clicksCounter == 1) && !doubleClickTimer.output{ + switchEvent = .singlePress } -// reevaluate(&clicked, characteristic:accessory.programmableSwitchEvent, hardwareFeedback: hardwarePowerState) -// reevaluate(&doubleClicked, characteristic:accessory.outlet.powerState, hardwareFeedback: hardwarePowerState) - - - - doubleClickTimer.input = inputPuls - longPressTimer.input = inputPuls + if inputTriggered.🔼 { + clicksCounter += 1 + }else if !doubleClickTimer.output{ + clicksCounter = 0 + } + reevaluate(&switchEvent, characteristic:accessory.primaryService.programmableSwitchEvent, hardwareFeedback: nil) } } diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/GarageDoor.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/GarageDoor.swift index af25204d9..38c866a64 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/GarageDoor.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/GarageDoor.swift @@ -16,7 +16,7 @@ import JVCocoa // MARK: - PLC level class class GarageDoor:PLCClassAccessoryDelegate, PulsOperatedCircuit{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.GarageDoorOpener.StatelessGarageDoorOpener var characteristicChanged: Bool = false diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/Light.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/Light.swift index 1bf9830fd..1fd179fe1 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/Light.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/Light.swift @@ -17,7 +17,7 @@ import OSLog // MARK: - PLC level class class Light:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulateable{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.Lightbulb var characteristicChanged:Bool = false @@ -50,7 +50,7 @@ class Light:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulateable{ outputSignal.logicalValue = puls } - // MARK: - Processing + // MARK: - PLC Processing public func runCycle(){ reevaluate(&powerState, characteristic:accessory.lightbulb.powerState, hardwareFeedback: hardwarePowerState) diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/Outlet.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/Outlet.swift index 77f66085c..f71b5ca67 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/Outlet.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/Outlet.swift @@ -16,7 +16,7 @@ import JVCocoa // MARK: - PLC level class class Outlet:PLCClassAccessoryDelegate{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.Outlet var characteristicChanged: Bool = false @@ -58,7 +58,7 @@ class Outlet:PLCClassAccessoryDelegate{ outputSignal.logicalValue = powerState ?? false } - // MARK: - Processing + // MARK: - PLC Processing public func runCycle() { reevaluate(&powerState, characteristic:accessory.outlet.powerState, hardwareFeedback: hardwarePowerState) diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/SmartSprinkler.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/SmartSprinkler.swift index 26715da8a..59214e935 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/SmartSprinkler.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/SmartSprinkler.swift @@ -19,7 +19,7 @@ import OSLog // MARK: - PLC level class class SmartSprinkler:PLCClassAccessoryDelegate{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.SmartSprinkler var characteristicChanged:Bool = false diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/ToggleableOutlet.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/ToggleableOutlet.swift index 165995c7b..441a14521 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/ToggleableOutlet.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/ToggleableOutlet.swift @@ -16,7 +16,7 @@ import JVCocoa // MARK: - PLC level class class ToggleableOutlet:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulateable{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.Outlet var characteristicChanged:Bool = false @@ -57,7 +57,7 @@ class ToggleableOutlet:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulatea } - // MARK: - Processing + // MARK: - PLC Processing public func runCycle() { reevaluate(&powerState, characteristic:accessory.outlet.powerState, hardwareFeedback: hardwarePowerState) diff --git a/Accessory Delegates/ PLCClassAccessoryDelegates/WindowCovering.swift b/Accessory Delegates/ PLCClassAccessoryDelegates/WindowCovering.swift index 14ede96b7..25f8afb20 100644 --- a/Accessory Delegates/ PLCClassAccessoryDelegates/WindowCovering.swift +++ b/Accessory Delegates/ PLCClassAccessoryDelegates/WindowCovering.swift @@ -16,7 +16,7 @@ import JVCocoa // MARK: - PLC level class class WindowCovering:PLCClassAccessoryDelegate, PulsOperatedCircuit, Simulateable{ - // Accessory binding + // MARK: - Accessory binding typealias AccessorySubclass = Accessory.WindowCovering var characteristicChanged:Bool = false diff --git a/Accessory Delegates/MilightAccessoryDelegate.swift b/Accessory Delegates/MilightAccessoryDelegate.swift index 8bd93ea3d..657e5b04b 100644 --- a/Accessory Delegates/MilightAccessoryDelegate.swift +++ b/Accessory Delegates/MilightAccessoryDelegate.swift @@ -15,8 +15,10 @@ import OSLog /// Handles characteristic changes for a Homekit Accessory. /// It uses the MilightDriver to pass those changes to the hardware -class MilightAccessoryDelegate:MilightDriverV6, AccessoryDelegate { - +class MilightAccessoryDelegate:MilightDriverV6, AccessoryDelegate, DimmedLight { + + var brightnessTimer: BrightnessTimer! + let zone:MilightDriver.Zone var name: String{ return zone.name @@ -25,11 +27,12 @@ class MilightAccessoryDelegate:MilightDriverV6, AccessoryDelegate { init(ipAddress: String, zone:MilightDriver.Zone){ self.zone = zone super.init(ipAddress: ipAddress) + self.brightnessTimer = BrightnessTimer(dimmer: self) } var characteristicChanged: Bool = false - var brightness:Int = 100{ + var brightness:Int? = 100{ didSet{ executeCommand(mode: .rgbwwcw, action: .brightNess, value: brightness, zone: zone) } @@ -66,7 +69,7 @@ class MilightAccessoryDelegate:MilightDriverV6, AccessoryDelegate { } } } - + func handleCharacteristicChange(accessory:Accessory, service: Service, characteristic: GenericCharacteristic, @@ -82,7 +85,9 @@ class MilightAccessoryDelegate:MilightDriverV6, AccessoryDelegate { case CharacteristicType.brightness: - brightness = characteristic.value as! Int +// brightness = characteristic.value as! Int + self.brightnessTimer.timeFor100percentChange = 1800 + self.brightnessTimer.targetBrightness = (characteristic.value as! Int) case CharacteristicType.hue: diff --git a/HAPiNest.xcodeproj/project.pbxproj b/HAPiNest.xcodeproj/project.pbxproj index 293005623..13b5af425 100644 --- a/HAPiNest.xcodeproj/project.pbxproj +++ b/HAPiNest.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ DC9963892AD3073000915EAC /* CirtcuitEnabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9963882AD3073000915EAC /* CirtcuitEnabler.swift */; }; DCA37CF926BB29EF003CC3BF /* HAPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA37CF826BB29EF003CC3BF /* HAPTests.swift */; }; DCA3DF312B28D97000554A8A /* BrightnessTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA3DF302B28D97000554A8A /* BrightnessTimer.swift */; }; + DCA626B12B3083C900BBDD91 /* ServiceNames.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DCA626B02B3083C900BBDD91 /* ServiceNames.xcstrings */; }; DCAB996A2A816734005FCC29 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = DCAB99692A816734005FCC29 /* README.md */; }; DCBBC7082B2EE4D80005EE6E /* StatelessProgrammableSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBBC7072B2EE4D80005EE6E /* StatelessProgrammableSwitch.swift */; }; DCBCFB5529678657000F0376 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBCFB5429678657000F0376 /* Enums.swift */; }; @@ -115,6 +116,7 @@ DCA37CF626BB29EF003CC3BF /* HAPiNestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HAPiNestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DCA37CF826BB29EF003CC3BF /* HAPTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAPTests.swift; sourceTree = ""; }; DCA3DF302B28D97000554A8A /* BrightnessTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessTimer.swift; sourceTree = ""; }; + DCA626B02B3083C900BBDD91 /* ServiceNames.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = ServiceNames.xcstrings; sourceTree = ""; }; DCAB99692A816734005FCC29 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; DCBBC7072B2EE4D80005EE6E /* StatelessProgrammableSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatelessProgrammableSwitch.swift; sourceTree = ""; }; DCBCFB5429678657000F0376 /* Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; @@ -202,6 +204,7 @@ DC54B6C12570586E00D9169F /* Assets.xcassets */, DC4D66792A956AC1004C9D30 /* Localizable.xcstrings */, DC4D667B2A9571BA004C9D30 /* AccessoryNames.xcstrings */, + DCA626B02B3083C900BBDD91 /* ServiceNames.xcstrings */, DCF7D5AB2AD0D106000010AA /* TVChannelNames.xcstrings */, DC90DA6A2732828200068839 /* MainWindow */, DC90DA5D2732817000068839 /* PreferencesWindow */, @@ -468,6 +471,7 @@ DCAB996A2A816734005FCC29 /* README.md in Resources */, DC54B6C22570586E00D9169F /* Assets.xcassets in Resources */, DC4D667A2A956AC1004C9D30 /* Localizable.xcstrings in Resources */, + DCA626B12B3083C900BBDD91 /* ServiceNames.xcstrings in Resources */, DC4D667C2A9571BA004C9D30 /* AccessoryNames.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/HAPiNest/AccessoryNames.xcstrings b/HAPiNest/AccessoryNames.xcstrings index 90bfd7e39..825c31b7a 100644 --- a/HAPiNest/AccessoryNames.xcstrings +++ b/HAPiNest/AccessoryNames.xcstrings @@ -2,6 +2,7 @@ "sourceLanguage" : "en", "strings" : { "%@" : { + "extractionState" : "stale", "localizations" : { "nl" : { "stringUnit" : { @@ -162,6 +163,7 @@ } }, "Electric Car" : { + "extractionState" : "stale", "localizations" : { "nl" : { "stringUnit" : { diff --git a/HAPiNest/Localizable.xcstrings b/HAPiNest/Localizable.xcstrings index 818b2c774..ae65b63af 100644 --- a/HAPiNest/Localizable.xcstrings +++ b/HAPiNest/Localizable.xcstrings @@ -11,6 +11,16 @@ } } }, + "%@" : { + "localizations" : { + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + } + } + }, "Are you sure?" : { "localizations" : { "nl" : { diff --git a/HAPiNest/ServiceNames.xcstrings b/HAPiNest/ServiceNames.xcstrings index 6b4f9a2be..3be8599c3 100644 --- a/HAPiNest/ServiceNames.xcstrings +++ b/HAPiNest/ServiceNames.xcstrings @@ -21,6 +21,16 @@ } } }, + "Function key" : { + "localizations" : { + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Functietoets" + } + } + } + }, "Manual" : { "localizations" : { "nl" : { diff --git a/MainConfiguration.swift b/MainConfiguration.swift index 07ddd799a..90177a666 100644 --- a/MainConfiguration.swift +++ b/MainConfiguration.swift @@ -40,10 +40,10 @@ struct MainConfiguration{ static let Accessories:[Accessory : any AccessoryDelegate] = [ // Function Keys - Accessory.ProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Frontdoor", table:"AccessoryNames"), serialNumber: "40000")) : PLCAccessoryDelegate(), - Accessory.ProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Left side bed", table:"AccessoryNames"), serialNumber: "40001")) : PLCAccessoryDelegate(), - Accessory.ProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Right side bed", table:"AccessoryNames"), serialNumber: "40002")) : PLCAccessoryDelegate(), - Accessory.ProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Landing", table:"AccessoryNames"), serialNumber: "40003")) : PLCAccessoryDelegate(), + Accessory.StatelessProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Frontdoor", table:"AccessoryNames"), serialNumber: "50000")) : PLCAccessoryDelegate(), + Accessory.StatelessProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Left side bed", table:"AccessoryNames"), serialNumber: "40001")) : PLCAccessoryDelegate(), + Accessory.StatelessProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Right side bed", table:"AccessoryNames"), serialNumber: "40002")) : PLCAccessoryDelegate(), + Accessory.StatelessProgrammableSwitch(info: Service.Info(name:String(localized:"Function Key Landing", table:"AccessoryNames"), serialNumber: "40003")) : PLCAccessoryDelegate(), // MARK: - Dimmable Lights