Ylianst / ESP-IQ2020

Connect a Hot Tub with a IQ2020 control board to Home Assistant and make it smart. Monitor and control temperature, jets, lights, power usage and more.
Apache License 2.0
11 stars 0 forks source link

Protocol Decoding & Discussion #2

Open wolfson292 opened 1 month ago

wolfson292 commented 1 month ago

I've been working extensively on reverse engineering the IQ2020 firmware from a hex file found online, and have identified the RS485 address (0x1F) of their internet gateway and some of the functions. Followed you on Twitter, and happy to collaborate.

Commands from address 0x1F to IQ2020 at 0x01 have 2 bytes to identify the command, and optional additional bytes. I've identified the following so far.

Get Versions 0x01 0x00

[18:50:41][I][iq2020.component:097]: sendCmd_ 1F -> 01 Length:02 Operation:40 Data:01:00 Checksum:63
[18:50:41][I][iq2020.component:349]: readline_ Full Packet 01 -> 1F Length:17 Operation:80 RESP Data:01:00:57:52:34:2E:30:34:64:65:31:63:45:30:30:32:44:4B:34:2E:30:30:06 DataS:..WR4.04de1cE002DK4.00. Checksum:Valid 4D=4D

Get/Set Timestamps 0x02 0x4C Seconds ... if Seconds >60, don't set, just return values

[18:50:41][I][iq2020.component:097]: sendCmd_ 1F -> 01 Length:03 Operation:40 Data:02:4C:FF Checksum:B0
[18:50:41][I][iq2020.component:349]: readline_ Full Packet 01 -> 1F Length:0A Operation:80 RESP Data:02:4C:37:12:11:0B:08:D1:07:01 DataS:.L7....... Checksum:Valid C1=C1 SinceLast:0.3s

Get lots of data 0x02 0x55

[18:50:41][I][iq2020.component:097]: sendCmd_ 1F -> 01 Length:02 Operation:40 Data:02:55 Checksum:B9
[18:50:41][I][iq2020.component:349]: readline_ Full Packet 01 -> 1F Length:75 Operation:80 RESP Data:02:55:00:08:40:04:00:00:06:04:00:0A:06:20:72:13:00:20:1C:20:1C:20:1C:84:03:60:54:00:00:00:00:31:30:32:46:10:66:09:00:C0:1C:06:00:61:A5:D9:02:1D:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:21:A3:06:00:02:00:2C:A4:D9:02:00:00:00:00:00:00:00:00:31:30:30:46:31:30:32:46:78:00:07:01:07:01:00:00:00:00:00:00:00:00:00:00:00:00:3A:00:00:00:00:00 DataS:.U..@........ r.. . . ...`T....102F.f......a............
Ylianst commented 1 month ago

Oh nice. Thanks. I spent a lot of the last 48 hours on this and made a lot of progress. You can see all of my protocol decoding work here. I got lights, temperature, jet pumps and more decoded and working.

I also made progress on the ESP32 integration and got lights and temperature working really well. I how have my Home Assistant automatically change the temperature based on electricity rates and recording the temperature graph. I can switch lights on and off and it's very fast, not much lag.

image

image

I still have a few more days to go before it's something others can use. I have to work a bit more to get both Celsius and Fahrenheit modes working well. More importantly, the Spa Connection Kit polls for the data every minute and so, I just listen to that traffic, no need to add more traffic to the RS485 bus if I don't need to. I am coding it so the ESP32 will detect if a Spa Connection Kit device is already present and will not poll, but if it's not present it will do the polling itself.

Are you a Home Assistant user? If so, I would go buy a M5Stack+RS485+Wires right away (it takes a while to deliver and it's cheap). If your not using Home Assistant, let me know what setup your looking to integrate with.

Also, if you have a music module on your Spa or any information on that module, let me know. I would absolutely love to get a traffic capture of the audio device. I want to impersonate it so I can control Home Assistant audio from the spa display,

Any decoding help you can provide is very much appreciated!

Ylianst commented 1 month ago

@wolfson292 - Just tried your commands and yes, they do work. Just added them to the protocol document. Huge thanks.

Ylianst commented 1 month ago

FYI for everyone, more progress this morning. I got a lot more features supported and exposed to Home Assistant.

image

wolfson292 commented 1 month ago

0x02, 0x55 and 0x02, 0x56 have the same handler, but 0x56 includes additional fields. Everything up to the length of 0x55 is the same in 0x56 response.

To tell if the device is in C or F mode, you can check byte 15, bit 6, of the response (1 = C, 0 = F).

Ylianst commented 1 month ago

@wolfson292 What are C and F modes?

Oh... Celsius or Fahrenheit probably. I can see that when I read the temperature. However, good to know.

npinguin commented 1 month ago

Excellent work, ordered the hardware and looking forward to try it out.

I am from Belgium and have a Hotspring Envoy 2017 with an ace saltwater system and I am using home assistant for all the automation

Ylianst commented 1 month ago

Thanks @npinguin. Hopefully you can help me test and give me feedback.

Today I am working on getting jets that have multiple speeds working nicely in Home Assistant. I also need to add data polling for this ESP-Home component to work for people that don't have the SPA connection kit, but I should get that done in the next few days.

image

fricker-ben commented 1 month ago

Great work! Following closely with much interest! Just the thing I have been looking to implement for a while now. Parts are on order & hopefully arriving soon.

Ylianst commented 1 month ago

Thanks @fricker-ben. Nice to hear this. Certainly motivating me to code. I can't wait to hear your feedback.

Ylianst commented 1 month ago

Thanks to the information provided by @wolfson292. I added the version string as a sensor.

image

Ylianst commented 1 month ago

I may have found the water heater wattage value. 3866W in the picture below. I am polling this every minute anyway and so, a good value for HA graphs.

image

Ylianst commented 1 month ago

Ok. I added the polling support long with a bunch more things and updated all the documentation. I think this is pretty much a v1.0 at this point, it's probably something most geeks/nerds can use.

Ylianst commented 1 month ago

Ok, one more feature. The integration will now show when the hot tub is heating in the climate user interface.

image

image

wolfson292 commented 1 month ago

@wolfson292 What are C and F modes?

Oh... Celsius or Fahrenheit probably. I can see that when I read the temperature. However, good to know.

I was originally determining this like you were seeing if the temp string ended with F, but having the status bit seemed cleaner.

The third temperature is the heater outlet temperature. You'll see it spike when the heater turns on.

Your identification of these functions has allowed me to significantly improve my disassembly of the controller firmware, identifying many more global variables in the code for various statuses. Unsure on the legality of publicly sharing the current disassembly, but it's based off a version WR4.04 and DK4.00 hex file of the firmware for the controller and wireless display that was available on a public website. I was previously doing a MiTM setup on the RS422 interface with the wireless display, but this RS485 interface is far simpler and doesn't impact the ability to use the display itself.

Plan to upload my version of an IQ2020 component shortly to GitHub. Started on it before I found yours.

Ylianst commented 1 month ago

Hi @wolfson292. To be clear, I do not want to ever see or be exposed to 3rd party code and please don't post any details about other code or binary from any sources here or on any forum. API's and interfaces are ok. I do appreciate the enthusiasm and that you are even capable of doing this, that is absolutely insane! But yes, please keep it legal and respect other's work.

In any case, yes, thanks for the information on exhaust temp. I will add the outlet temperature to my component right away and the C/F bit to the documentation.

Ylianst commented 1 month ago

I just added the heater outlet temperature to the integration. One more thing to graph. Thanks @wolfson292!

image

wolfson292 commented 1 month ago

Hi @wolfson292. To be clear, I do not want to ever see or be exposed to 3rd party code and please don't post any details about other code or binary from any sources here or on any forum. API's and interfaces are ok. I do appreciate the enthusiasm and that you are even capable of doing this, that is absolutely insane! But yes, please keep it legal and respect other's work.

In any case, yes, thanks for the information on exhaust temp. I will add the outlet temperature to my component right away and the C/F bit to the documentation.

Completely understand having worked on many commercial reverse engineering projects.

Identified a few more of the data fields from the 0x02, 0x55/0x56 command.

Data[35-38] Heater Total Runtime Seconds
Data[39-42] Jets 1 Total Runtime Seconds
Data[55-58] Jets 2 Total Runtime Seconds
Data[59-61] Jets 3 Total Runtime Seconds
Data[67-70] Lights Total Runtime Seconds
Data[73-76] Lifetime Runtime Seconds
Data[43-46] Lifetime Runtime Seconds (6 minutes higher than previous value, 1.5yrs, for my own Spa)
Data[47-50] Unknown Counter, 30 on my own Spa
Data[63-66] Unknown Seconds Timer, 0 on my own Spa
Data[63-66] Unknown Seconds Timer, 0 on my own Spa
Data[77-80] Unknown Seconds Timer Related to Jets 1, 0 on my own Spa
Data[81-84] Unknown Seconds Timer Related to Jets 2, 0 on my own Spa

My own Spa only has 1 set of Jets, and they only support On/Off, no speeds. This might be related to the lack of data for some counters.

Ylianst commented 1 month ago

Wow @wolfson292 that is amazing data! I will add these runtime sensors in the next few hours. I am also reassured you know this business.

By the way, if you ever find a command to control light brightness and colors, that would be a fun one. I could add the brightness control on the existing light. I can read the lights brightness (0 to 5) using 0x0256 but can't control it.

Any other tricks we can do we the spa would be great!!

Ylianst commented 1 month ago

I just added your counters to the C# DataViewer app. I index starting from the start of the packet.

int HeaterTotalRuntime = getIntFromByteArray(data, 40);
int Jets1TotalRuntime = getIntFromByteArray(data, 44);
int LifetimeRuntimeSeconds1 = getIntFromByteArray(data, 48);
int UnknownCounter1 = getIntFromByteArray(data, 52);
int Jets2TotalRuntime = getIntFromByteArray(data, 60);
int Jets3TotalRuntime = getIntFromByteArray(data, 64);
int UnknownCounter2 = getIntFromByteArray(data, 68);
int LightsTotalRuntime = getIntFromByteArray(data, 72);
int LifetimeRuntimeSeconds2 = getIntFromByteArray(data, 78);
int UnknownCounter3 = getIntFromByteArray(data, 82);
int UnknownCounter4 = getIntFromByteArray(data, 86);

These are my own values, very similar to yours.

image

Ylianst commented 1 month ago

Done. Total runtime sensors are now included. I just added the ones we know what they indicate. Big thank you to @wolfson292 for this. I also updated the RS485 protocol page.

image

Ylianst commented 1 month ago

This is a temperature graph with my electricity time-of-use rules for the last 24h. Looks good. I am only using electricity at the lower cost rates now.

image

Ylianst commented 1 month ago

@wolfson292. I just added the protocol decoding to get/set the current time. This time is also at the end of the 0x0256 response.

Get Current Time

--> 01 1F 40 024C
<-- 1F 01 80 024C3431011400D40701
                 SSMMHHDDMMYYYY
Encoded as:
SS:MM:HH Seconds (0 to 59), Minutes (0 to 59), Hours (0 to 23).
DD:MM:YYYY Days (1 to 31), Month (0 to 11), Year (2 byte Big-Endian).

Set Current Time

--> 01 1F 40 024C2C2B140405E807
<-- 1F 01 80 024C2C2B140405E80701

Same encoding as Get Current Time.

Just for kicks, I added a new "Set Current Time" to the Data Viewer to set the spa time to your computer's clock.

image

npinguin commented 1 month ago

Would it be possible to transform the timers in seconds in yyy/mm/dd hh:mm:ss in home assistant?

Ylianst commented 1 month ago

@npinguin I will look into that. In general, I want to keep the sensor as seconds since this allows Home Assistant to perform computations on the results. I could return a string in HH:MM:SS format, but then you can't add/subtract the value easily. One way to change the format is to use a Home Assistant template sensor. This is a generic example below, but when I get a chance, I will see if it works.

sensor:
  - platform: template
    sensors:
      time_in_hh_mm_ss:
        friendly_name: "Time in HH:MM:SS"
        value_template: >
          {% set seconds = states('input_number.your_input_seconds') | int %}
          {% set hours = seconds // 3600 %}
          {% set minutes = (seconds % 3600) // 60 %}
          {% set secs = seconds % 60 %}
          {{ '%02d:%02d:%02d' | format(hours, minutes, secs) }}
Ylianst commented 4 weeks ago

@npinguin I just added a new document with Home Assistant Sensor Templates. This allows you to change the seconds into any format you like. I provide a full example to show the following:

337095349-04f2112f-6d3e-407e-ab42-3bf2d6b780c5

Hope that helps.

Ylianst commented 4 weeks ago

More data on my hot tub power usages over 2 days. Temperature is automatically adjusted. Where I live the prices are approximately:

So this makes a huge difference if I can heat only during off-peak. It's also good for the grid and the environment. I also per-warm the tub by 1 degree F ahead of mid-peak starting at 7am. On weekends it's all off-peak and this system is not used. I wrote up a full blog on this here. I expect to be saving over 330$ a year with this integration.

image

Ylianst commented 4 weeks ago

I just found, documented and added support for the power on counter. Goes up by one each time the hot tub is started.

image

Ylianst commented 4 weeks ago

I just fixed the problem with controlling jets that have a low/high settings. Works great now.

npinguin commented 3 weeks ago

Just installed it, so far so good. Filling up the tub to see if the pumps work etc.

just noticed that power on counter is not visible for me. Tried a new install but did not see the sensor same for some other sensors like the version. Do I need to force something to get a true new install?

Ylianst commented 3 weeks ago

Good catch @npinguin . I just updated the configuration yaml file on the main page to add the power_on_counter value in the sensor section. That should work now.

image

Let let us know how it goes.

Ylianst commented 3 weeks ago

I received a private email from someone trying to use the RS485MAX module. They can send commands but not receive any. I think the module looks like this:

image

I just updated the code to support flow_control_pin in the iq2020 section of the configuration yaml.

iq2020:
   uart_id: SpaConnection
   flow_control_pin: GPIO0
   port: 1234

If you use this specific board, I think you need to set flow_control_pin to the DE GPIO pin. However, I will need confirmation that it work, I don't have this setup. I also added a new section in the Devices Document for this setup.

npinguin commented 3 weeks ago

Filled up the tub and all is working perfectly!

Absolutely great work, very happy with this.

how did you get the software version from the spa?

Ylianst commented 3 weeks ago

Thanks @npinguin for reporting back. If you can, please let me know what your software version and spa model are. I will keep a running list of tested versions and models. Also, do you happen to have any of the extra modules? Music/ACE/Freshwater?

For the version, I used the following command given by @wolfson292 above.

--> 01 1F 40 0100
<-- 1F 01 80 01005752342E30346465316345303032444B342E303006
npinguin commented 3 weeks ago

Thanks @npinguin for reporting back. If you can, please let me know what your software version and spa model are. I will keep a running list of tested versions and models. Also, do you happen to have any of the extra modules? Music/ACE/Freshwater?

For the version, I used the following command given by @wolfson292 above.

--> 01 1F 40 0100
<-- 1F 01 80 01005752342E30346465316345303032444B342E303006

It is a Hotspring Envoy 2017 with an ace system.

Regarding the command to get the software version, how can I execute it and display it in home assistant? Is it possible to add it as a sensor ?

Ylianst commented 3 weeks ago

Thanks! I am a dummy, I forgot to add the version sensor to the yaml on the front page. I just added it. It looks like this.

text_sensor:
  - platform: iq2020
    versionstr:
      name: Version

For the ACE system, if you ever want added support for it, follow the debugging guide and send over a traffic dump.

npinguin commented 3 weeks ago

No don’t see it, is also not listed in the yaml?

Ylianst commented 3 weeks ago

I just added it at the end of the yaml on the main page. Add it and let me know if it works.

image

npinguin commented 3 weeks ago

Top, just added it and confirms that my firmware is old i guess

image

Ylianst commented 3 weeks ago

Wow. That is super impressive.

npinguin commented 3 weeks ago

anyone experience with firmware upgrades of the tub and what it could bring ? If it ain't broken do not upgrade is the typical recommendation. But i like keeping things up to date

Ylianst commented 3 weeks ago

@npinguin - Yes, getting the firmware change log for different versions would be great. I wonder if dealers have that.

Ylianst commented 3 weeks ago

@wolfson292 - I sent all 64k possible commands to the IQ2020 from 0x1F and looked for responses. I made the following table and if you have any guesses as to what some of the unknown commands are, please let me know. Thanks!

0100 - Get version string
0109 - Change temperature
0241 - Unknown (02413C001E0000)
024C - Unknown (024C1B0C110300E80701)
0255 - Get status short
0256 - Get status long
0B02 - Jets 1 & Lights on/off - (0B028F)
0B03 - Jets 2 on/off - (0B038E)
0B04 - Jets 3? on/off - (0B0400)
0B07 - Jets 4? on/off - (0B0700)
0B1C - Summer Timer
0B1D - Spa Lock
0B1E - Temperature Lock
0B1F - Clean Cycle
0B20 - Unknown (0B2001)
0B27 - Unknown (0B278C)
1702 - Lights on/off
1705 - Read lights status
1900 - Unknown (190015)
1901 - Unknown Periodic Polling (190100190000000B0004010000)
1D07 - Unknown Periodic Polling (1D07FFFF)
1E02 - Freshwater Salt System - Set Power / Start Test
1E03 - Get FreshWater Salt Module Data
Ylianst commented 3 weeks ago

Just published YouTube videos on this integration. Same video with two different edits.

Short Video (5:38) Long Video (24:42)

Ylianst commented 2 weeks ago

I just put in more updates. When changing settings, the ESP32 will auto-retry a command 3 times at 1/5 second interval. This is needed since there is a tiny chance that two commands would collide on the bus or there is an error and the IQ2020 controller did not get the command.

Also, I added a new sensor with the salt system power level (0 is off and 10 is max power). Only useful if you have this system. I could also remotely change the setting, I am not sure what Home Assistant control I could use for it.

Lastly, if anyone has this document... that would be super useful.

339955382-8bf8a20c-c2db-4dc0-bb6a-b8401201241c

fricker-ben commented 2 weeks ago

Finally home after a week away. All plugged in & working nicely on my Hot Spring Sovereign! Is there a way to set & view the current temperature in 0.5°C increments?

Ylianst commented 2 weeks ago

@fricker-ben - Yes, Fahrenheit is set in 1.0F increments and Celsius in 0.5C increments. I have tested Fahrenheit a lot more than Celsius. You need to change some _f_ to _c_ in the ESPHome device config. In the view below, hit +/- and it should change by 0.5. If it does not, let me know, I will work to fix.

image

fricker-ben commented 2 weeks ago

I have changed the f to c, but when pushing the plus/minus button it's going in 1°C increments. Also the temp display only shows to 0 decimal places. The heater outlet temp displays to 0.1°C resolution.

Ylianst commented 2 weeks ago

Ok. I will do some testing and fixing on this later today. This is an essential feature of this integration.

Ylianst commented 2 weeks ago

@fricker-ben I see the problem, working on it.

npinguin commented 2 weeks ago

I have been testing the new version with the salt water, unfortunately it is not reusing the ace system interfaces which I was hoping it would. Would be great to have the ace system integrated, let me know what you need