@@ -102,13 +102,19 @@ const BUTTON_MAP = new Map<string, Map<number, { label: string, index: number, i
102102export class PicoRemote {
103103 private services : Map < string , Service > = new Map ( )
104104 private trackers : Map < string , ButtonTracker > = new Map ( )
105+ // Map button href to ButtonNumber for event lookup
106+ private hrefToButtonNumber : Map < string , number > = new Map ( )
105107
108+ private matterApi ?: any
106109 constructor (
107110 private readonly platform : LutronCasetaLeap ,
108111 private readonly accessory : PlatformAccessory ,
109112 private readonly bridge : SmartBridge ,
110113 private readonly options : GlobalOptions ,
111- ) { }
114+ matterApi ?: any ,
115+ ) {
116+ this . matterApi = matterApi
117+ }
112118
113119 public async initialize ( ) : Promise < DeviceWireResult > {
114120 const fullName = this . accessory . context . device . FullyQualifiedName . join ( ' ' )
@@ -125,8 +131,8 @@ export class PicoRemote {
125131 )
126132
127133 const label_svc
128- = this . accessory . getService ( this . platform . api . hap . Service . ServiceLabel )
129- || this . accessory . addService ( this . platform . api . hap . Service . ServiceLabel )
134+ = this . accessory . getService ( this . platform . api . hap . Service . ServiceLabel )
135+ || this . accessory . addService ( this . platform . api . hap . Service . ServiceLabel )
130136 label_svc . setCharacteristic (
131137 this . platform . api . hap . Characteristic . ServiceLabelNamespace ,
132138 this . platform . api . hap . Characteristic . ServiceLabelNamespace . ARABIC_NUMERALS , // ha ha
@@ -187,6 +193,9 @@ export class PicoRemote {
187193 }
188194 }
189195
196+ // Map href to ButtonNumber for event lookup
197+ this . hrefToButtonNumber . set ( button . href , button . ButtonNumber )
198+
190199 this . platform . log . debug (
191200 `setting up ${ button . href } named ${ button . Name } numbered ${ button . ButtonNumber } as ${ inspect (
192201 alias ,
@@ -196,12 +205,12 @@ export class PicoRemote {
196205 )
197206
198207 const service
199- = this . accessory . getServiceById ( this . platform . api . hap . Service . StatelessProgrammableSwitch , alias . label )
200- || this . accessory . addService (
201- this . platform . api . hap . Service . StatelessProgrammableSwitch ,
202- button . Name ,
203- alias . label ,
204- )
208+ = this . accessory . getServiceById ( this . platform . api . hap . Service . StatelessProgrammableSwitch , alias . label )
209+ || this . accessory . addService (
210+ this . platform . api . hap . Service . StatelessProgrammableSwitch ,
211+ button . Name ,
212+ alias . label ,
213+ )
205214 service . addLinkedService ( label_svc )
206215
207216 service . setCharacteristic ( this . platform . api . hap . Characteristic . Name , alias . label )
@@ -305,11 +314,51 @@ export class PicoRemote {
305314
306315 handleEvent ( response : Response ) : void {
307316 const evt = ( response . Body ! as OneButtonStatusEvent ) . ButtonStatus
317+ // Look up ButtonNumber from href
318+ const buttonHref = evt . Button . href
319+ const buttonNumber = this . hrefToButtonNumber . get ( buttonHref )
320+ // Emit Matter cluster events for LevelControl and Scenes clusters if present
321+ if ( this . matterApi && ( this . accessory as any ) . clusters && buttonNumber !== undefined ) {
322+ const dentry = BUTTON_MAP . get ( this . accessory . context . device . DeviceType )
323+ if ( dentry ) {
324+ const alias = dentry . get ( buttonNumber )
325+ if ( alias ) {
326+ // LevelControl: Raise/Lower
327+ if ( alias . label . toLowerCase ( ) === 'raise' ) {
328+ this . matterApi . emitClusterEvent ( this . accessory , 'levelControl' , 'raise' )
329+ } else if ( alias . label . toLowerCase ( ) === 'lower' ) {
330+ this . matterApi . emitClusterEvent ( this . accessory , 'levelControl' , 'lower' )
331+ }
332+ // Scenes: Button 1-4
333+ if ( alias . label . toLowerCase ( ) . startsWith ( 'button ' ) ) {
334+ const sceneNum = Number . parseInt ( alias . label . split ( ' ' ) [ 1 ] , 10 )
335+ if ( ! Number . isNaN ( sceneNum ) ) {
336+ this . matterApi . emitClusterEvent ( this . accessory , 'scenes' , 'recallScene' , sceneNum )
337+ }
338+ }
339+ }
340+ }
341+ }
308342 const fullName = this . accessory . context . device . FullyQualifiedName . join ( ' ' )
309343 this . platform . log . info (
310344 `Button ${ evt . Button . href } on Pico remote ${ fullName } got action ${ evt . ButtonEvent . EventType } ` ,
311345 )
312346 this . trackers . get ( evt . Button . href ) ! . update ( evt . ButtonEvent . EventType )
347+
348+ // Emit Matter cluster event for On/Off cluster if present
349+ if ( this . matterApi && ( this . accessory as any ) . clusters ?. onOff && buttonNumber !== undefined ) {
350+ const dentry = BUTTON_MAP . get ( this . accessory . context . device . DeviceType )
351+ if ( dentry ) {
352+ const alias = dentry . get ( buttonNumber )
353+ if ( alias ) {
354+ if ( alias . label . toLowerCase ( ) === 'on' ) {
355+ this . matterApi . emitClusterEvent ( this . accessory , 'onOff' , 'on' )
356+ } else if ( alias . label . toLowerCase ( ) === 'off' ) {
357+ this . matterApi . emitClusterEvent ( this . accessory , 'onOff' , 'off' )
358+ }
359+ }
360+ }
361+ }
313362 }
314363
315364 handleUnsolicited ( response : Response ) : void {
@@ -321,4 +370,36 @@ export class PicoRemote {
321370 }
322371 }
323372 }
373+
374+ /**
375+ * Returns a Matter clusters object for this Pico remote, based on its button map.
376+ */
377+ public getMatterClusters ( ) : Record < string , any > {
378+ const type = this . accessory . context . device . DeviceType
379+ const dentry = BUTTON_MAP . get ( type )
380+ if ( ! dentry ) {
381+ return { }
382+ }
383+ // Gather all button labels for this remote
384+ const buttonLabels = Array . from ( dentry . values ( ) ) . map ( v => v . label . toLowerCase ( ) )
385+ const clusters : Record < string , any > = { }
386+ // On/Off cluster for remotes with On/Off buttons
387+ if ( buttonLabels . includes ( 'on' ) && buttonLabels . includes ( 'off' ) ) {
388+ clusters . onOff = { onOff : false }
389+ }
390+ // LevelControl cluster for Raise/Lower
391+ if ( buttonLabels . includes ( 'raise' ) && buttonLabels . includes ( 'lower' ) ) {
392+ clusters . levelControl = { currentLevel : 0 , minLevel : 0 , maxLevel : 254 }
393+ }
394+ // Scenes cluster for 4-button scene/zone remotes
395+ if ( type . includes ( '4ButtonScene' ) || type . includes ( '4ButtonZone' ) ) {
396+ clusters . scenes = { sceneCount : 4 }
397+ }
398+ // 4Button2Group: treat as two on/off pairs
399+ if ( type . includes ( '4Button2Group' ) ) {
400+ clusters . onOff = { onOff : false }
401+ clusters . onOff2 = { onOff : false }
402+ }
403+ return clusters
404+ }
324405}
0 commit comments