Open HyperActiveJ opened 1 year ago
Debug code is in to capture the data from the new messages and initially pass to existing functions. Several button types have been identified and will be added. Second pass on status message decoding is in process to identify differences in field ordering.
@jackbrown1993 I pushed a quick filter fix if you have a chance to run it. I may be able to get the status message fix in as well.
The fields are more different then first pass guess made them be. In the status message I don't see the time at all. I do see fields correlating well to the set temperature, jet state, heat state, actual temperature, and what I think is the active display data. If the new code removes the '10 bf 17' warnings and cleans up the output then send a single long capture with the temp set point going up up up till heater turns on then down down down till heatertirns off, jets one on, jets one off, jets 2 on, jets 2 off, set time to a different time, then set time back, set date to a different date, then set date back. At each change we should see an info message printed with one or two fields changing. From there we can deduse the remaining data. I'll commit a change to remove the invalid status printing so it's just the info message.
I am at work but just tried to run the script and got an error after the first few messages as attached. Error.txt
Hopefully fixed, give it another try. Good news is that it looks like it's going thru the process of attempting to get a channel number assigned to the client so you have a time slot and don't conflict with the panel when responding on the bus.
Yep, just found the error in the code and then noticed you had pushed a commit :) I just run the code for 2 minutes and 10 seconds and didn't get much output as attached. Capture 1712.txt
C4 updates are every minute, just timed a couple.
Interesting hmm. Well that some good data at least. Bad crcs can be due to a bad wireless connection dropping data. More data makes more patterns apparent, so send more. I have some theories from what I've see but I need a full test set to run.
Data capture starts at 07:45pm with the circulation pump already running and temp 31.5C.
Temp up 3 times to 33C set, heater comes straight on.
Temp down 3-5 times, the heater turns off
Temp Up: Got Button Press x7E 06 10 BF 17 01 7C 7
Temp Down: Got Button Press x7E 06 10 BF 17 02 75 7E
Jet 1 on: Got Button Press x7E 06 10 BF 17 04 67 7E
Jet 1 off: Got Button Press x7E 06 10 BF 17 04 67 7E
Jet 2 on: Got Button Press x7E 06 10 BF 17 05 60 7E
Jet 2 off: Got Button Press x7E 06 10 BF 17 05 60 7E
Menu button: Got Button Press x7E 06 10 BF 17 1E 21 7E
Time set from 07:47pm 08:48pm
Left a few minutes
Time set back to 07:52pm
Some messing around with buttons trying to get into date configuration
Date set to 10th December 2022
Left for 2-3 minutes
Date set back to 9th November 2022
Left for 3-4 minutes Long Output with config changes.txt
Looks like enough to make a first stab at it. All the button identifiers are there, I'll add code to the next test to validate by setting the temp up 3 times then down 5, then jets 1 on, jets 1 off, jets 2 on, jets 2 off, with 5 seconds between each. Do you have a "clear ray" button and function? As for decoding the it seems like first byte of the status message is minutes, the second is unknown, third is const, forth is temp sensor a, 5=set temp 6=pump state, 7=fixed, 8=const, 9=unknown, 10=temp sensor b, 11-15=const,16= current menu, 17=menu data. I have an initial decoder to convert the encoded values to human readable and display. Will push as soon as i can.
Sure - appreciate your help on this and will send you some coffee/beer tokens. Yes, I have clear ray, will capture the button press when I’m home in an hour or so. Not sure the button actually exists but will check and confirm.
No worries, I haven't been home so I'm doing this all from my phone, so its a bit more cumbersome then ideal.
Clear Ray button: Got Button Press x7E 06 10 BF 17 0F 56 7E Lights on / off: Got Button Press x7E 06 10 BF 17 11 0C 7E Lights change colour: Got Button Press x7E 06 10 BF 17 12 05 7E
I can send commands to the hot tub using the below code which appears to work OK
BTN_CLEAR_RAY = 0x0F
BTN_P1 = 0x04
BTN_P2 = 0x05
BTN_TEMP_DOWN = 0x02
BTN_TEMP_UP = 0x01
BTN_MENU = 0x1E
BTN_LIGHT_ON = 0x11
BTN_LIGHT_COLOR = 0x12
message_length = 0x06
data = bytearray(8)
data[0] = M_STARTEND
data[1] = message_length
data[2] = 0x10
data[3] = 0xBF
data[4] = CC_REQ_ALT_17
data[5] = val
data[6] = 0x67
data[7] = M_STARTEND
@HyperActiveJ any advice on where I should start in decoding the status messages?
https://github.com/jackbrown1993/Jacuzzi-RS485
I have Temp and Set Temp being read and sent to MQTT...still very hacked together but it's reporting the data.
https://github.com/jackbrown1993/Jacuzzi-RS485
I have Temp and Set Temp being read and sent to MQTT...still very hacked together but it's reporting the data.
Sorry I disappeared! Had some urgent things come up. Looks like you got the temp stuff i forgot to push before i left in and it's working?
No worries! Temp is working but using a manual lookup array....it works as per below:
https://github.com/jackbrown1993/Jacuzzi-RS485/blob/master/app/jacuzziRS485.py#L62 https://github.com/jackbrown1993/Jacuzzi-RS485/blob/master/app/jacuzziRS485.py#L99
I also have the ability to send button commands to the hot tub, by sending defined HEX values as per below:
https://github.com/jackbrown1993/Jacuzzi-RS485/blob/master/app/jacuzziRS485.py#L40
Next steps I would like to be able to read heat and pump status if you have any advice :)
Hi guys -
Late to the party I know. But I have just posted the actual decryption algorithm. Tested even!
See https://github.com/dhmsjs/jacuzzi_decrypt.
I'm pretty confident this is the real algorithm, but if you find any issues just let me know.
It should clarify your analysis of the panel status message fields. I am also trying to figure out the same but don't have an "encrypted" controller in my J-235 tub, so I can't do any testing myself. I am interested in seeing what you can tie down.
Looks like a more valid solution. I'll see what I can do with it when I have some time.
Somewhat academic perhaps, but this looks interesting:
If I add support for the CC message type to the decryption algorithm, with the decrypt key1 value = packet[5] XOR 0xDE, as in:
# Encrypted packets have an extra byte that we use to form the first key value
packet_type = packet[4]
if packet_type == 0xc4:
key1 = packet[5] ^ 0x19
elif packet_type == 0xca:
key1 = packet[5] ^ 0x59
elif packet_type == 0xcc:
key1 = packet[5] ^ 0xde
else:
# Done if not an encrypted message type
return packet
then a "decrypted" CC message has these values:
225 - Nothing Pressed (0xE1) decrypted = 0x00
238 Clear ray button (0xEE) decrypted = 0x0F = 15
229 Pump 1 Button (0xE5) decrypted = 0x04
228 Pump 2 Button (0xE4) decrypted = 0x05
227 Temp Down (0xE3) decrypted = 0x02
224 Temp Up (0xE0) decrypted = 0x01
255 Menu (0xFF) decrypted = 0x1E = 30
I just threw it in a test program and it certainly seems like it works, im refactoring around it now. I should say it seems like Jacuzzi/Sundane/Balboa is callign it 'encrypted', its really more like 'encoded'.
Happy to also do some testing when you have a branch / PR setup.
I've converted most of the field sover to the new version. Im still getting odd results with Temp 2 having offset when the heater is running. Otherwise im close to one for one replacement. I have to run some errands, i'll get a branch up when i can.
So here's my experience with the second temp sensor on my J-235. It acts like it is in the piping just before or after the heater. So when the pumps are idle it will drift to a lower temp than the actual tub temperature, but as soon as my pump 1 comes on (which is also the circ pump), then the temperature quickly comes up to within a degree or so of the tub temp.
I also have a bit which comes on whenever the pump is running. Not the "Pump 1" status bit, but another, undocumented one further down the status message. That one seems to me to be the flow sensor.
And by "undocumented" I just mean that the Prolink app does not try to read it.
@jackbrown1993 I've been reviewing your discussion above and it looks to me (as @HyperActiveJ pointed out) that your system is probably not "encrypted". Do you know for sure it is?
In any case what I see here is that HyperActiveJ's "encrypted" status packets have an entirely different format from my unencrypted status packets (I have a Jacuzzi 2021 J-235). By "format" I just mean how the bytes for temperatures, pump status, time and date, etc are arranged in the message packet.
I wonder if your status messages might be more similar to mine than to an "encrypted" packet. I looked at some of your captures but they seemed to be focusing on button commands, not status messages so I didn't find what I was looking for.
The snippet below is from my notes describing my "Panel Update" status messages (type code = 0x16). These are similar in function to the encrypted 0xC4 type, but clearly their formats are completely different.
---snip---
Real message from Jacuzzi J-235 hot tub received by Home Assistant balboa.py integration:
(byte position: 000102030405060708091011121314151617181920212223242526272829303132333435363738)
Spa sent an unknown message: 7e25ffaf16133a1c081612005dfa500000008200005d0206000a8000008d000000ff000000d87e
NOTE: the Clearray, Water, and Filter timer values have their LSB and MSB values mislabeled (swapped). So the MSB is actually the LSB, and the LSB is the MSB.
enum value = ? (not included in list of static constructors)
id property is initialized to 1
Prolink code will ignore this message if total packet size is not at least 15 bytes
Message packet contents: (parenthetical values are examples taken from actual J-235 message packet above)
Byte 0 = 0x7E ('~') = message start flag
Byte 1 = ?? = message length (may vary; not including start and end flag bytes) (0x25 = 37)
Byte 2 = 0xFF = "Address" byte (always either 0x0A or 0xFF = broadcast?)
Byte 3 = 0xAF = "PF" byte (always either 0xAF or 0xBF)
Byte 4 = 0x16 (BMTR_STATUS_UPDATE = 0x13)
Byte 5 = CurrentTimeHour (0x13 = 19)
Byte 6 = CurrentTimeMinute (0x3A = 58)
Byte 7 Bits 7,6,5 = currentWeek (day of week??) (0b000 = 0) -- actually day of week 1 = Monday 0 = Sunday
Byte 7 Bits 4,3,2,1,0 = daysInMonth (0b11100 = 28) -- actually day of month
Byte 8 = currentMonth (0x08 = Aug?)
Byte 9 = currentYear (since 2000) (0x16 = 2022)
Byte 10 Bits 7,6 = Filter2Mode (0b00 = off)
Byte 10 Bits 5,4 = HeatModeState (0b01 = on-low?)
Byte 10 Bits 3,2,1,0 = SpaState (values of 1,2,8,9 or 10 get forced to -1) (0b0010 = 2)
Byte 11 = errorCode (0x00 = no error)
If errorCode value = 21 and TabActivityClass.modalval == 1 then errorCode gets forced to 0
If errorCode value is 4,5,6,7,8,9,10 or 14, then errorCode gets forced to -1
Byte 12 = ActualTemperature (0x5D = 93)
Byte 13 = don't care? (0xFA)
Byte 14 = SetTemperature (0x50 = 80)
Byte 15 Bits 7,6 = Pump3State (0b00)
Byte 15 Bits 6,5 = Pump2State (bit posn off by 1??) (0b00)
Byte 15 Bits 4,3,2 = Pump1State (0b000)
Byte 15 Bits 2,1,0 read but not used (0b000)
Byte 16 Bits 6,5 = IsSecondaryON (0b00)
Byte 16 Bits 5,4 = IsPrimaryON (Bit posn off by 1??) (0b00)
Byte 16 Bits 4,3 = IsBlowerON (Bit posn off by 1??) (0b00)
Byte 16 Bits 2,1 = IsUVON (0b00)
Byte 17 = don't care? (0x00) -- this appears to go to 0x01 whenever pump 1 is not Off -- flow sensor?
Byte 18 Bits 2,1 = Is24HourTime (12 hr only if both bits are 0) (0b01 = 24hr)
Byte 18 Bit 0 = TemperatureScale (Degrees F only if bit is 0) (0b0 = Fahrenheit)
Byte 19 = don't care? (0x00)
Byte 20 Bits 4,3 = settingLock (0b00)
Byte 20 Bits 2,1 = accessLock (0b00)
Byte 20 Bits 1,0 = maintenanceLock (Bit posn error off by 1??) (0b00)
Byte 21 = don't care? (0x5D) -- this is actually current temperature -- same as byte 12
Byte 22 = don't care? (0x02)
Byte 23 = don't care? (0x06)
Byte 24 = CLEARRAYLSB (0x00) -- actually MSB
Byte 25 = CLEARRAYMSB (0x0A) -- actually LSB
Byte 26 = WATERLSB (0x80) -- actually MSB
Byte 27 = WATERMSB (0x00) -- actually LSB
The following bytes may not be present
Byte 28 = OUTERFILTERLSB (0x00) -- actually MSB
Byte 29 = OUTERFILTERMSB (0x8D) -- actually LSB
The following bytes may not be present
Byte 30 = INNERFILTERLSB (0x00) -- actually MSB
Byte 31 = INNERFILTERMSB (0x00) -- actually LSB
The following byte may not be present
Byte 32 Bits 7,6,5,4 = WiFiState (0b0000 = unknown)
0 = SpaWifiState.Unknown
1 = SpaWifiState.SoftAPmodeUnavailable
2 = SpaWifiState.SoftAPmodeAvailable
3 = SpaWifiState.InfrastructureMode
4 = SpaWifiState.InfrastructureModeConnectedToNeworkNotCloud
5 = SpaWifiState.InfrastructureModeConnectedToNeworkCloud
14 = SpaWifiState.LINKINGTONETWORK
15 = SpaWifiState.NOTCOMMUNICATINGTOSPA
(There could be more bytes in the packet but if so, Prolink software ignores them
Byte 33 = don't care (0xFF)
Byte 34 = don't care (0x00)
Byte 35 = don't care (0x00)
Byte 36 = don't care (0x00)
Byte 37 = ?? = Checksum -- any value will pass though; Prolink code accepts any value as valid (0xD8)
Byte 34? = 0x7E = message end flag
---snip---
(The field names came from studying a decompiled Android version of the Prolink app)
I don't know if that helps you or not, but anyway there it is.
@dhmsjs don't think my packets are encrypted although it's been a while since I looked at this, would love to get this working reliably for my hot tub, did you make any more progress for yours?
@jackbrown1993 I have a working jacuzzi.py at https://github.com/dhmsjs/pyjacuzzi which (like @HyperActiveJ's work it extends garbled1's pybalboa (https://github.com/garbled1) as well as work by ccutrer (https://github.com/ccutrer), and others.
jacuzzi.py does not yet support the maintenance timers (water, filters, cleaRay) but it does display and control most everything else that my J-235 has. I have a simple console app to handle the UI for now. You might try that out to see what works and what doesn't with your tub. I would be interested to know how it goes since my only test bed is my J-235.
My long-term objective is to integrate jacuzzi.py into HomeAssistant. I haven't done any of that work yet. Doesn't look to be too difficult though.
Another spa owner has also been seeking to control his Jacuzzi hot tub directly. He recently had to replace his (non-encrypted) main control board and the replacement was an encrypted type. That's how I got involved with the encrypted versions. But since my tub is not encrypted that other owner has to do all of our testing.
It is somewhat surprising to me how different the message packets are for each hot tub brand (Sundance, Jacuzzi, Balboa etc) given that they all seem to use Balboa controllers. The RS485 protocol is consistent across brands and there is a lot of shared code apparently, but also quite a bit of differences. Not sure why -- maybe to make sure users can't easily use hardware for one in another brand spa?
@dhmsjs Just tried your code and it shows temp and set temp correctly when connecting to my Jacuzzi J335. Will have a bit more of a play and report back :)
@dhmsjs are you on Discord, be good to setup a space to discuss a few bits.
I am not (yet anyway). But email is dhmsjs at sbcglobal dot net.
Thanks, I don't seem to be seeing panel update messages reflect reliably / quickly so trying to figure that out, if I change set point temp it doesn't reflect in the UI reliably / consistently.
Jack.
On Sat, 10 Jun 2023 at 18:58, David Holland-Moritz @.***> wrote:
I am not (yet anyway). But email is dhmsjs at sbcglobal dot net.
— Reply to this email directly, view it on GitHub https://github.com/HyperActiveJ/sundance780-jacuzzi-balboa-rs485-tcp/issues/1#issuecomment-1585755234, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPOGNYRPO2TKGFZTC4LYWDXKSYS3ANCNFSM6AAAAAAR3P2LH4 . You are receiving this because you were mentioned.Message ID: <HyperActiveJ/sundance780-jacuzzi-balboa-rs485-tcp/issues/1/1585755234@ github.com>
On my tub (as seems common with most) the status updates spam out every second or so (or more). jacuzzi.py has a has_changed() method that remembers prior checksum bytes and ignores a new packet unless it has changed; you won't see status update packets unless they have changed.
I suspect your tub is spamming those status packets too, but if it were not, that would explain why no regular update.
The other thing I see is that the Prolink box itself seems to essentially go to sleep randomly. When it does you just cannot connect via WiFi until it wakes up again. Are you using the Prolink interface or a generic WiFi-to-RS485 connection?
I am using a WiFi-to-RS485 connection - https://www.amazon.co.uk/gp/product/B097C8PT6F
Well that is good to know. That means all of the initial work to establish the connection is purely from balboa.py. I've only used the Prolink module though I'd like to move away from it eventually anyway.
You might try adding some more log messaging to help find out what is causing the delay. Standard python logging should work and show up in the message window.
I seem to be getting 0x23 messages every 10 seconds, not seeing constant 0x16 messages, as below, will check again with previous code I used to see.
2023-06-10 19:16:14,162:INFO:Connecting...
2023-06-10 19:16:14,172:INFO:Sending: 7e070abf190100957e
2023-06-10 19:16:15,110:INFO:New msg: 7e25ffaf1616047f0517420033fa262214018100004f011c00008000000000000001000000e27e
2023-06-10 19:16:18,702:INFO:New msg: 7e070abf1b1503497e
2023-06-10 19:16:23,136:INFO:New msg: 7e21ffaf238004640af50000ff003c000000ff0000000000000000000000000000f97e
2023-06-10 19:16:23,136:INFO:Light status: L: 100 R: 10 G: 245 B: 0
2023-06-10 19:16:33,138:INFO:New msg: 7e21ffaf2380046400e31b00ff003c000000ff00000000000000000000000000009b7e
2023-06-10 19:16:33,139:INFO:Light status: L: 100 R: 0 G: 227 B: 27
2023-06-10 19:16:43,141:INFO:New msg: 7e21ffaf2380046400bb4300ff003c000000ff0000000000000000000000000000857e
2023-06-10 19:16:43,141:INFO:Light status: L: 100 R: 0 G: 187 B: 67
2023-06-10 19:16:53,224:INFO:New msg: 7e21ffaf2380046400966800ff003c000000ff00000000000000000000000000008a7e
2023-06-10 19:16:53,224:INFO:Light status: L: 100 R: 0 G: 150 B: 104
2023-06-10 19:17:03,298:INFO:New msg: 7e21ffaf23800464006f8f00ff003c000000ff0000000000000000000000000000157e
2023-06-10 19:17:03,300:INFO:Light status: L: 100 R: 0 G: 111 B: 143
2023-06-10 19:17:13,299:INFO:New msg: 7e21ffaf23800464004ab400ff003c000000ff00000000000000000000000000008c7e
2023-06-10 19:17:13,302:INFO:Light status: L: 100 R: 0 G: 74 B: 180
2023-06-10 19:17:23,503:INFO:New msg: 7e21ffaf238004640022dc00ff003c000000ff0000000000000000000000000000c47e
2023-06-10 19:17:23,504:INFO:Light status: L: 100 R: 0 G: 34 B: 220
2023-06-10 19:17:33,634:INFO:New msg: 7e21ffaf238004640300fb00ff003c000000ff0000000000000000000000000000267e
2023-06-10 19:17:33,635:INFO:Light status: L: 100 R: 3 G: 0 B: 251
2023-06-10 19:17:43,640:INFO:New msg: 7e21ffaf238004642a00d400ff003c000000ff0000000000000000000000000000fb7e
2023-06-10 19:17:43,640:INFO:Light status: L: 100 R: 42 G: 0 B: 212
2023-06-10 19:17:45,285:INFO:Requesting module ID.
2023-06-10 19:17:45,285:INFO:Sending: 7e050abf04777e
2023-06-10 19:17:53,814:INFO:New msg: 7e21ffaf238004644f00af00ff003c000000ff0000000000000000000000000000977e
2023-06-10 19:17:53,814:INFO:Light status: L: 100 R: 79 G: 0 B: 175
2023-06-10 19:18:03,945:INFO:New msg: 7e21ffaf2380046477008700ff003c000000ff0000000000000000000000000000937e
2023-06-10 19:18:03,945:INFO:Light status: L: 100 R: 119 G: 0 B: 135
2023-06-10 19:18:14,037:INFO:New msg: 7e21ffaf238004649c006200ff003c000000ff0000000000000000000000000000ce7e
2023-06-10 19:18:14,039:INFO:Light status: L: 100 R: 156 G: 0 B: 98
2023-06-10 19:18:24,123:INFO:New msg: 7e21ffaf23800464c4003b00ff003c000000ff0000000000000000000000000000337e
2023-06-10 19:18:24,123:INFO:Light status: L: 100 R: 196 G: 0 B: 59
2023-06-10 19:18:34,223:INFO:New msg: 7e21ffaf23800464e8001600ff003c000000ff0000000000000000000000000000a87e
2023-06-10 19:18:34,223:INFO:Light status: L: 100 R: 232 G: 0 B: 22
2023-06-10 19:18:44,180:INFO:New msg: 7e21ffaf23800464ec120000ff003c000000ff0000000000000000000000000000757e
2023-06-10 19:18:44,182:INFO:Light status: L: 100 R: 236 G: 18 B: 0
2023-06-10 19:18:54,398:INFO:New msg: 7e21ffaf23800464c7370000ff003c000000ff0000000000000000000000000000997e
2023-06-10 19:18:54,399:INFO:Light status: L: 100 R: 199 G: 55 B: 0
2023-06-10 19:19:04,648:INFO:New msg: 7e21ffaf23800464a05e0000ff003c000000ff0000000000000000000000000000157e
2023-06-10 19:19:04,649:INFO:Light status: L: 100 R: 160 G: 94 B: 0
2023-06-10 19:19:14,722:INFO:New msg: 7e21ffaf238004647b830000ff003c000000ff0000000000000000000000000000f17e
2023-06-10 19:19:14,723:INFO:Light status: L: 100 R: 123 G: 131 B: 0
2023-06-10 19:19:15,414:INFO:Requesting module ID.
2023-06-10 19:19:15,415:INFO:Sending: 7e050abf04777e
2023-06-10 19:19:24,872:INFO:New msg: 7e21ffaf2380046453ab0000ff003c000000ff0000000000000000000000000000857e
2023-06-10 19:19:24,875:INFO:Light status: L: 100 R: 83 G: 171 B: 0
2023-06-10 19:19:31,504:INFO:----Dumping log buffer to stdout----
2023-06-10 19:19:31,505:INFO:----Log dump complete----```
The 0x23 messages are LED status messages. They are showing up because maybe your lights are doing the automatic changing thing right now?
Because jacuzzi.py is suppressing duplicate messages you will only see 0x16 messages when something changes -- which at a minimum will occur each minute, as the currentMinutes value changes.
The "Requesting Module ID" message comes up whenever (I think) balboa.py loses the WiFi-to-RS485 connection. So it looks like the connection drops, then reconnects. I also see this with Prolink, but perhaps not as often as you are seeing there.
@dhmsjs Yes odd that, is your plan to keep your pyjacuzzi repo into the HASS integration?
Nothing so lofty; I just want to monitor and control my tub from my HA instance. If others can also use it to do the same, great!
Something I thought I should point out to you is that Jacuzzi's Prolink boxes always seem to use RS485 bus address 0x0a, whereas the general Balboa connection sequence is for a device to request an address from the controller and then use that. My jacuzzi.py isn't doing any of that specifically (though it may be happening in balboa.py; not sure).
In any case all of jacuzzi.py currently assumes it has a bus address of 0x0a.
The controller sends out status messages to bus address 0xff -- I suspect that is meant to be a broadcast address that all devices listen to. But if jacuzzi.py sends commands from bus address 0x0a to the controller and the controller hasn't established a connection with the device at address at 0x0a, it may decide to ignore those packets, or try to negotiate a connection, etc. I've never studied that much because it wasn't needed for me to get jacuzzi.py to work with the Prolink box.
With your WiFi-to-RS485 bridge we're in somewhat uncharted waters here so what you discover may be helpful in understanding exactly how the controller behaves.
@dhmsjs I am going to incorporate some of your code in mine. Will provide credit to you, I am facing issues with your code which I suspect is based on the channel selection, looking at my existing app and code I seem to be using channel 17.
@dhmsjs I have opened a pull request on my own repo on the below URL, I will also soon be opening a pull request for home assistant integration.
You may also find ProlinkMessageTypes.txt (in my docs subdirectory) to be helpful for showing the differences between Balboa and Jacuzzi message packets.
For @HyperActiveJ this is what I currently have captured regarding the format of an encrypted panel status message.
Day of month seems likely correct, but month number still remains a bit perplexing to me.
Message Type: PanelUpdate (for "Encrypted" Jacuzzi Controllers)
-------------------------
Decrypted example:
byte#: 00010203040506070809101112131415161718192021222324252627282930313233343536373839
Status: 7e26ffafc400160000080c92f2070064023818101e64000117080c19089a0000536638383800fd7e
key1^^ curYear^^ ^^curTimeMinute
currentHour^^ ^^curTemp ^^setTemp
bit6=UVOn^^ ^^ Pump1=bits5&4
^^Pump2=bits3&2
^^Pump0=bits7&6
Message packet contents: (parenthetical values are examples taken from actual message packets)
Byte 0 = 0x7E ('~') = message start flag
Byte 1 = ?? = message length (may vary; not including start and end flag bytes) (0x25 = 37)
Byte 2 = 0xFF = "Address" byte (always either 0x0A or 0xFF = broadcast?)
Byte 3 = 0xAF = "PF" byte (always either 0xAF or 0xBF)
Byte 4 = 0xC4 (Balboa = 0x13, Jacuzzi non-encrypted = 0x16)
Byte 5 = Encryption Key1 value
Byte 6 = CurrentTimeHour (24 hr time) (0x16)
Byte 7 Bit 6 = UV On (0b0, 0x00, 0x40, 0x60)
Byte 8 Bits 7,6 = pump0State (circ pump?) (0b00)
Byte 8 Bits 3,2 = pump2State (blower?) (0b00, 0x00,0x04)
Byte 9 = ?? (Always seems to be 0x08)
Byte 10 Bit 7 = ManualCirc (0b0)
Byte 10 Bit 6 = AutoCirc (0b0)
Byte 10 Bits 5,4 = pump1State (0b00, 0x0c, 0x0d, 0x1d, 0x0e, 0x1e)
Byte 11 = ?? (0x91, 0x92)
Byte 12 = ?? (Always seems to be 0xF2)
Byte 13 = ?? (Always seems to be 0x07)
Byte 14 = ?? (0x00) -- this appears to go to 0x40 whenever pump 1 is not Off -- bit 6 = flow sensor?
Byte 15 currentTemp (0x64)
Byte 16 = ?? (Always seems to be 0x02)
Byte 17 = ?? (0x38, 0x98)
Byte 18 = heatMode (0x18)
Byte 19 = Bits 7-3 = dayOfMonth? (0x88, 0x50, 0x10, 0x08) (May 18, May 22?, May 31, June 2) -- Looks possibly correct
10001000 01010000 00010000 10000000 Th Mon Weds Fri
Byte 20 = currentMonth ?? (bits 3-0) (0x1d, 0x1d, 0x1f, 0x1e) (May 18, May 22?, May 31, June 2) -- Does not appear to be correct
00011101 00011101 00011111 00011110 Th Mon Weds Fri
(Byte 20 seems to only take on values like 0x1d, 0x1e, 0x1f -- not likely to be current month info at all.
Lowest 3 bits may be day of week??)
Byte 20 Bits 7,6,5,4,3 = ??
Byte 21 = setTemp (0x64)
Byte 22 = ?? (Always seems to be 0x00)
Byte 23 = ?? (Always seems to be 0x01)
Byte 24 = currentYear (0 = 2000) (0x17)
Byte 25 = ?? (Always seems to be 0x08)
Byte 26 = Bit 6 = heaterOn (0x0c, 0x0d, 0x8d)
Byte 27 = CurrentTimeMinute (probably only bits 5-0) (0x19)
Byte 28 = ?? (Always seems to be 0x08)
Byte 29 = ?? (0x99, 0x9a, 0xb1)
Byte 30 = ?? (Always seems to be 0x00)
Byte 31 = ?? (Always seems to be 0x00)
Byte 32 = displayField (0x53="S", 0x54="T")
Byte 33 = ?? (0x66="f", 0x5f="_", 0x5e="^", 0x52="R", 0x50="P")
Byte 34 = ?? (0x38) -- synchronized with flow in byte14? Byte14=0x40 => byte34=0x18. Byte14=0x00 => byte34=0x38
Byte 35 = ?? (Always seems to be 0x38)
Byte 36 = ?? (Always seems to be 0x38)
Byte 37 = ?? (Always seems to be 0x00)
Byte 38 = Packet checksum (0xfd)
Byte 39 = 0x7E = message end flag
From HyperActiveJ:
DATE_FIELD_2 = 7
-- Converting to true offest in packet:
offset = 6 + (2 * DATE_FIELD_2) = 6 + 14 = 20
possibly combined by XOR with byte 19
DAY_SHIFT = 3 #Shift date field 2 by this amount to get day of month
-- i.e. day of month is bits 7-3 of the byte -- looks possibly correct
MONTH_AND = 7 #Shift date field 2 by this to get Month of year
-- i.e. month number is lowest 3 bits of the byte -- only allows for month 0 thru 7 (?)
day = (data[DATE_FIELD_2] >> DAY_SHIFT)
month = (data[DATE_FIELD_2] & MONTH_AND)
@dhmsjs what is the sacrifice of not having the has_changed function, were you seeing issues with the duplicate messages being processed?
For me the value of has_changed() was only to be able to ignore the regular flood of duplicate panel status messages that spam out roughly each second, so that I could focus on only the status messages showing an actual change in state.
@dhmsjs understood, I have removed that function now as it was causing me issues with channel assignment.
I have made some progress on my branch, it correctly identifies and sets the appropriate channel which seems to be 17 for me. Pump controls are confusing me a little bit, why does pump 2 have two 'on' states in the UI?
Depending on the spa model, pumps may have several speed levels. The message packets (on my J-235 at least, but I suspect it is a general solution) use 2 bits to report pump state. If the pump has multiple speeds then 2 bits can support up to 3 separate speeds, as well as off. When the pump only has simple on/off control, the additional bit is not really needed. The way pybalboa managed this was to set up an array of possible pump states, and then populate that array based on the config information the controller reports during the initial connection negotiation.
Jacuzzi spas don't seem to have this dynamic configuration feature so their configuration needs to be static instead.
My J-235 has no pump 0. Pump 1 is the jets pump; it cycles through off-low-high state. My pump 2 is the blower for injecting air bubbles; it can only be on or off.
As far as the UI is concerned, I'd have to study it to know for sure, but likely it was just a way to keep pump 2 from displaying as "low" or "high" when it could only be "on" or "off."
And in fact here is a comment from the jacuzziui.py code:
# Jacuzzi spas return a pump status of 0x02 when pump
# 2 is On, which means the text returned by get_pump()
# is "High". Since pump 2 has only 2 states (On or Off)
# we use this local string array instead.
I'm impressed with how this is all evolving and fast. you guys rock. @jackbrown1993. I will make the 485 to wifi cable this week as I was still using prolink and will see how your code works with my encrypted board. I have a J-355, which should be very similar to yours, except for the encryption which makes me think there is some sadistic programmer at Balboa that enjoyed switching bits around.
I've been dormant the last 2 weeks and was still chasing the bit locations in relation to @dhmsjs unencrypted message findings. PIA to find the needle in the haystack amongst the 40 bytes. had made a simple script to colour the changed bytes on screen to help chase, but with your progress maybe it won't be needed. script here if it could be of interest. Not a programmer, so kudos to chatGPT entirely. https://github.com/pddc/jacuzzypy-compare
I'm impressed with how this is all evolving and fast. you guys rock. @jackbrown1993. I will make the 485 to wifi cable this week as I was still using prolink and will see how your code works with my encrypted board. I have a J-355, which should be very similar to yours, except for the encryption which makes me think there is some sadistic programmer at Balboa that enjoyed switching bits around.
I've been dormant the last 2 weeks and was still chasing the bit locations in relation to @dhmsjs unencrypted message findings. PIA to find the needle in the haystack amongst the 40 bytes. had made a simple script to colour the changed bytes on screen to help chase, but with your progress maybe it won't be needed. script here if it could be of interest. Not a programmer, so kudos to chatGPT entirely. https://github.com/pddc/jacuzzypy-compare
Hey @pddc, it's great seeing a few of us coming together and tinkering to try and build something great. If you want to branch off of my repo on what you are doing we can maybe create a script for development/testing which includes your color changing bytes?
Move from this thread https://github.com/garbled1/pybalboa/issues/11