Been working on this code to work with HRV home recovery ventilation system, happy to share the code... basically utilises an Arduino Uno REV3 + Ethernet shield. It connects into the HRV Control Panel and grabs TTL serial data, interprets it and sends the HRV Roof and House Temperatures (and control panel temperature settings + fan speed, if you want to edit code and send that too) to an MQTT Broker (which I have running on OpenHAB) The ribbon cable from roof to HRV control panel has 6 wires, the outside 2 wires (1 and 6) are 5V power, the next two wires in (2 and 5) are GND and the middle two wires (3 and 4) are TTL serial RX/TX (which it both sends and receives on the same wire) Put an RJ11 splitter between HRV control panel and ribbon cable from roof controller, then make your own ribbon cable into the split port. You only need to split one of each wire (1 x 5V, 1 x GND and 1 x data) Run 5V into Arduino 5V (be aware, this will power the Arduino but is not recommended, best to put a diode in - I take no responsibility if you fry your Arduino) then run GND to GND then run one of the data lines into GPIO pin 7.
Assumes you have OpenHAB and MQTT broker already setup. Change your IP in MQTT broker to suit your setup.
No doubt pasting will lose indentation..., can't use BBCode coz I have array declared in code!
//
// Reads HRV serial data and sends to MQTT broker
// Author: chimera
// Date: 19 April 2016
//
// RX/TX native generates an err02 in HRV control panel
// Credit to: http://www.hexperiments.com/?page_id=47 for data structure
// Credit to: https://github.com/benrugg/Arduino-Hex-Decimal-Conversion for Dec/Hex conversion
//
#include <PubSubClient.h>
#include <Ethernet.h>
#include <SoftwareSerial.h>
// HRV
#define MSGSTARTSTOP 0x7E
#define HRVROOF 0x30
#define HRVHOUSE 0x31
// MQTT subs
#define OPENHABHRVSUBHOUSE "openhab/hrv/housetemp"
#define OPENHABHRVSUBROOF "openhab/hrv/rooftemp"
// TTL Serial on GPIO ports (HRV does not like RX/TX ports!)
SoftwareSerial hrvSerial(7, 8); // RX, TX
// MQTT Broker
IPAddress MQTT_SERVER(192, 168, 222, 254);
// The IP address of the Arduino
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
// Define message buffer and publish string
char message_buff[10];
String pubString;
// TTL serial data indicators
bool bStarted = false;
bool bEnded = false;
// Temperature from Roof or House?
char eTempLoc;
// TTL Serial data array, bIndex, bIndex of checksum and temperature
byte inData[10];
byte bIndex;
byte bChecksum;
float fHRVTemp;
// Ethernet Initalization
EthernetClient ethClient;
// Callback to Arduino from MQTT (inbound message arrives for a subscription)
void callback(char* topic, byte* payload, unsigned int length) {
// Messaging inbound from MQTT broker
int iChar = 0;
for(iChar=0; iChar<length; iChar++) {
message_buff[iChar] = payload[iChar];
}
message_buff[iChar] = '\0';
// This can be used in a future revision to control the HRV control panel
}
// Define Publish / Subscribe client (must be defined after callback function above)
PubSubClient mqttClient(MQTT_SERVER, 1883, callback, ethClient);
void setup()
{
// Start HRV serial
hrvSerial.begin(1200);
// Start Network (replace with 'Ethernet.begin(mac, ip);' for fixed IP)
while (Ethernet.begin(mac) == 0) {
Serial.println("Could not obtain DHCP lease");
delay(5000);
}
// Let network have a chance to start up
delay(1500);
// Debug USB serial
Serial.begin(1200);
// Initialize defaults
bIndex = 0;
bChecksum = 0;
// Show the IP address
printIPAddress();
}
void loop()
{
// If not MQTT connected, try connecting
if (!mqttClient.connected()) {
// Connect to MQTT broker on the openhab server, retry constantly
while (mqttClient.connect("hrv") != 1) {
Serial.println("Error connecting to MQTT (State:" + String(mqttClient.state()) + ")");
delay(1000);
}
}
// Flush stale data
hrvSerial.flush();
// Only if we're plugged in...
while (hrvSerial.available() > 0)
{
// Read serial data
int inChar = hrvSerial.read();
// Start/Stop marker, which is it? Wait til next loop
if (inChar == MSGSTARTSTOP)
{
// Start if first time we've got the message
if (bIndex == 0)
{
bStarted = true;
}
else
{
bChecksum = bIndex-1;
bEnded = true;
break;
}
}
// Grab data
if (bStarted == true)
{
// Double check we actually got something
if (sizeof(inChar) > 0)
{
//Serial.print(inChar, HEX);
//Serial.print(",");
inData[bIndex] = inChar;
bIndex++;
}
}
}
// Validate data, or if not enough data will fail
if (bStarted && bEnded && bChecksum > 0)
{
int iChar;
int iLess;
// Checks
byte bCalc;
String sCalc;
byte bCheck;
String sCheck;
// Subtract from zero
iChar = 0;
// Subtract each byte in ID and data
for (int iPos=1; iPos < bChecksum; iPos++)
{
iLess = inData[iPos];
iChar = iChar - iLess;
}
// Convert calculations
bCalc = (byte) (iChar % 0x100);
sCalc = decToHex(bCalc, 2);
bCheck = (byte) inData[bChecksum];
sCheck = decToHex(bCheck, 2);
// Mod result by 256 and compare to checksum, or not enough data
if (sCalc != sCheck || bIndex <6)
{
// Checksum failed, reset
bStarted = false;
bEnded = false;
bIndex = 0;
// Need to flush, maybe getting the end marker
hrvSerial.flush();
}
// Reset checksum
bChecksum = 0;
}
// We got both start and end messages, process the data
if (bStarted && bEnded)
{
// Only process if we got enough data, minimum 6 characters
if (bIndex > 5)
{
String sHexPartOne;
String sHexPartTwo;
String hrvSetTemp;
int iPos;
// Pull data out of the array, position 0 is 0x7E (start and end of message)
for (int iPos=1; iPos <= bIndex; iPos++)
{
// Position 1 defines house or roof temperature
if (iPos==1) { eTempLoc = (char) inData[iPos]; }
// Position 2 and 3 are actual temperature, convert to hex
if (iPos == 2) { sHexPartOne = decToHex(inData[iPos], 2); }
if (iPos == 3) { sHexPartTwo = decToHex(inData[iPos], 2); }
if (eTempLoc == HRVHOUSE && iPos == 4)
{
// Fan full speed
if (inData[iPos] == 100 )
{
Serial.println("Fan speed: FULL");
}
else
{
Serial.print("Fan speed: ");
Serial.println(inData[iPos]);
}
}
// If message is from control panel
if (eTempLoc == HRVHOUSE && iPos == 5)
{
hrvSetTemp = inData[iPos];
Serial.print("Control Panel: ");
Serial.println(hrvSetTemp);
}
}
// Concatenate first and second hex, convert back to decimal, 1/16th of dec + rounding
// Note: rounding is weird - it varies between roof and house, so below is rough - its accurate 99% of the time!
fHRVTemp = hexToDec(sHexPartOne + sHexPartTwo);
fHRVTemp = (fHRVTemp * 0.0625) + 0.66;
// Cast back to integer
fHRVTemp = (int) fHRVTemp;
// Send data to MQTT broker
SendMQTTMessage();
}
// Chill a bit, sometimes we'll time the data, sometimes not
delay(2000);
// Reset defaults for processing
bStarted = false;
bEnded = false;
bIndex = 0;
}
else
{
// Wait for serial to come alive
delay(2000);
}
mqttClient.loop();
}
String decToHex(byte decValue, byte desiredStringLength)
{
String hexString = String(decValue, HEX);
while (hexString.length() < desiredStringLength) hexString = "0" + hexString;
return hexString;
}
unsigned int hexToDec(String hexString)
{
unsigned int decValue = 0;
int nextInt;
for (int i = 0; i < hexString.length(); i++) {
nextInt = int(hexString.charAt(i));
if (nextInt >= 48 && nextInt <= 57) nextInt = map(nextInt, 48, 57, 0, 9);
if (nextInt >= 65 && nextInt <= 70) nextInt = map(nextInt, 65, 70, 10, 15);
if (nextInt >= 97 && nextInt <= 102) nextInt = map(nextInt, 97, 102, 10, 15);
nextInt = constrain(nextInt, 0, 15);
decValue = (decValue * 16) + nextInt;
}
return decValue;
}
// Send data to MQTT broker
void SendMQTTMessage()
{
// Define and send message about door state
pubString = String(fHRVTemp);
pubString.toCharArray(message_buff, pubString.length()+1);
if (eTempLoc == HRVHOUSE)
{
Serial.print("House ");
Serial.println(message_buff);
mqttClient.publish(OPENHABHRVSUBHOUSE, message_buff);
}
else if (eTempLoc == HRVROOF)
{
Serial.print("Roof ");
Serial.println(message_buff);
mqttClient.publish(OPENHABHRVSUBROOF, message_buff);
}
else
{
Serial.println("Undefined");
}
}
// Print IP for debugging, can be removed if needed
void printIPAddress()
{
Serial.print("My IP address: ");
for (byte thisByte = 0; thisByte < 4; thisByte++) {
// print the value of each byte of the IP address:
Serial.print(Ethernet.localIP()[thisByte], DEC);
Serial.print(".");
}
Serial.println();
}









