Making some progress here! Again - thanks to @suva for all his decoding! I started with some generic python code to make sure I could actually talk/receive information from the device. The code I wrote to receive the information from my equipment is below. I am certain that it’s not efficient, and likely doesn’t follow too many rules for coding. If anyone has any tips here they are appreciated!
import time
import serial
print ("Opening port")
try:
ser = serial.Serial('COM3',19200,bytesize=8,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0)
print ("Port is open")
except serial.SerialException:
serial.Serial('COM3', 19200).close()
print ("Port is closed")
serial_port = serial.Serial("COM3",19200)
print ("Port is open again")
print ("Ready to use")
buffer = bytearray(b'')
buffer_length = 99
buf_list = []
while True:
try:
buffer += ser.read()
if len(buffer) <= buffer_length:
buffer += buffer
elif len(buffer) >= buffer_length:
buf_list = (list(buffer))
# print(buf_list)
ser.flushInput()
buffer = bytearray(b'')
# print(buf_list[0:23])
if buf_list[5] == 34:
# print("Water Info Received!")
sys_power = buf_list[9]
sys_power_text = "Off"
set_temp = (((buf_list[11]/2)*9/5) + 32)
outlet_temp = (((buf_list[12]/2)*9/5) + 32)
inlet_temp = (((buf_list[13]/2)*9/5) + 32)
wtr_flow = round(((buf_list[18]/10)* 0.2642),2)
unknown_flow = buf_list[19]
print(" ")
print("*************Water Information**************")
print(" ")
if sys_power == 5:
sys_power_text= " On"
print("The Power is: ",sys_power_text)
elif sys_power ==0:
sys_power_text= " Off"
print("The Power is: ",sys_power_text)
else:
print("The Power is Unknown")
print("The Outlet Temp is: ",outlet_temp,"",u"\u00b0","F")
print("The Setpoint is: ", set_temp,u"\u00b0", "F")
print("The Water flow is: ",wtr_flow, "GPM")
print("The Inlet? Temp is: ",inlet_temp,"",u"\u00b0", "F")
print("The Unknown flow is:",unknown_flow, " GPM")
elif buf_list[5] == 42:
# print("Gas Info Received!")
print(" ")
print("**************Gas Information***************")
print(" ")
total_gas_use = round(((buf_list[24]/10)*0.3508),2)
low_byte_kcal = (buf_list[22])
high_byte_kcal = (buf_list[23])
total_kcal = high_byte_kcal + low_byte_kcal
print("Total Gas Usage is: ", total_gas_use, "therm")
print("Low Byte kCal is: ", low_byte_kcal, "kCal")
print("High Byte kCal is: ", high_byte_kcal, "kCal")
print("Total kCal: ", total_kcal, "kCal")
else:
pass
print("Bad Packet")
time.sleep(1)
except:
pass
This was successful enough to produce the following output.
*************Water Information**************
The Power is: On
The Outlet Temp is: 63.5 ° F
The Setpoint is: 140.0 ° F
The Water flow is: 0.0 GPM
The Inlet? Temp is: 68.9 ° F
The Unknown flow is: 0 GPM
**************Gas Information***************
Total Gas Usage is: 2.17 therm
Low Byte kCal is: 0 kCal
High Byte kCal is: 0 kCal
Total kCal: 0 kCal
So having this information I went towards my final goal of using ESPHome to create this similar interface. Below is the ESPHome Config.
# Include basic info in all ESP Home Configs
# Basic Info Includes
# logger:
# wifi:
# ap:
# web_server:
# captive_portal:
# button: to restart the device
<<: !include .base1.yaml
esphome:
name: navien
friendly_name: Navien Water Heater
includes:
- uart_read_line_sensor.h
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
level: NONE #makes uart stream available in esphome logstream
baud_rate: 0 #disable logging over uart
# Enable Home Assistant API
api:
encryption:
key: "redacted"
ota:
password: "redacted"
substitutions:
comment: "redacted"
friendly_name: Navien Water Heater
globals:
- id: byte_timer
type: int
initial_value: "1"
interval:
- interval: 5s
then:
- lambda: |-
id(byte_timer) = (id(byte_timer) + 1);
if (id(byte_timer) > 25) {
id(byte_timer) = 1;
}
# Example configuration entry
uart:
id: uart_bus
tx_pin: 1
rx_pin: 3
baud_rate: 19200
data_bits: 8
stop_bits: 1
parity: NONE
debug:
direction: BOTH
dummy_receiver: false
after:
delimiter: "\n"
sequence:
- lambda: |-
UARTDebug::log_string(direction, bytes);
//looking at this string f7 05 50 50 90 22 42
//where the 22 represents water information to follow
//and in the same byte if the number is 34 it is
//representative of gas information
if (bytes[0] == 247 && bytes[5] == 34) {
//id(uart_byte0).publish_state(bytes[0]);
//id(uart_byte1).publish_state(bytes[1]);
//id(uart_byte2).publish_state(bytes[2]);
//id(uart_byte3).publish_state(bytes[3]);
//id(uart_byte4).publish_state(bytes[4]);
//id(uart_byte5).publish_state(bytes[5]);
//id(uart_byte6).publish_state(bytes[6]);
//id(uart_byte7).publish_state(bytes[7]);
//id(uart_byte8).publish_state(bytes[8]);
//id(uart_byte9).publish_state(bytes[9]);
//id(uart_byte10).publish_state(bytes[10]);
id(set_temp).publish_state(((bytes[11]/2)*9/5) + 32);
id(outlet_temp).publish_state(((bytes[12]/2)*9/5) + 32);
id(inlet_temp).publish_state(((bytes[13]/2)*9/5) + 32);
//id(uart_byte14).publish_state(bytes[14]);
//id(uart_byte15).publish_state(bytes[15]);
//id(uart_byte16).publish_state(bytes[16]);
//id(uart_byte17).publish_state(bytes[17]);
id(water_flow).publish_state((bytes[18]/10)* 0.2642);
//id(uart_byte19).publish_state(bytes[19]);
//id(uart_byte20).publish_state(bytes[20]);
//id(uart_byte21).publish_state(bytes[21]);
//id(uart_byte22).publish_state(bytes[22]);
//id(uart_byte23).publish_state(bytes[23]);
//id(uart_byte24).publish_state(bytes[24]);
} else if (bytes[0] == 247 && bytes[5] == 42) {
id(low_byte_kcal).publish_state(bytes[22]);
id(high_byte_kcal).publish_state(bytes[23]);
id(total_gas_use).publish_state((bytes[24]/10)*0.3508);
} else (bytes[0] != 247); {
}
# Example configuration entry
switch:
- platform: template
name: "HW Heater On/Off"
id: navien_switch
restore_mode: ALWAYS_ON
lambda: |-
if ((id(navien_switch).state) != (id(navien_switch).state)) {
return true;
} else {
return 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);
text_sensor:
- platform: custom
lambda: |-
auto my_custom_sensor = new UartReadLineSensor(id(uart_bus));
App.register_component(my_custom_sensor);
return {my_custom_sensor};
text_sensors:
id: "uart_readline"
text:
- platform: template
name: "Temp Set - Write"
optimistic: true
min_length: 0
max_length: 5
mode: text
on_value:
then:
- sensor.template.publish:
id: num_from_text
state: !lambda |-
auto n = parse_number<float>(x);
return n.has_value() ? n.value() : NAN;
sensor:
# - platform: template
# name: "Byte 0"
# id: uart_byte0
# - platform: template
# name: "Byte 1"
# id: uart_byte1
# - platform: template
# name: "Byte 2"
# id: uart_byte2
# - platform: template
# name: "Byte 3"
# id: uart_byte3
# - platform: template
# name: "Byte 4"
# id: uart_byte4
# - platform: template
# name: "Byte 5"
# id: uart_byte5
# - platform: template
# name: "Byte 6"
# id: uart_byte6
# - platform: template
# name: "Byte 7"
# id: uart_byte7
# - platform: template
# name: "Byte 8"
# id: uart_byte8
# - platform: template
# name: "Byte 9"
# id: uart_byte9
# - platform: template
# name: "Byte 10"
# id: uart_byte10
- platform: template
name: "Temp Set - Read"
id: set_temp
unit_of_measurement: °F
- platform: template
name: "Temp Outlet"
id: outlet_temp
unit_of_measurement: °F
accuracy_decimals: 2
- platform: template
name: "Temp Inlet"
id: inlet_temp
unit_of_measurement: °F
# - platform: template
# name: "Byte 14"
# id: uart_byte14
# - platform: template
# name: "Byte 15"
# id: uart_byte15
# - platform: template
# name: "Byte 16"
# id: uart_byte16
# - platform: template
# name: "Byte 17"
# id: uart_byte17
- platform: template
name: "Water Flow"
id: water_flow
unit_of_measurement: GPM
# - platform: template
# name: "Byte 19"
# id: uart_byte19
# - platform: template
# name: "Byte 20"
# id: uart_byte20
# - platform: template
# name: "Byte 21"
# id: uart_byte21
# - platform: template
# name: "Byte 22"
# id: uart_byte22
# - platform: template
# name: "Byte 23"
# id: uart_byte23
# - platform: template
# name: "Byte 24"
# id: uart_byte24
- platform: template
name: "Total Gas Use"
id: total_gas_use
unit_of_measurement: therms
accuracy_decimals: 2
- platform: template
name: "kCal Low Byte"
id: low_byte_kcal
unit_of_measurement: kCal
- platform: template
name: "kCal High Byte"
id: high_byte_kcal
unit_of_measurement: kCal
- platform: template
id: num_from_text
name: "Temp Set - Write"
internal: True
This seems to produce similar results as what I was seeing with the python code.
It looks like this:
I’m struggling with a few things here that I could use some tips on.
-
When using the switch I created in ESPHome to turn on/off the Heater, ideally I’d like the switch to set it’s position based on the current status of the unit, then if the switch changes - send the appropriate command. Last, I’d like to know that the command was received and that it has gone into that mode.
-
Similar question regarding the setpoint. In my config, I created a second “write” setpoint to try and begin configuring this function - but I’m not sure where to go from here.
In both of those cases, I’d like to have a single value/entity show on the ESPHome web page or entity in HA.
Any and all feedback is appreciated if you see something else here that would make this project better!
Hope this helps someone as they dive into their journey of doing the same!