Skip to content

Commit 8609aab

Browse files
Implement device announcement interval for IntelliCenter v3 in IntelliCenterBoard, enhancing device registration handling. Introduce methods to start and stop the announcement interval, ensuring efficient state updates. Update heat source and mode handling to suppress preferred options in UI for v3, aligning with controller behavior. #1090
1 parent 02968ee commit 8609aab

2 files changed

Lines changed: 66 additions & 14 deletions

File tree

controller/boards/IntelliCenterBoard.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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' }]]);

controller/boards/SystemBoard.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,24 +1787,28 @@ export class BodyCommands extends BoardCommands {
17871787
public getHeatSources(bodyId: number) {
17881788
let heatSources = [];
17891789
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
1790+
// IntelliCenter v3.004+: "preferred" heat sources (e.g. Solar Preferred / UltraTemp Pref) appear to be
1791+
// firmware-phantom options: dashPanel may show them, but Wireless/Outdoor Panel do not list/track them reliably.
1792+
// To keep UI options aligned with controller UX, suppress preferred modes for IntelliCenter v3.
1793+
const suppressPreferred = (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3);
17901794
heatSources.push(this.board.valueMaps.heatSources.transformByName('nochange'));
17911795
if (heatTypes.total > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('off'));
17921796
if (heatTypes.gas > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('heater'));
17931797
if (heatTypes.mastertemp > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('mastertemp'));
17941798
if (heatTypes.solar > 0) {
17951799
let hm = this.board.valueMaps.heatSources.transformByName('solar');
17961800
heatSources.push(hm);
1797-
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('solarpref'));
1801+
if (!suppressPreferred && heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('solarpref'));
17981802
}
17991803
if (heatTypes.heatpump > 0) {
18001804
let hm = this.board.valueMaps.heatSources.transformByName('heatpump');
18011805
heatSources.push(hm);
1802-
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('heatpumppref'));
1806+
if (!suppressPreferred && heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('heatpumppref'));
18031807
}
18041808
if (heatTypes.ultratemp > 0) {
18051809
let hm = this.board.valueMaps.heatSources.transformByName('ultratemp');
18061810
heatSources.push(hm);
1807-
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('ultratemppref'));
1811+
if (!suppressPreferred && heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('ultratemppref'));
18081812
}
18091813
if (heatTypes.hybrid > 0) {
18101814
heatSources.push(this.board.valueMaps.heatSources.transformByName('hybheat'));
@@ -1817,6 +1821,8 @@ export class BodyCommands extends BoardCommands {
18171821
public getHeatModes(bodyId: number) {
18181822
let heatModes = [];
18191823
sys.board.heaters.updateHeaterServices();
1824+
// IntelliCenter v3.004+: suppress preferred modes in UI lists; see note in getHeatSources().
1825+
const suppressPreferred = (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3);
18201826

18211827
// RKS: 09-26-20 This will need to be overloaded in IntelliCenterBoard when the other heater types are identified. (e.g. ultratemp, hybrid, maxetherm, and mastertemp)
18221828
heatModes.push(this.board.valueMaps.heatModes.transformByName('off')); // In IC fw 1.047 off is no longer 0.
@@ -1837,17 +1843,17 @@ export class BodyCommands extends BoardCommands {
18371843
if (heatTypes.solar > 0) {
18381844
let hm = this.board.valueMaps.heatModes.transformByName('solar');
18391845
heatModes.push(hm);
1840-
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('solarpref'));
1846+
if (!suppressPreferred && heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('solarpref'));
18411847
}
18421848
if (heatTypes.heatpump > 0) {
18431849
let hm = this.board.valueMaps.heatModes.transformByName('heatpump');
18441850
heatModes.push(hm);
1845-
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('heatpumppref'));
1851+
if (!suppressPreferred && heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('heatpumppref'));
18461852
}
18471853
if (heatTypes.ultratemp > 0) {
18481854
let hm = this.board.valueMaps.heatModes.transformByName('ultratemp');
18491855
heatModes.push(hm);
1850-
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('ultratemppref'));
1856+
if (!suppressPreferred && heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('ultratemppref'));
18511857
}
18521858
return heatModes;
18531859
}

0 commit comments

Comments
 (0)