Navien, ESP32 Navilink interface

@tsquared I would ask a mod to most post x (fill in) down to the last into a new thread in the esphome section.

The GPIO of the esp32 is 3.3V and only 5V tolerant so whole it works it would be better to use a rs485 to 3.3V board. I will look for one and report back. I will also dig into digikey / mouser to see if I can pinpoint that connector. Do you see any numbers or letters marked on the connector?

Edit: Could it be this one - VHR-5N JST Sales America Inc. | Connectors, Interconnects | DigiKey

Apologies in advance, I don’t know how/who to ask to move the topic, and unfortunately created a post in the ESPHome category before I realized that @aruffell posted that this could be moved. Do you just @ a moderator and they should see it?

If it does move, it probably should begin starting with the post below.

https://community.home-assistant.io/t/navien-hot-water-heater-navilink/330044/87?u=tsquared
The ESPHome category post is here for combining/changing etc.

Please let me know if I need to do something else.

Thank you all for your help here! Certainly appreciated!

Also, @aruffell Awesome find here! The connector you show certainly looks like the one I have. Do they usually come with any of the internal pins that you would need to connect the wires? Or can you get them with wires attached.

Could you post pinout of that connector? Which one is 12VDC, which are RS485 and ground pins.

I am thinking of using this RS485 base and add this controller to it. It seems that in that case it can directly power controller from 12VDC input of RS485 and looks neat. They also have RS485 in other forms for other controllers, also with 12-24VDC input to power controller.

Connector does not look like JST VH one. Here is the spec for VH connector. Note how bumps are on the outside edge while Navien connector has bumps little closer to the center of connector.

I realize now what you were saying about the 5vdc scenario. I remembered, I purchased this adapter instead. This is actually what is in the circuit now.

image

The link to this device is here.

I will update the original post to reflect this difference.

1 Like

Here is the pinout I have.

image

1 Like

@kkopachev - true, not the one. How about the JST XHB?

image

I can’t find dimensional drawings, but this picture looks really similar. I need to measure the pitch of the actual connector… that would help narrow down the search.

1 Like

@tsquared - your post about where to clip the thread so it can be moved to your other post that is in the esphome section seems to reference only that other thread. If you can correct that there is a chance @tom_l can fix it for is.

@aruffell Good catch! Apparently too much copying and pasting lately! :slight_smile: Thanks for the heads up. It has been modified to reflect the correct link.

@tsquared - I believe the two threads were merged. The order may be out of sequence, not sure, but at least it is all here.

Posts are definitely out of order. I wonder if the admins can can re-index them by date? Gonna flag this thread to bring it to their attention.

1 Like

Appreciate the direct links to those controllers! I love how this RS485 controller steps down voltage to 5v for the ESP. Just ordered mine, will arrive in a few weeks.

I’m eyeing these JST XHB connectors on aliexpress. Going with XHB over the XH to ensure a nice secure fit. Can always snip off the latching tab anyway. Likely ordering the 5-pin model in 300mm length, since I’d much rather cut & re-strip than try extending short wires.

Are we handling enough current here that wire gauge matters? Options are 22, 24, and 26awg. Will probably get the 22 since it’s better to err on the side of thicker wire.

Edit to answer my own question about wire gauges and current. This wire gauge chart outlines the maximum amperage for power transmission by gauge:

Gauge Max Amps
22 awg 920 mA
24 awg 577 mA
26 awg 361 mA

ESP32 needs 5v @ 500mA, so the 26awg could overheat and melt. 24awg should work, but it’s probably best to spend an extra $0.27 on the 22awg. Ordering now :slight_smile:

@kkopachev - Nice solution! I was looking at the compatible Atom controllers and the gray one is the one to go with for me as it can operate up to 60C, while the white Atom S3 Lite only reaches 40C. My attic easily exceeds 40C in the summer.

Based on posts above, it appears the Navien can supply 12V, not 5V. How are you powering the Atom? Did you add a 12V to 5V buck converter?

Edit: Never mind, I just noticed the RS485 part does the 12V to 5V

But supplied voltage is 12v from the heater, so less current. I guess any gauge can work

Has anyone created a Grafana Dashboard and docker container for all this yet?

@kkopachev - Would you mind sharing the yaml for the M5Stack RS485 base and the Atom? I purchased the same hardware. Thanks!

I am using one posted above, just with slight changes for board and tx/rx pins. Also added LED just for testing, does not have practical value since board is hidden inside heater.

esp32:
  board: m5stack-atom
  framework:
    type: arduino
uart:
  id: uart_bus
  tx_pin: 19
  rx_pin: 22
  ... the rest
Full yaml
esphome:
  name: navien-water-heater
  friendly_name: Navien Water Heater

esp32:
  board: m5stack-atom
  framework:
    type: arduino

# Enable Home Assistant API
api:
  encryption:
    key: "<redacted>"

ota:
  password: "<redacted>"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Navien-Water-Heater"
    password: "<redacted>"

captive_portal:


binary_sensor:
  - platform: status
    name: Status
    internal: True



# Enable logging
logger:
  level: DEBUG
  logs:
    uart.component: DEBUG
    sensor: NONE
  baud_rate: 0 #disable logging over uart


globals:
   - id: init_set_temp
     type: int
     restore_value: yes
     initial_value: '120'


light:
  - platform: fastled_clockless
    chipset: SK6812
    pin: 27
    num_leds: 1
    rgb_order: GRB
    name: "FastLED Light"     


#################################
#### UART SETUP/DECODE BYTES ####
#################################

uart:
  id: uart_bus
  tx_pin: 19
  rx_pin: 22
  baud_rate: 19200
  data_bits: 8
  stop_bits: 1
  parity: NONE
  debug:
    direction: BOTH
    dummy_receiver: True
    after:
      delimiter: "\n"
    sequence:
      - lambda: |-
          //
          // SPECIAL THANKS TO @suva on the HomeAssistant forum for Decoding these.
          // https://community.home-assistant.io/t/navien-hot-water-heater-navilink/330044/92
          //  
          //UARTDebug::log_string(direction, bytes);
          if (bytes[0] == 247 && bytes[1] == 5 && bytes[2] == 80 && bytes[3] == 80 && bytes[4] == 144 && bytes[5] == 34) {
          // Water information
          id(uart_water_byte0).publish_state(bytes[0]); //Common To all Packets
          id(uart_water_byte1).publish_state(bytes[1]); //Common To all Packets
          id(uart_water_byte2).publish_state(bytes[2]); //Packet id Byte 0
          id(uart_water_byte3).publish_state(bytes[3]); //Packet id Byte 1
          id(uart_water_byte4).publish_state(bytes[4]); //Packet id Byte 2
          id(uart_water_byte5).publish_state(bytes[5]); //Data Length
          id(uart_water_byte6).publish_state(bytes[6]); 
          id(uart_water_byte7).publish_state(bytes[7]);
          id(uart_water_byte8).publish_state(bytes[8]);
          id(uart_water_byte9).publish_state(bytes[9]); // System Power: high nibble: Unknown (values 0 and 0x20 observed); low nibble = 0=off 0x5=on
          id(uart_water_byte10).publish_state(bytes[10]);
          id(set_temp_read).publish_state((bytes[11]/2)); //Set temp - measured in 0.5 degrees C.
          id(outlet_temp).publish_state((bytes[12]/2));  //Outlet temp - measured in 0.5 degrees C.
          id(inlet_temp).publish_state((bytes[13]/2)); //Inlet temp - measured in 0.5 degrees C.
          id(uart_water_byte14).publish_state(bytes[14]);
          id(uart_water_byte15).publish_state(bytes[15]);
          id(uart_water_byte16).publish_state(bytes[16]);
          id(uart_water_byte17).publish_state(bytes[17]);
          id(water_flow_lpm).publish_state(bytes[18] / 10); //Flow rate - measured in 0.1 liters per minute (divide by 10 to get LPM)
          id(uart_water_byte19).publish_state(bytes[19]);
          id(uart_water_byte20).publish_state(bytes[20]);
          id(uart_water_byte21).publish_state(bytes[21]);
          id(uart_water_byte22).publish_state(bytes[22]);
          id(uart_water_byte23).publish_state(bytes[23]);
          id(uart_water_byte24).publish_state(bytes[24]); //System status. Probably a bitwise field. Partially decoded: display units: 0x08 position: 1=metric 0=imperial. 0x02 position: 1=weekly 0=hotbutton
          id(uart_water_byte25).publish_state(bytes[25]);
          id(uart_water_byte26).publish_state(bytes[26]);
          id(uart_water_byte27).publish_state(bytes[27]);
          id(uart_water_byte28).publish_state(bytes[28]);
          id(uart_water_byte29).publish_state(bytes[29]);
          id(uart_water_byte30).publish_state(bytes[30]);
          id(uart_water_byte31).publish_state(bytes[31]);
          id(uart_water_byte32).publish_state(bytes[32]);
          id(uart_water_byte33).publish_state(bytes[33]);
          id(uart_water_byte34).publish_state(bytes[34]);
          id(uart_water_byte35).publish_state(bytes[35]);
          id(uart_water_byte36).publish_state(bytes[36]);
          id(uart_water_byte37).publish_state(bytes[37]);
          id(uart_water_byte38).publish_state(bytes[38]);
          id(uart_water_byte39).publish_state(bytes[39]);
          id(uart_water_byte40).publish_state(bytes[40]);          
          }
          else if (bytes[0] == 247 && bytes[1] == 5 && bytes[2] == 80 && bytes[3] == 15 && bytes[4] == 144 && bytes[5] == 42) {
          // Gas information
          id(uart_gas_byte0).publish_state(bytes[0]); //Common to All Packets
          id(uart_gas_byte1).publish_state(bytes[1]); //Common to All Packets
          id(uart_gas_byte2).publish_state(bytes[2]); //Packet id Byte 0
          id(uart_gas_byte3).publish_state(bytes[3]); //Packet id Byte 1
          id(uart_gas_byte4).publish_state(bytes[4]); //Packet id Byte 2
          id(uart_gas_byte5).publish_state(bytes[5]); //Data Length
          id(uart_gas_byte6).publish_state(bytes[6]);
          id(uart_gas_byte7).publish_state(bytes[7]);
          id(uart_gas_byte8).publish_state(bytes[8]);
          id(uart_gas_byte9).publish_state(bytes[9]);
          id(uart_gas_byte10).publish_state(bytes[10]);
          id(uart_gas_byte11).publish_state(bytes[11]);
          id(uart_gas_byte12).publish_state(bytes[12]);
          id(uart_gas_byte13).publish_state(bytes[13]);                    
          id(uart_gas_byte14).publish_state(bytes[14]);
          id(uart_gas_byte15).publish_state(bytes[15]);
          id(uart_gas_byte16).publish_state(bytes[16]);
          id(uart_gas_byte17).publish_state(bytes[17]);
          id(uart_gas_byte18).publish_state(bytes[18]);
          id(uart_gas_byte19).publish_state(bytes[19]);
          id(uart_gas_byte20).publish_state(bytes[20]);
          id(uart_gas_byte21).publish_state(bytes[21]);
          id(low_byte_kcal).publish_state(bytes[22]); //Low byte current gas usage in kcal
          id(high_byte_kcal).publish_state(bytes[23]); //High byte current gas usage in kcal
          id(total_gas_use_m3).publish_state(bytes[24] / 10); //Total gas usage in 0.1m^3 (divide by 10 to get m^3)
          id(uart_gas_byte25).publish_state(bytes[25]);
          id(uart_gas_byte26).publish_state(bytes[26]);
          id(uart_gas_byte27).publish_state(bytes[27]);
          id(uart_gas_byte28).publish_state(bytes[28]);
          id(uart_gas_byte29).publish_state(bytes[29]);
          id(uart_gas_byte30).publish_state(bytes[30]);
          id(uart_gas_byte31).publish_state(bytes[31]);
          id(uart_gas_byte32).publish_state(bytes[32]);
          id(uart_gas_byte33).publish_state(bytes[33]);
          id(uart_gas_byte34).publish_state(bytes[34]);
          id(uart_gas_byte35).publish_state(bytes[35]);
          id(uart_gas_byte36).publish_state(bytes[36]);
          id(uart_gas_byte37).publish_state(bytes[37]);
          id(uart_gas_byte38).publish_state(bytes[38]);
          id(uart_gas_byte39).publish_state(bytes[39]);
          id(uart_gas_byte40).publish_state(bytes[40]);          
          } else {
          // Handle other cases
          }
      

###############################
#### ENABLE/DISABLE HEATER ####
###############################

switch:
  - platform: template
    name: "HW Heater On/Off"
    id: navien_switch
    restore_mode: ALWAYS_ON
    icon: mdi:water-boiler
    lambda: |-
      if ((id(uart_water_byte9).state) == 5) {
        return true;
        id(navien_switch).publish_state(true);
      } else {
        return false;
        id(navien_switch).publish_state(false);
      }
    turn_on_action:
      - uart.write: !lambda 
          return {0xf7, 0x05, 0x0f, 0x50, 0x10, 0x0c, 0x4f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xce};
          id(navien_switch).publish_state(true);
    turn_off_action:
      - uart.write: !lambda 
          return {0xf7, 0x05, 0x0f, 0x50, 0x10, 0x0c, 0x4f, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa};
          id(navien_switch).publish_state(false);
  
  - platform: template
    name: "Fake Thermostat Switch"
    id: fake_switch
    restore_mode: ALWAYS_ON
    optimistic: True
    internal: True


####################################################################
#### SETPOINT ENTRY TEMPLATE  - STILL WORKING ON THIS 4/29/2024 ####
####################################################################

climate:
  - platform: thermostat
    name: "Climate Controller"
    visual:
      min_temperature: 120 °F
      max_temperature: 140 °F
      temperature_step: 1.0 °F
    sensor: outlet_temp
    min_heating_off_time: 300s
    min_heating_run_time: 300s
    min_idle_time: 30s
    heat_action:
      - switch.turn_on: fake_switch
    idle_action:
      - switch.turn_off: fake_switch

################################
#### WATER TEMPLATE SENSORS ####
################################
sensor:
  - platform: template
    name: "WByte_0"
    id: uart_water_byte0
    internal: True
  - platform: template
    name: "WByte_01"
    id: uart_water_byte1
    internal: True
  - platform: template
    name: "WByte_02"
    id: uart_water_byte2
    internal: True
  - platform: template
    name: "WByte_03"
    id: uart_water_byte3
    internal: True
  - platform: template
    name: "WByte_04"
    id: uart_water_byte4
    internal: True
  - platform: template
    name: "WByte_05"
    id: uart_water_byte5
    internal: True
  - platform: template
    name: "WByte_06"
    id: uart_water_byte6
    internal: True
  - platform: template
    name: "WByte_07"
    id: uart_water_byte7
    internal: True
  - platform: template
    name: "WByte_08"
    id: uart_water_byte8
    internal: True
  - platform: template
    name: "WByte_09"
    id: uart_water_byte9
    internal: true
  - platform: template
    name: "WByte_10"
    id: uart_water_byte10
    internal: True
  - platform: template
    name: "Temp Set - Read"
    id: set_temp_read
    icon: mdi:thermometer-water
    unit_of_measurement: °C
    accuracy_decimals: 1
    device_class: "temperature"
    state_class: "measurement"
  - platform: template
    name: "Temp Outlet"
    id: outlet_temp
    icon: mdi:thermometer-water
    unit_of_measurement: °C
    accuracy_decimals: 1
    device_class: "temperature"
    state_class: "measurement"
  - platform: template
    name: "Temp Inlet"
    id: inlet_temp
    icon: mdi:thermometer-water
    unit_of_measurement: °C
    accuracy_decimals: 1
    device_class: "temperature"
    state_class: "measurement"
  - platform: template
    name: "WByte_14"
    id: uart_water_byte14
    internal: True
  - platform: template
    name: "WByte_15"
    id: uart_water_byte15
    internal: True
  - platform: template
    name: "WByte_16"
    id: uart_water_byte16
    internal: True
  - platform: template
    name: "WByte_17"
    id: uart_water_byte17
    internal: True
  - platform: template
    name: "Water Flow LPM"
    id: water_flow_lpm #multiply by 0.2642 to get GPM
    icon: mdi:water-percent
    unit_of_measurement: l/min
  - platform: template
    name: "Water Flow GPM"
    id: water_flow_gpm #multiply by 0.2642 to get GPM
    icon: mdi:water-percent
    unit_of_measurement: GPM
    lambda: |-
      return (id(water_flow_lpm).state * 0.2642);
  - platform: template
    name: "WByte_19 Unknown Flow"
    id: uart_water_byte19
    # internal: True
  - platform: template
    name: "WByte_20"
    id: uart_water_byte20
    internal: True
  - platform: template
    name: "WByte_21"
    id: uart_water_byte21
    internal: True
  - platform: template
    name: "WByte_22"
    id: uart_water_byte22
    internal: True
  - platform: template
    name: "WByte_23"
    id: uart_water_byte23
    internal: True
  - platform: template
    name: "WByte_24 Sys Status"
    id: uart_water_byte24
    # internal: True
  - platform: template
    name: "WByte_25"
    id: uart_water_byte25
    internal: True
  - platform: template
    name: "WByte_26"
    id: uart_water_byte26
    internal: True
  - platform: template
    name: "WByte_27"
    id: uart_water_byte27
    internal: True
  - platform: template
    name: "WByte_28"
    id: uart_water_byte28
    internal: True
  - platform: template
    name: "WByte_29"
    id: uart_water_byte29
    internal: True
  - platform: template
    name: "WByte_30"
    id: uart_water_byte30
    internal: True
  - platform: template
    name: "WByte_31"
    id: uart_water_byte31
    internal: True
  - platform: template
    name: "WByte_32"
    id: uart_water_byte32
    internal: True
  - platform: template
    name: "WByte_33"
    id: uart_water_byte33
    internal: True
  - platform: template
    name: "WByte_34"
    id: uart_water_byte34
    internal: True
  - platform: template
    name: "WByte_35"
    id: uart_water_byte35
    internal: True
  - platform: template
    name: "WByte_36"
    id: uart_water_byte36
    internal: True
  - platform: template
    name: "WByte_37"
    id: uart_water_byte37
    internal: True
  - platform: template
    name: "WByte_38"
    id: uart_water_byte38
    internal: True
  - platform: template
    name: "WByte_39"
    id: uart_water_byte39
    internal: True
  - platform: template
    name: "WByte_40"
    id: uart_water_byte40
    internal: True
##############################
#### GAS TEMPLATE SENSORS ####
##############################
  - platform: template
    name: "GByte_0"
    id: uart_gas_byte0
    internal: True
  - platform: template
    name: "GByte_01"
    id: uart_gas_byte1
    internal: True
  - platform: template
    name: "GByte_02"
    id: uart_gas_byte2
    internal: True
  - platform: template
    name: "GByte_03"
    id: uart_gas_byte3
    internal: True
  - platform: template
    name: "GByte_04"
    id: uart_gas_byte4
    internal: True
  - platform: template
    name: "GByte_05"
    id: uart_gas_byte5
    internal: True
  - platform: template
    name: "GByte_06"
    id: uart_gas_byte6
    internal: True
  - platform: template
    name: "GByte_07"
    id: uart_gas_byte7
    internal: True
  - platform: template
    name: "GByte_08"
    id: uart_gas_byte8
    internal: True
  - platform: template
    name: "GByte_09"
    id: uart_gas_byte9
    internal: true
  - platform: template
    name: "GByte_10"
    id: uart_gas_byte10
    internal: True
  - platform: template
    name: "GByte_11"
    id: uart_gas_byte11
    internal: True
  - platform: template
    name: "GByte_12"
    id: uart_gas_byte12
    internal: True
  - platform: template
    name: "GByte_13"
    id: uart_gas_byte13
    internal: True
  - platform: template
    name: "GByte_14"
    id: uart_gas_byte14
    internal: True
  - platform: template
    name: "GByte_15"
    id: uart_gas_byte15
    internal: True
  - platform: template
    name: "GByte_16"
    id: uart_gas_byte16
    internal: True
  - platform: template
    name: "GByte_17"
    id: uart_gas_byte17
    internal: True
  - platform: template
    name: "GByte_18"
    id: uart_gas_byte18
    internal: True
  - platform: template
    name: "GByte_19"
    id: uart_gas_byte19
    internal: True
  - platform: template
    name: "GByte_20"
    id: uart_gas_byte20
    internal: True
  - platform: template
    name: "GByte_21"
    id: uart_gas_byte21
    internal: True
  - platform: template
    name: "GByte_22"
    id: uart_gas_byte22
    internal: True
  - platform: template
    name: "GByte_23"
    id: uart_gas_byte23
    internal: True
  - platform: template
    name: "GByte_24"
    id: uart_gas_byte24
    internal: True
  - platform: template
    name: "GByte_25"
    id: uart_gas_byte25
    internal: True
  - platform: template
    name: "GByte_26"
    id: uart_gas_byte26
    internal: True
  - platform: template
    name: "GByte_27"
    id: uart_gas_byte27
    internal: True
  - platform: template
    name: "GByte_28"
    id: uart_gas_byte28
    internal: True
  - platform: template
    name: "GByte_29"
    id: uart_gas_byte29
    internal: True
  - platform: template
    name: "GByte_30"
    id: uart_gas_byte30
    internal: True
  - platform: template
    name: "GByte_31"
    id: uart_gas_byte31
    internal: True
  - platform: template
    name: "GByte_32"
    id: uart_gas_byte32
    internal: True
  - platform: template
    name: "GByte_33"
    id: uart_gas_byte33
    internal: True
  - platform: template
    name: "GByte_34"
    id: uart_gas_byte34
    internal: True
  - platform: template
    name: "GByte_35"
    id: uart_gas_byte35
    internal: True
  - platform: template
    name: "GByte_36"
    id: uart_gas_byte36
    internal: True
  - platform: template
    name: "GByte_37"
    id: uart_gas_byte37
    internal: True
  - platform: template
    name: "GByte_38"
    id: uart_gas_byte38
    internal: True
  - platform: template
    name: "GByte_39"
    id: uart_gas_byte39
    internal: True
  - platform: template
    name: "GByte_40"
    id: uart_gas_byte40
    internal: True
  - platform: template
    name: "Total Gas Use Cubic Meters"
    id: total_gas_use_m3 # multiply by 0.3508 to get therms
    icon: mdi:gas-cylinder
    unit_of_measurement: m³
    accuracy_decimals: 1
  - platform: template
    name: "Total Gas Use Therms"
    id: total_gas_use_therms # multiply by 0.3508 to get therms
    icon: mdi:gas-cylinder
    unit_of_measurement: therms
    accuracy_decimals: 1
    lambda: |-
      return (id(total_gas_use_m3).state * 0.358);
  - platform: template
    name: "kCal Low Byte"
    id: low_byte_kcal
    icon: mdi:gas-burner
    unit_of_measurement: kCal
  - platform: template
    name: "kCal High Byte"
    id: high_byte_kcal
    icon: mdi:gas-burner
    unit_of_measurement: kCal
  - platform: template
    name: "Instant BTUs"
    id: instant_btus
    icon: mdi:gas-burner
    unit_of_measurement: "kbtu/hr"
    accuracy_decimals: 3
    state_class: "measurement"
    lambda: |-
      return max((id(outlet_temp).state - id(inlet_temp).state) * id(water_flow_gpm).state * 8.334 * 60 / 0.92 / 1000, 0.0);
  # calculated as shown here https://www.plctalk.net/threads/low-byte-high-byte.78315/
  - platform: template
    name: "Total kCal"
    id: total_kcal
    icon: mdi:gas-burner
    unit_of_measurement: kCal
    accuracy_decimals: 2
    state_class: "measurement"
    lambda: |-
      return (id(high_byte_kcal).state*256) + (id(low_byte_kcal).state);
1 Like

@kkopachev - Thanks! I was going down the same path but since mine is not installed yet, I was not seeing anything and wasn’t sure whether the RS485 base needed to be supported with some custom component given it also has i2c which we are not using. I wonder what it is for… will research.

I want to place the ESP outside the chassis, probably using a magnet to attach on a side, so I need a longer cable. I wish I could find the XHB connectors to crimp on amazon but no luck yet. Also, not sure I want a huge number for just one connector given I don’t typically use them.

Has some of this communication happened without an NaviLink in place?

My 10-pack of connectors with 300mm cables is currently crossing the pacific. Happy to send a couple your way once they land.