import { clamp } from "alcalzone-shared/math" ; import { Accessory } from "./accessory" ; import { conversions , deserializers , serializers } from "./conversions" ; import { IPSODevice } from "./ipsoDevice" ; import { deserializeWith , doNotSerialize , ipsoKey , IPSOOptions , required , serializeWith } from "./ipsoObject" ; import { MAX_COLOR , predefinedColors , whiteSpectrumHex } from "./predefined-colors" ; export type LightOperation = Partial < Pick < Light , "onOff" | "dimmer" | "whenPowerRestored" | "color" | "colorTemperature" | "colorX" | "colorY" | "hue" | "saturation" | "transitionTime" > > ; export enum PowerRestoredAction { TurnOn = 2 , RememberStatus = 4 } export class Light extends IPSODevice { constructor ( options ? , accessory ? ) { super ( options ) ; if ( accessory != null && ! accessory . isProxy ) { accessory = accessory . createProxy ( ) ; } this . _accessory = accessory ; if ( accessory != null && accessory . deviceInfo != null && accessory . deviceInfo . modelNumber != null && accessory . deviceInfo . modelNumber . length > 0 ) { this . _modelName = accessory . deviceInfo . modelNumber ; } } @ doNotSerialize private _modelName : string | undefined ; @ doNotSerialize private _accessory : Accessory | undefined ; @ ipsoKey ( "5706" ) @ doNotSerialize public color : string = "f1e0b5" ; @ ipsoKey ( "5707" ) @ serializeWith ( serializers . hue ) @ deserializeWith ( deserializers . hue ) @ required ( ( me , ref ? ) => ref != null && me . saturation !== ref . saturation ) public hue ! : number ; @ ipsoKey ( "5708" ) @ serializeWith ( serializers . saturation ) @ deserializeWith ( deserializers . saturation ) @ required ( ( me , ref ? ) => ref != null && me . hue !== ref . hue ) public saturation ! : number ; @ ipsoKey ( "5709" ) @ doNotSerialize public colorX ! : number ; @ ipsoKey ( "5710" ) @ doNotSerialize public colorY ! : number ; @ ipsoKey ( "5711" ) @ serializeWith ( serializers . colorTemperature ) @ deserializeWith ( deserializers . colorTemperature ) public colorTemperature ! : number ; internal @ ipsoKey ( "5717" ) public UNKNOWN1 : any ; @ ipsoKey ( "5712" ) @ required ( ) @ serializeWith ( serializers . transitionTime , { neverSkip : true } ) @ deserializeWith ( deserializers . transitionTime , { neverSkip : true } ) public transitionTime : number = 0.5 ; @ ipsoKey ( "5805" ) public cumulativeActivePower ! : number ; @ ipsoKey ( "5851" ) @ serializeWith ( serializers . brightness ) @ deserializeWith ( deserializers . brightness ) public dimmer ! : number ; @ ipsoKey ( "5850" ) public onOff ! : boolean ; @ ipsoKey ( "5849" ) public whenPowerRestored ! : PowerRestoredAction ; @ ipsoKey ( "5852" ) public onTime ! : number ; @ ipsoKey ( "5820" ) public powerFactor ! : number ; @ ipsoKey ( "5701" ) public unit ! : string ; /**
     * Returns true if the current lightbulb is dimmable
     */ public get isDimmable ( ) : boolean { return true ; } /**
     * Returns true if the current lightbulb is switchable
     */ public get isSwitchable ( ) : boolean { return true ; } public clone ( ) { const ret = super . clone ( this . _accessory ) ; ret . _modelName = this . _modelName ; return ret ; } /**
     * Returns the supported color spectrum of the lightbulb
     */ private _spectrum : Spectrum | undefined ; public get spectrum ( ) : Spectrum { if ( this . _spectrum == null ) { if ( this . hue != null && this . saturation != null ) { this . _spectrum = "rgb" ; } else if ( this . colorTemperature != null ) { this . _spectrum = "white" ; } else { this . _spectrum = "none" ; } } return this . _spectrum ; } /**
     * Creates a proxy which redirects the properties to the correct internal one
     */ public createProxy ( ) { const raw = this . _accessory instanceof Accessory ? this . _accessory . options . skipValueSerializers : false ; switch ( this . spectrum ) { case "rgb" : { const proxy = createRGBProxy ( raw ) ; return super . createProxy ( proxy . get , proxy . set ) ; } default : return this ; } } throws private ensureLink ( ) { if ( this . client == null ) { throw new Error ( "Cannot use the simplified API on devices which aren't linked to a client instance." ) ; } if ( ! ( this . _accessory instanceof Accessory ) ) { throw new Error ( "Cannot use the simplified API on lightbulbs which aren't linked to an Accessory instance." ) ; } } /** Turn this lightbulb on */ public turnOn ( ) { this . ensureLink ( ) ; return this . client ! . operateLight ( this . _accessory ! , { onOff : true , } ) ; } /** Turn this lightbulb off */ public turnOff ( ) { this . ensureLink ( ) ; return this . client ! . operateLight ( this . _accessory ! , { onOff : false , } ) ; } /** Toggles this lightbulb on or off */ public toggle ( value = ! this . onOff ) { this . ensureLink ( ) ; return this . client ! . operateLight ( this . _accessory ! , { onOff : value , } ) ; } private operateLight ( operation , transitionTime ? ) { this . ensureLink ( ) ; if ( transitionTime != null ) { transitionTime = Math . max ( 0 , transitionTime ) ; operation . transitionTime = transitionTime ; } return this . client ! . operateLight ( this . _accessory ! , operation ) ; } returns public setBrightness ( value , transitionTime ? ) { this . ensureLink ( ) ; value = clamp ( value , 0 , 100 ) ; return this . operateLight ( { dimmer : value , } , transitionTime ) ; } param value returns public setColor ( value , transitionTime ? ) { this . ensureLink ( ) ; switch ( this . spectrum ) { case "rgb" : { return this . operateLight ( { color : value , } , transitionTime ) ; } case "white" : { if ( ! ( value in whiteSpectrumHex ) ) { throw new Error ( "White spectrum bulbs only support the following colors: " + Object . keys ( whiteSpectrumHex ) . join ( ", " ) ) ; } return this . operateLight ( { colorTemperature : whiteSpectrumHex [ value ] , } , transitionTime ) ; } default : { throw new Error ( "setColor is only available for RGB lightbulbs" ) ; } } } param value returns public setColorTemperature ( value , transitionTime ? ) { this . ensureLink ( ) ; if ( this . spectrum !== "white" ) throw new Error ( "setColorTemperature is only available for white spectrum lightbulbs" ) ; value = clamp ( value , 0 , 100 ) ; return this . operateLight ( { colorTemperature : value , } , transitionTime ) ; } returns public setHue ( value , transitionTime ? ) { this . ensureLink ( ) ; if ( this . spectrum !== "rgb" ) throw new Error ( "setHue is only available for RGB lightbulbs" ) ; value = clamp ( value , 0 , 360 ) ; return this . operateLight ( { hue : value , } , transitionTime ) ; } returns public setSaturation ( value , transitionTime ? ) { this . ensureLink ( ) ; if ( this . spectrum !== "rgb" ) throw new Error ( "setSaturation is only available for RGB lightbulbs" ) ; value = clamp ( value , 0 , 100 ) ; return this . operateLight ( { saturation : value , } , transitionTime ) ; } /** Turns this object into JSON while leaving out the potential circular reference */ public toJSON ( ) { return { onOff : this . onOff , whenPowerRestored : this . whenPowerRestored , dimmer : this . dimmer , color : this . color , colorTemperature : this . colorTemperature , colorX : this . colorX , colorY : this . colorY , hue : this . hue , isDimmable : this . isDimmable , isSwitchable : this . isSwitchable , spectrum : this . spectrum , saturation : this . saturation , transitionTime : this . transitionTime , } ; } /**
     * Fixes property values that are known to be bugged
     */ public fixBuggedProperties ( ) { super . fixBuggedProperties ( ) ; if ( this . onOff === false && this . dimmer === MIN_BRIGHTNESS ) this . dimmer = 0 ; return this ; } } const MIN_BRIGHTNESS = deserializers . brightness ( 1 ) ; export type Spectrum = "none" | "white" | "rgb" ; const rgbRegex = /^[0-9A-Fa-f]{6}$/ ; /**
 * Creates a proxy for an RGB lamp,
 * which converts RGB color to CIE xy
 */ function createRGBProxy ( raw = false ) { function get ( me , key ) { switch ( key ) { case "color" : { if ( typeof me . color === "string" && rgbRegex . test ( me . color ) ) { return me . color ; } else { const { r , g , b } = conversions . rgbFromHSV ( me . hue , me . saturation / 100 , 1 ) ; return conversions . rgbToString ( r , g , b ) ; } } default : return me [ key as keyof Light ] ; } } function set ( me , key , value ) { switch ( key ) { case "color" : { if ( predefinedColors . has ( value ) ) { const definition = predefinedColors . get ( value ) ! ; if ( raw ) { me . hue = definition . hue_raw ; me . saturation = definition . saturation_raw ; } else { me . hue = definition . hue ; me . saturation = definition . saturation ; } } else { if ( rgbRegex . test ( value ) ) { const { r , g , b } = conversions . rgbFromString ( value ) ; const { h , s } = conversions . rgbToHSV ( r , g , b ) ; if ( raw ) { me . hue = Math . round ( h / 360 * MAX_COLOR ) ; me . saturation = Math . round ( s * MAX_COLOR ) ; } else { me . hue = h ; me . saturation = s * 100 ; } } } break ; } default : ( me as any ) [ key ] = value ; } return true ; } return { get , set } ; }