While integrating the HAI Omnipro2 and Somfy US URTS transmitter, there is very little about integrating the OP2 with the European RS485 RTS here which is much more complicated. http://www.somfyarchitecture.co.uk/downloads/buildings/rs485rts-transmitter-18108031.pdf .
The HAI documentation is referring to ILT hardwired motors rather than the RTS wireless motors and the serial strings given are completely different for the RS485 transmitter. The RS485 transmitter also talks 4800 baud, 8O1 while the OP2 talks 9600 baud 8N1.
So, for anyone who may want to do this in the future here is how I did it.
What you need:
1. Somfy RS485 transmitter
2. Arduino Mega 2560, Serial to TTL converter, RJ11 cable as described here http://cocoontech.com/forums/topic/26641-arduino-and-omni-pro-ii-via-serial-first-working-code/
3. TTL-RS485 Shield http://www.dx.com/p/ttl-to-rs485-module-for-arduino-green-163849#.VsRvjvl95hE
Basic description: there are 3 serial outputs used on the Arduino Mega 2560,
-Serial which is the USB connection that is used to output info on the serial monitor
- Serial1 which is connected to the RS485 transmitter
- Serial2 which is connected to the OP2
The Arduino polls the OP2 looking at the status of flag #424 (Flag32) and #425 (Flag33). If it detects that they are set then it knows to call the relevant somfy blind function. The way it’s set up currently is with 2 buttons in PC Access, one to raise the blinds and one to lower but it can be made as flexible as needed.
Here is the wiring
Serial Shield Arduino RS485 Shield
GND GND
VCC 3.3v
RX TX2
TX RX2
RX1 RO
GND RE
Pin 7 DE
TX1 DI
5v 680ohm resistor connected to both VCC and A
GND 680ohm resistor connected to both GND and B
Next thing is to work out what the Somfy byte stream should be for different functions. This is detailed in the manual but the easiest way if you have a RS485 adaptor for your PC is to download the demo tool (just google it) and then use it to produce the byte codes. Alternatively, there is an Excel Frame Builder spreadsheet that will help you build the stream. Message me if you need this.
Here is the code, mainly taken from the above link with some small changes to the serial reading. Yes I realise there is quite a bit in the code that is not used...may come in useful some day! If you are tight on space, and get rid of everything unused, debugging messages, it will probably take half the space.
The HAI documentation is referring to ILT hardwired motors rather than the RTS wireless motors and the serial strings given are completely different for the RS485 transmitter. The RS485 transmitter also talks 4800 baud, 8O1 while the OP2 talks 9600 baud 8N1.
So, for anyone who may want to do this in the future here is how I did it.
What you need:
1. Somfy RS485 transmitter
2. Arduino Mega 2560, Serial to TTL converter, RJ11 cable as described here http://cocoontech.com/forums/topic/26641-arduino-and-omni-pro-ii-via-serial-first-working-code/
3. TTL-RS485 Shield http://www.dx.com/p/ttl-to-rs485-module-for-arduino-green-163849#.VsRvjvl95hE
Basic description: there are 3 serial outputs used on the Arduino Mega 2560,
-Serial which is the USB connection that is used to output info on the serial monitor
- Serial1 which is connected to the RS485 transmitter
- Serial2 which is connected to the OP2
The Arduino polls the OP2 looking at the status of flag #424 (Flag32) and #425 (Flag33). If it detects that they are set then it knows to call the relevant somfy blind function. The way it’s set up currently is with 2 buttons in PC Access, one to raise the blinds and one to lower but it can be made as flexible as needed.
Here is the wiring
Serial Shield Arduino RS485 Shield
GND GND
VCC 3.3v
RX TX2
TX RX2
RX1 RO
GND RE
Pin 7 DE
TX1 DI
5v 680ohm resistor connected to both VCC and A
GND 680ohm resistor connected to both GND and B
Next thing is to work out what the Somfy byte stream should be for different functions. This is detailed in the manual but the easiest way if you have a RS485 adaptor for your PC is to download the demo tool (just google it) and then use it to produce the byte codes. Alternatively, there is an Excel Frame Builder spreadsheet that will help you build the stream. Message me if you need this.
Here is the code, mainly taken from the above link with some small changes to the serial reading. Yes I realise there is quite a bit in the code that is not used...may come in useful some day! If you are tight on space, and get rid of everything unused, debugging messages, it will probably take half the space.
Code:
//Define the arrays that will contain the data
#define DATABUFFERSIZE 255
//deine message types for easier human readable parsing in code
#define COMMAND 0x0F
#define RS485Transmit HIGH
#define RS485Receive LOW
#define Pin13LED 13
#define DEpin 7
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[] = { 0x0, 0xx, 0x0, 0x0 }; //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
//somfy control function
byte dataframe[13];
void blinds (int dir) //if dir =1 then raise, if dir=0 then lower
{
dataframe[0]=0x7f;
dataframe[1]=0xf2;
dataframe[2]=0xfa;
dataframe[3]=0x01;
dataframe[4]=0x00;
dataframe[5]=0x00;
dataframe[6]=0x49;
dataframe[7]=0x55;
dataframe[8]=0xfa;
dataframe[9]=0xfb;
dataframe[11]=0x05;
if (dir==1)//up
{
dataframe[10]=0xfe;
dataframe[12]=0xfd;
//dataframe={0x7f,0xf2,0xfa,0x01,0x00,0x00,0x49,0x55,0xfa,0xfb,0xfe,0x05,0xfd}; //note that this has already been checksummed and inverted
}
else if (dir==0) //down
{
dataframe[10]=0xfd;
dataframe[12]=0xfc;
// dataframe={0x7f,0xf2,0xfa,0x01,0x00,0x00,0x49,0x55,0xfa,0xfb,0xfd,0x05,0xfc}; //note that this has already been checksummed and inverted
}
digitalWrite(DEpin,HIGH); //enable rs485 transmission on shield
// Start the software serial port, to another device
Serial1.begin(4800,SERIAL_8O1); // set the data rate & odd parity on Serial1 (pin 18) to RS485 transmitter
for(int i=0;i<13;i++)
{
String a =String(dataframe[i],HEX);
Serial.println (a); // Send byte stream to USB debug
Serial1.write(dataframe[i]); // Send byte stream to Remote device
}
Serial1.flush(); // this command will hang until all of the data is shifted out the UART
// after Serial1.flush();
Serial.println("Flushed Serial1.\nCharacters waiting in Serial1 input queue:");
Serial.println(Serial1.available(),DEC);
digitalWrite(DEpin,LOW); // release BUS
Serial1.end();
}
//==============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[i]);
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[i]);
HAI_send[8] = lowByte (crc);
HAI_send[9] = highByte(crc);
}//END HAI_login
void amIloggedin(){
//send standard ack
HAI_send[1] = 0x5A; //header
HAI_send[2] = 0x01; //message length
HAI_send[3] = 0x05; //it's a command
HAI_send[4] = 0xC1; //crc1
HAI_send[5] = 0x93; //crc2
}
/*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[i]);
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[i]);
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[i]);
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[i]);
HAI_send[4] = lowByte (crc);
HAI_send[5] = highByte (crc);
}
void getStatusSummary (){
//using base 1 for the array for human readabilit
//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] = 0x28; //it's a system status request
HAI_send[4] = 0x01;
HAI_send[5] = 0x8e;
}
//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[i]);
//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("Sending 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
Serial2.write(HAI_send[i]); //to HAI
Serial.print(HAI_send[i],HEX);//for debugging - comment out in final release
Serial.write (" ");
}//END for
Serial.println();//DEBUG
Serial2.flush(); //added by ML - pauses until transmission is complete
}//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(Serial2.available()>0){
//Serial.print("Receiving data: ");//DEBUG
//delay(50);// ML added delay
//Serial.println(dataBufferIndex);
incomingbyte = Serial2.read();//put data in incomingbyte variable
//delay(50);//ML added
//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 ("Messsage 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 Serial2.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
Serial2.begin(9600); //for communications with HAI panel
HAI_login();
sendToHAI();
while (Serial2.available()>0){
if (getSerialArray()){ //the function places valid series of bytes in the HAI_receive[] array
Serial.print ("HAI receive array: ");
//For debug the content is sent on Serial
for (int i=1;i<=receivedMessageSize;i++){
Serial.print (HAI_receive[i],HEX);
Serial.print (" ");
}//END for
}
}
/*else {
Serial.println("Serial2 not available immediately after sending login");
}//taken out when changing if to while statement*/
amIloggedin();
sendToHAI();
if (Serial2.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[i],HEX);
Serial.print (" ");
}//END for
if (HAI_receive[receivedMessageSize]==0x93)
{
Serial.println("Successful login");
}
else if (HAI_receive[receivedMessageSize]==0x92)
{
Serial.println("Failed to login");
}
}
}
else {
Serial.println("Serial2 not available when sending ACK");
}
pinMode(13, OUTPUT);
}//END Setup()
void loop() {
Serial.println("Beginning loop function");
digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
getStatusSummary();
sendToHAI();
if (Serial2.available()>0){
if (getSerialArray()){ //the function places valid series of bytes in the HAI_receive[] array
//Serial.println ("HAI response: ");
//Serial.print ("Message Size: ");//debug
// Serial.print(receivedMessageSize);//debug
//For debug the content is sent on Serial
Serial.println("Ended for loop");
//test flags 424 (raise blinds) and 425 (lower blinds)
//get flag value
byte flag424 =HAI_receive[32]& B00000001;
byte flag425 =HAI_receive[33]& B10000000;
if (flag424==0x01)//flag 424=1. note need a bitwise and op to
{
Serial.println("Blinds up triggered");
blinds(1);
SetFlagTo(424,0);
sendToHAI();
}
if (flag425==0x80)//flag 425=1
{
Serial.println("Blinds down triggered");
blinds(0);
SetFlagTo(425,0);
sendToHAI();
}
}
else {
Serial.println("Serial Port (Serial2) not available when getting system status");
}
}
digitalWrite(13, LOW); // turn the LED off by making the voltage LOW
Serial2.flush();
Serial.println("End of loop function");
}