Anybody else get their RS485 ESP32 hardware yet?
I've got everything working except vacation mode.
Anybody else get their RS485 ESP32 hardware yet?
I've got everything working except vacation mode.
My stuff hasn't arrived yet. Looking forward to getting it working!
Didn’t receive mine either. Shipping estimate was out a bit, so I don’t expect to see anything before a couple of weeks…
I am really looking forward to getting it though!!!
Same here
Thank you for your service sir, the case of those strings threw me off. This is great work!
Just got my shipment notice from m5-stack, so I’m guessing it’s 10-14 days out. Psyched to try this out.
Same here. I guess they might have shipped them at the same time!
@klinquist, I have updated the app to support the use of either Fahrenheit or Celsius. Is there any interest in adding this to the main code?
/**
* Rheem EcoNet Water Heater
*
* Originally by Dominick Meglio
* Forked by Kris Linquist in 2022
*
*/
preferences {
input("tempUnit", "enum", options:["C", "F"], defaultValue:"C", title: "Unit", description: "The unit to use for temperature", required: true, displayDuringSetup: true)
}
metadata {
definition (name: "Rheem Econet Water Heater (New)", namespace: "klinquist.rheem", author: "kris@linquist.net") {
capability "Initialize"
capability "Thermostat"
capability "Actuator"
capability "Sensor"
capability "ThermostatHeatingSetpoint"
capability "ThermostatSetpoint"
capability "ThermostatOperatingState"
capability "ThermostatMode"
command "setWaterHeaterMode", [[name:"Mode*","type":"ENUM","description":"Mode","constraints":["Heat Pump", "Energy Saver", "High Demand", "Normal", "Vacation", "Off"]]]
attribute "waterHeaterMode", "ENUM"
//attribute "waterHeaterMode", "ENUM", ["Heat Pump", "Energy Saver", "High Demand", "Normal", "Vacation", "Off"]
}
}
import groovy.transform.Field
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import java.text.SimpleDateFormat
@Field static String apiUrl = "ssl://rheem.clearblade.com:1884"
@Field static String systemKey = "e2e699cb0bb0bbb88fc8858cb5a401"
@Field static String systemSecret = "E2E699CB0BE6C6FADDB1B0BC9A20"
def installed() {
initialize()
}
def updated() {
initialize()
}
def mqttConnectUntilSuccessful() {
try {
interfaces.mqtt.connect(apiUrl, parent.getClientId(), parent.getAccessToken(), systemKey, cleanSession: false)
pauseExecution(3000)
interfaces.mqtt.subscribe("user/${parent.getAccountId()}/device/reported", 2)
interfaces.mqtt.subscribe("user/${parent.getAccountId()}/device/desired", 2)
if (state.queuedMessage != null) {
interfaces.mqtt.publish("user/${parent.getAccountId()}/device/desired", state.queuedMessage)
state.queuedMessage = null
}
return true
}
catch (e)
{
log.warn "Lost connection to MQTT, retrying in 15 seconds"
runIn(15, "mqttConnectUntilSuccessful")
return false
}
}
def initialize() {
if (device.getDataValue("enabledDisabled") == "true")
sendEvent(name: "supportedThermostatModes", value: ["off", "heat"])
else if (device.getDataValue("tempOnly") == "true")
sendEvent(name: "supportedThermostatModes", value: [])
else
sendEvent(name: "supportedThermostatModes", value: ["off", "heat", "emergency heat", "auto"])
sendEvent(name: "supportedThermostatFanModes", value: [])
if (interfaces.mqtt.isConnected())
interfaces.mqtt.disconnect()
mqttConnectUntilSuccessful()
}
def publishWithRetry(msg) {
def payload = buildMQTTMessage()
for (property in msg.keySet()) {
payload."$property" = msg[property]
}
if (interfaces.mqtt.isConnected()) {
interfaces.mqtt.publish("user/${parent.getAccountId()}/device/desired", JsonOutput.toJson(payload))
}
else {
log.error "Not connected to MQTT, reconnecting and queuing command"
state.queuedMessage = JsonOutput.toJson(payload)
mqttConnectUntilSuccessful()
}
}
def mqttClientStatus(String message) {
if (message == "Status: Connection succeeded") {
parent.logDebug "Connected to MQTT"
}
else if (message.contains("Connection lost") || message.contains("Client is not connected") || message.startsWith("Error:")) {
parent.logDebug "Lost MQTT connection, reconnecting."
try {
interfaces.mqtt.disconnect() // Guarantee we're disconnected
}
catch (e) {
}
mqttConnectUntilSuccessful()
}
else
log.warn "Status: " + message
}
def parse(String message) {
def topic = interfaces.mqtt.parseMessage(message)
log.info topic
def payload = new JsonSlurper().parseText(topic.payload)
if ("rheem:" + payload?.device_name + ":" + payload?.serial_number == device.deviceNetworkId) {
parent.logDebug "MQTT Message was: ${topic.payload}"
if (payload."@SETPOINT" != null) {
//Conver to Celcius if unit is "C"
def setPointTemp = payload."@SETPOINT"
if (tempUnit=="C")
setPointTemp = (setPointTemp-32)*5/9
setPointTemp = Math.round(setPointTemp)
setPointTemp = setPointTemp.toInteger()
device.sendEvent(name: "heatingSetpoint", value: setPointTemp, unit: tempUnit)
device.sendEvent(name: "thermostatSetpoint", value: setPointTemp, unit: tempUnit)
device.sendEvent(name: "unit", value: tempUnit)
}
if (device.getDataValue("enabledDisabled") == "true" && payload."@ACTIVE" != null) {
def mode = payload."@ACTIVE" == true ? "heat" : "off"
device.sendEvent(name: "thermostatMode", value: mode)
}
if (payload."@MODE" != null) {
if (!payload."@MODE".toString().isInteger()) {
device.sendEvent(name: "thermostatMode", value: parent.translateThermostatMode(payload."@MODE".status))
device.sendEvent(name: "waterHeaterMode", value: payload."@MODE".status)
}
else {
def mode = translateEnumToWaterHeaderMode(payload."@MODE")
device.sendEvent(name: "thermostatMode", value: parent.translateThermostatMode(mode))
device.sendEvent(name: "waterHeaterMode", value: mode)
}
}
if (payload."@RUNNING" != null) {
def isRunning = payload."@RUNNING".toUpperCase().contains("RUNNING")
device.sendEvent(name: "thermostatOperatingState", value: isRunning ? "heating" : "idle")
}
}
}
def getDeviceName() {
return device.deviceNetworkId.split(':')[1]
}
def getSerialNumber() {
return device.deviceNetworkId.split(':')[2]
}
def buildMQTTMessage() {
def sdf = new SimpleDateFormat("Y-M-d'T'H:m:s.S")
def payload = [
transactionId: "ANDROID_"+sdf.format(new Date()),
device_name: getDeviceName(),
serial_number: getSerialNumber()
]
return payload
}
def setCoolingSetpoint(temperature) {
log.error "setCoolingSetpoint called but not supported"
}
def setSchedule(obj) {
log.error "setSchedule called but not supported"
}
def setThermostatFanMode(fanmode) {
log.error "setThermostatFanMode called but not supported"
}
def setHeatingSetpoint(temperature) {
//Conver back to Farenhite if unit is "C"
if (tempUnit=="C")
temperature=(temperature*9/5)+32
def minTemp = new BigDecimal(device.getDataValue("minTemp"))
def maxTemp = new BigDecimal(device.getDataValue("maxTemp"))
if (temperature < minTemp)
temperature = minTemp
else if (temperature > maxTemp)
temperature = maxTemp
publishWithRetry(["@SETPOINT": temperature])
}
def setThermostatMode(thermostatmode) {
if (device.getDataValue("enabledDisabled") == "true") {
def onOff = thermostatmode == "off" ? 0 : 1
publishWithRetry(["@ENABLED": onOff])
}
else if (device.getDataValue("tempOnly") != "true") {
publishWithRetry(["@MODE": translateThermostatModeToEnum(thermostatmode)])
}
else
log.error "setThermostatMode called but not supported"
}
def setWaterHeaterMode(waterheatermode) {
if (device.getDataValue("enabledDisabled") == "true") {
def onOff = thermostatmode == "off" ? 0 : 1
publishWithRetry(["@ENABLED": onOff])
}
else if (device.getDataValue("tempOnly") != "true") {
if (waterheatermode != "Vacation") {
log.debug "Setting away mode to false and waiting 15 seconds"
parent.setAwayMode(false)
pauseExecution(15000)
}
log.debug "Setting mode to ${waterheatermode}"
def onOff = thermostatmode == "off" ? 0 : 1
publishWithRetry(["@ENABLED": onOff, "@MODE": translateWaterHeaterModeToEnum(waterheatermode)])
}
else
log.error "setWaterHeaterMode called but not supported"
}
def translateWaterHeaterModeToEnum(waterheatermode) {
switch (waterheatermode) {
case "Off":
return 0
case "Energy Saver":
return 1
case "Heat Pump":
return 2
case "High Demand":
return 3
case "Normal":
return 4
case "Vacation":
return 5
}
}
def translateEnumToWaterHeaderMode(enumVal) {
switch (enumVal) {
case 0:
return "OFF"
case 1:
return "ENERGY SAVING"
case 2:
return "HEAT PUMP ONLY"
case 3:
return "HIGH DEMAND"
case 4:
return "ELECTRIC"
case 5:
return "VACATION"
}
}
def translateThermostatModeToEnum(waterheatermode) {
switch (waterheatermode) {
case "off":
return 0
case "auto":
return 1
case "heat":
if (parent.hasHeatPump(device))
return 2
return 4
case "emergency heat":
return 3
}
}
def auto() {
if (device.getDataValue("tempOnly") != "true") {
setThermostatMode("auto")
}
else
log.error "auto called but not supported"
}
def emergencyHeat() {
if (device.getDataValue("tempOnly") != "true") {
setThermostatMode("emergency heat")
}
else
log.error "emergencyHeat called but not supported"
}
def off() {
if (device.getDataValue("tempOnly") != "true") {
setThermostatMode("off")
}
else
log.error "off called but not supported"
}
def heat() {
if (device.getDataValue("tempOnly") != "true") {
setThermostatMode("heat")
}
else
log.error "heat called but not supported"
}
def cool() {
log.error "cool called but not supported"
}
def fanAuto() {
log.error" fanAuto called but not supported"
}
def fanCirculate() {
log.error" fanCirculate called but not supported"
}
def fanOn() {
log.error" fanOn called but not supported"
}
Yes, could you open up a pull request?
Will do!
Thanks @Sebastien I made a few changes and merged it in.
Awesome! Thank you!
I was wondering the other day. Is there a way to get the energy consumption data from it? We can see in the app and I keep pulling it manually. Would be nice if it could be polled…
I haven’t checked yet to see if they have an API…
I have power usage as part of the ESPHome implementation
Can’t wait for the hardware to arrrive. I’ve got that itch. @klinquist, did you ever get Vacation mode working too?
I just got my M5 hardware today. I hope to get it setup asap...
I got my ESP M5 hardware connected with home assistant. Have you published a Hubitat driver? If so, I am willing to try it out.
It appears the latest 1.0.6 HPM update is not loading. I got some error message that said I needed to check that the package was in use in devices. It seems unusual for HPM that I would have to delete the device to update the App/Driver. I tried an HPM Repair but got the same result.
LJ
Directions for M5 Hardware
Installation of the hardware can be found here .
And you use this page to configure it for your wifi
Then....
api:
section. This removes the encryption.Weird. Maybe you could try manually updating?
Go into Drivers Code, find the Rheem entry, paste this in and hit Save. https://raw.githubusercontent.com/klinquist/hubitat-rheem/main/drivers/Rheem_EcoNet_Water_Heater.groovy
OK, I know almost nothing about Home Assistant. Where do I get the ESPHome yaml code?
Without editing the Home Assistant Yaml code, it appears that I am communicating with the ESP and able to change states on the water heater. I am also getting updates from it. But I do have some messages in the log that appear there might be issues...
dev:992024-02-29 09:18:06.297 AMwarn[W][component:215]: Components should block for at most 20-30ms.
dev:992024-02-29 09:18:06.245 AMwarn[W][component:214]: Component econet took a long time for an operation (0.05 s).
dev:992024-02-29 09:17:36.987 AMwarnESPHome received unexpected message type #25 (expected #47)
dev:992024-02-29 09:17:36.956 AMwarnESPHome received unexpected message type #25 (expected #47)
dev:992024-02-29 09:17:36.922 AMwarnESPHome received unexpected message type #25 (expected #47)