Oh great, another thermostat…
But what is a thermostat? Once upon a time, it was simply a device to open or close a circuit to activate a heating device. Then we started adding more things to out HVAC systems and thus more wires 2 became 4 became 5 became 8. Now were back to 2 or 3 wires with these new communicating systems that don’t simply turn on or off but might be trying to run at a variable state to reduce energy consumption and increase comfort.
My specific living situation has me relying on a propane furnace central air system running MODBUS as the thermostat communication protocol. Because I’d rather not use all of my propane as fast as my furnace alone can I also have regular wall plug space heaters.
How can I control 2 completely separate heating devices using one temperature sensor(I live in a very small space and have no need for multiple sensors or zone control).
Easy I thought, some wifi wall plugs(rated for my space heaters amperage) and some way to control the Modbus signal. Luckily my Modbus system(Dometic) is only communicating from the thermostat to the system so I don’t have to worry about errors coming back to the thermostat or anything like that.
A simple D1 Mini board is able to control the modbus signal that has been recorded from my thermostat, and relay the temperature sensor data to home assistant, home assistant is able to take the temperature sensor data and act as the actual thermostat controlling the space heater plugs via the normal switch’s as well as control the modbus signal as if it where switches also because that’s how the d1 mini is setup.
Originally I wasn’t using Home Assistant because anything I owned that integrated I just had on my iPhone, Ive been aware of Home Assistant and following its progression since 2021 I believe. Because I wasn’t on home assistant I originally tried to just make the switches work with the Arduino(d1 mini(I was testing with allot of microcontrollers)) and have the Arduino be the thermostat. I soon realized that on such a small scale if I had to deal with MQTT or anything like that I might as well use home assistant and have access to my thermostat over the internet.
ESPHOME, YAML, and LAMBDA
These are some amazing tools immediately squashed by an inability to delayMicroseconds
that’s all ok unless your modbus control signal looks like this
digitalWrite(AC_signal_genPIN, LOW); // sets the digital pin AC_signal_genPIN on
delayMicroseconds(500);
digitalWrite(AC_signal_genPIN, HIGH); // sets the digital pin AC_signal_genPIN off
delayMicroseconds(1000);
repeating with many delayMicroseconds.
Solution
BACK TO MQTT
so well go back to the Arduino.ino setup the d1 mini as an mqtt wifi device, assign our digitalWrite outputs for all the different central hvac system operations as switches(also assigning them as switches in home assistant). Relay our temp/humidity sensor data to the home assistant(it also has a screen but who cares when my phone can tell me).
Ok but now I only have a current temperature and a bunch of switches on my home assistant. Nothing controlling/automating anything.
Solution!
generic_thermostat !?!?!
No. Why? Only offers control over one heating OR cooling device, not both and not in a heating primary/auxiliary configuration like most modern heat pumps or anyone with two controlled heat sources.
Next Solution?
HACS
Home Assistant Dual Smart Thermostat component
This combines all of the HVAC operations into one card with one setpoint even allowing for way more control over zones and active statuses(not my concern). But what about Auxilary heat? This is once again where the water got dirty. Although “Home Assistant Dual Smart Thermostat component” does offer aux heating ability it is very clear that.
“If the timeout ends and the heater
was on for the whole time the thermostat switches to the secondary heater
. In this case the primary heater (heater
) will be turned off. This will be remembered for the day it turned on and in the next heating cycle the secondary heater
will turn on automatically. On the next day the primary heater will turn on again the second stage will again only turn on after a timeout. If the third secondary heater_dual_mode
is set to true
the secondary heater will be turned on together with the primary heater.”
this is not what I need or want. Luckily we have access to
/homeassistant/custom_components/dual_smart_thermostat/hvac_device/heater_aux_heater_device.py
Originally
@property
def _has_aux_heating_ran_today(self) -> bool:
"""Determines if the aux heater has been used today."""
if self._aux_heater_last_run is None:
return False
if self._aux_heater_last_run.date() == datetime.datetime.now().date():
return True
return False
is now
@property
def _has_aux_heating_ran_today(self) -> bool:
"""Determines if the aux heater has been used today."""
if self._aux_heater_last_run is None:
return False
if self._aux_heater_last_run.date() == datetime.datetime.now().date():
return False
return False
Explanation: I don’t care if the aux ran today and don’t want to switch it to the primary for the day.
the other portion that I adjusted was to account for some inconsistency with the aux and primary running at the same time
if not self._aux_heater_dual_mode:
await self.heater_device.async_turn_off()
await self.aux_heater_device.async_turn_on()
becomes
if not self._aux_heater_dual_mode:
await self.heater_device.async_turn_on()
await self.aux_heater_device.async_turn_on()
I know these are cheesy nasty work around to anyone with a degree in this stuff but for me its ok.
And just like that I can add my intigrations to my configuration.yaml and setup the d1 minis c code.
configuration.yaml(this is currently only setup for heating and cooling code will need to be added)
switch:
- platform: template
switches:
spaceheaters: #Setting up cloud based plugs as switches in home assistant
friendly_name: "Space Heaters"
value_template: >
{{ is_state('light.plugin1', 'on') or is_state('light.plugin2', 'on') }}
turn_on:
- service: light.turn_on
target:
entity_id:
- light.plugin1
- light.plugin2
turn_off:
- service: light.turn_off
target:
entity_id:
- light.plugin1
- light.plugin2
# Template Switch
- platform: template
switches:
gasheater: #Setting up the mqtt wemos switches as HA switches(HA wont do this for me automatically or I missed something)
friendly_name: "Gas Heater"
value_template: "{{ is_state('switch.heating', 'on') }}"
turn_on:
service: switch.turn_on
target:
entity_id: switch.heating
turn_off:
service: switch.turn_off
target:
entity_id: switch.heating
# Existing configuration...
mqtt:
sensor:
- name: "Wemos D1 Temperature"
state_topic: "home/wemosd1r1/temperature"
unit_of_measurement: "°F"
device_class: temperature
- name: "Wemos D1 Humidity"
state_topic: "home/wemosd1r1/humidity"
unit_of_measurement: "%"
device_class: humidity
# MQTT Switches
switch:
- name: "Heating"
state_topic: "home/wemosd1r1/heating"
command_topic: "home/wemosd1r1/heating"
payload_on: "ON"
payload_off: "OFF"
- name: "AC Low"
state_topic: "home/wemosd1r1/ac_low"
command_topic: "home/wemosd1r1/ac_low"
payload_on: "ON"
payload_off: "OFF"
- name: "AC High"
state_topic: "home/wemosd1r1/ac_high"
command_topic: "home/wemosd1r1/ac_high"
payload_on: "ON"
payload_off: "OFF"
- name: "Fan"
state_topic: "home/wemosd1r1/fan"
command_topic: "home/wemosd1r1/fan"
payload_on: "ON"
payload_off: "OFF"
# ... existing configuration ...
climate:
- platform: dual_smart_thermostat
name: "Home Thermostat"
unique_id: home_thermostat
# Primary Heater (Space Heaters)
heater: switch.spaceheaters
# Secondary Heater (Wemos Heating Switch)
secondary_heater: switch.gasheater
secondary_heater_timeout: '00:15:00' # Time after which secondary heating activates
secondary_heater_dual_mode: false # Secondary heater replaces primary when activated
# Cooler (Wemos Cooling Switch)
cooler: switch.ac_high
# Temperature Sensor (from Wemos D1 Mini)
target_sensor: sensor.wemos_d1_temperature
# Temperature Settings
min_temp: 50
max_temp: 90
# target_temp: 72
cold_tolerance: 0.5
hot_tolerance: 0.5
min_cycle_duration:
seconds: 60
# initial_hvac_mode: "off"
away_temp: 62
# Presets
away:
temperature: 62
home:
temperature: 72
sleep:
temperature: 68
# Fan Control (if applicable)
fan: switch.wemos_fan # Define this switch if you have a fan
# Openings (Window/Door Sensors)
# openings:
# - binary_sensor.window1
# - binary_sensor.door1
# - entity_id: binary_sensor.window2
# timeout: '00:05:00' # Delay before considering the opening as open
# Floor Temperature Control (if using floor heating)
# floor_sensor: sensor.floor_temperature
# max_floor_temp: 85 # Maximum allowed floor temperature
# min_floor_temp: 60 # Minimum floor temperature to maintain
# HVAC Action Reasons (optional)
# sensor_stale_duration:
# minutes: 15 # If sensor data is stale, thermostat turns off
My wemos d1 mini esp826 sketch
This code is only setup for a heating comm signal at the moment and will need
void sendACHighSignal
You can find the domestic modbus digitalwrite from
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>
#include <ArduinoOTA.h>
// Wi-Fi credentials
const char* ssid = "ssidSetup";
const char* password = "password";
// MQTT server settings
const char* mqtt_server = "10.0.0.0"; // Your MQTT broker IP
const int mqtt_port = 1883; // Typically 1883
const char* mqtt_user = "mqtt_user"; // Your MQTT username
const char* mqtt_password = "mqtt_password"; // Your MQTT password
// MQTT topics
const char* temperature_topic = "home/wemosd1r1/temperature";
const char* humidity_topic = "home/wemosd1r1/humidity";
const char* setpoint_topic = "home/wemosd1r1/setpoint";
const char* heating_topic = "home/wemosd1r1/heating";
const char* ac_low_topic = "home/wemosd1r1/ac_low";
const char* ac_high_topic = "home/wemosd1r1/ac_high";
const char* fan_topic = "home/wemosd1r1/fan";
// Hardware pins
#define DHTPIN D4
#define DHTTYPE DHT22
#define AC_SIGNAL_PIN D5 // Pin used for communication signal to AC/heat system
// Initialize DHT sensor
DHT dht(DHTPIN, DHTTYPE);
// Initialize LCD (I2C address 0x27)
LiquidCrystal_PCF8574 lcd(0x27);
// Initialize Wi-Fi and MQTT client
WiFiClient espClient;
PubSubClient client(espClient);
// Variables for storing data
float currentTemperature = 0;
float currentHumidity = 0;
float setpointTemperature = 72.0; // Default setpoint temperature in Fahrenheit
// Variables for modes
bool heatingOn = false;
bool acLowOn = false;
bool acHighOn = false;
bool fanOn = false;
void setup() {
Serial.begin(115200);
delay(10);
// Initialize I2C bus
Wire.begin(D2, D1); // SDA = D2, SCL = D1
// Initialize DHT sensor
dht.begin();
// Initialize LCD
lcd.begin(16, 2); // For 16x2 LCD
lcd.setBacklight(255); // Set backlight brightness (0-255)
lcd.clear(); // Clear any previous data
// Set up pin modes
pinMode(AC_SIGNAL_PIN, OUTPUT);
digitalWrite(AC_SIGNAL_PIN, HIGH); // Assuming HIGH is off
// Connect to Wi-Fi
setup_wifi();
// Initialize MQTT
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
// Initialize OTA
ArduinoOTA.onStart([]() {
Serial.println("Start updating firmware...");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd updating firmware");
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
});
ArduinoOTA.begin();
}
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA); // Station mode
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("Wi-Fi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
String messageTemp;
for (unsigned int i = 0; i < length; i++) {
messageTemp += (char)payload[i];
}
Serial.print("Message arrived on topic: ");
Serial.print(topic);
Serial.print(". Message: ");
Serial.println(messageTemp);
if (String(topic) == setpoint_topic) {
setpointTemperature = messageTemp.toFloat();
Serial.print("Setpoint temperature updated to: ");
Serial.println(setpointTemperature);
} else if (String(topic) == heating_topic) {
heatingOn = (messageTemp == "ON");
Serial.println(heatingOn ? "Heating turned ON" : "Heating turned OFF");
} else if (String(topic) == ac_low_topic) {
acLowOn = (messageTemp == "ON");
Serial.println(acLowOn ? "AC Low turned ON" : "AC Low turned OFF");
} else if (String(topic) == ac_high_topic) {
acHighOn = (messageTemp == "ON");
Serial.println(acHighOn ? "AC High turned ON" : "AC High turned OFF");
} else if (String(topic) == fan_topic) {
fanOn = (messageTemp == "ON");
Serial.println(fanOn ? "Fan turned ON" : "Fan turned OFF");
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect with MQTT authentication
if (client.connect("WemosD1R1Client", mqtt_user, mqtt_password)) {
Serial.println("connected");
// Subscribe to topics
client.subscribe(setpoint_topic);
client.subscribe(heating_topic);
client.subscribe(ac_low_topic);
client.subscribe(ac_high_topic);
client.subscribe(fan_topic);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(". Trying again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void loop() {
ArduinoOTA.handle();
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long currentMillis = millis();
static unsigned long previousSensorMillis = 0;
const long sensorInterval = 5000; // Interval to read sensors and update LCD
if (currentMillis - previousSensorMillis >= sensorInterval) {
previousSensorMillis = currentMillis;
// Read DHT sensor in Fahrenheit
float newTemperature = dht.readTemperature(true); // Pass 'true' for Fahrenheit
float newHumidity = dht.readHumidity();
if (!isnan(newTemperature)) {
currentTemperature = newTemperature;
// Publish temperature
client.publish(temperature_topic, String(currentTemperature).c_str(), true);
}
if (!isnan(newHumidity)) {
currentHumidity = newHumidity;
// Publish humidity
client.publish(humidity_topic, String(currentHumidity).c_str(), true);
}
// Update LCD
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(currentTemperature, 1);
lcd.print("F "); // Update unit to Fahrenheit
lcd.setCursor(0, 1);
lcd.print("Setpoint: ");
lcd.print(setpointTemperature, 1);
lcd.print("F "); // Update unit to Fahrenheit
}
// Control AC signal
controlACSignal();
}
void controlACSignal() {
if (heatingOn) {
sendHeatingSignal();
} else if (acLowOn) {
sendACLowSignal();
} else if (acHighOn) {
sendACHighSignal();
} else if (fanOn) {
sendFanSignal();
} else {
// No mode is active, ensure pin is off
digitalWrite(AC_SIGNAL_PIN, LOW); // Assuming HIGH is off
}
}
void sendHeatingSignal() {
// Implement the heating signal as per your protocol
Serial.println("the heater loop is running");
digitalWrite(AC_SIGNAL_PIN, LOW);// sets the digital pin 13 on
delayMicroseconds(1000);// waits for a second
digitalWrite(AC_SIGNAL_PIN, HIGH);// sets the digital pin 13 AC_SIGNAL_PIN
delayMicroseconds(500);// waits for half a second
}
void sendFanSignal() {
// Implement the Fan signal as per your protocol
}
Please give me feedback. Also sorry about my readability.