HAI Omnipro2 and Somfy RS485 Transmitter -Solved

cestor

Active Member
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.
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");
                
}
 

BirdingPix

Member
Fantastic info!   Thanks for posting.   I too have an HAI OP2 controller and Somfy RS485 RTS SDN network for my solar shades.  
 
I finally got everything working once I realized that the OP2 serial ports cannot send out 8O1 (8 data bits, ODD parity, 1 stop bit)... and I too then added an Arduino Uno controller which does support 8O1 serial port.  I added the RS485 daughter board to the Arduino and interfaced the Arduino digital inputs to the OUTPUTS from OP2 controller for each shade via the relay board.  That way the Arduino and Somfy SDN is electrically isolated from the HAI OP2.     The Arduino scans its digital inputs (using internal pullups on the Arduino) to sense the N.O. relay contact closures controlled by the OP2 to indicate UP or DOWN for each shade. 
 
I have photos too but unable to create My Gallery and upload since I guess I am too new here and my member profile is 'disabled' from uploading any photos.!^[email protected]!#
 
Top