nodejs-poolcontroller 8.3.0 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.eslintrc.json +36 -36
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/copilot-instructions.md +63 -63
  7. package/.github/workflows/ghcr-publish.yml +67 -67
  8. package/AGENTS.md +597 -0
  9. package/CONTRIBUTING.md +74 -74
  10. package/Changelog +292 -284
  11. package/Dockerfile +62 -62
  12. package/Gruntfile.js +40 -40
  13. package/LICENSE +661 -661
  14. package/README.md +318 -309
  15. package/anslq25/MessagesMock.ts +221 -221
  16. package/anslq25/boards/MockBoardFactory.ts +49 -49
  17. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  18. package/anslq25/boards/MockSystemBoard.ts +216 -216
  19. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  20. package/anslq25/pumps/MockPump.ts +83 -83
  21. package/app.ts +115 -115
  22. package/config/Config.ts +0 -0
  23. package/config/VersionCheck.ts +0 -0
  24. package/controller/Constants.ts +809 -805
  25. package/controller/Equipment.ts +2688 -2664
  26. package/controller/Errors.ts +181 -181
  27. package/controller/Lockouts.ts +549 -549
  28. package/controller/State.ts +3738 -3701
  29. package/controller/boards/AquaLinkBoard.ts +1003 -1003
  30. package/controller/boards/BoardFactory.ts +53 -53
  31. package/controller/boards/EasyTouchBoard.ts +3202 -3202
  32. package/controller/boards/IntelliCenterBoard.ts +4393 -3899
  33. package/controller/boards/IntelliComBoard.ts +69 -69
  34. package/controller/boards/IntelliTouchBoard.ts +382 -382
  35. package/controller/boards/NixieBoard.ts +1944 -1944
  36. package/controller/boards/SunTouchBoard.ts +400 -400
  37. package/controller/boards/SystemBoard.ts +5268 -5268
  38. package/controller/comms/Comms.ts +1272 -1255
  39. package/controller/comms/ScreenLogic.ts +1665 -1665
  40. package/controller/comms/messages/Messages.ts +1433 -1406
  41. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  42. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  43. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  44. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  45. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  46. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  47. package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
  48. package/controller/comms/messages/config/ExternalMessage.ts +96 -10
  49. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  50. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  51. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  52. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  53. package/controller/comms/messages/config/OptionsMessage.ts +194 -174
  54. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  55. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  56. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  57. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  58. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  59. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  60. package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
  61. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
  62. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  63. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  64. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  65. package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
  66. package/controller/comms/messages/status/VersionMessage.ts +103 -41
  67. package/controller/nixie/Nixie.ts +173 -173
  68. package/controller/nixie/NixieEquipment.ts +104 -104
  69. package/controller/nixie/bodies/Body.ts +120 -120
  70. package/controller/nixie/bodies/Filter.ts +135 -135
  71. package/controller/nixie/chemistry/ChemController.ts +2724 -2724
  72. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  73. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  74. package/controller/nixie/circuits/Circuit.ts +478 -478
  75. package/controller/nixie/heaters/Heater.ts +834 -834
  76. package/controller/nixie/pumps/Pump.ts +1193 -1193
  77. package/controller/nixie/schedules/Schedule.ts +401 -401
  78. package/controller/nixie/valves/Valve.ts +170 -170
  79. package/defaultConfig.json +352 -352
  80. package/docker-compose.yml +31 -31
  81. package/logger/DataLogger.ts +448 -448
  82. package/logger/Logger.ts +448 -436
  83. package/package.json +58 -58
  84. package/sendSocket.js +32 -32
  85. package/tsconfig.json +25 -25
  86. package/types/express-multer.d.ts +32 -32
  87. package/web/Server.ts +1937 -1927
  88. package/web/bindings/aqualinkD.json +559 -559
  89. package/web/bindings/influxDB.json +1066 -1066
  90. package/web/bindings/mqtt.json +721 -721
  91. package/web/bindings/mqttAlt.json +746 -746
  92. package/web/bindings/rulesManager.json +54 -54
  93. package/web/bindings/smartThings-Hubitat.json +31 -31
  94. package/web/bindings/valveRelays.json +20 -20
  95. package/web/bindings/vera.json +25 -25
  96. package/web/interfaces/baseInterface.ts +188 -188
  97. package/web/interfaces/httpInterface.ts +148 -148
  98. package/web/interfaces/influxInterface.ts +283 -283
  99. package/web/interfaces/mqttInterface.ts +695 -695
  100. package/web/interfaces/ruleInterface.ts +101 -87
  101. package/web/services/config/Config.ts +1063 -1053
  102. package/web/services/config/ConfigSocket.ts +0 -0
  103. package/web/services/state/State.ts +0 -0
  104. package/web/services/state/StateSocket.ts +0 -0
  105. package/web/services/utilities/Utilities.ts +233 -233
package/AGENTS.md ADDED
@@ -0,0 +1,597 @@
1
+ # AGENTS.md - Lessons Learned & Guidelines
2
+
3
+ **Purpose:** Document patterns, corrections, and lessons learned during development to improve future interactions.
4
+
5
+ ## ⚠️ FIRST: Check .plan/ Directory
6
+
7
+ **Before debugging protocol/packet issues:**
8
+ 1. Read `.plan/INDEX.md` - Master index for protocol documentation (links to controller-specific indexes)
9
+ 2. Check equipment-specific protocol files (e.g., `.plan/201-intellicenter-circuits-features.md`, `.plan/202-intellicenter-bodies-temps.md`)
10
+ 3. Review `.plan/203-intellicenter-action-registry.md` for action code meanings
11
+
12
+ **Protocol documentation is in `.plan/`, NOT in this file.** This file contains coding patterns and lessons learned.
13
+
14
+ ---
15
+
16
+ ## Architecture Patterns (CRITICAL)
17
+
18
+ ### 0. State Access Pattern: Use Accessors, Not Raw JSON
19
+ **Rule:** Do NOT set/get nested state JSON objects directly unless the accessor is explicitly designed to work with an object.
20
+ - Most state fields are **persisted as JSON**, but the public API should be **scalar getters/setters** (number/string/boolean) that:
21
+ - normalize persisted shapes
22
+ - apply transforms/valueMaps
23
+ - manage `hasChanged` / `state.dirty` correctly
24
+ - Only use object-level access when there is an **explicit object accessor** whose contract is “you pass an object” (and it manages diffing/emits).
25
+
26
+ **Examples**
27
+ - **Scalar (preferred)**:
28
+ - Set: `state.status = 0`
29
+ - Get: `if (state.status === 0) { /* ... */ }`
30
+ - **Explicit object accessor (allowed)**:
31
+ - Set: `state.temps.bodies.getItemById(1).heaterOptions = { total: 2, gas: 1, heatpump: 1 }`
32
+ - Get: `const opts = state.temps.bodies.getItemById(1).heaterOptions`
33
+
34
+ ### 1. Message Processing Location
35
+ **Rule:** NO direct message processing logic in `Messages.ts`
36
+ - `Messages.ts` is a ROUTER only
37
+ - Delegate to appropriate message files:
38
+ - Status messages → `EquipmentStateMessage.ts`
39
+ - Config messages → `ConfigMessage.ts`, `ExternalMessage.ts`, etc.
40
+
41
+ **Violated in this chat:**
42
+ - Action 217 handler with logic directly in Messages.ts (lines 792-810)
43
+ - Should be in `EquipmentStateMessage.ts` or similar
44
+
45
+ **Correction:** ✅ FIXED - Action 217 now delegated to EquipmentStateMessage.ts
46
+
47
+ ### 2. State Persistence
48
+ **Rule:** NO local class variables for cross-session state
49
+ - Ephemeral state (current session only) → Local variables OK
50
+ - Persistent state (survives restarts) → Must use:
51
+ - `poolConfig.json` (configuration data)
52
+ - `poolState.json` (runtime state)
53
+ - With appropriate Equipment/State class methods
54
+
55
+ **Violated in this chat:**
56
+ - `_hasRegistered` and `_registrationConfirmed` as class variables
57
+ - These need to survive restarts if OCP remembers registration
58
+
59
+ **Correction:** ✅ FIXED - Now using `state.equipment.registration` in poolState.json
60
+
61
+ ### 3. Equipment State vs Top-Level State
62
+ **Rule:** Equipment-related state belongs in `EquipmentState`, not top-level `State`
63
+ - Follow existing pattern: properties like `controllerType`, `model`, `maxCircuits` are in `EquipmentState`
64
+ - Equipment-related state → `state.equipment.propertyName`
65
+ - Top-level state is for collections (pumps, circuits, etc.) or system-wide state (temps, heliotrope)
66
+
67
+ **Violated in this chat:**
68
+ - Initially added `registration` property to top-level `State` class
69
+ - Should be in `EquipmentState` since it's controller registration state
70
+
71
+ **Correction:** ✅ FIXED - Moved to `state.equipment.registration` with getter/setter in EquipmentState
72
+
73
+ ### 4. Protocol Values vs Invented States
74
+ **Rule:** Only use values that come from the actual protocol
75
+ - Don't invent intermediate states that aren't in the protocol
76
+ - State should reflect what OCP reports, not our internal workflow
77
+ - Example: Action 217 byte[2] has values 0, 1, 4 - don't add status=5 for "requested"
78
+
79
+ **Violated in this chat:**
80
+ - Initially added status=5 for "requested" state
81
+ - This value doesn't exist in the protocol, only 0/1/4 come from OCP
82
+
83
+ **Correction:** ✅ FIXED - Removed status=5, only track OCP's response (0/1/4)
84
+
85
+ ### 5. Addressing Confusion
86
+ **Rule:** Always verify address mappings before implementing
87
+ - 15 = Broadcast
88
+ - 16 = OCP
89
+ - Got this backwards initially, causing confusion
90
+
91
+ **Lesson:** When dealing with protocol constants, explicitly verify from working examples first
92
+
93
+ ### 6. Board Access Pattern (CRITICAL)
94
+ **Rule:** Never “rebind” / “re-initialize” the singleton board instance to a local variable (e.g., `const board = sys.board as IntelliCenterBoard`)
95
+ - Boards are initialized once at app startup
96
+ - Access the board via `sys.board` directly
97
+ - Persistent config/state should be accessed via `sys` (poolConfig.json) and `state` (poolState.json), not via cached board references
98
+
99
+ **Violated in this chat:**
100
+ - `EquipmentStateMessage.ts` used `const board = sys.board as IntelliCenterBoard` inside message handling
101
+
102
+ **Correction:** ✅ FIXED - Use `sys.board` directly without local rebinding
103
+
104
+ ### 7. Controller-Specific Overrides (CRITICAL): SystemBoard.ts must be controller-agnostic
105
+ **Rule:** Do NOT add controller-specific branching (e.g., `if (sys.controllerType === ControllerType.IntelliCenter) ...`, `if (sys.equipment.isIntellicenterV3) ...`) inside shared base command classes in `controller/boards/SystemBoard.ts`.
106
+
107
+ - **Why:** The default `byteValueMaps` in `SystemBoard.ts` include generic/*Touch-style defaults (e.g., `heatModes`) which are **wrong for IntelliCenter v3.004+**.
108
+ - **Correct pattern:** Each controller board overrides the relevant command object (e.g., `IntelliCenterBoard` uses `IntelliCenterHeaterCommands extends HeaterCommands`) and implements controller-accurate behavior there.
109
+ - **Call-site rule:** Always call `sys.board.heaters.updateHeaterServices()` (or the appropriate `sys.board.<domain>` command), and rely on polymorphism to dispatch to the controller-specific implementation.
110
+
111
+ **Bug example (dashPanel heat mode labels):**
112
+ - `/config/options/heaters` returns `sys.board.valueMaps.heatModes.toArray()`
113
+ - If IntelliCenter-specific heater service mapping isn’t used, the UI can map numeric heatMode values to the wrong names (e.g., mapping `5` to “Solar Only” instead of “UltraTemp Only” on IC v3).
114
+
115
+ **Fix pattern:**
116
+ - Put IntelliCenter mapping in `IntelliCenterHeaterCommands.updateHeaterServices()` (already present in `controller/boards/IntelliCenterBoard.ts`)
117
+ - Keep `HeaterCommands.updateHeaterServices()` in `SystemBoard.ts` generic for non-IntelliCenter controllers
118
+
119
+ **Same pattern for body picklists:**
120
+ - Keep `BodyCommands.getHeatSources()` / `getHeatModes()` in `SystemBoard.ts` generic.
121
+ - Any IntelliCenter v3-specific filtering (e.g., suppressing `solarpref` / `ultratemppref` options for body-level mode picklists) must live in `IntelliCenterBodyCommands` in `controller/boards/IntelliCenterBoard.ts`.
122
+
123
+ **Generalization:** This applies to *anything* that is overridden by controller boards (IntelliCenter, EasyTouch, IntelliTouch, AquaLink, SunTouch, Nixie, etc.).
124
+ If you find yourself writing `if (sys.controllerType === ...)` inside `SystemBoard.ts`, that is almost always a design smell—create/extend the controller-specific command class in the appropriate board file and keep `SystemBoard.ts` purely generic.
125
+
126
+ ## Analysis Patterns
127
+
128
+ ### 6. Examine Working State, Not Just Failures
129
+ **Rule:** When debugging, look for WORKING periods in captures
130
+ - User insight: "Packet #576 shows byte[2]=1 (REGISTERED)"
131
+ - This means njsPC WAS working at that point
132
+ - Should analyze: What changed between #576 (working) and #999 (failed)?
133
+ - Should check: Are config requests working between #576 and #999?
134
+
135
+ **Violated in this chat:**
136
+ - Focused on failure at packet #999
137
+ - Didn't analyze the working period from #576 to #999
138
+ - Missed opportunity to see if config WAS loading successfully
139
+
140
+ **Correction needed:** Analyze packets #576-#999 to see if config requests worked
141
+
142
+ ### 7. Source of Truth Identification
143
+ **Rule:** Identify authoritative data sources in protocol
144
+ - User insight: "Action 217 is the source of truth"
145
+ - Should have recognized this earlier from captures
146
+
147
+ **Lesson:** When multiple messages contain similar data, identify which is authoritative
148
+
149
+ ### 8. Temperature Debugging: Use Time-Series Sources, Not `poolState.json`
150
+ **Rule:** When debugging temperature-driven behavior (solar/heater thresholds), do NOT rely on `poolState.json` for trends.
151
+ - `poolState.json` represents the **last/current** state snapshot, not a time series.
152
+ - To understand when thresholds were crossed, extract temps from **packet logs** (and/or other raw-sample logs that capture temps over time).
153
+ - Use the time-series to correlate: **AIR/WATER/SOLAR temps** ↔ **heater/valve commands** ↔ **triggering events**.
154
+
155
+ ## Communication Patterns
156
+
157
+ ### 8. Documentation Organization
158
+ **Rule:** ONE markdown file per topic in `.plan/` directory
159
+ - Before creating new `.plan/*.md` file, check if topic already covered
160
+ - Update existing file rather than creating duplicate
161
+ - If splitting needed, cross-reference between files
162
+ - Periodically review and delete outdated/superseded files
163
+ - **When completing work:** Update the EXISTING plan file with status, don't create new "_COMPLETE" files
164
+
165
+ **Violated in this chat (Dec 10, 2025):**
166
+ - Created `V3_ARCHITECTURE_FIX_COMPLETE.md` immediately after establishing "one file per topic" rule
167
+ - Should have updated existing `V3_REGISTRATION_FIX_PLAN.md` instead
168
+ - User feedback: "stop creating new .md files in .plan. Consolidate with the latest known info. follow the new rules in agent.md"
169
+
170
+ **Correction:** ✅ FIXED - Deleted duplicate file, updated existing plan file with completion status
171
+
172
+ **Hierarchy:**
173
+ 1. **Master docs** (keep these):
174
+ - `200-intellicenter-v3-index.md` - Main implementation guide
175
+ - `204-intellicenter-v3-protocol.md` - Complete protocol reference / findings summary
176
+ - `203-intellicenter-action-registry.md` - Action code reference
177
+
178
+ 2. **Active work** (current tasks):
179
+ - `V3_REGISTRATION_FIX_PLAN.md` - Current work plan
180
+
181
+ 3. **Key insights** (important discoveries):
182
+ - `V3_ACTION_217_DISCOVERY.md` - Action 217 as source of truth
183
+
184
+ 4. **Reference** (technical analysis):
185
+ - `ACTION_204_V1_VS_V3_ANALYSIS.md` - Module detection
186
+
187
+ **Delete:** Temporary files, superseded analysis, old troubleshooting
188
+
189
+ ### 9. Meta-Rule: Ask About Documentation
190
+ **Rule:** When user provides correction or clarification, ASK:
191
+ > "Should I add this to AGENTS.md for future reference?"
192
+
193
+ This creates a feedback loop for continuous improvement.
194
+
195
+ ### 10. Validate Assumptions Before Implementation
196
+ **Rule:** When making architectural changes, state assumptions and ask for validation
197
+ - Example: "I'm planning to use local variables for this. Is that appropriate?"
198
+ - Don't assume patterns - verify first
199
+
200
+ ### 10.1 UI Error Attribution (dashPanel)
201
+ **Rule:** Treat dashPanel errors as **UI-initiated command failures only**.
202
+ - dashPanel only shows errors for actions a user initiates from dashPanel (e.g., a “turn on/off” click).
203
+ - It will NOT surface retries/timeouts from njsPC background flows (config polling, heartbeats, etc.).
204
+ - Therefore: absence of dashPanel errors is **not evidence** that the underlying protocol traffic is healthy; always confirm via packet logs and njsPC logs.
205
+
206
+ ### 10.2 IntelliCenter v3 Source-of-Truth: Do NOT Process Wireless→OCP Requests
207
+ **Rule:** For IntelliCenter v3 (v3.004+), the **OCP (addr 16)** is the **only source of truth** for state/config changes.
208
+
209
+ - **Do not process** packets that are **Wireless/ICP/Indoor → OCP** (e.g. src=36 dest=16) as if they were authoritative.
210
+ - These are **requests**; the OCP may ACK or ignore them, and the final state can differ.
211
+ - Only update state/config from **OCP-originated** messages (source=16), and preferably from the established authoritative message types:
212
+ - Config/state broadcasts/responses (e.g., Action 30 / Action 204 / other status broadcasts)
213
+ - NOT from third-party device requests
214
+
215
+ **Implementation note:** In `Messages.ts`, gate IntelliCenter Action 168 handling to ignore non-OCP sources (especially src!=16 dest==16).
216
+
217
+ ### 11. Modifying Existing Rules
218
+ **Rule:** Never modify, remove, or contradict existing rules in AGENTS.md without explicit user approval
219
+ - When a situation arises that conflicts with an existing rule, STOP and ask the user
220
+ - Present: "Existing rule X says Y, but I think Z. Which should I follow?"
221
+ - Let user decide whether to update the rule, create an exception, or follow existing rule
222
+ - Document the resolution in AGENTS.md
223
+
224
+ **Rationale:** Rules in AGENTS.md represent learned patterns from user feedback. Changing them without approval risks losing important context.
225
+
226
+ ## Protocol Discovery Patterns
227
+
228
+ ### 12. Hardware Protocol Work Requires User Collaboration
229
+ **Rule:** Don't guess protocol behavior - ask user to test
230
+ - We can't test with actual hardware
231
+ - User has the hardware and can capture real behavior
232
+ - Iterate based on real captures, not assumptions
233
+
234
+ **This chat did well:** Multiple capture iterations with user feedback
235
+
236
+ ### 13. Incremental Understanding
237
+ **Rule:** Protocol understanding evolves through captures
238
+ - Initial understanding: "Action 179 is proactive heartbeat" ✗
239
+ - Corrected: "Action 179 is request/response from OCP" ✓
240
+ - User observation: "Wireless sends Action 180 to OCP, not broadcast"
241
+
242
+ **Lesson:** Be ready to revise understanding as new evidence emerges
243
+
244
+ ## Current Issues to Address
245
+
246
+ ### Issue 1: Action 217 Handler Location ✅ RESOLVED
247
+ - **Problem:** Logic in Messages.ts
248
+ - **Solution:** Moved to EquipmentStateMessage.ts
249
+ - **Status:** Complete
250
+
251
+ ### Issue 2: Registration State Storage ✅ RESOLVED
252
+ - **Problem:** Using local class variables
253
+ - **Solution:** Stored in poolState.json via state.equipment.registration
254
+ - **Status:** Complete
255
+
256
+ ### Issue 3: Duplicate Registration Flags ✅ RESOLVED
257
+ - **Problem:** Two flags when one suffices
258
+ - **Solution:** Single state.equipment.registration status (values: 0/1/4) exposed via accessor (persisted JSON is internal)
259
+ - **Status:** Complete
260
+
261
+ ### Issue 4: Registration Status != Config Working (CRITICAL)
262
+ - **Problem:** byte[2]=1 (registered) doesn't mean config is loading
263
+ - **Evidence:** Packets #576-#999 showed byte[2]=1, heartbeat working, but ZERO Action 30 responses
264
+ - **Analysis:**
265
+ - OCP acknowledged njsPC (Action 179/180 heartbeat working)
266
+ - njsPC sent 462 Action 222 config requests
267
+ - OCP responded to NONE of them
268
+ - Registration alone is not sufficient for config loading
269
+ - **Question:** What else is required beyond registration?
270
+ - **Priority:** CRITICAL (blocks entire feature)
271
+
272
+ ## Questions for Future
273
+
274
+ 1. What triggers config responses beyond registration?
275
+ 2. Why does byte[2]=1 not enable config loading?
276
+ 3. Is there a timing window after registration?
277
+ 4. Does OCP need fresh registration each session (not remember from previous)?
278
+
279
+ ---
280
+
281
+ ## Quick Reference for Debugging
282
+
283
+ ### 14. Always Check .plan/ First
284
+ **Rule:** Before debugging IntelliCenter v3 issues, read:
285
+ - `.plan/INDEX.md` - Protocol documentation index (then IntelliCenter: `.plan/200-intellicenter-v3-index.md`)
286
+ - `.plan/203-intellicenter-action-registry.md` - Action code meanings
287
+ - `.plan/204-intellicenter-v3-protocol.md` - Protocol findings summary
288
+
289
+ ### 14.1 Packet Header DEST/SRC Order (CRITICAL)
290
+ **Rule:** The packet header is:
291
+ `[165, 1, DEST, SRC, ACTION, LEN]`
292
+
293
+ **Do NOT grep/read this backwards.** DEST comes before SRC in the header, for every packet.
294
+
295
+ **Examples:**
296
+ - **Wireless → OCP command**: `DEST=16`, `SRC=36` → header starts with **`[165, 1, 16, 36, ...]`**
297
+ - **OCP → Wireless ACK**: `DEST=36`, `SRC=16` → header starts with **`[165, 1, 36, 16, ...]`**
298
+
299
+ ### 15. Message Flow Architecture
300
+ **Packet → Messages.ts (router) → Handler Files:**
301
+
302
+ ```
303
+ ┌─────────────────┐
304
+ │ RS-485 Packet │
305
+ └────────┬────────┘
306
+
307
+ ┌─────────────────┐
308
+ │ Messages.ts │ ← ROUTER ONLY, no business logic!
309
+ │ (by action #) │
310
+ └────────┬────────┘
311
+
312
+ ┌─────────────────────────────────────────────────────────┐
313
+ │ Action 2/204 → EquipmentStateMessage.ts (status) │
314
+ │ Action 30 → ConfigMessage.ts → category handlers │
315
+ │ Action 168 → ExternalMessage.ts (wireless changes) │
316
+ │ Action 184 → EquipmentStateMessage.ts (v3 circuits) │
317
+ │ Action 217 → EquipmentStateMessage.ts (device list) │
318
+ │ Action 251/253→ Registration handshake │
319
+ │ Chlorinator → ChlorinatorStateMessage.ts │
320
+ │ Pump protocol → PumpStateMessage.ts │
321
+ └─────────────────────────────────────────────────────────┘
322
+ ```
323
+
324
+ **Config Handlers (Action 30, routed by payload byte[0]):**
325
+
326
+ | Category | Handler | Purpose |
327
+ |----------|---------|---------|
328
+ | 0 | OptionsMessage | System options |
329
+ | 1 | CircuitMessage | Circuit config |
330
+ | 2 | FeatureMessage | Feature config |
331
+ | 3 | ScheduleMessage | Schedule times/days |
332
+ | 4 | PumpMessage | Pump config |
333
+ | 5 | RemoteMessage | Remote buttons |
334
+ | 6 | CircuitGroupMessage | Light/circuit groups |
335
+ | 7 | ChlorinatorMessage | Chlorinator settings |
336
+ | 8 | IntellichemMessage | Chemistry controller |
337
+ | 9 | ValveMessage | Valve assignments |
338
+ | 10 | HeaterMessage | Heater config |
339
+ | 11 | SecurityMessage | PIN codes |
340
+ | 12 | GeneralMessage | Pool name, location |
341
+ | 13 | EquipmentMessage | Body/equipment config |
342
+ | 14 | CoverMessage | Pool covers |
343
+
344
+ **External Message Handlers (Action 168, routed by payload byte[0]):**
345
+
346
+ | Case | Handler Method | Purpose |
347
+ |------|----------------|---------|
348
+ | 0 | processTempSettings | Setpoints/heat mode |
349
+ | 1 | processCircuit | Circuit changes |
350
+ | 2 | processFeature | Feature changes |
351
+ | 3 | processSchedules | Schedule changes |
352
+ | 4 | processPump | Pump changes |
353
+ | 7 | processChlorinator | Chlorinator changes |
354
+ | 10 | processHeater | Heater changes |
355
+
356
+ ### 16. v3.004 Development Baseline (CRITICAL)
357
+ **Rule:** All v3.004+ IntelliCenter changes started on **November 26, 2025**. Everything in the codebase before that date was working correctly for v1.x.
358
+
359
+ **Implications:**
360
+ - When investigating v3 issues, only look at commits from 11/26/2025 onwards
361
+ - Any code that existed before 11/26/2025 should be assumed correct for v1.x
362
+ - v3 changes MUST be gated behind `sys.equipment.isIntellicenterV3` to avoid breaking v1
363
+ - Do NOT modify pre-11/26 code paths without explicit approval
364
+
365
+ **Git command to see v3 changes:**
366
+ ```bash
367
+ git log --oneline --since="2025-11-26" -- <file>
368
+ ```
369
+
370
+ ### 17. v3.004 Endianness Pattern (CRITICAL)
371
+ **Rule:** v3.004 uses BIG-ENDIAN for 16-bit time values; v1.x uses LITTLE-ENDIAN.
372
+
373
+ **Check:** Any `extractPayloadInt()` for schedule/time values needs v3 handling.
374
+
375
+ **Fix pattern:**
376
+ ```typescript
377
+ if (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3) {
378
+ time = msg.extractPayloadIntBE(offset); // Big-endian for v3.004+
379
+ } else {
380
+ time = msg.extractPayloadInt(offset); // Little-endian for v1.x
381
+ }
382
+ ```
383
+
384
+ **Already fixed:**
385
+ - `ScheduleMessage.processStartTimes()` - schedule start times
386
+ - `ScheduleMessage.processEndTimes()` - schedule end times
387
+ - `ExternalMessage.processSchedules()` - wireless schedule changes
388
+
389
+ **Check for similar issues in:** Any message handler parsing 16-bit time values.
390
+
391
+ ### 18. State Sync Pattern
392
+ **Rule:** When config properties are set, ALWAYS sync corresponding state properties.
393
+
394
+ **Bug pattern:** Config property set but state not synced → UI shows undefined/blank.
395
+
396
+ **Example (chlorinator name bug we fixed):**
397
+ ```typescript
398
+ // BAD - name set in config but never synced to state
399
+ chlor.name = "Chlorinator 1";
400
+ // schlor.name never set → dashPanel shows blank name!
401
+
402
+ // GOOD - always sync config properties to state
403
+ chlor.name = "Chlorinator 1";
404
+ schlor.name = chlor.name; // ← Don't forget this!
405
+ ```
406
+
407
+ **Checklist when reviewing config handlers:**
408
+ 1. Find all `chlor.property = value` assignments
409
+ 2. Verify each has corresponding `schlor.property = chlor.property`
410
+ 3. Check for default values when property might be undefined
411
+
412
+ ### 19. Finding Configuration Data for Debugging
413
+ **Rule:** Feature/circuit names and IDs are in configuration files, not guesswork.
414
+
415
+ **Locations:**
416
+ - Live system: `./data/poolConfig.json` and `./data/poolState.json`
417
+ - Replay captures: `[replayname]/njspc/data/poolConfig.json` and `poolState.json`
418
+
419
+ **Key ID ranges (IntelliCenter):**
420
+ - Circuits: 1-40 (varies by system)
421
+ - Features: Start at 129 (`sys.board.equipmentIds.features.start`)
422
+ - Circuit Groups: Start at 192 (`sys.board.equipmentIds.circuitGroups.start`)
423
+
424
+ **Example lookup:**
425
+ ```json
426
+ // From poolConfig.json
427
+ "features": [
428
+ { "id": 129, "name": "Test_Feature" },
429
+ { "id": 130, "name": "Air Blower222" },
430
+ { "id": 131, "name": "TestF2" }
431
+ ]
432
+ ```
433
+
434
+ ### 20. Packet Log Analysis
435
+ **Rule:** When analyzing packet captures, decode payloads systematically.
436
+
437
+ **CRITICAL: Always include packet IDs when discussing packets.** The `id` field in each packet log entry is essential for:
438
+ - Cross-referencing with user observations
439
+ - Correlating request/response pairs
440
+ - Debugging timing issues
441
+ - Reproducing specific scenarios
442
+
443
+ **Format for discussing packets:**
444
+ ```
445
+ #2605 23:09:49 Action30/15 byte9=3 features: ['129', '130'] <-- CONFIG RESPONSE
446
+ #2607 23:09:52 Action204 byte19=2 features: ['130'] <-- OVERWRITES!
447
+ ```
448
+ Always prefix with `#` and packet ID.
449
+
450
+ **Packet structure:** `[header][payload][checksum]`
451
+ - Header: `[165, 1, dest, src, action, length]`
452
+ - Action codes: See `.plan/203-intellicenter-action-registry.md`
453
+
454
+ **Common analysis steps:**
455
+ 1. Filter by action code: `grep "action\":168"` for external messages
456
+ 2. Decode payload bytes using handler code as reference
457
+ 3. Check timestamps for sequence/timing issues
458
+ 4. Look for request/response pairs (outbound `dir":"out"` → inbound `dir":"in"`)
459
+ 5. **Always extract and display packet IDs** in any analysis output
460
+
461
+ **Decoding times (v3.004 big-endian):**
462
+ - Two bytes `[hi, lo]` → `hi * 256 + lo` = minutes since midnight
463
+ - Example: `[2, 33]` → `2*256 + 33 = 545` → 9:05 AM
464
+
465
+ ### 21. v3.004+ Action 184 Circuit Control (CRITICAL)
466
+ **Rule:** v3.004+ uses Action 184 for circuit control, NOT Action 168.
467
+
468
+ **Problem:** When njsPC sends Action 168 to control circuits on v3.004+, OCP accepts it briefly then reverts the state. The Wireless remote uses Action 184, which OCP accepts permanently.
469
+
470
+ **Solution:**
471
+ - Each circuit has a unique `targetId` (16-bit value stored in poolConfig.json)
472
+ - njsPC learns Target IDs from OCP Action 184 broadcasts
473
+ - When controlling circuits, njsPC sends Action 184 with the learned Target ID
474
+
475
+ **Action 184 Payload (10 bytes):**
476
+ ```
477
+ [channelHi, channelLo, seq, format, targetHi, targetLo, state, 0, 0, 0]
478
+ ├─ Channel: 104,143 (default) or circuit-specific (e.g., 108,225 for Pool)
479
+ ├─ Target: Circuit's unique ID (e.g., 168,237 for Spa, 108,225 for Pool)
480
+ └─ State: 0=OFF, 1=ON
481
+ ```
482
+
483
+ **Known Target IDs (user's system):**
484
+ | Circuit | Target ID | Hex |
485
+ |---------|-----------|-----|
486
+ | Spa (circuit 1) | 168,237 | 0xA8ED |
487
+ | Pool (circuit 6) | 108,225 | 0x6CE1 |
488
+ | Body status | 212,182 | 0xD4B6 (not a circuit) |
489
+
490
+ **Learning strategies:**
491
+ 1. **Channel=Target pattern**: When bytes 0-1 equal bytes 4-5, identifies circuit
492
+ 2. **Unique state match**: When only one circuit matches broadcast state (ON/OFF)
493
+ 3. **State correlation**: Schedule/automation triggers state change → learn mapping
494
+
495
+ **Unknowns (to investigate):**
496
+ - How Target IDs are assigned (hardware serial? config order?)
497
+ - Whether Target IDs can change (suspected: stable)
498
+ - Full decode of body status (212,182) payload
499
+
500
+ **Code locations:**
501
+ - `Circuit.targetId` property: `controller/Equipment.ts`
502
+ - Learn from broadcasts: `EquipmentStateMessage.ts` (case 184)
503
+ - Send commands: `IntelliCenterBoard.createAction184Message()`
504
+ - Circuit control: `IntelliCenterBoard.setCircuitStateAsync()`
505
+
506
+ ### 22. v3.004+ Action 168 vs Action 30 Offset Difference (CRITICAL)
507
+ **Rule:** v3.004 Wireless Action 168 type 0 has DIFFERENT byte offsets than Action 30 type 0!
508
+
509
+ **Problem:** When parsing body setpoints/heat modes from Wireless remote messages (Action 168 type 0), using Action 30 offsets (19-24) produces garbage values.
510
+
511
+ **Root Cause:** The Wireless Action 168 payload has an extra byte in early positions, shifting all indices by +1.
512
+
513
+ **Correct Offsets:**
514
+ | Field | Action 30 | Action 168 |
515
+ |-------|-----------|------------|
516
+ | Pool setpoint | 19 | **20** |
517
+ | Pool cool | 20 | **21** |
518
+ | Spa setpoint | 21 | **22** |
519
+ | Spa cool | 22 | **23** |
520
+ | Pool mode | 23 | **24** |
521
+ | Spa mode | 24 | **25** |
522
+
523
+ **Code Location:** `ExternalMessage.ts` → `processTempSettings()` detects v3 and uses correct offsets.
524
+
525
+ **Verified from captures:** Replay 30 (5 packets), Replay 48 (multiple packets).
526
+
527
+ ### 23. Protocol Documentation Structure
528
+
529
+ **Rule:** Detailed packet/flow documentation lives in `.plan/` directory, organized by controller type and equipment type.
530
+
531
+ **ALWAYS read `.plan/INDEX.md` first** when working on protocol issues. It indexes all protocol documentation (including IntelliCenter: `.plan/200-intellicenter-v3-index.md`).
532
+
533
+ #### Protocol Files by Controller Type
534
+
535
+ **IntelliCenter:**
536
+ - `.plan/201-intellicenter-circuits-features.md` - Circuits, features, groups (current)
537
+ - `.plan/203-intellicenter-action-registry.md` - Action code reference
538
+ - `.plan/204-intellicenter-v3-protocol.md` - Protocol findings summary
539
+ - `.plan/200-intellicenter-v3-index.md` - IntelliCenter v3.004 index (linked from `.plan/INDEX.md`)
540
+
541
+ **Active protocol files:**
542
+ - `201-intellicenter-circuits-features.md` - Circuits, features, groups
543
+ - `202-intellicenter-bodies-temps.md` - Body temps/setpoints/heat modes
544
+
545
+ **Future files (create as needed):**
546
+ - `INTELLICENTER_PUMPS_PROTOCOL.md` - Pump control/status
547
+ - `INTELLICENTER_HEATERS_PROTOCOL.md` - Heater control/status
548
+ - `INTELLICENTER_SCHEDULES_PROTOCOL.md` - Schedule management
549
+ - `INTELLICENTER_CHEMISTRY_PROTOCOL.md` - IntelliChem/chlorinator
550
+
551
+ **Other Controllers (create as needed):**
552
+ - `INTELLITOUCH_*.md` - IntelliTouch protocols
553
+ - `EASYTOUCH_*.md` - EasyTouch protocols
554
+ - `NIXIE_*.md` - Nixie/virtual protocols
555
+
556
+ #### File Organization
557
+
558
+ Each protocol file should include:
559
+ 1. **Message Types Summary** - Table of all relevant actions
560
+ 2. **Complete Flow Diagrams** - Step-by-step packet sequences
561
+ 3. **Byte Offset Tables** - Where data lives in payloads
562
+ 4. **Version Differences** - v1.x vs v3.004+ variations
563
+ 5. **Handler Routing** - Which code files process each message
564
+ 6. **Troubleshooting** - Common issues and solutions
565
+
566
+ #### Quick Reference: Circuits/Features (v3.004+)
567
+
568
+ **Authoritative source:** Action 30 case 15 (NOT Action 204!)
569
+
570
+ **Key v3.004+ bug:** Action 204 byte 19 contains STALE feature state. Must skip processing.
571
+
572
+ **See:** `.plan/201-intellicenter-circuits-features.md` for full details.
573
+
574
+ #### Quick Reference: Bodies/Setpoints (v3.004+)
575
+
576
+ **⚠️ CRITICAL: Action 168 Wireless has DIFFERENT offsets than Action 30!**
577
+
578
+ | Field | Action 30 (config) | Action 168 (Wireless) |
579
+ |-------|-------------------|----------------------|
580
+ | Pool setpoint | byte 19 | byte 20 |
581
+ | Pool cool | byte 20 | byte 21 |
582
+ | Spa setpoint | byte 21 | byte 22 |
583
+ | Spa cool | byte 22 | byte 23 |
584
+ | Pool mode | byte 23 | byte 24 |
585
+ | Spa mode | byte 24 | byte 25 |
586
+
587
+ **Root cause:** Wireless Action 168 payload has extra byte in early positions, shifting indices by +1.
588
+
589
+ **Heat mode valueMap fix:** Use `htypes.total > 1` to check for multi-heater setups (Solar+UltraTemp).
590
+
591
+ **See:** `.plan/202-intellicenter-bodies-temps.md` for full details.
592
+
593
+ ---
594
+
595
+ **Last Updated:** December 16, 2025
596
+ **Source:** nodejs-poolController IntelliCenter v3.004 compatibility work
597
+