The Hookup
For proof of concept seems to run reliably in the lab. I don't want to be up and down ladders to make update I intend keeping the control system in Hubitat so I can tinker to my Hearts content.
Boost fan PWM control as well as start relays.
The valve position is a separate driver and device in Hubitat, This will use one of the PWM
Four boost fans in the ducts for each room inlet.
One fan in the two way turbo exhaust valve.
The valve position is a separate driver and device in Hubitat, This will use one of the PWM pins on the Wemos and its analogue input for the position pot
Hubitat Valve Position Driver
import groovy.json.JsonSlurper
metadata {
definition(name: "Greenway ERV Turbo Valve", namespace: "Greenway", author: "Nick Goodey") {
capability "Initialize"
capability "Switch"
capability "Switch Level"
command "on"
command "off"
}
preferences {
section("Device") {
input("DeviceIndex", "number", title: "Index for device relay or fan", required: true, defaultValue: 0)
}
section("URIs") {
input "URI", "text", title: "Device URI", required: false, defaultValue: "http://192.168.1.17"
}
input("logEnable", "bool", title: "Enable logging", required: true, defaultValue: true)
}
}
def installed() {
log.info "installed..."
}
def updated() {
if (logEnable) log.info "Updated..."
initialize()
}
def uninstalled() {
if (logEnable) log.info "Disconnecting from mqtt"
}
def initialize() {
if (logEnable) runIn(900, logsOff)
}
def logsOff() {
log.warn "Debug logging disabled."
device.updateSetting("logEnable", [value: "false", type: "bool"])
}
def on() {
try {
url = "$settings.URI/?RELAY=$settings.DeviceIndex%201"
if (logEnable) log.debug url
httpGet(url) { resp ->
if (resp.success) {
sendEvent(name: "switch", value: "on", isStateChange: true)
}
if (logEnable)
if (resp.data) log.debug "${resp.data}"
}
} catch (Exception e) {
log.warn "Call to on failed: ${e.message}"
}
}
def off() {
try {
url = "$settings.URI/?RELAY=$settings.DeviceIndex%200"
if (logEnable) log.debug url
httpGet(url) { resp ->
if (resp.success) {
sendEvent(name: "switch", value: "off", isStateChange: true)
}
if (logEnable)
if (resp.data) log.debug "${resp.data}"
}
} catch (Exception e) {
log.warn "Call to off failed: ${e.message}"
}
}
def setLevel(value, rate = null) {
if(value < 0) value = 0;
if(value > 100) value = 100;
try {
url = "$settings.URI/?VALVE=$settings.DeviceIndex%20$value"
if (logEnable) log.debug url
httpGet(url) { resp ->
if (resp.success) {
sendEvent(name: "level", value: value)
}
if (logEnable){
log.debug "$bits"
if (resp.data) log.debug "${resp.data}"
}
}
} catch (Exception e) {
log.warn "Call to off failed: ${e.message}"
}
if (value == 0) {
sendEvent(name: "switch", value: "off")
}
}
Arduino Wemos D1 R2
#include <Arduino_JSON.h>
#include <ESP8266WiFi.h>
#include <MQTT.h>
#include <ESP8266WebServer.h>
#define LED_PIN LED_BUILTIN //LED_BUILTIN is built in LED
String inputString = ""; // a String to hold incoming data
bool stringComplete = false; // whether the string is complete
WiFiClient net;
MQTTClient MQTTclient;
ESP8266WebServer server(80);
//WiFi
const char ssid[] = "????";
const char pass[] = "???";
IPAddress local_IP(192, 168, 1, 17);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
// the IP address for the MQTT server
char MQTTip[] = "192.168.1.71";
unsigned long UpdateCount = 6;
unsigned long TickCount = UpdateCount-1;
unsigned long PacketCount = 0;
unsigned long lastMillis = 0;
void handleRoot() {
//http://192.168.1.17/?fan=1&speed=120
//http://192.168.1.17/?relay=1
/*
String message = "RESPONSE\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET)?"GET":"POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
*/
String message = "";
for (uint8_t i=0; i<server.args(); i++){
message += " " + server.argName(i) + "=" + server.arg(i) + "\n";
}
String payload = server.argName(0) + " " + server.arg(0);
Serial.println(payload);
inputString = "";
int tout = 1000;
while( (!Serial.available()) && ((--tout) > 0) )
{
delay(1);
if(tout <= 0)
{
message = "IO TIMEOUT";
break;
}
else//GET RESPONSE
{
while (Serial.available())
{
char inChar = (char)Serial.read();
if( (inChar == '\r') || (inChar == '\n') )
{
message += inputString;
}
else
{
inputString += inChar;
}
}//End of while (Serial.available())
break;
}//End of if(tout <= 0)
}//End of while( (!Serial.available()) && ((--tout) > 0) )
//message += " TOUT=" + String(tout);
server.send(200, "text/plain", message);
}
void handleNotFound(){
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET)?"GET":"POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i=0; i<server.args(); i++){
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
// Configures static IP address
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("STA Failed to configure");
}
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
// You need to set the IP address directly.
MQTTclient.begin(MQTTip, net);
MQTTclient.onMessage(messageReceived);
WiFiconnect();
MQTTclient.publish("/ervcontrol/status", "start");
MQTTclient.publish("/ervcontrol/status", "run");
server.on("/", handleRoot);
server.on("/status", [](){
server.send(200, "text/plain", "machine status");
});
server.onNotFound(handleNotFound);
server.begin();
Serial.println("/ervcontrol/status HTTP server started");
MQTTclient.publish("/ervcontrol/status", "HTTP server started");
}
void loop() {
MQTTclient.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!MQTTclient.connected()) {
WiFiconnect();
}
server.handleClient();
// publish a message roughly every second.
if (millis() - lastMillis > (UpdateCount * 1000))
{
lastMillis = millis();
SendMQTT();
}
delay(100);
}
void WiFiconnect() {
Serial.print("checking wifi...");
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
digitalWrite(LED_PIN, HIGH);
}
Serial.println("\nWiFi Connected! ");
Serial.println( WiFi.localIP());
Serial.print("\nMQTT connecting...");
while (!MQTTclient.connect(WiFi.localIP().toString().c_str() , "try", "try")) {
Serial.print(".");
delay(800);
digitalWrite(LED_PIN, LOW);
delay(200);
digitalWrite(LED_PIN, HIGH);
}
Serial.println("\nMQTT Connected!");
digitalWrite(LED_PIN, LOW);
MQTTclient.subscribe("/ervcontrol/control");
MQTTclient.publish("/ervcontrol/status", "start");
MQTTclient.publish("/ervcontrol/localip", "/ervcontrol/localip/" + WiFi.localIP().toString());
}
void SendMQTT()
{
digitalWrite(LED_PIN, LOW);
JSONVar myArray;
//STALE
myArray[0] = analogRead(0);
myArray[1] = analogRead(1);
myArray[2] = analogRead(2);
myArray[3] = analogRead(3);
myArray[4] = analogRead(4);
myArray[5] = analogRead(5);
//MISC
myArray[6] = (PacketCount++);
myArray[7] = WiFi.RSSI();
String jsonString = JSON.stringify(myArray);
MQTTclient.publish("/ervcontrol/sensors", jsonString);
digitalWrite(LED_PIN, HIGH);
}
//Test MQTT mon
//Topic: /ervcontrol/control
//Payload: RELAYS 1 0 1 0 1 1
void messageReceived(String &topic, String &payload) {
digitalWrite(LED_PIN, LOW);
if(topic == "/ervcontrol/control")
{
if(payload.startsWith("read"))
{
SendMQTT();
MQTTclient.publish("/ervcontrol/localip", "/ervcontrol/localip/" + WiFi.localIP().toString());
}
else
{
Serial.println(payload);
}
MQTTclient.publish("/ervcontrol/payload", topic + "/" + payload);
}
digitalWrite(LED_PIN, HIGH);
}
Arduino Uno Remote IO
#include <Arduino_JSON.h>
const int Relay[6] = {2, 4, 7, 8, 12, 13};
const int Fan[6] = {3, 5, 6, 9, 10, 11};
String inputString = "";
bool stringComplete = false;
String LastCommand = "";
String Status[6] = {"","","","","",""};
void setup()
{
// initialize serial:
Serial.begin(115200);
pinMode(Relay[0], OUTPUT);
pinMode(Relay[1], OUTPUT);
pinMode(Relay[2], OUTPUT);
pinMode(Relay[3], OUTPUT);
pinMode(Relay[4], OUTPUT);
pinMode(Relay[5], OUTPUT);
// reserve 200 bytes for the inputString:
inputString.reserve(200);
}
void loop() {
if (stringComplete) {
if(inputString.substring(0,6) == "STATUS")
{
JSONVar myArray;
myArray[0] = analogRead(0);
myArray[1] = analogRead(1);
myArray[2] = analogRead(2);
myArray[3] = analogRead(3);
myArray[4] = analogRead(4);
myArray[5] = analogRead(5);
myArray[6] = LastCommand;
myArray[7] = Status;
inputString = JSON.stringify(myArray);
}
else if(inputString.substring(0,6) == "RELAY ")
{
int key = getValue(inputString, ' ', 1, 0, 5);
int value = getValue(inputString, ' ', 2, 0, 1);
digitalWrite(Relay[key], value);
Status[key] = inputString.substring(0,6) + " " + key + " " + value;
}
else if(inputString.substring(0,7) == "RELAYS ")
{
digitalWrite(Relay[0], getValue(inputString, ' ', 1, 0, 1) );
digitalWrite(Relay[1], getValue(inputString, ' ', 2, 0, 1) );
digitalWrite(Relay[2], getValue(inputString, ' ', 3, 0, 1) );
digitalWrite(Relay[3], getValue(inputString, ' ', 4, 0, 1) );
digitalWrite(Relay[4], getValue(inputString, ' ', 5, 0, 1) );
digitalWrite(Relay[5], getValue(inputString, ' ', 6, 0, 1) );
Status[0] = getValue(inputString, ' ', 1, 0, 1);
Status[1] = getValue(inputString, ' ', 2, 0, 1);
Status[2] = getValue(inputString, ' ', 3, 0, 1);
Status[3] = getValue(inputString, ' ', 4, 0, 1);
Status[4] = getValue(inputString, ' ', 5, 0, 1);
Status[5] = getValue(inputString, ' ', 6, 0, 1);
}
else if(inputString.substring(0,4) == "FAN ")
{
int key = getValue(inputString, ' ', 1, 0, 5);
int value = getValue(inputString, ' ', 2, 0, 255);
analogWrite(Fan[key], value);
Status[key] = inputString.substring(0,6) + " " + key + " " + value;
}
else if(inputString.substring(0,5) == "FANS ")
{
analogWrite(Fan[0], getValue(inputString, ' ', 1, 0, 255) );
analogWrite(Fan[1], getValue(inputString, ' ', 2, 0, 255) );
analogWrite(Fan[2], getValue(inputString, ' ', 3, 0, 255) );
analogWrite(Fan[3], getValue(inputString, ' ', 4, 0, 255) );
analogWrite(Fan[4], getValue(inputString, ' ', 5, 0, 255) );
analogWrite(Fan[5], getValue(inputString, ' ', 6, 0, 255) );
Status[0] = getValue(inputString, ' ', 1, 0, 255);
Status[1] = getValue(inputString, ' ', 2, 0, 255);
Status[2] = getValue(inputString, ' ', 3, 0, 255);
Status[3] = getValue(inputString, ' ', 4, 0, 255);
Status[4] = getValue(inputString, ' ', 5, 0, 255);
Status[5] = getValue(inputString, ' ', 6, 0, 255);
}
LastCommand = inputString;
Serial.println(inputString);
inputString = "";
stringComplete = false;
}//End of if (stringComplete)
}
/*
SerialEvent occurs whenever a new data comes in the hardware serial RX. This
routine is run between each time loop() runs, so using delay inside loop can
delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
// add it to the inputString:
inputString += inChar;
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if( (inChar == '\r') || (inChar == '\n') ) {
stringComplete = true;
}
}
}
int getValue(String data, char separator, int index, int imin, int imax)
{
int found = 0;
int strIndex[] = { 0, -1 };
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
String sresp = found > index ? data.substring(strIndex[0], strIndex[1]) : "";
return constrain(sresp.toInt(), imin, imax);
}