parnic / node-screenlogic

Pentair ScreenLogic Javascript library using Node.JS
https://www.npmjs.com/package/node-screenlogic
MIT License
53 stars 15 forks source link

Decoding SLEquipmentConfigurationMessage #26

Open parnic opened 4 years ago

parnic commented 4 years ago

@mikemucc @bshep @tomblevins Figured I'd go ahead and make an issue for decoding this thing so nobody's duplicating work.

As a reminder, most of this is coming from decompiling the slconfig app with jadx + experimentation.

0:128 128 if pump exists, 0 if no pump, i think
1:0  priming time (in minutes, 0-10)
2:2  pump type (0 = none, 1 = IntelliFloVF, 2 = IntelliFloVS, 3 = IntelliFloVSF)
3:0  ? (maybe "background filtering"? this option is grayed out for me in slconfig)
4:11 circuit id (for me this is my "floor cleaner" circuit)
5:12 circuit val upper 8 bits
6:6  circuit id (for me this is "pool")
7:10 circuit val upper 8 bits
8:1  circuit id (for me this is "spa")
9:11 circuit val upper 8 bits
10:0 circuit id
11:3 circuit val upper 8 bits
12:0 circuit id
13:3 circuit val upper 8 bits
14:0 circuit id
15:3 circuit val upper 8 bits
16:0 circuit id
17:3 circuit val upper 8 bits
18:0 circuit id
19:3 circuit val upper 8 bits
20:3 priming RPM upper 8 bits
21:28 circuit 1 (4) val lower 8 bits
22:90 circuit 2 (6) val lower 8 bits
23:184 circuit 3 (8) val lower 8 bits
24:232 circuit 4 (10) val lower 8 bits
25:232 circuit 5 (12) val lower 8 bits
26:232 circuit 6 (14) val lower 8 bits
27:232 circuit 7 (16) val lower 8 bits
28:232 circuit 8 (18) val lower 8 bits
29:232 priming RPM lower 8 bits
30:0 unused?
31:0 unused?
32:0 unused?
33:0 unused?
34:0 unused?
35:0 unused?
36:0 unused?
37:0 unused?
38:0 unused?
39:0 unused?
40:0 unused?
41:0 unused?
42:0 unused?
43:0 unused?
44:0 unused?

Circuit id's are decoded via SLControllerConfigMessage's bodyArray array, deviceId property. The circuit and priming rpms are really strange with the upper and lower bits separated in the array. Valid values for RPMs is 400 - 3450 with the default value being 1000 (upper 8 bits = 3, lower 8 bits = 232).

I'm having trouble reconciling this information with what I'm seeing in the SLConfig code, though. PageIntelliFloPriming and PageIntelliFloFiltering are both doing things with the flow data array, but the indices they're using don't match up quite right with what I'm seeing from my equipment. Still working on all this.

parnic commented 4 years ago

Quick-and-dirty first shot at interpreting circuit RPMs: https://gist.github.com/parnic/998f0b1d89a0fecc25df2dd1c1916c8e

Example usage:

    console.log(`pump 1 pool rpm=${config.getCircuitRPMs(0, 6)}`);
    console.log(`pump 2 waterfall rpm=${config.getCircuitRPMs(1, 8)}`);

Feedback appreciated.

bshep commented 4 years ago

Quick-and-dirty first shot at interpreting circuit RPMs: https://gist.github.com/parnic/998f0b1d89a0fecc25df2dd1c1916c8e

Example usage:

    console.log(`pump 1 pool rpm=${config.getCircuitRPMs(0, 6)}`);
    console.log(`pump 2 waterfall rpm=${config.getCircuitRPMs(1, 8)}`);

Feedback appreciated.

This seems to work for me, i tweaked the code to be:

    for (var i = 0; i < this.controllerConfig.bodyArray.length; i++) {
      let deviceId = this.controllerConfig.bodyArray[i].deviceId;
      let circuitName = this.controllerConfig.bodyArray[i].name;
      console.log('pump 1 ' + circuitName + ' rpm= ' + config.getCircuitRPMs(0, deviceId));
    }

I get:

pump 1 Spa rpm= 2600
pump 1 Feat. Pump rpm= 2900
pump 1 Air Blower rpm= 3000
pump 1 Pool Light rpm= 0
pump 1 Spa Light rpm= 0
pump 1 Pool rpm= 1600
pump 1 Water Feature rpm= 0
pump 1 Jets rpm= 3450
pump 1 High Speed rpm= 2800

Which is correct

bshep commented 4 years ago

Also of note byte0 = 64 for me, i only have one VS pump so maybe that is why ( maybe a sort of length field?)

parnic commented 4 years ago

Also of note byte0 = 64 for me, i only have one VS pump so maybe that is why ( maybe a sort of length field?)

Interesting! The SLConfig log seems to deal with byte 0 like this, which seems nonsensical to me:

        byte byCircuit = poolConfig.getEquipconfig().getFlowDataArray().get(iOffset).byteValue();
        if ((byCircuit & 128) == 0) {
            byte byCircuit2 = (byte) (byCircuit & 63);
        }

This could be a decompilation or deobfuscation bug. Either way, I'm not sure that byte is actually important for anything (byCircuit isn't used anywhere after this and obviously neither is byCircuit2). Byte 2 seems to be the actually important one.

bshep commented 4 years ago

Also of note byte0 = 64 for me, i only have one VS pump so maybe that is why ( maybe a sort of length field?)

Interesting! The SLConfig log seems to deal with byte 0 like this, which seems nonsensical to me:

        byte byCircuit = poolConfig.getEquipconfig().getFlowDataArray().get(iOffset).byteValue();
        if ((byCircuit & 128) == 0) {
            byte byCircuit2 = (byte) (byCircuit & 63);
        }

This could be a decompilation or deobfuscation bug. Either way, I'm not sure that byte is actually important for anything (byCircuit isn't used anywhere after this and obviously neither is byCircuit2). Byte 2 seems to be the actually important one.

Hmmm, i looked at the code too and its unclear to me as well, it may not be important.

Interestingly i could not find the current speed/gpm/power use on that message, however I did find another message called GETPUMPSTATUS (msg 12584) that seems to have the needed info and also contains the set RPMs per circuit, unfortunately this message is not used in the android app so I'm guessing as to the meaning of the data (i interpreted everything as uint32, that may be wrong for the first couple of bytes):

Byte Range:Data
0-3:3  --> unknown
4-7:??--> unknown, sometimes value is 1 sometimes 4294967295 (FF FF FF FF)
8-12:1377  --> currently used watts
13-16:2800, --> currentl rpms
17-20:0, --> unknown
21-24:65, --> currentl GPM
25-28:255, --> unknown

Settings for different circuits starts at byte 29, each circuit is 12 bytes long
0-3:6 --> settings for CircuitId = 6
4-7:1600 --> set to 1600 RPM of GPM (see next byte),
8-12:1 --> 1 for RPM, 0 for GPM
(data repeats as above)
1, 2600,       1,  --> same as above for circuit 1
2, 2900,    1,--> same as above for circuit 2
9,       2800,    1,    --> same as above for circuit 9
3,    3000,          1,    --> same as above for circuit 3
8, 3450,       1,        --> same as above for circuit 8
132, 2200,    1,--> same as above for circuit 132 (freeze mode)
0,         30,    0 --> unknown (unused circuit maybe? since there should be 8 settings)

Hopefully the formatting is not confusing

See this gist for my current work on decoding the data, if I can figure out more on this or the EquipmentConfigurationMesage i'll post more: https://gist.github.com/bshep/5d7968639238832bd20c2fbb6769d990.js

bshep commented 4 years ago

Just a random thought, maybe a bit OT, could we use the SLEquipmentConfigurationMessage as a way to 'backup' our settings? Although most of the code dealing with its data is too complex for me to understand at the moment, it seems that the Android app sometimes edits values in memory and then sends it back to the controller, and looking at the field names it seems to be a pretty complete list of settings for the controller.

parnic commented 4 years ago

Yeah that could work. I'm not sure how complete this data is either. My slconfig app somehow wiped the circuits on my first pump while I was messing with it, but since I had a dump of one of these messages I was able to restore it all.

bshep commented 4 years ago

did some work on interpreting the valveDataArray, pretty much just ported the java code over and cleaned up some of the logic, TBH i'm not 100% sure what some of the checks are doing, but i do get appropriate data

Testing Needs:

https://github.com/bshep/node-screenlogic/commit/30dec79089505de461e788a54ce699d51f2de565

bshep commented 4 years ago

Also I was able to get the following info on SensorDataArray, MiscDataArray, DelayDataArray:

SensorDataArray:

DelayDataArray:

MiscDataArray:

bshep commented 4 years ago

See PR #32 for implementation of above decoding.

Still a lot of work to be done for the rest.

michaelmaston commented 3 years ago

Also of note byte0 = 64 for me, i only have one VS pump so maybe that is why ( maybe a sort of length field?)

Interesting! The SLConfig log seems to deal with byte 0 like this, which seems nonsensical to me:

        byte byCircuit = poolConfig.getEquipconfig().getFlowDataArray().get(iOffset).byteValue();
        if ((byCircuit & 128) == 0) {
            byte byCircuit2 = (byte) (byCircuit & 63);
        }

This could be a decompilation or deobfuscation bug. Either way, I'm not sure that byte is actually important for anything (byCircuit isn't used anywhere after this and obviously neither is byCircuit2). Byte 2 seems to be the actually important one.

Hmmm, i looked at the code too and its unclear to me as well, it may not be important.

Interestingly i could not find the current speed/gpm/power use on that message, however I did find another message called GETPUMPSTATUS (msg 12584) that seems to have the needed info and also contains the set RPMs per circuit, unfortunately this message is not used in the android app so I'm guessing as to the meaning of the data (i interpreted everything as uint32, that may be wrong for the first couple of bytes):

Byte Range:Data
0-3:3  --> unknown
4-7:??--> unknown, sometimes value is 1 sometimes 4294967295 (FF FF FF FF)
8-12:1377  --> currently used watts
13-16:2800, --> currentl rpms
17-20:0, --> unknown
21-24:65, --> currentl GPM
25-28:255, --> unknown

Settings for different circuits starts at byte 29, each circuit is 12 bytes long
0-3:6 --> settings for CircuitId = 6
4-7:1600 --> set to 1600 RPM of GPM (see next byte),
8-12:1 --> 1 for RPM, 0 for GPM
(data repeats as above)
1, 2600,       1,  --> same as above for circuit 1
2, 2900,    1,--> same as above for circuit 2
9,       2800,    1,    --> same as above for circuit 9
3,    3000,          1,    --> same as above for circuit 3
8, 3450,       1,        --> same as above for circuit 8
132, 2200,    1,--> same as above for circuit 132 (freeze mode)
0,         30,    0 --> unknown (unused circuit maybe? since there should be 8 settings)

Hopefully the formatting is not confusing

See this gist for my current work on decoding the data, if I can figure out more on this or the EquipmentConfigurationMesage i'll post more: https://gist.github.com/bshep/5d7968639238832bd20c2fbb6769d990.js

Based on some digging I was doing around the list and number of pumps, I believe that value of 64 indicates an IntelliFlo VSF pump. 0 is no pump, 128 is VS and 6 is VF. I just opened a case about getNumPumps() as it returns 8 pumps for me when I only have 1. By using byte0 of each 45 byte pump record in the FlowDataArray, you can determine if there really is a pump there or not. I added a 2nd pump in SLConfig to test even though I don't have a 2nd pump and the 2nd pump record started reporting its pump type (per the values I mentioned above) in byte0. No idea why they report different values for pump type in the FlowDataArray bytes vs. what you get in the pumpstatus message but I'm pretty sure of the mapping.

If your one pump is an IntelliFlo VSF (like mine) you should see 64 in that first byte of the first record.