Build your Somfy smart transmitter

Hello everyone, a small tutorial to create a Somfy command emitter.

Operation:

This tutorial allows you to create a 433.42MHz emitter connected via WiFi to the Gladys MQTT broker and command buttons for roller shutters.

You will thus be able to control blinds, roller shutters or any other system working on the principle of Somfy command.

Prerequisites

You will need:

  • a soldering iron with solder

Connection

The most delicate step is to remove the resonator on the transmitter to solder its replacement.

The blue LED will flash with each code sent between the ESP8266 and the transmitter. A small check to make sure everything works well!

Software Side:

The driver for ESP8266 copies on aliexpress can be found here:

Follow the installation tutorial.

In Arduino, choose the COM port that corresponds to your little beast:


Choose your board (For ESP8266 board copies, I use this board ref:

Wemos

Programming

In order for the Gladys buttons to send the values expected by the transmitter, you need to create several MQTT fake devices.

In the MQTT integration, create 2 “fake devices”:

  • Shutter control with its 3 buttons that will send the values -1, 0 and 1.
  • A dimmer switch that is activated from 2 to 6 inclusive to program and create the interface code between the shutter button and your module.
    Indicated Link1 in the code

You can create another text fake device to see in Gladys the command feedback taken into account by the transmitter. It is provided in the code, otherwise put nothing.
Indicated Link2 in the code



Shutter status
Dimmer
Text

Be sure to get the Topic addresses that you will need to indicate in the code for the ESP8266.

Code:

It probably contains gross errors but I managed to make it run with my limited coding skills. It is therefore very perfectible .. :pray:

I am not the creator of the code, so I do not take the credit. I just adapted it to accept numeric values instead of letters.

2 = Down
3 = Stop
4 = Up
5 = Prog
6 =

The 6 is intentionally left blank to perform the programming with the original command.

If you want to create multiple remote controls or if you need to re-register after modification, remember to increment this value as indicated in the example for each of the transmitters: #define REMOTE 0x122000

/* This sketch allows you to emulate a Somfy RTS or Simu HZ remote.
   
   This is a fork of the original sketch written by Nickduino (https://github.com/Nickduino)
    
   If you want to learn more about the Somfy RTS protocol, check out https://pushstack.wordpress.com/somfy-rts-protocol/
   
   The rolling code will be stored in EEPROM, so that you can power the D1 Mini.
   
   Easiest way to make it work for you:
    - Choose a remote number
    - Choose a starting point for the rolling code. Any unsigned int works, 1 is a good start
    - Upload the sketch
    - Long-press the program button of YOUR ACTUAL REMOTE until your blind goes up and down slightly
    - send 'p' to the serial terminal oder via 'MQTT'
   To make a group command, just repeat the last two steps with another blind (one by one)
  
   Then:
    - u will make it to go up
    - s make it stop
    - d will make it to go down
    - p sets the program mode
    - you can also send a HEX number directly for any weird command you (0x9 for the sun and wind detector for instance)
*/

#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <AsyncElegantOTA.h>

WiFiClient espClient;
PubSubClient client(espClient);

String header;
const char* ssid = "xx";   // <-- Enter your Wifi-SSID
const char* password = "xx";  // <-- Enter your Wifi-Password

const char* mqtt_server = "xx";  // <-- Enter the IP of your MQTT-Server
const unsigned int mqtt_port = 1883;
const char* mqtt_user =   "xx"; 
const char* mqtt_password =   "xx"; // <-- Enter the Password of your MQTT-Server
String clientId = "Awning";

// MQTT topic parameters
const char* topic = "link1"; // <-- The topic with encrypted code
const char* topic2 = "link2"; // <-- The command return text topic
 
#define SYMBOL 640
#define UP 0x2
#define STOP 0x1
#define DOWN 0x4
#define PROG 0x8
#define EEPROM_ADDRESS 0
#define REMOTE 0x121000    //<-- Change it to a unique no., if you have more than one Remote (0x121305, 0x121306, 0x121307, ...)

AsyncWebServer server(80);

char demand = '6'; // w = waiting

const int transmitPin = D1;

unsigned long rollingCode = 1;

byte frame[7];
byte checksum;



void BuildFrame(byte *frame, byte button) {
  unsigned int code;
  EEPROM.get(EEPROM_ADDRESS, code);
  frame[0] = 0xA7; // Encryption key. Doesn't matter much
  frame[1] = button << 4;  // Which button did  you press? The 4 LSB will be the checksum
  frame[2] = code >> 8;    // Rolling code (big endian)
  frame[3] = code;         // Rolling code
  frame[4] = REMOTE >> 16; // Remote address
  frame[5] = REMOTE >>  8; // Remote address
  frame[6] = REMOTE;       // Remote address

  Serial.print("Frame         : ");
  for(byte i = 0; i < 7; i++) {
    if(frame[i] >> 4 == 0) { //  Displays leading zero in case the most significant
      Serial.print("0");     // nibble is a 0.
    }
    Serial.print(frame[i],HEX); Serial.print(" ");
  }
  
// Checksum calculation: a XOR of all the nibbles
  checksum = 0;
  for(byte i = 0; i < 7; i++) {
    checksum = checksum ^ frame[i] ^ (frame[i] >> 4);
  }
  checksum &= 0b1111; // We keep the last 4 bits only


//Checksum integration
  frame[1] |= checksum; //  If a XOR of all the nibbles is equal to 0, the blinds will
                        // consider the checksum ok.

  Serial.println(""); Serial.print("With checksum : ");
  for(byte i = 0; i < 7; i++) {
    if(frame[i] >> 4 == 0) {
      Serial.print("0");
    }
    Serial.print(frame[i],HEX); Serial.print(" ");
  }

  
// Obfuscation: a XOR of all the bytes
  for(byte i = 1; i < 7; i++) {
    frame[i] ^= frame[i-1];
  }

  Serial.println(""); Serial.print("Obfuscated    : ");
  for(byte i = 0; i < 7; i++) {
    if(frame[i] >> 4 == 0) {
      Serial.print("0");
    }
    Serial.print(frame[i],HEX); Serial.print(" ");
  }
  Serial.println("");
  Serial.print("Rolling Code  : "); Serial.println(code);
  EEPROM.put(EEPROM_ADDRESS, code + 1); //  We store the value of the rolling code in the
                                        // EEPROM. It should take up to 2 adresses but the
                                        // Arduino function takes care of it.
 EEPROM.commit();                                       
}



void SendCommand(byte *frame, byte sync) {
  if(sync == 2) { // Only with the first frame.
  //Wake-up pulse & Silence
    digitalWrite(transmitPin, HIGH);
    delayMicroseconds(9415);
    digitalWrite(transmitPin, LOW);
    delayMicroseconds(89565);
  }

// Hardware sync: two sync for the first frame, seven for the following ones.
  for (int i = 0; i < sync; i++) {
    digitalWrite(transmitPin, HIGH);
    delayMicroseconds(4*SYMBOL);
    digitalWrite(transmitPin, LOW);
    delayMicroseconds(4*SYMBOL);
  }

// Software sync
  digitalWrite(transmitPin, HIGH);
  delayMicroseconds(4550);
  digitalWrite(transmitPin, LOW);
  delayMicroseconds(SYMBOL);
  
  
//Data: bits are sent one by one, starting with the MSB.
  for(byte i = 0; i < 56; i++) {
    if(((frame[i/8] >> (7 - (i%8))) & 1) == 1) {
      digitalWrite(transmitPin, LOW);
      delayMicroseconds(SYMBOL);
      digitalWrite(transmitPin, HIGH);
      delayMicroseconds(SYMBOL);
    }
    else {
      digitalWrite(transmitPin, HIGH);
      delayMicroseconds(SYMBOL);
      digitalWrite(transmitPin, LOW);
      delayMicroseconds(SYMBOL);
    }
  }
  
  digitalWrite(transmitPin, LOW);
  delayMicroseconds(30415); // Inter-frame silence
}




void callback(char* topic, byte* payload, unsigned int length) {
    if (strcmp(topic,"link1") == 0) {
      char demand_str[length + 1];
      strncpy (demand_str, (char*)payload, length);
      demand_str[length] = '\0';
      Serial.println(demand_str);
      
      // u = up, d = down, s = stop, p = program, w = wait
      
      if (strcmp(demand_str,"4") == 0) {
        demand = '4';
      } else if (strcmp(demand_str,"2") == 0) {
        demand = '2';
      } else if (strcmp(demand_str,"3") == 0) {
        demand = '3';
      } else if (strcmp(demand_str,"5") == 0) {
        demand = '5';
      } else {
        demand = '6';  
      }
      Serial.println(demand_str);
      Serial.println(demand);
    }
}

void setup() {
  Serial.begin(115200);
  Serial.println(" ");
  Serial.println("Starting Somfy");
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  WiFi.hostname("Box-Somfy");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  Serial.println(WiFi.localIP());

  pinMode(transmitPin, OUTPUT); // Pin D1 on the Wemos D1 mini
  digitalWrite(transmitPin, LOW);

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
  request->send(200, "text/plain", "Hi! I am a Wemos D1 mini.");
  });

  AsyncElegantOTA.begin(&server);    // Start ElegantOTA
  server.begin();


  EEPROM.begin(4);
  EEPROM.get(EEPROM_ADDRESS, rollingCode);

  Serial.println(" ");
  Serial.print("Simulated remote number : "); 
  Serial.println(REMOTE, HEX);
  Serial.print("Current rolling code    : "); 
  Serial.println(rollingCode);
}

void reconnect() {
  // MQTT broker reconnection loop
  while (!client.connected()) {
    Serial.println("Attempting MQTT connection...");
    if (client.connect("WemosClient", mqtt_user, mqtt_password)) {
      Serial.println("Connected to MQTT");
      // MQTT topic subscription
      client.subscribe(topic);
    } else {
      Serial.print("Failed, rc=");
      Serial.print(client.state());
      Serial.println(" Retrying in 5 seconds...");
      delay(5000);
    }
  }
}

void loop() {
  AsyncElegantOTA.loop();

// Check connection to the MQTT broker
  if (!client.connected()) {
    reconnect();
  }
  // Update MQTT client
  client.loop();

  if (demand == '4' || demand == '2' || demand == '3' || demand == '5') {

//    char serie = (char)Serial.read();
    char serie = (char)demand;
    if(serie == '4') {
      demand = '4';
      Serial.println("up"); // Somfy is a French company, after all.
      BuildFrame(frame, UP);

      client.publish(topic2, "up");
      delay(50);
      client.subscribe(topic2);
      Serial.println("moving up");
    }
    else if(serie == '2') {
      demand = '2';
      Serial.println("down");
      BuildFrame(frame, DOWN);
      client.publish(topic2, "down");
      delay(50);
      client.subscribe(topic2);
      Serial.println("moving down");
    }
    else if(serie == '5') {
      demand = '5';
      Serial.println("prog");
      BuildFrame(frame, PROG);
      client.publish(topic2, "prog");
      delay(50);
      client.subscribe(topic2);
      Serial.println("prog mode");
    }
    else if(serie == '3') {
      demand = '3';
      Serial.println("stop");
      BuildFrame(frame, STOP);
      client.publish(topic2, "stop");
      delay(50);
      client.subscribe(topic2);
      Serial.println("stop");
    }
    
    demand = '6';

    Serial.println("");
    SendCommand(frame, 2);
    for(int i = 0; i<2; i++) {
      SendCommand(frame, 7);
    }
  }
}

Consider changing these values according to your own installation:
• const char* ssid = « your SSID network name »;
• const char* password = « your Wi-Fi code »;
#define MQTT_BROKER « broker IP address »
#define MQTT_BROKER_PORT 1883
#define MQTT_USERNAME « MQTT broker username »
#define MQTT_KEY « broker password »

If you use this code for multiple modules on the same MQTT broker, consider changing this value in the « void reconnect » section:
if (client.connect(« WemosClient2 », mqtt_user, mqtt_password)) {

Gladys Scenes:

Finally, to work with Gladys’ new alarm system, you need to create 3 scenes for controlling the shutter based on this one:

Lower shutter: -1 → 2



Same for Stop: 0 → 3
Same for Raise: 1 → 4

Recording the new module on the shutter.

Last step, put the shutter in new remote control recording mode. You should have a programming button on your remote that you need to press for a few seconds. The shutter should go up and down a bit to confirm.


In Gladys, set the dimmer to 5. The shutter should react and repeat the movement.

If yes, that’s it :+1:

Otherwise, try again by switching the dimmer to 6 then 5. It may be necessary to change the code for the shutter to interpret this as a new remote control.

The shutter exits remote control recording mode on its own.

Good luck.
Tiboys

Great tutorial even though I don’t have soldering and other skills :frowning:

I’ll have a Somfy installed in a few months and it’s cool to know there’s a way to connect it to Gladys :slight_smile:

Thanks

Thanks.
My skills date back to middle school :face_savoring_food:

Small update, the AsyncElegantOTA library is deprecated, to install it you need to download the src files from this git repo: GitHub - ayushsharma82/AsyncElegantOTA: Deprecated: Perform OTAs for ESP8266 / ESP32 Elegantly! Uses AsyncWebServer and put them into your sketch, then change this line :

#include \u003cAsyncElegantOTA.h\u003e

to :

#include "AsyncElegantOTA.h"

Same thing in the file AsyncElegantOTA.cpp
You will also need to install some other dependencies, remove the #error on line 4 of AsyncElegantOTA.h
It would be best to see if

@jgcb00 , I had retrieved the code here :

Would that mean the library is no longer available?
The code was last updated 3 years ago :face_with_diagonal_mouth:
I have the library locally but I don’t know if it can be transferred to a PC.
I have 2 SOMFY shutters at home, so I set up 2 different ESPs.
I don’t know enough about coding to adapt it for 2 shutters. :grimacing:
There are several pages of the same type floating around on the net :