Navien, ESP32 Navilink interface

@brystmar That is interesting. Curious about the model number of your Navien. I have a NPE-210S (NG), and mine shows these values for input/output - which seem more likely. This is essentially all of a day’s use. My input temperature seems to hover between 75 and 80 degrees F. Still a bit warmer than I think the city water is, but I’d think the 80 degree numbers seem to be when the unit shuts down and there is no longer water flowing through the unit.

@niharmehta Good Point here, I wonder if those HX temperatures maybe what I have captured here. It seems that my system operates a bit differently than @brystmar. Guess maybe we are back to some further decoding to see what we can find! :slight_smile:

Appreciate the feedback!

My Navien model is NPE-240A2. Your graph looks more like what I’d expect from the actual inlet.

Thank you @niharmehta for the NaviLink Lite sensor info. Makes perfect sense — my current “inlet temp” sensor is definitely the Heat Exchanger Inlet sensor. I’ll get that renamed in my config shortly.

One more request for @tsquared: Want to upload your yaml config file to GitHub? Makes it easier to see what’s changed for each new version. I’ve already made some minor customizations for my setup (that I wish to retain), and I expect more will be coming soon.

How would I go about decoding additional stream data to figure out which is for the actual inlet temp sensor? This is new territory for me, and I’m eager to learn :slight_smile:

For reference, here are my Navien entities while the shower is running. (The Instant BTUs entity is typically >0 while running. I must have taken the screenshot at an odd time).

Here’s what they show when the system is idle for awhile:

It seems like one of the differences between the A/S and A2/S2 versions of the NPE series is the addition of a separate HX Sensor to the Cold Inlet sensor (or the other way around). This seems unique to the S2/A2 models.
I also searched for a bit if another field is updating with the Inlet Temp values and with and did not pop out to me. Possibly a different packet type (although that would not make sense) . I plan to do some more testing soon to see if the Cold Inlet temp value is in another field.

If you are curious if there can be other packet types, there are a couple of ways you can look at ‘other’ packet types if they exist. I tired to see if there are other packets types not matching the first standard bytes by extending the else statement and creating new byte names and associated templates. I am sure this is not the best way, but it was easy to start for me.

          else  
          {
          id(uart_new_byte0).publish_state(bytes[0]); //Common to All Packets
          id(uart_new_byte1).publish_state(bytes[1]); //Common to All Packets
          id(uart_new_byte2).publish_state(bytes[2]); //Packet id Byte 0
          id(uart_new_byte3).publish_state(bytes[3]); //Packet id Byte 1
          id(uart_new_byte4).publish_state(bytes[4]); //Packet id Byte 2
          id(uart_new_byte5).publish_state(bytes[5]); //Data Length
          } 


########## Test Templates ############
  - platform: template
    name: "NByte_0"
    id: uart_new_byte0
  #  internal: True
  - platform: template
    name: "NByte_01"
    id: uart_new_byte1
  #  internal: True
  - platform: template
    name: "NByte_02"
    id: uart_new_byte2
  #  internal: True
  - platform: template
    name: "NByte_03"
    id: uart_new_byte3
  #  internal: True
  - platform: template
    name: "NByte_04"
    id: uart_new_byte4
  #  internal: True
  - platform: template
    name: "NByte_05"
    id: uart_new_byte5
  #  internal: True      

Or, you can add an additional else if and negative match the expected first bytes.

          else  // if  (bytes[0] != 247 && bytes[1] != 5 || bytes[2] != 80 || bytes[3] != 15 || bytes[4] != 144 || bytes[5] != 42 || bytes[5] != 34 || bytes[3] != 80) 
          {
          id(uart_new_byte0).publish_state(bytes[0]); //Common to All Packets
          id(uart_new_byte1).publish_state(bytes[1]); //Common to All Packets
          id(uart_new_byte2).publish_state(bytes[2]); //Packet id Byte 0
          id(uart_new_byte3).publish_state(bytes[3]); //Packet id Byte 1
          id(uart_new_byte4).publish_state(bytes[4]); //Packet id Byte 2
          id(uart_new_byte5).publish_state(bytes[5]); //Data Length
          } 

I think this may catch any additional packet types missed as long its within the 40 bytes of packets length.

I also noticed the BTU readings taking a bit of time to update, even with the KCal values changing quickly. I am completely new to ESP, and not sure what triggers the lambda to update. My plan is to probably offload that calculation to a HA template and let it recalc at each state change.

Another thing I found this weekend.
If you are using GitHub - nikshriv/hass_navien_water_heater: Home Assistant custom component for Navien water heaters using NaviLink HACS Navien integration (btw… the maintainer just started merging several fixed this weekend after almost a year), it shows the CCF, even though the app only shows ‘therms’.
The Navilink cloud must be holding the long term history of my heaters gas consumption as the value on the ESP is a fraction of the value from the Navilink cloud (app therms or CCF in HACS Navien Integration) . I have had the heater about 10 months. I assume the value will roll back to zero at the beginning of the month.
So if you are looking to create long term stats on volume/therms, you may need to handle this within HA.

@brystmar As I have mentioned a few times, I am certainly not a programmer therefore I really don’t know if I have setup the GitHub page correctly - But I did try. So here is the link. Let me know if you have trouble.

I really don’t know much about decoding either, but I’m willing to try when I get some time again. Work has been crazy busy as it usually is this time of year.

2 Likes

This thread is jam packed with a lot of information. Much thanks to everyone contributing so that us less experienced HA users can benefit. I have some high-level knowledge of RS485 communication applications in the commercial automation space, but I rarely get to see how the gravy is made. I have a few questions that I’d like to ask of some of you (@suva @tsquared @kkopachev @aruffell) and I apologize if it’s in poor taste to @ you directly.

1 - There are many types of Navien boilers and I assume there is variation in the control points that are available. For example, I have a combination heating hot water / domestic hot water unit (NCB-240/130H) so there are two different set temperatures depending on which mode it’s in. Does that mean that the packet sniffing process would have to be redone on my boiler in order to derive the device-specific points?

2 - I also have the smartzone pump controller installed to pick up signals from my thermostats and enable my 6 hot water zone pumps. The rs485 network appears to connect the pump controller, the boiler controller, and my navilink controller. In the commercial space, the typical communication protocol for these types of communication buses would be Modbus RTU and I see from the ESPhome documentation that Modbus components and Modbus controllers are supported. Could it be the case that these might be able to perform that packet sniffing process to more easily digest the information on the rs485 wire?

3 - If Navien is using a proprietary communication protocol across the rs485 and the above scenario is not possible, do you think it would be possible to use this Navien Modbus/Bacnet Gateway in order to integrate everything on the communication trunk via Modbus or BACnet?

4 - Finally, I just purchased a Waveshare Relay Module to automate a fireworks show. The board has an ESP32-S3 and a RS485 Interface, which I am considering repurposing for my hot water system afterwards. The relays will be able to replicate the thermostat signal if I ever need to manually enable a zone pump (which I’m currently accomplishing with Shelly UNIs) and in seeing the RS485 terminals, I’m hoping to pull in all of the points from the boiler rs485 trunk. The plan is to try and recreate what you all have figured out here, but I am wondering what I’ll need to keep in mind considering that I am using a completely different board. Any guidance would be appreciated.

Thanks in advanced!

1 Like

@NinjaJim I’m not sure I can answer all of your questions, but I will do my best to help.

We recently noticed that there are a few differences between the A/S and the A2/S2 versions of the hot water heater, so we are trying to sort those out now. As for the combi units - I’d have to guess there’s quite a bit of different information packed in there as it is trying to control/maintain 2 very different temperatures, under 2 very different circumstances. So it’s likely that the information here very well may not apply to your system. And it is likely that packet sniffing may be required here. The interesting part is, if you were to hook this up to your system, as the initial configuration is today, I have not created any points that are writing to the unit except for on/off. So hooking it up, you may find that there are entities that begin to populate, though they may not be the right names?

As you mention above, in the commercial space - this type of communication normally would be either Modbus, or BacNet - some other type of token passing protocol. However, in all the decoding and configuration building I have done, it seems that this is just straight RS-485 serial communication. I have not needed to use any Modbus, or other decoding to get this information.

As it sits today, using the hot water unit I have, the communication does not appear to be proprietary, just standard RS-485 serial communication. If the combi systems in fact use a different protocol, then I’d have to believe that the Navien Gateway would work quite well for this purpose. Although I have not used this device at all to confirm or deny it’s functions.

Aside from what a few others and myself have done to get this far, I don’t know if there is a better course of action, or better way to decode these signals. I like what you are doing with that device, I do wonder if you could remove the the shelly’s and the pump controller to make it all in one device? But that would certainly make your life a bit more complicated for decoding the information.

Not sure if any of this information was helpful, but I wish you well on your project. Keep us posted, I’m interested in your efforts with that device. (Also interested in your fireworks show you are working on - Do you have a thread in a forum for that?)

Good Luck!

I’ve been working through this today for my water heater. I have an NPE-240A2.
You can see my diff on github here: add more values for gas sensor, fix float values, fix on value for NPE2 · evanjarrett/ESPHome-Navien@16115ab · GitHub

Couple of things. I’m using an M5 Atom Lite S3, so my TX/RX lines are different. (I didn’t realize the lower operating temps, but my attic hasn’t hit 40C this summer, though its close)
I’m also just using an ethernet cable I took 4 of the lines from to make my cable. They are either 26/24awg solid core, which is more than enough at 12v 210mah

The biggest confusion I had is that my on/off values are not 5/0, they are 37/32.
I would be interested to know if anyone else with A2/S2 versions also have those same values.

Also, byte5 is the data length, yet for the gas values we weren’t logging all the received data.

What I’d like to be able to do is set up my weekly re-circ schedules with this, since I had them set up in the app, but it’s not clear if those get set on the device side. Seems like the navilink was responsible for sending those commands to the heater at the appropriate time.

1 Like

It seems like I’ve figured the checksum algorithm. I’ve put together some notes and a working example at https://github.com/htumanyan/navien

My observations of other fields are largely consistent with yours. I plan to upload those and also put together an esphome config for everyone.

3 Likes

Looking at the repo, I just wanted to say that was great work and a fantastic explanation. Thank you!

Here is a link to working C implementation of the checksum algorithm - navien/doc/checksum.md at b57886e1416715d2e5c0cccdca3e6ad8ffbda5eb · htumanyan/navien · GitHub

Thank you! The work still continues, unfortunately very slow due to contention with my day job :slight_smile:

Fantastic thread that I just happened to run across today.

I replaced my water heater a few years ago, and had an external recirculating pump. After installing the water heater, the recirculating pump died a few weeks later. I should have just purchased the internal recirculating pump version and NaviLink, but didn’t think it was necessary at the time.

My current setup is that I have an ESP32 connected to my water softener to detect if there’s water flowing in the house, if so, it turns the external recirculating pump on and runs until water flow is no longer detected at the water softener. Not the greatest, but keeps the heater from running too frequently, and does help get hot water to the farthest areas of the house a bit faster.

Water softener was just replaced a couple weeks ago, and I’ve not yet hooked up my ESP32 water flow sensor, so the recirc pump now runs constantly and the heater turns on every couple of minutes 24/7.

This give me a more elegant solution! Thanks everyone!

Which water flow sensor are you using? I’d love to add one of those to my setup.

I was too cheap to buy a commercially available flow meter, but water softeners typically have a turbine meter to measure water flow in order to know when to regenerate. I figured out where the sensor was connected to the main board of the water softener using a multi-meter, attached a wire going to an analog input on an ESP32 and measured the voltage of the signal in order to determine if water is flowing or not.

Here’s the key bit of ESPHome code. You could probably expand this to calculate flow rates, water usage and such, but I didn’t need to go that far. I just wanted to know when water was flowing to turn the recirculating pump on and off.

globals:
  - id: prior_volts
    type: double
    restore_value: no
    initial_value: '0'
  - id: thecounter
    type: int
    restore_value: no
    initial_value: '0'
  - id: water_flow
    type: bool
    restore_value: no
    initial_value: 'false'

sensor:
  - platform: adc
    id: water_flow_volts
    pin: GPIO36
    update_interval: 1s
    name: "Water Flow Volts"
    attenuation: Auto
    on_raw_value:
      then:
        lambda: |-
          if (id(water_flow_volts).state != (id(prior_volts))) {
            // water is flowing
            id(prior_volts) = (id(water_flow_volts).state); 
            id(thecounter) = 0;
            id(water_flow) = true;
            ESP_LOGD("Volts Changed", "Voltage changed. Counter reset: %d", id(thecounter));
          } else {
            id(thecounter) +=1; 
            id(prior_volts) = (id(water_flow_volts).state); 
            //ESP_LOGD("main", "Equal. Current is: %d", int(id(water_flow_volts).state));
     
              if (id(thecounter) >= 10) {
                id(thecounter) = 0;
                id(water_flow) = false; 
                ESP_LOGD("No Volts Change", "Counter was reset due to no voltage change: %d", id(thecounter));
              }
          }  

binary_sensor:
  - platform: template
    id: water_flow_state
    name: "Water Flow"
    icon: "mdi:waves-arrow-right"
    lambda: |-
      return id(water_flow);

Thanks for all the work on decoding the packets. I think that the 20th byte in the gas payload/2 looks like the operating capacity % of the heater and the 36th byte is the total lifetime operating hours. Trying to figure out some of the others by recording several minutes of an on/off cycle.

I have got these by comparing the app/navilite to the payloads.

edited the uptime byte to the correct value (byte 36)

@PaulJ This is good information. I’d be interested if/when you have that confirmed. Do you know if that value is available on all versions of these heaters?

I have an NPE-240A2. The capacity seems to track with the Navien app. The uptime is likely a two byte value, but my uptime has not yet used the high byte. Byte 35 and 37 are both zero as I sample the packets. In about 150 hours of operation I will know what byte for sure. :slight_smile:

gas byte 36 is operating time.
37 is most likely the high as the data is always organized as low followed by high.
(I also can’t confirm as my operating time is only 119hrs)

for gas byte 20… I noticed it was almost identical to “kcal high byte” which is byte 23.

water byte 30,31 look like low, high
id(uart_water_byte30).publish_state(bytes[30]); // low byte related to gas usage?
id(uart_water_byte31).publish_state(bytes[31]); // high byte

gas byte 30,31 also seem like low, high byte values but I don’t know what for.
id(uart_gas_byte30).publish_state(bytes[30]); // slowly increasing low byte?
id(uart_gas_byte31).publish_state(bytes[31]); // Always 6 high byte?