Thursday, March 17, 2016

Building an IR-to-RS485 Bridge


I wanted to be able to control my Somfy ST30 RS-485 shades via my universal infrared remote, so I built a simple project using an Arduino (aka a Genuino these days, if you live outside the USA) to do this. This could easily be adapted to control any serial device, either RS-232 or RS-485.

This also acts as a sniffer for my RS-485 network, which I used to observe and reverse engineer the Somfy protocol. Please see my previous post (Somfy Wire Protocol) for my documentation of this protocol.

I started with an Arduino Mega 2560, because it has four serial ports. I'm using only two at this time, one for debugging/setup over the built-in USB port, and a second for the RS-485 connection to control my shades. I plan to control addition devices later with the extra serial ports. If you only wan to control one thing, you could probably also use an ATmega32u4-based Arduino, like the Leonardo or Micro, since those have a separate USB port from the TX/RX pins.

You could mostly implement this functionality with a Raspberry Pi, but I feel a microcontroller is really the right thing, in this case. The ATmega2560 has four serial ports versus the Pi's one, and that's a problem for me, as stated above. I also prefer the simplicity of the microcontroller. The Raspberry Pi far more complex, which is necessary for some projects, but not for this one. The Arduino loses to the Pi in cost, though. The Arduino Mega goes for $46, while the top-of-the line Pi goes for $35. If I want to add the Arduino Ethernet Shield to my project, that's another $45, while the Pi has it built in. (In fact, if I want to add ethernet, it's cheaper to connect a Pi to my Arduino!) On the other hand, if I ever wanted mass-produce my project (admittedly not likely), it might be cheaper to go with the microcontroller design. (Hmm.. you can get ATmega2560 chips in bulk for about $10 a piece, which makes it a very expensive microcontroller. Haven't found the cost of the Pi's ARM1176JZF-S.)

Parts List

I chose the MAX483 chip specifically because it is designed for improperly terminated networks. Per Somfy's directions, my network is setup in a star configuration, without any termination, so this chip seemed like the right thing. (And, indeed, it works!)

The RJ45 jack is specific to how my network is setup. I wired it up via Somfy's specifications, which matches the RJ45 jack on the back of the Somfy smart switches. You may want a different kind of connector for your project, or you may need the connector wired up differently. There's no wider standard that I'm aware of. You could even plug the wires from your RS-485 network directly into your breadboard.

Schematic



A few notes about this schematic:

The IR LED is switched via the 2N2222 transistor, so as to drive it at higher brightness than the AVR can drive directly. I'm not sure these are the optimal resistor values, but they work for me.

The IRremote library by default requires this to be connected to pin 9 on the Arduino Mega. There are several other options that can be chosen by modifying IRremoteInt.h in the library directory and recompiling. And if you are using a different Arduino, it's very important to consult this file to determine the proper pin. The library uses different pins for different AVRs!

The DE and RE pins of the MAX483 are connected together with pin 2 on the Arduino. This is possible because RE is inverted on the transceiver chip. Thus, when pin 2 is high, the transceiver is configured for sending but not receiving, and the opposite is true when pin 2 is low. This means the Arduino won't see the data it is putting on the RS-485 bus, but that's fine for my purposes.

Putting it Together

See the top of this post for what this project looks like put together on a breadboard.

I added a second RJ45 jack, which is just split off the first. This gives me a port to hook up a USB-to-RS485 adapter, which allows me to use the Somfy software on the same network as my bridge. (This is how I logged protocol data, which I used to reverse engineer the protocol.) I could have simply used an RJ45 splitter, instead. Neither of these is necessary for the finished product.

In it's final home, I power the circuit by connecting the Arduino to my DVR via a USB cable. (For setup, it's powered off of my computer's USB port.)

Software

I used Ken Shirriff's IRremote libary, which is pretty awesome! One thing to note is that newer versions of the Arduino tools contain a bastardized version of this library. (Seriously, Arduino folks, what were you thinking?) You might want to remove the "RobotIRremote" directory from your Arduino installation to make sure there's no conflict. Unlike the original IRremote, RobotIRremote has limited receiving and no sending capabilities. No good!

I've uploaded my Arduino sketch to GitHub:

https://github.com/baysinger/ir2rs485/tree/1.0

I plan to keep this git branch in sync with this blog post. The master branch may drift.

Build the sketch and upload it to the Arduino using the Arduino software suite.

Using It

Test the RS-485 Connection

With the project connected to the RS-485 network, and the USB port on the Arduino connected to a computer running the Arduino software, it's time to test things out. Open the Arduino serial monitor and set the line ending to carriage return.

Find out your motor address. If the label on the motor says the address is AB C1 23, you can convert this to network format by reversing the byte order (23 C1 AB) and then flipping every bit (DC 3E 54). I'll use DC3E54 in this example.

Try lowering the shade by issuing this command in the serial monitor:

mffffffdc3e54fdffff (substitute your motor address, in network format, for dc3e54)

Note that it is important for all letters to be lowercase. I didn't bother to write the code to process uppercase letters.

Now to raise the shade again:

mffffffdc3e54feffff

Programming the Remote

You should pick a 16-bit IR code for each command you wish to issue with the remote. I wrote the code to partition the 16 bits into a high order byte to specify the motor or group, and a low order byte to specify the command. So, for instance, I have these motors and groups (in hexadecimal):

10: All shades
20: Most shades (a group the contains all but one of my shades)
30: All the dining room shades
01: Sliding door
02: Dining room left
03: Dining room middle
04: Dining room right
05: Living room
06: Stairs

And these commands:

01: Up
02: Down
04: Stop

So, for example, the IR command for all shades up is 1001. The command for living room down is 0502.

To program the remote, you need to tell the Arduino to send an IR signal, and then you can have the remote learn that signal. To tell the Arduino to send the IR code for all shades up, enter this command into the serial monitor:
i1001
You'll also need to modify the Arduino sketch to recognize these codes when the remote sends them. (You can do this before or after programming the remote.) Open the sketch and find this switch statement:
switch (results.value >> 8) {
This section maps the high order byte received via IR to a motor or group address. You'll need to modify the case labels below that. For instance, this case label maps the value 10 to the group address 323456:
case 0x10:
   Serial.print("all ");
   uGrp = 0x323456;
   break;
And this one maps the value 01 to the motor address DBF6F9:
case 0x1:
   Serial.print("sliding door ");
   uAddr = 0xDBF6F9;
   break;
Modify this switch statement to match your setup.

Done!

Now, as long as everything is connected, you should be able to control the shades with the remote control!

Tuesday, March 1, 2016

Somfy Wire (RS-485) Protocol

Somfy uses a very simple protocol for controlling the motors over RS-485, and it has been fairly easy to reverse engineer, so far. Somfy even provides the "ILT Command Calculator" utility (available at https://www.somfypro.com/downloads), which will show the raw bytes of every control command for a given motor or group address. I have provided information on all the control commands that are covered by Somfy's command calculator. There are also a number of other messages, and I have decoded a couple of those, too.

Overall Message Format

All messages that I've decoded so far follow the same basic format:

  • Byte 0: Message ID
  • Byte 1: Message length, subtracted from 0xFF
  • Byte 2: Unknown
  • Bytes 3..N: Payload
  • Bytes N..N+1: Checksum

Length

The length is the overall length of the message, including the ID, length field, payload, and checksum. It is encoded by subtractin from 0xFF. For example, a length of 14 bytes would be encoded as F1.

Checksum

The checksum bytes simply contains the lower two bytes of the summation of all the previous bytes of the message, in big-endian order.

Control Commands

The control command message used a message ID of 0xAB. The entire message contains these fields:
  • Byte 0: Message ID (AB)
  • Byte 1: Message length (F1, decoded to 14)
  • Byte 2: Unknown (observed to be FF)
  • Bytes 3..5: Group address
  • Bytes 6..8: Motor address
  • Byte 9: Command
  • Bytes 10..11: Parameters
  • Bytes 12..13: Checksum

 

Addresses

The command either addresses a motor or a group, but not both at once. The unused address field is set to FF FF FF.

The motor address is the real address of the motor, not the ST30 address that is printed on the motor label (assuming it's an ST30 motor). I can't figure out why Somfy just doesn't print the real address on the motor label! Anyway, the conversion formula is very simple: reverse the order of the bytes and flip every bit. So, for instance:

Label Address: 06091F (00000110 00001001 00011111‬)
Real Address: E0F6F9 (11100000 11110110 11111001‬)

 

Command Byte

The command byte is one of the following:
  • FE: Up
  • FD: Down
  • FC: Stop
  • FA: Next IP UP
  • F9: Next IP DOWN
  • FB: Go to IP
  • EF: Go to %
  • F5: Jog Up 10 ms
  • F4: Jog Down 10 ms
IPs are preset positions which are configured into the motor via the configuration utility at the above link. There are also some lock commands visible in the calculator tool, but I haven't documented them, because I haven't really looked into what they do.

Parameters

The parameter bytes are only used for some commands. For commands that don't take parameters,  these bytes should be FF. If a parameter byte is used, it's encoded by subtracting from FF. So 10 is encoded as F5. Of the above commands, the following take one parameter in the first parameter byte (values shown without encoding):

  • Down: 0 or 10-255 (I'm not sure what this parameter is used for. The default seems to be 0, encoded as FF.)
  • Go to IP: IP index (1-16)
  • Go to %: Position (0-255)
  • Jog Up/Down 10 ms: 1-255 (I'm not sure what this is used for. Number of 10 ms increments?)

 


Examples

Here are some examples of complete commands:
Tell motor with address ABCDEF to go up: AB F1 FF FF FF FF AB CD EF FE FF FF 0A FB
Tell group with address FEDCBA to go up: AB F1 FF FE DC BA FF FF FF FE FF FF 0B 28
Tell motor with address ABCDEF to go to 50%: AB F1 FF FF FF FF AB CD EF EF 80 FF 0A 6D

 

Other Messages


There are a number of other messages that are not shown in the ILT Command Calculator. These are are used for things like configuration (setting limits, group addresses, preset positions, etc.) as well as querying information from the motors (position, configuration). These are used by the "ILT2-ST30 Configuration Tool" (see Somfy link above). I was interested in being able to query the positions of my shades, so I decoded those messages. I haven't looked into any other messages.

Position Query

To query the position of the motor, the tool sends a message like this:
BB F4 FF 80 80 80 E0 F6 F9 6 FD
This contains the following fields:
  • Byte 0: Message ID (BB)
  • Byte 1: Message length (F1, decodes to 14)
  • Byte 2: Unknown (observed to be FF)
  • Bytes 3..5: Group address
  • Bytes 6..8: Motor address
  • Bytes 9..10: Checksum
The fields are used just like in the command messages above, except I've noticed the tool sets the group address to 80 80 80 instead of FF FF FF. In my experimentation, it doesn't seem to matter as long as a motor address is supplied.

You can send a position query to a group of motors by setting the motor address field to FF FF FF while setting the group address appropriately, and all the motors in that group will reply. I'm not sure this is a good idea, because the motors might try to talk over each other, but in practice, I haven't had this be an issue.

Position Reply

The motor replies with a message like this:
9B F1 DF E0 F6 F9 80 80 80 38 FB 60 08 4D
This contains the following fields:
  • Byte 0: Message ID (9B)
  • Byte 1: Message length (F1)
  • Byte 2: Unknown (observed to be DF)
  • Bytes 3..5: Motor address
  • Bytes 6..8: Group address, always the same as in the query message
  • Bytes 9..10: Position in pulses from top (encoded)
  • Byte 11: Relative position (0 is bottom limit, FF is top limit)
  • Bytes 12..13: Checksum
The position in pulses is encoded in little-endian, unlike the checksum (go figure!) and subtracted from 0xFFFF (as Somfy seems to like to do).