So seeing that I have not gotten much response on this topic other than that mentioned by @rmtucker, and the fact that I was itching for a solution to this, I decided to look more at the solution posted by @mbk .
Right off the bat, I decided to try just uploading the D_HVAC_ZoneThermostatHC1.json and D_HVAC_ZoneThermostatHC1.xml files and changing my Vera thermostat object to point to these instead of the standard thermostat files in the advanced tab. Doing this alone gave me control of both my heating and cooling setpoints as well as my mode and fan options. The "HVAC State" and "Fan Status" sections were obviously blank as I was not in any way sending this information. One option I liked with the original object, though not a huge deal, was the fact that it showed the temp in the icon like this:
With the new object, it just shows empty like this:
Like I say, not a huge deal, but I think with the temp on the icon it looks more professional.
My next step was to upload the new L_Arduino.lua, and I_Arduino1.xml luup files to Vera. Testing after the upload, I did not see any affect on any of my other MySensors node objects, so then all that was needed was tomake the changes to my Thermostat Bridge Node sketch.
First, @mbk suggested a modified MyMessage.h file. I opted not to do that. I simply added these lines at the beginning of my Sketch:
//New sensor type S_HVACHC
const int S_HVACHC = 39;
//New variable types
const int V_HVAC_FAN_STATUS = 51;
const int V_HVAC_MODE_STATE = 52;
const int V_HVAC_ENERGY_MODE = 53;
These gave me all the functionality I needed to use the new luup files. In the sample sketch from @mbk, he has the following:
gw.send(msgSetpointFanStatus.set("On")); // On, Off
gw.send(msgSetpointModeState.set("Idle")); // Off, Heating, Cooling, FanOnly, Idle, Error!
gw.send(msgSetpointEnergyMode.set("Normal")); //EnergySavingsMode, Normal
He mentions the options for msgSetpointFanStatusto be On or Off, though I found through experimentation that you can use any text for these. My HVAC system is a multi stage system. This allowed me to display the corresponding fan stage for msgSetpointFanStatus such as Off, Stage 1, Stage 2, or Stage 3. I could have also used Low, Medium and High. I would imagine I would also be able to use any text for msgSetpointModeState and msgSetpointEnergyMode, though I have not tested this.
Anyways, on to my full sketch. This node is just as it's name implies. It is to bridge the gap between my RCS RS485 serial thermostat network and my MySensors setup. Things have not been 100% tested as of yet, but the majority of the things as far as I can see are working good. I have near full control of my thermostat from Vera now. Here is the full sketch:
/*
RCS Thermostat for MySensors
Arduino RCS RS485 thermostat control
July 7,2016
This is a gateway bridge to allow an RCS serial thermostat to be accessible to a MySensors
gateway. The thermostat being used to test this is an RCS TR40 RS485 thermostat. The published
protocol for RCS thermostats can be found at the following address;
http://www.rcstechnology.com/oldsite/docs/thermostats/serial/SERIAL%20PROTOCOL%20REV%204.3%20%20150-00225-43.pdf
This sketch features the following:
Add features here
*/
// Enable debug prints to serial monitor
#define MY_DEBUG
//Set up the nRF24L01+
#define MY_RADIO_NRF24
// Import needed libraries
#include <SPI.h>
#include <MySensors.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>
#define SKETCH_NAME "RCS Thermostat"
#define SKETCH_VERSION "1.0"
/*
#define CHILD_ID_TEMP 0 // V_TEMP
#define CHILD_ID_STATUS 1 // V_STATUS
#define CHILD_ID_SETPOINT_COOL 2 // V_HVAC_SETPOINT_COOL
#define CHILD_ID_SETPOINT_HEAT 3 // V_HVAC_SETPOINT_HEAT
#define CHILD_ID_FLOW_STATE 4 // V_HVAC_FLOW_STATE
#define CHILD_ID_FLOW_MODE 5 // V_HVAC_FLOW_MODE
#define CHILD_ID_HVAC_SPEED 6 // V_HVAC_SPEED
*/
#define CHILD_ID_HVAC 0 // S_HVAC
#define CHILD_ID_OUTSIDE_AIR 2 // S_INFO
// Declare Constants and Pin Numbers
#define SSerialRX 5 //Serial Receive pin 8
#define SSerialTX 3 //Serial Transmit pin 7
#define SSerialTxControl 4 //RS485 Direction control
#define RS485Transmit HIGH
#define RS485Receive LOW
#define MY_REPEATER_FEATURE
//New sensor type S_HVACHC
const int S_HVACHC = 39;
//New variable types
const int V_HVAC_FAN_STATUS = 51;
const int V_HVAC_MODE_STATE = 52;
const int V_HVAC_ENERGY_MODE = 53;
// Declare objects
SoftwareSerial RS485Serial(SSerialRX, SSerialTX); // RX, TX
// Sensor values
// V_TEMP, V_STATUS, V_HVAC_FLOW_STATE, V_HVAC_SPEED
// V_HVAC_SETPOINT_COOL, V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_MODE
MyMessage msgStatus( CHILD_ID_HVAC, V_STATUS );
MyMessage msgTemp( CHILD_ID_HVAC, V_TEMP );
MyMessage msgSetCool( CHILD_ID_HVAC, V_HVAC_SETPOINT_COOL );
MyMessage msgSetHeat( CHILD_ID_HVAC, V_HVAC_SETPOINT_HEAT );
MyMessage msgFlowState( CHILD_ID_HVAC, V_HVAC_FLOW_STATE );
MyMessage msgFlowMode( CHILD_ID_HVAC, V_HVAC_FLOW_MODE );
MyMessage msgHvacSpeed( CHILD_ID_HVAC, V_HVAC_SPEED );
MyMessage msgFanStatus( CHILD_ID_HVAC, V_HVAC_FAN_STATUS );
MyMessage msgModeState( CHILD_ID_HVAC, V_HVAC_MODE_STATE );
MyMessage msgEnergyMode( CHILD_ID_HVAC, V_HVAC_ENERGY_MODE );
// This is used to get the outzide air temp to display on the thermostat
MyMessage msgSetOutsideAir( CHILD_ID_OUTSIDE_AIR, V_TEXT );
// DECLARE VARIABLES
//the last sent set point
String setPoint;
//The current mode (O, H, C, A). the initial mode of "-" is to tell the sketch to call for initial status
String mode = "-";
//The last mode received from the thermostat
String LastMode = "";
//The current fan mode received from the thermostat
String FanMode = "";
//The last fan mode received from the thermostat
String LastFanMode = "";
//The last mode received from Vera
String LastVeraMode = "";
//This is the RS485 address of this node that gets sent to the thermostat
String serialAddr = "1";
//The originator code identifier of the originator of the message
String originator = "00";
//Placeholder for the last command sent
String LastSent = "";
//Counter used in place of sleep to request information at certain intervals
int updateCounter = 0;
//The last outside air value received fromVera for display on the WDU
float lastOutsideAir = 0;
/**
* presentation - Present the parts of the thermostat
*/
void presentation() {
sendSketchInfo( SKETCH_NAME, SKETCH_VERSION );
// Register the thermostat with the gateway
//present( CHILD_ID_HVAC, S_HVAC );
present( CHILD_ID_HVAC, S_HVACHC );
present( CHILD_ID_OUTSIDE_AIR, S_INFO );
send(msgSetOutsideAir.setSensor(CHILD_ID_OUTSIDE_AIR).set("-"));
} //End presentation
/**
* setup - Set up and initialize objects and variables
*/
void setup() {
Serial.begin(9600);
// put your setup code here, to run once:
pinMode( SSerialTxControl, OUTPUT );
// Initialize RS485 Transceiver
digitalWrite( SSerialTxControl, RS485Receive );
// Start the software serial port, to another device
RS485Serial.begin(9600); // set the data rate
//Call for the initial status of the thermostat
SendCmd("R=1");
} //End setup
/**
* loop - The main program loop
*/
void loop() {
String dataIn;
if (RS485Serial.available()) {
dataIn = RS485Serial.readStringUntil('\n');
ParseReceived(dataIn);
}
// The updateCounter is used to request information at different intervals. Requesting
// information at different intervals helps prevent data errors on the serial transmission.
updateCounter ++;
// Initiate a status request at 15000 which is one half of the cycle
if (updateCounter == 10000) {
SendCmd("R=1");
request( CHILD_ID_HVAC, V_TEXT );
}
// Request the outside air temp from the controller at the second half of the cycle
if (updateCounter == 20000) {
SendCmd("R=2");
request( CHILD_ID_OUTSIDE_AIR, V_TEXT );
// Reset the counter
updateCounter = 0;
}
} //End loop
/**
* ParseReceived - Used to parse data received from the RS485 network. Usually the thermostat
*
* Attributes:
* Message - The message string to parse
*/
void ParseReceived(String Message) {
String StatusData;
//int Count;
String StatusString;
String Status;
String Type;
String Value;
int Index;
int Start;
int End;
int StatIndex;
Index = Message.indexOf(' ');
Start = 0;
Serial.println("Message: " + Message);
if (Message.startsWith("A=")) {
while (End != Message.length()) {
End = (Index == -1) ? Message.length() : Index;
//Get the status string to process
StatusString = Message.substring(Start, End);
//Change our start position to 1 over the last space found
Start = Index + 1;
//Find the end of the next status string
Index = Message.indexOf(' ', Start);
//Now we need to process the status string into it's Type and Value
StatIndex = StatusString.indexOf('=');
Type = StatusString.substring(0, StatIndex);
Value = StatusString.substring(StatIndex + 1);
ParseStatus(Type, Value);
}
} else {
SendCmd(LastSent);
}
} //End ParseReceived
/**
* ParseStatus - Used to parse a status parameter sent from the thermostat
*
* Attributes:
* type - The status type
* Value - The status value
*/
void ParseStatus(String Type, String Value) {
if (Type == "OA") {
//Outside Air not used
} else if (Type == "Z") {
//Zone not used
} else if (Type == "T") {
//Current temperature
send(msgTemp.set( Value.toFloat(), 1 ));
} else if (Type == "SP") {
//Set Point (for single setpoint systems)
if (mode == "H" || mode == "EH") {
send(msgSetHeat.set( Value.toFloat(), 1 ));
} else if (mode == "C") {
send(msgSetCool.set( Value.toFloat(), 1 ));
}
} else if (Type == "SPH") {
//Heating set point
send(msgSetHeat.set( Value.toFloat(), 1 ));
} else if (Type == "SPC") {
//Cooling set point
send(msgSetCool.set( Value.toFloat(), 1 ));
} else if (Type == "M") {
//RCS thermostat mode (MySensors V_HVAC_FLOW_STATE)
mode = Value.c_str();
Serial.println("mode=" + mode);
send(msgEnergyMode.set("Normal"));
if (LastMode != mode) {
if (mode == "O") {
send(msgFlowState.set("Off"));
send(msgModeState.set("Off"));
} else if (mode == "H") {
send(msgFlowState.set("HeatOn"));
send(msgModeState.set("Heating"));
} else if (mode == "C") {
send(msgFlowState.set("CoolOn"));
send(msgModeState.set("Cooling"));
} else if (mode == "A") {
send(msgFlowState.set("AutoChangeOver"));
} else if (mode == "EH") {
send(msgFlowState.set("HeatOn"));
send(msgModeState.set("Heating"));
}
LastMode = mode;
}
} else if (Type == "FM") {
//RCS fan mode (MySensors V_STATUS 0=off 1=on)
FanMode = Value.c_str();
if (LastFanMode != FanMode) {
if (FanMode == "1") {
send(msgFlowMode.set("ContinuousOn"));
} else {
send(msgFlowMode.set("Auto"));
}
LastFanMode = FanMode;
}
//Type 2 status message types
} else if (Type == "H1A" && Value == "1") {
//RCS heating stage 1
send(msgHvacSpeed.set("Min"));
send(msgFanStatus.set("Stage 1"));
} else if (Type == "H2A" && Value == "1") {
//RCS heating stage 2
send(msgHvacSpeed.set("Normal"));
send(msgFanStatus.set("Stage 2"));
} else if (Type == "H3A" && Value == "1") {
//RCS heating stage 3
send(msgHvacSpeed.set("Max"));
send(msgFanStatus.set("Stage 3"));
} else if (Type == "C1A" && Value == "1") {
//RCS cooling stage 1
send(msgHvacSpeed.set("Normal"));
send(msgFanStatus.set("Stage 1"));
} else if (Type == "C2A" && Value == "1") {
//RCS cooling stage 2
send(msgHvacSpeed.set("Max"));
send(msgFanStatus.set("Stage 2"));
// We may receive values of 0 for both C1A and H1A we may get an on/Auto cycling if one or the
// other is on. To prevent this we must verify the mode we are in and only set it to auto if
// the heating or cooling stage with a 0 value matches the mode we are in
} else if (((Type == "C1A" && mode == "C") || (Type == "H1A" && (mode == "H" || mode == "EH"))) && Value == "0") {
send(msgFanStatus.set("Off"));
} else if (Type == "FA") {
//RCS fan status (MySensors V_HVAC_FLOW_MODE)
if (Value == "1") {
send(msgFlowMode.set("ContinuousOn"));
} else {
send(msgHvacSpeed.set("Auto"));
}
} else if (Type == "VA") {
//Vent damper not used
} else if (Type == "D1") {
//Damper #1 not used
} else if (Type == "SCP") {
//This is for MOT (Minimum Off Time) and MRT (Minimum Run Time) statuses for
//stages 1 and 2. I may find a way to implement this into Vera, but for now
//it is not used
//String stg1 = Value.substring(0, 1);
//String stg2 = Value.substring(0, Value.length() - 1);
}
} //End ParseStatus
/**
* SendCmd - Sends a command out to the RS485 network
*
* Attributes:
* cmd - The command to send
*/
void SendCmd(String cmd) {
//Assemble the command string using the defined serial address and originator codes
String commandStr = "A=" + serialAddr + " O=" + originator + " " + cmd;
// Enable RS485 Transmit only for the duration of the send
digitalWrite(SSerialTxControl, RS485Transmit);
RS485Serial.print(commandStr + "\r");
LastSent = cmd;
//Return to RS485 receive mode
digitalWrite(SSerialTxControl, RS485Receive);
delay(50);
} //End SendCmd
/**
* receive - Process the incoming messages and watch for an incoming boolean 1
* to toggle the garage door opener.
*
* Attributes:
* MyMessage - The referenced message object received
*/
void receive(const MyMessage &message) {
//Check messages from controller
if ( message.type == V_TEXT ) {
float OT;
String temp;
OT = atof(message.data);
temp = String( OT, 0 );
if (lastOutsideAir != OT) {
SendCmd("OT=" + temp);
}
lastOutsideAir = OT;
} //End V_TEXT
if (message.type == V_TEMP) {
//Get the temp setting passed
setPoint = String(atof(message.data), 0);
if (mode == "H" || mode == "EH") {
SendCmd("SPH=" + setPoint + " R=1");
} else if (mode == "C") {
SendCmd("SPC=" + setPoint + " R=1");
}
} //End V_TEMP
if ( message.type == V_STATUS ) {
String M = message.data;
SendCmd("F=" + M + " R=1");
} //End V_STATUS
if ( message.type == V_HVAC_FLOW_STATE ) {
String M = message.data;
send(msgEnergyMode.set("Normal"));
if (M == "Off") {
mode = "O";
SendCmd("M=O R=1");
send(msgModeState.set("Off"));
} else if (M == "HeatOn") {
mode = "H";
SendCmd("M=H R=1");
send(msgModeState.set("Heating"));
} else if (M == "CoolOn") {
mode = "C";
SendCmd("M=C R=1");
send(msgModeState.set("Cooling"));
} else if (M == "AutoChangeOver") {
mode = "A";
SendCmd("M=A R=1");
}
} //End V_HVAC_FLOW_STATE
if ( message.type == V_HVAC_SPEED ) {
//Serial.println("Speed: " + String(message.data));
} //End V_HVAC_SPEED
if (message.type == V_HVAC_SETPOINT_COOL) {
setPoint = String(atof(message.data), 0);
SendCmd("SPC=" + setPoint + " R=1");
} //End V_HVAC_SETPOINT_COOL
if (message.type == V_HVAC_SETPOINT_HEAT) {
setPoint = String(atof(message.data), 0);
SendCmd("SPH=" + setPoint + " R=1");
} //End V_HVAC_SETPOINT_HEAT
if ( (message.type == V_HVAC_FLOW_MODE) ) {
String SM = message.data;
if (SM == "Auto") {
SendCmd("F=0 R=1");
} else if (SM == "ContinuousOn" || SM == "PeriodicOn") {
SendCmd("F=1 R=1");
}
} //End V_HVAC_FLOW_MODE
} //End receive
I think that this information should be put into the next release of MySensors. Though I don't think we necessarily need a fully new sensor type such as S_HVACHC, the HVAC_FAN_STATUS, V_HVAC_MODE_STATE, and V_HVAC_ENERGY_MODE variable types could be added to work with the S_HVAC sensor type.
If anyone has any questions, comments or criticisms, feel free to post here.
Happy New Year to all