@@ -266,6 +266,9 @@ export class IntelliCenterBoard extends SystemBoard {
266266
267267 }
268268 private _configQueue : IntelliCenterConfigQueue = new IntelliCenterConfigQueue ( ) ;
269+ private _announceDeviceInterval ?: NodeJS . Timeout ;
270+ private _announceDeviceTickInFlight : boolean = false ;
271+ private _announceDeviceLastSentMs : number = 0 ;
269272 public system : IntelliCenterSystemCommands = new IntelliCenterSystemCommands ( this ) ;
270273 public circuits : IntelliCenterCircuitCommands = new IntelliCenterCircuitCommands ( this ) ;
271274 public features : IntelliCenterFeatureCommands = new IntelliCenterFeatureCommands ( this ) ;
@@ -284,17 +287,41 @@ export class IntelliCenterBoard extends SystemBoard {
284287 console . log ( 'RESETTING THE CONFIGURATION' ) ;
285288 this . modulesAcquired = false ;
286289 }
290+ private startAnnounceDeviceInterval ( ) : void {
291+ // v3-only: Wireless remote appears to periodically re-announce itself (captures show ~5-10s).
292+ // For now we emit Action 251 every 5s while the board is running.
293+ if ( ! sys . equipment . isIntellicenterV3 ) return ;
294+ if ( this . _announceDeviceInterval ) return ;
295+
296+ this . _announceDeviceInterval = setInterval ( async ( ) => {
297+ if ( this . _announceDeviceTickInFlight ) return ;
298+ this . _announceDeviceTickInFlight = true ;
299+ try {
300+ // Avoid spamming if the event loop stalls and intervals bunch up.
301+ const now = Date . now ( ) ;
302+ if ( now - this . _announceDeviceLastSentMs >= 4500 ) {
303+ await this . announceDevice ( ) ;
304+ this . _announceDeviceLastSentMs = now ;
305+ }
306+ } catch ( err ) {
307+ logger . warn ( `announceDevice interval tick failed: ${ err ?. message || err } ` ) ;
308+ } finally {
309+ this . _announceDeviceTickInFlight = false ;
310+ }
311+ } , 5000 ) ;
312+ }
313+ private stopAnnounceDeviceInterval ( ) : void {
314+ if ( this . _announceDeviceInterval ) {
315+ clearInterval ( this . _announceDeviceInterval ) ;
316+ this . _announceDeviceInterval = undefined ;
317+ }
318+ this . _announceDeviceTickInFlight = false ;
319+ this . _announceDeviceLastSentMs = 0 ;
320+ }
287321 public async announceDevice ( ) : Promise < void > {
288322 // v3.004 requires device registration via Action 251→253 before responding to config requests
289323 // In mock mode we still want to "send" the packet so it is logged/emitted like a real write.
290-
291- // Check registration status from persistent state (from Action 217 responses)
292- // status: 0=unknown, 1=registered, 4=failed
293- if ( state . equipment . registration === 1 ) {
294- logger . silly ( 'Already registered with IntelliCenter v3.004 (status=1), skipping duplicate registration' ) ;
295- return ;
296- }
297-
324+
298325 logger . info ( 'Announcing device to IntelliCenter v3.004...' ) ;
299326 // Action 251 payload structure (22 bytes total) verified from wireless remote cradle reset:
300327 // [0]: Device address (33 for njsPC)
@@ -398,6 +425,7 @@ export class IntelliCenterBoard extends SystemBoard {
398425 }
399426 }
400427 public async stopAsync ( ) {
428+ this . stopAnnounceDeviceInterval ( ) ;
401429 this . _configQueue . close ( ) ;
402430 return super . stopAsync ( ) ;
403431 }
@@ -502,6 +530,8 @@ export class IntelliCenterBoard extends SystemBoard {
502530 // Defer to the next tick so that any state extracted from the same inbound packet
503531 // (e.g., firmware bytes from Action 204) is available before we decide v1 vs v3 behavior.
504532 setTimeout ( ( ) => this . checkConfiguration ( ) , 0 ) ;
533+ // Start v3 announce loop once we're initialized/running.
534+ this . startAnnounceDeviceInterval ( ) ;
505535 }
506536 public processMasterModules ( modules : ExpansionModuleCollection , ocpA : number , ocpB : number , inv ?) {
507537 // Map the expansion panels to their specific types through the valuemaps. Sadly this means that
@@ -3831,6 +3861,22 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
38313861 else if ( ultratempInstalled ) sys . board . valueMaps . heatModes . merge ( [ [ 5 , { name : 'ultratemp' , desc : 'UltraTemp' } ] ] ) ;
38323862 if ( heatPumpInstalled && htypes . total > 1 ) sys . board . valueMaps . heatModes . merge ( [ [ 9 , { name : 'heatpump' , desc : 'Heatpump Only' } ] , [ 25 , { name : 'heatpumppref' , desc : 'Heat Pump Preferred' } ] ] ) ;
38333863 else if ( heatPumpInstalled ) sys . board . valueMaps . heatModes . merge ( [ [ 9 , { name : 'heatpump' , desc : 'Heat Pump' } ] ] ) ;
3864+
3865+ // IntelliCenter v3.004+: "preferred" heat mode/source codes (e.g. 4=solarpref, 6=ultratemppref) appear to be
3866+ // displayed/handled inconsistently across Pentair clients (dashPanel vs Wireless/Outdoor Panel).
3867+ // Keep the numeric codes intact (so encode/transformByName remain stable), but align the *descriptions* with
3868+ // what Wireless/OP actually show (i.e., treat preferred as equivalent to the base mode for UI display).
3869+ if ( sys . equipment . isIntellicenterV3 ) {
3870+ const hs4 = sys . board . valueMaps . heatSources . get ( 4 ) ;
3871+ if ( typeof hs4 !== 'undefined' ) sys . board . valueMaps . heatSources . set ( 4 , { ...hs4 , desc : 'Solar Only' } ) ;
3872+ const hs6 = sys . board . valueMaps . heatSources . get ( 6 ) ;
3873+ if ( typeof hs6 !== 'undefined' ) sys . board . valueMaps . heatSources . set ( 6 , { ...hs6 , desc : 'UltraTemp Only' } ) ;
3874+
3875+ const hm4 = sys . board . valueMaps . heatModes . get ( 4 ) ;
3876+ if ( typeof hm4 !== 'undefined' ) sys . board . valueMaps . heatModes . set ( 4 , { ...hm4 , desc : 'Solar Only' } ) ;
3877+ const hm6 = sys . board . valueMaps . heatModes . get ( 6 ) ;
3878+ if ( typeof hm6 !== 'undefined' ) sys . board . valueMaps . heatModes . set ( 6 , { ...hm6 , desc : 'UltraTemp Only' } ) ;
3879+ }
38343880 }
38353881 else {
38363882 sys . board . valueMaps . heatSources = new byteValueMap ( [ [ 0 , { name : 'off' , desc : 'Off' } ] ] ) ;
0 commit comments