Arduino and Omni PRO II via serial - first working code.

tigers

Active Member
OK, thanks to the help of many people in the forum I finally manged to get a version of the code that I can consider "working". In fact the code needs much much refining and optimization (I am no programmer). The sketch I am posting will allow you to login with the OP II panel, send commands, set flag values and receive answers and it also has examples on how to parse the response from the board.
Many many parts are still missing and must be completed, but I feel I need some feedback.
I think I'll put all the details on hardware and software development on a web page or a pdf, for now I'll sum up the hardware I used:
 
I'm expecting some feedback from anyone interested. At the moment I'm using this to set a flag value in OPII reflecting my power consumption coming from an energy meter. The plan is to expand this to send e-mail alerts based on HAI events, graph logging of temperatures, sensors, etc (I already have a sketch working with these things separately, but I'll have to put it all together).
 
The sketch goes as follows:
 

//Define the arrays that will contain the data
#define DATABUFFERSIZE 255
//deine message types for easier human readable parsing in code
#define COMMAND 0x0F

const byte headerByte = 0x5A; // or '!', or whatever your start character is

byte byteRead;
byte HAI_send[DATABUFFERSIZE];
byte HAI_receive[DATABUFFERSIZE];
byte receivedMessageSize;;//size of the received message

const byte HAI_OK[] = { 0x5A, 0x01, 0x05, 0xC1, 0x93}; // Standard acknowledge OK answer
const byte HAI_KO[] = { 0x5A, 0x01, 0x05, 0xC1, 0x92}; // Standard acknowledge but NOT OK answer

const byte PASSWORD[] = { 0, 0, 0, 0 }; //the master code used to login with the HAI console
// PLEASE NOTE: THIS WILL BE SENT IN PERFECTLY CLEAR MODE (NO ENCRYPTION) OVER THE SERIAL PORT
// THIS IS HOW HAI DOES IT, THE SERIAL PROTOCOL ISN'T ENCRYPTED SINCE IT REQUIRES PHYSICAL ACCESS TO
// THE PANEL

//==============CRC ROUTINE===========
//From HAI docs:
/* starting with the message length byte, call Update_CRC for each byte
of the message passing the message byte in Data. The low byte of CRC will contain
the low byte of the CRC-16 remainder and should be sent first. The high byte of
CRC will contain the high byte of the CRC-16 remainder and should be sent last. */
//----------------------------------------------------------------------

//DAVIDE may 12 2014:
//I rewrote the routine from the HAI docs translating form the turbo Pascal version,
//which was easier for me than the C version that used pointers and did not run on Arduino
//Someone who knows how to program should modify it and maybe write a CRC append routine

unsigned int crc16_update(unsigned int crc, byte x)
{
static int const Poly = 0xA001; // CRC-16 polynomial
int i;
bool flag;

crc ^= x;
for (i=0; i<8; i++)
{
flag = (crc & 1) != 0;
crc = (crc >> 1);
if (flag)
crc ^= Poly;
}//END for
return crc;
}//END crc16_update()

/*
//calculates crc for an entire output message, putting the result in the 2 bytes after message ending
unsigned int messageOutCrc (){ (//no args because length is in the 2nd place of the array
unsigned int crc = 0, i;
unsigned byte totalLength; //total length of the message in the array, including header, length but excluding the 2 crc bytes

totalLength = HAI_send[2] + 2 //+2 is for accounting the header and length bytes that are not computed in the length byte
for (i = 2; i <= totalLength; i++) //i=2 because the CRC is WITHOUT message header HAI_send [2] is message length (excluding header and length) so I add +2
crc = crc16_update(crc, HAI_send);
HAI_send[totalLength + 1] = lowByte (crc);//penultimate byte of the message CRC1
HAI_send[totalLength + 2] = highByte(crc);//last byte of the message CRC2
}//END messageOutCrc*/

//LOGIN - composes the login string starting from the master code
void HAI_login (){

HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x05; //message length
HAI_send[3] = 0x20; //it's a login
HAI_send[4] = PASSWORD[0]; //first digit of the code
HAI_send[5] = PASSWORD[1];
HAI_send[6] = PASSWORD[2];
HAI_send[7] = PASSWORD[3];

unsigned int crc = 0, i;
for (i = 2; i <= 7; i++) //i=2 because the CRC is WITHOUT message header
crc = crc16_update(crc, HAI_send);

HAI_send[8] = lowByte (crc);
HAI_send[9] = highByte(crc);

}//END HAI_login


/*The COMMAND message is used to send an immediate control command to the HAI controller. Commands are
provided to control lights, appliances, temperatures, security, and messaging. Each command follows the same
format: a single byte command, followed by a single byte parameter, and then a two byte secondary parameter. The
command message is formatted as follows:

Start character 0x5A
Message length 0x05
Message type 0x0F
Data 1 Command
Data 2 Parameter 1
Data 3 High byte of parameter 2
Data 4 Low byte of parameter 2
CRC 1 varies
CRC 2 varies */

//compose the array for a set flag command (command 0x0C = set flag P2 to value P1)
void SetFlagTo (int flagNumber, byte level){
//using base 1 for the array for human readability
//the first part of the set flag command are fixed values:
HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x05; //message length
HAI_send[3] = 0x0F; //it's a command
HAI_send[4] = 0x0C; //it's a set flag command: 0x0B to increment counter - 0x0C to decrement counter
HAI_send[5] = level; //the level to set to

// then we must calculate high and low byte of the Flag number
//byte low_byte = 0xff & flagNumber;
//byte high_byte = flagNumber >> 8;
//...and append to the previous part
HAI_send[6] = highByte(flagNumber);
HAI_send[7] = lowByte (flagNumber);

//now the CRC
//this should become a function unsigned int messageOutCrc (byte length) - length could be determined from the 2nd element
//no array passed to the function (don't know how to) - data will be get and put in the HAI_send global array
unsigned int crc = 0, i;
for (i = 2; i <= 7; i++) //i=2 because the CRC is WITHOUT message header
crc = crc16_update(crc, HAI_send);

HAI_send[8] = lowByte (crc);
HAI_send[9] = highByte (crc);

}//END SetFlagTo

void IncrementFlag (int flagNumber){
//using base 1 for the array for human readability
//the first part of the set flag command are fixed values:
HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x05; //message length
HAI_send[3] = 0x0F; //it's a command
HAI_send[4] = 0x0B; //it's a set flag command: 0x0B to increment counter - 0x0C to decrement counter
HAI_send[5] = 0; //parameter 1 not specified in the HAI doc: must set to ??

// then we must calculate high and low byte of the Flag number
//...and append to the previous part, High byte first
HAI_send[6] = highByte(flagNumber);
HAI_send[7] = lowByte (flagNumber);

//now the CRC
//this should become a function unsigned int messageOutCrc (byte length) - length could be determined from the 2nd element
//no array passed to the function (don't know how to) - data will be get and put in the HAI_send global array
unsigned int crc = 0, i;
for (i = 2; i <= 7; i++) //i=2 because the CRC is WITHOUT message header
crc = crc16_update(crc, HAI_send);

HAI_send[8] = lowByte (crc);
HAI_send[9] = highByte (crc);

}//END IncrementFlag

void SetUnitOn (int unitNumber, byte time){ //time is 0 = permanent OR 1-99 seconds OR 101-199 for n-100 minutes OR 201-218 for n-200 hours
//using base 1 for the array for human readability
//the first part of the set flag command are fixed values:
HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x05; //message length
HAI_send[3] = 0x0F; //it's a command
HAI_send[4] = 0x01; //it's a unit ON command
HAI_send[5] = time; //parameter 1

// then we must calculate high and low byte of the Flag number
//...and append to the previous part, High byte first
HAI_send[6] = highByte(unitNumber);
HAI_send[7] = lowByte (unitNumber);

//now the CRC
//this should become a function unsigned int messageOutCrc (byte length) - length could be determined from the 2nd element
//no array passed to the function (don't know how to) - data will be get and put in the HAI_send global array
unsigned int crc = 0, i;
for (i = 2; i <= 7; i++) //i=2 because the CRC is WITHOUT message header
crc = crc16_update(crc, HAI_send);

HAI_send[8] = lowByte (crc);
HAI_send[9] = highByte (crc);

}

void SetUnitOff (int unitNumber, byte time){ //time is 0 = permanent OR 1-99 seconds OR 101-199 for n-100 minutes OR 201-218 for n-200 hours

//TBD - command 0x00

}

void executeButton (int buttonNumber){

//TBD - command 0x07

}

void getSystemStatus (){
//using base 1 for the array for human readability
//the first part of the set flag command are fixed values:
HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x01; //message length
HAI_send[3] = 0x13; //it's a system status request

//now the CRC
//this should become a function unsigned int messageOutCrc (byte length) - length could be determined from the 2nd element
//no array passed to the function (don't know how to) - data will be get and put in the HAI_send global array
unsigned int crc = 0, i;
for (i = 2; i <= 3; i++) //i=2 because the CRC is WITHOUT message header
crc = crc16_update(crc, HAI_send);

HAI_send[4] = lowByte (crc);
HAI_send[5] = highByte (crc);

}

//to be improved for multi-zone polling
void getTemperature (int startZone, int endZone){
//using base 1 for the array for human readability
//the first part of the set flag command are fixed values:
HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x03; //message length
HAI_send[3] = 0x19; //it's a aux temp poll
HAI_send[4] = startZone; //Data 1 = first sensor to poll
HAI_send[5] = endZone; //Data 2 = last sensor to poll - for the moment one at a time

//now the CRC

//this should become a function unsigned int messageOutCrc (byte length) - length could be determined from the 2nd element
//no array passed to the function (don't know how to) - data will be get and put in the HAI_send global array
unsigned int crc = 0, i;

for (i = 2; i <= 5; i++) //i=2 because the CRC is WITHOUT message header
crc = crc16_update(crc, HAI_send);

//calculate high and low byte of the CRC
byte low_byte = 0xff & crc;
byte high_byte = crc >> 8;

HAI_send[6] = low_byte;// (CRC 1);
HAI_send[7] = high_byte;// (CRC 2);

}

//=====SEND DATA TO HAI ==============

void sendToHAI() {
int i=0;

Serial.print("Sent to HAI:");//DEBUG
for (i=1;i<= (HAI_send[2] + 2 + 2); i++){//HAI_send[2] is message length +2 for headerand length bytes +2 for CRC bytes
Serial1.write(HAI_send); //to HAI

Serial.print(HAI_send,HEX);//for debugging - comment out in final release
Serial.write (" ");
}//END for
Serial.println();//DEBUG

}//END SendTo HAI

//convert Omni temperature to C
float omniToC (byte omniT) {
//OmniT is just 0 when -40 °C and every OmniT step is 0.5 °C
//omnitoF in a little more complicated, I think the best thing to do is convert the °C value to °F
return (omniT*0.5 - 40);
}//END OmnitoC

//===========================SERIAL READ=============================

//the serial array function comes from http://jhaskellsblog.blogspot.it/2011/05/serial-comm-fundamentals-on-arduino.html?m=1
//with modifications since the HAI protocol doesn't have end byte but a length instead, also it doesn't read strings or chars
//and doesn't need NULL termination
boolean getSerialArray(){
static byte dataBufferIndex=0;
static byte messageSize=DATABUFFERSIZE; //length of the message, at the beginning it can be the maximum value
static boolean storeVal=false; //flag to define if the incomingbyte has to be put in the array
boolean ended = false; //flag for end of message
byte incomingbyte;

while(Serial1.available()>0){
//Serial.print("Receiving data: ");//DEBUG
//Serial.println(dataBufferIndex);
incomingbyte = Serial1.read();//put data in incomingbyte variable
//Serial.print("Data received: ");
//Serial.println(incomingbyte,HEX);
if(incomingbyte==headerByte ){ //&& dataBufferIndex == 0){ //it is possible that a generic data byte could be 5A so check if data Buffer==0
dataBufferIndex = 1; //Initialize our dataBufferIndex variable
storeVal = true; //all the values will be saved from now on
}//END if

if(storeVal){
if (dataBufferIndex == 2) { //it's the byte containing the message length - set the variable accordingly
HAI_receive[dataBufferIndex++] = incomingbyte;
messageSize = incomingbyte + 2 + 2; //adding the 2 CRC values and Header and length - this is the final index
//Serial.print ("Message length set to: ");
//Serial.println(messageSize);
//Serial.print ("Read data, now proceed to data n. ");
//Serial.println(dataBufferIndex);
} else if (dataBufferIndex == DATABUFFERSIZE) {//Buffer overflow - do nothing
storeVal=false;
dataBufferIndex=0;
ended=true;
return false;
break;
} else if (dataBufferIndex == messageSize) { //this is the last one - put it in the array and return true
HAI_receive[dataBufferIndex] = incomingbyte;
receivedMessageSize=messageSize;
storeVal = false;
//Serial.print ("Last data read, it was n. ");
//Serial.println(dataBufferIndex);
dataBufferIndex=0;
return true;
break;
} else { //it's a generic byte of the message - put it in the array
HAI_receive[dataBufferIndex++] = incomingbyte;
//Serial.print ("Read data, now proceed to data n. ");
//Serial.println(dataBufferIndex);
}//END else
}//END if (storeval)
}//END while Serial1.available()
//We've read in all the available Serial data, and don't have a valid string yet, so return false
return false;
} // END getSerialArray

//======MAIN PROGRAM==================================
void setup() {
// Turn the Serial Protocol ON
Serial.begin(9600); //for debug
Serial1.begin(9600); //for communications with HAI panel

while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only
}

delay(2000);



}//END Setup()

void loop() {

if (Serial.available()){
char inChar = (char)Serial.read();
if (inChar == 'f') {

SetFlagTo (417,100); //set flag 417 to X value
sendToHAI();

SetUnitOn (4, 0); // 0= infinite time
sendToHAI();
}//END IF inchar == 'f'
if (inChar == 'l') {
//TEST SetFlag Routine
HAI_login(); //login to HAI console sets up the HAI_send with the message
sendToHAI(); //send the content of HAI_send array to the OPII
}//END if intchar='l'
if (inChar == 't') {
//TEST get temperature routine
getTemperature(49,59); //get temperature of zone 49 = 0x31 = first temperature sensor
sendToHAI();
}//END if intchar='t'
if (inChar == 's') {
//TEST get system status
getSystemStatus(); //get system status
Serial.println("Message ready");
sendToHAI();

}//END if intchar='s'
}//IF Serial.available


/*
//TEST routine that echoes the Serial input to the Serial1 (connected to HAI with a TTL to RS232 adapter)
// check if data has been sent from the computer:
if (Serial.available()) {
// read the most recent byte
byteRead = Serial.read();
//ECHO the value that was read, back to the serial port.
Serial1.write(byteRead);
}

//TEST routine that echoes the Serial1 input from HAI to the Serial port of Arduino for debbugging
if (Serial1.available()) {
// read the most recent byte
byteRead = Serial1.read();
//ECHO the value that was read, back to the serial port.
Serial.write(byteRead);
}//END IF */

// SERIAL READ

//There should be a check to wait a few millis until HAI answers. If it times out = no answer, else proceed
if (Serial1.available()>0){
if (getSerialArray()){ //the function places valid series of bytes in the HAI_receive[] array
Serial.print ("HAI response: ");
//For debug the content is sent on Serial
for (int i=1;i<=receivedMessageSize;i++){
Serial.print (HAI_receive,HEX);
Serial.print (" ");

}//END for

Serial.println("now parsing...");

//CRC CHECKING GOES HERE

//HAI_receive PARSING GOES HERE
switch (HAI_receive[3]){
case 0x14: //SYSTEM STATUS
//can do anything with the data, as a demo it prints out some of them to serial
//the codes and structure for each message are in the HAI omniLINK documentation
Serial.print("Year: ");
Serial.print(HAI_receive[5]);
Serial.print(" - Month: ");
Serial.print(HAI_receive[6]);
Serial.print(" - Day: ");
Serial.println(HAI_receive[7]);
Serial.print ("time: ");
Serial.print(HAI_receive[9]); //HH 8 is day of the week
Serial.print (":");
Serial.print(HAI_receive[10]);//MM
Serial.print (":");
Serial.print(HAI_receive[11]);//SS
break;
case 0x1A: //TEMPERATURE SENSOR
byte numberOfSensors = (HAI_receive[2]-1)/4;
for (int i=0;i<numberOfSensors;i++){
Serial.print("sensor ");
Serial.print(i+1);
Serial.print(": T= ");
Serial.print(omniToC(HAI_receive[5 + 4*i]));
Serial.println(" C");
}//END for
break;
}//END switch
}//END if getSerialArray
}//END if Serial1.available()>0

}//END loop()

 
 

pete_c

Guru
Thank-you Tiger.
 
Here got the litte TP-Link micro router yesterday to play with. 
 
Looking to extend the built in memory on it to 8/16 Mb via a memory chip upgrade.  Need to order memory chip in the next few days.
 
Dropping OpenWRT on it and concurrently will probably also do similiar with the Almond + to goof around a bit with it.
 

tigers

Active Member
I finally found someone that messes around with his OP II more than I do... :)

Thank you for giving me the start for this project.
 

pete_c

Guru
Thank you Tigers for joining us here on Cocootech and for posting working code for Arduino and the OPII via a serial link.
 
Took apart the TP-Link Microrouter.  I lucked out as it appears to be a first generation TP-Link 710N.  Its labeled V1.0.
 
No logo on the motherboard and using Winbond chips (8M of flash and 256M (DDR SDRAM 256M-Bit 16Mx16) of RAM). 
 
Looks like it should work sans any hardware mods.
 

pete_c

Guru
Wow; everything is working fine on the little TP-Link microrouter.  The OS appears to have written only 32M of the 256M; not really an issue.  I did a power connection and currently using a POE.  Just sitting now in a little plastic jar as the board is a PITA to get back into the case.  I see the serial connection via the USB port.  I found a tweak too for a second USB port.  I still am amazed as to how tiny this this is. The little patch also provides me with a 3G/4G connection.  
 

tigers

Active Member
So now you have serial access via 3G?
 
But how can you communicate via this serial port? How do you send commands?
 

pete_c

Guru
The software just has the stuff for communication to a 3G / 4G GSM USB stick / or my PDA phone.  Its not built in.  I just tell the software to use the 3G connection. (well its a serial connection though).
 
That said though I have to install another USB port such that I can use the two of the USB ports.  The one that is sold in China has two USB ports and second one is also used to power device while first one provides power out for charging devices.
 
So in recap the device would use the 3G/4G connection as an out of band device.  I shut off the WLAN N radio and using the router portions for incoming NIC and outgoing NIC plus the serial connection to the HAI OPII panel. 
 
I do not think that the 3G/4G or WLAN N would work inside of the HAI OPII can though.
 
This will provide me with a tiny little box next to the NIC / Serial ports on the OPII.
 
This was a very good deal here at $19.95 USD.  Only add has been the POE adapter / soldering power leads.  Might change it to a 12VDC to 5VDC instead and take the power from the panel.
 
What is interesting is that there is no TP-Link written on the pc board anywhere.  Just the little paper tag on the NIC port.  It doesn't really matter much cuz all I wanted was the 8MB of flash to write the software too.  The new ones sold here today only have 2MB.  The OS is also configured to utilize the USB port for an extra network storage drive.
 

pete_c

Guru
So now you have serial access via 3G?
 
But how can you communicate via this serial port? How do you send commands?
 
Today / yesterday put the board back into the case and redid some wiring stuff. 
 
Leaving it be for a bit looking for a 12VDC to 5VDC tiny transformer.
 
This one is too big.
 
transformer-big.jpg
 
Found a little buck module that would probably work fine.
 
Made more room inside of the case removing the 120VAC stuff soldered to the board.
 
Connected a USB to Serial cable and installed MiniTerm.  Tested it to works fine.
 
It will have 2 serial port connections from the board. 
 
Here is a GUI page for the modem connection.
 
TP-Link-Modem.jpg
 

ubergeek

Member
I have been watching this thread for months hoping someone would take this a bit further...I think it would be HUGE to get the Arduinos communicating via ethernet vs. serial! I know it is more work with the encryption but please if anyone with the ability is listening, help get this started! With ethernet, we could drive many arduinos controlling many things from coffeemakers to LED pwm, etc.
 
This would really help get the "Internet Of Things" tied in with a panel that is the heart of our homes...
 
Perhaps it would be easier to script through HailkuHelper instead to communicate with Arduino? I really wish I was a little stronger on the programming end...oh the possibilities! I guess I can dream....
 

ubergeek

Member
I have been watching this thread for months hoping someone would take this a bit further...I think it would be HUGE to get the Arduinos communicating via ethernet vs. serial! I know it is more work with the encryption but please if anyone with the ability is listening, help get this started! With ethernet, we could drive many arduinos controlling many things from coffeemakers to LED pwm, etc.
 
This would really help get the "Internet Of Things" tied in with a panel that is the heart of our homes...
 
Perhaps it would be easier to script through HailkuHelper instead to communicate with Arduino? I really wish I was a little stronger on the programming end...oh the possibilities! I guess I can dream....
 

tigers

Active Member
Well I have good news for you. I am currently doing exactly this with my OPII right now. Look at my other post, then you can contact me in private if you will, I think I can help you do the same.
 
As for the haiku comparison I'll say you can do pretty much everything HaikuHelper does, but it is of course much more harder (and maybe fun), but at a little fraction of the price.
 
Feel free to PM me and I'll give you some examples of what I'm doing and help you achieve the same.
 

pete_c

Guru
Welcome to the forum ubergeek.
 
What is neato is that you can do it with anything these days.  (well that tiny tinkering with tiny things).  In a way its a good regression of sorts to "pure" meat of talking to a machine; no fluff; pure code.
 
Geez why da heck do  you need an OS for anyways..a kernel/OS is just an in between piece...
 

ubergeek

Member
tigers said:
Well I have good news for you. I am currently doing exactly this with my OPII right now. Look at my other post, then you can contact me in private if you will, I think I can help you do the same.
 
As for the haiku comparison I'll say you can do pretty much everything HaikuHelper does, but it is of course much more harder (and maybe fun), but at a little fraction of the price.
 
Feel free to PM me and I'll give you some examples of what I'm doing and help you achieve the same.
Tigers, you rock! That is indeed very exciting!!! I will definitely PM you to get more info :)
 

ubergeek

Member
pete_c said:
Welcome to the forum ubergeek.
 
What is neato is that you can do it with anything these days.  (well that tiny tinkering with tiny things).  In a way its a good regression of sorts to "pure" meat of talking to a machine; no fluff; pure code.
 
Geez why da heck do  you need an OS for anyways..a kernel/OS is just an in between piece...
Thanks for the Welcome Pete. You are like the venerable dragon that I have been listening to for a couple years as I became acclimated with the controls and automation world. I was running a building at the time with industrial control systems and I bought my first house which was has really just been an expensive development platform for a single guy like me :) I read boards for a few weeks, including many comments by you before I decided to buy a used Omnipro II on ebay a couple years ago. I've worked on things in spurts as real life keeps popping up, but I do have relay control to my garage door opener, ALL UPB light switches in the 1800sf home, Honeywell z-wave (which has some bug that keeps saying "comm error" but I can control it still, multiple z-wave appliance outlets and also 6 zone security (that I should break up into more zones).
 
You steered me (indirectly) into choosing the HAI platform over the ELK at the time and I still think it was a good choice for me as a beginner. I think this is one of the "coolest" markets in tech that hasnt even realized a fraction of what it is going to be in the everyday home. It will not be long now as you see the big players trying to gobble in the space. But it's the user cutomization that will keep this "hobby" strong and hopefully open sourced enough to continue to develop and play.
 
Yes the OS is a bit heavy sometimes "in-between", but then again sometimes the Raspberry-pi is the perfect application for a certain job?
 
It is amazing what can be done today with just a little imagination, ingenuity and a knowledge of "search" ie. google. I have gone from leaving computers as a kid in the early 80's programming Apple II's to picking things back up in 2009, becoming Comptia certified A+, network+, etc. Now I am just jealous of my younger brother who is a web developer professionally...if I just had a fraction of his programming experience I feel that I have the imagination to do such cool things at home! It's all possible NOW. Look at the customized solutions that Mavromatic puts out with his know how.
 
Anyway, I hope that I can ultimately move from being a silent watcher of these forums that I have watched for years to learn and worked alongside into a contributor as well that can hopefully share something of value as you all do so graciously!
 
Thanks again.
 
Top