Files
Arduino/ESP8266_DOMOTICZ_OCTOPRINT_WEB_VOLETS/Somfy.cpp
Jérôme Delacotte 7b30d6e298 first commit
2025-03-06 11:15:32 +01:00

758 lines
21 KiB
C++

// -------------------------------------------------------------
//
// Fichier principal de votre Box compatible Somfy RTS ou Chacon Dio
// Version 1.1.0
// Mise à jour par TOST Corp. le 02 janvier 2020
// www.tostcorp.com/boxsomfyrts
//
// -------------------------------------------------------------
#include "config.h"
// #include "Somfy.h"
void blink_function();
Ticker mqtt_error_timer(blink_function, 10, 11);
Ticker blink_timer(blink_function, 200, 21);
Ticker publish_timer(blink_function, 10, 11);
void receivedCallback(char* topic, byte* payload, unsigned int length);
Somfy::Somfy()
{
}
void Somfy::init()
{
// USB serial port
delay(100);
while(! Serial);
Serial.println(" ------------------------------------------------");
Serial.println("| |");
Serial.println("| TOST Corp. Box V1.0.2 - Copyright 2018-2020 |");
Serial.println("| |");
Serial.println(" ------------------------------------------------");
Serial.println("");
Serial.println("Available features:");
Serial.println("1 SOMFY RTS");
Serial.println("2 CHACON DiO 1.0");
Serial.println("");
Serial.println("Starting the Box...");
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Open the output for 433.42MHz transmitter
pinMode(PORT_TX, OUTPUT);
// Open the output for 433.92MHz transmitter
pinMode(PORT_DIO_TX, OUTPUT);
SIG_LOW;
digitalWrite(PORT_DIO_TX, LOW);
String device_id = WiFi.macAddress();
// Remove :
while (device_id.indexOf(':') != -1)
{
int index_to_remove = device_id.indexOf(':');
device_id.remove(index_to_remove, 1);
}
Serial.print("Box ID: ");
Serial.println(device_id);
// Connect to WiFi
Serial.print("WiFi connection to ");
Serial.print(wifi_ssid);
Serial.print(" ");
WiFi.begin(wifi_ssid, wifi_password);
WiFi.hostname("ESP8266-somfy");
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi | IP adress ");
Serial.println(WiFi.localIP());
// Configure MQTT
PubSubClient _mqtt(wifiClient);
mqtt = _mqtt;
mqtt.setServer(mqtt_server, mqtt_port);
//mqtt.setCallback(receivedCallback);
mqtt.setCallback([this] (char* topic, byte* payload, unsigned int length) {
this->receivedCallback(topic, payload, length);
});
EEPROM.begin(1024);
// Print out all the configured remotes.
// Also reset the rolling codes for ESP8266 if needed.
Serial.print("");
Serial.println("Read the configuration...");
if (update_database)
{
clean_database(device_id);
}
for ( REMOTE remote : remotes )
{
Serial.print("SOMFY RTS: ");
Serial.print(remote.mqtt_topic);
Serial.print(" | number: ");
Serial.print(remote.id, HEX);
Serial.print(" | rolling code: ");
unsigned int current_code;
if ( reset_rolling_codes )
{
EEPROM.put( remote.eeprom_address, remote.default_rolling_code );
EEPROM.commit();
}
EEPROM.get( remote.eeprom_address, current_code );
Serial.print( current_code );
Serial.print(" | description: ");
Serial.println( remote.description );
// Add to database
if (update_database)
{
add_to_database(device_id, remote.mqtt_topic, remote.description, "somfy_rts", remote.device_group);
}
}
for ( DIO_REMOTE dio_remote : dio_remotes )
{
Serial.print("CHACON DiO: ");
Serial.print(dio_remote.mqtt_topic);
Serial.print(" | number: ");
Serial.print(dio_remote.id, HEX);
Serial.print(" | description: ");
Serial.println(dio_remote.description);
// Add to database
if (update_database)
{
add_to_database(device_id, dio_remote.mqtt_topic, dio_remote.description, "chacon_dio_1.0", dio_remote.device_group);
}
} // End of for
Serial.print("");
digitalWrite(LED_BUILTIN, HIGH);
blink_timer.start();
}
void Somfy::loop()
{
publish_timer.update();
// Reconnect MQTT if needed
if ( !mqtt.connected() )
{
mqttconnect();
// We are connected stop blinking to Wifi
blink_timer.stop();
Serial.println("The box is ready.\n");
Serial.println("");
}
mqtt.loop();
loopSerial();
delay(100);
}
void Somfy::mqttconnect()
{
// Loop until reconnected
while ( !mqtt.connected() )
{
Serial.print("Connecting to MQTT ...");
Serial.print(mqtt_id);
Serial.print(mqtt_user);
Serial.print(mqtt_password);
Serial.print(status_topic);
// Connect to MQTT, with retained last will message "offline"
if (mqtt.connect(mqtt_id, mqtt_user, mqtt_password, status_topic, 1, 1, "offline")) {
Serial.println(" Connected.");
Serial.print("Suscribe to MQTT .");
// Subscribe to the topic of each remote with QoS 1
for ( REMOTE remote : remotes ) {
mqtt.subscribe(remote.mqtt_topic, 1);
mqtt.subscribe(remote.device_group, 1);
Serial.print(".");
// Serial.print("\nLOG - Subscribed to SOMFY RTS topic: ");
// Serial.println(remote.device_group);
}
// Subscribe to the topic of each remote with QoS 1
for ( DIO_REMOTE remote : dio_remotes ) {
mqtt.subscribe(remote.mqtt_topic, 1);
mqtt.subscribe(remote.device_group, 1);
Serial.print(".");
// Serial.print("\nLOG - Subscribed to DIO topic: ");
// Serial.println(remote.mqtt_topic);
}
// Update status, message is retained
mqtt.publish(status_topic, "online", true);
Serial.println("Done.");
}
else
{
Serial.print("ERR - Failed, status code = ");
Serial.print(mqtt.state());
Serial.println(" Try again in 5 seconds");
// Wait 5 seconds before retrying
blink_blue_light(5000, 200);
}
}
blink_blue_light(500, 50);
}
void Somfy::receivedCallback(char* topic, byte* payload, unsigned int length) {
char command = *payload; // 1st byte of payload
bool commandIsValid = false;
bool dioCommandIsValid = false;
String s_topic = String(topic);
REMOTE currentRemote;
REMOTE remote;
// DIO_REMOTE dio_remote;
DIO_REMOTE dio_currentRemote;
Serial.print("MQTT message received: ");
digitalWrite(BUILTIN_LED, LOW);
publish_timer.start();
Serial.println(topic);
Serial.print("Payload: ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Somfy command is valid if the payload contains one of the chars below AND the topic corresponds to one of the remotes
if ( length == 1 && ( command == 'u' || command == 's' || command == 'd' || command == 'p' ) )
{
Serial.println("SOMFY RTS command received");
int nb_remotes = sizeof remotes/sizeof remotes[0];
for (int i=0; i<nb_remotes; i++)
{
remote = remotes[i];
String s_mqtt_topic = String(remote.mqtt_topic);
String s_device_group = String(remote.device_group);
// if ( strcmp(remote.mqtt_topic, s_topic) == 0 )
if ( s_mqtt_topic.compareTo(s_topic) == 0 )
{
commandIsValid = true;
send_somfy_command(command, remote, false);
}
// if ( strcmp(remote.device_group, s_topic) == 0 )
else if ( s_device_group.compareTo(s_topic) == 0 )
{
Serial.print("SOMFY RTS command is grouped ");
Serial.println(remote.mqtt_topic);
commandIsValid = true;
send_somfy_command(command, remote, true);
}
} // End of for
}
// DIO command
if ( length ==1 && ( command == '1' || command == '0' ) )
{
Serial.println("DiO command received");
for ( DIO_REMOTE dio_remote : dio_remotes )
{
String s_mqtt_topic = String(dio_remote.mqtt_topic);
String s_device_group = String(dio_remote.device_group);
// Single command
// if ( strcmp(dio_remote.mqtt_topic, topic) == 0 )
if ( s_mqtt_topic.compareTo(s_topic) == 0 )
{
dioCommandIsValid = true;
send_dio_command(command, dio_remote, false);
}
// Multiple command
// else if ( strcmp(dio_remote.device_group, topic) == 0 )
else if ( s_device_group.compareTo(s_topic) == 0 )
{
Serial.println("DiO command is grouped");
dioCommandIsValid = true;
send_dio_command(command, dio_remote, true);
}
} // End of for
}
}
void Somfy::send_somfy_command(char command, REMOTE currentRemote, bool group)
{
if ( command == 'u' )
{
Serial.println("COMMAND - Up"); // Somfy is a French company, after all.
BuildFrame(frame, HAUT, currentRemote);
}
else if ( command == 's' )
{
Serial.println("COMMAND - Stop");
BuildFrame(frame, STOP, currentRemote);
}
else if ( command == 'd' )
{
Serial.println("COMMAND - Down");
BuildFrame(frame, BAS, currentRemote);
}
else if ( command == 'p' )
{
Serial.println("COMMAND - Prog");
BuildFrame(frame, PROG, currentRemote);
}
Serial.println("");
SendCommand(frame, 2);
for ( int i = 0; i<2; i++ ) {
SendCommand(frame, 7);
}
// Send the MQTT ack message
String ackString = "id: 0x";
ackString.concat( String(currentRemote.id, HEX) );
ackString.concat(", cmd: ");
ackString.concat(command);
mqtt.publish(ack_topic, ackString.c_str());
if (group)
{
delay(delay_group_radio_signal);
}
}
void Somfy::send_dio_command(char command, DIO_REMOTE dio_currentRemote, bool group)
{
if ( command == '0' )
{
Serial.println("COMMAND - 0");
// Serial.println(dio_currentRemote.sender);
for (int i = 0; i < 5; i++)
{
transmit(dio_currentRemote.sender, dio_currentRemote.interruptor, 0);
delay(10);
}
}
else if ( command == '1' )
{
Serial.println("COMMAND - 1");
// Serial.println(dio_currentRemote.sender);
for (int i = 0; i < 5; i++)
{
transmit(dio_currentRemote.sender, dio_currentRemote.interruptor, 1);
delay(10);
}
}
Serial.println("");
// Send the MQTT ack message
String ackString = "id: 0x";
ackString.concat( String(dio_currentRemote.id, HEX) );
ackString.concat(", cmd: ");
ackString.concat(command);
mqtt.publish(ack_topic, ackString.c_str());
if (group)
{
delay(delay_group_radio_signal);
}
}
void Somfy::BuildFrame(byte *frame, byte button, REMOTE remote) {
unsigned int code;
EEPROM.get( remote.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.id >> 16; // Remote address
frame[5] = remote.id >> 8; // Remote address
frame[6] = remote.id; // 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 nibble is a 0.
Serial.print("0");
}
Serial.print(frame[i],HEX); Serial.print(" ");
}
// Checksum calculation: a XOR of all the nibbles
byte 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( remote.eeprom_address, code + 1 );
EEPROM.commit();
}
void Somfy::SendCommand(byte *frame, byte sync) {
if(sync == 2) { // Only with the first frame.
//Wake-up pulse & Silence
SIG_HIGH;
delayMicroseconds(9415);
SIG_LOW;
delayMicroseconds(89565);
}
// Hardware sync: two sync for the first frame, seven for the following ones.
for (int i = 0; i < sync; i++) {
SIG_HIGH;
delayMicroseconds(4*SYMBOL);
SIG_LOW;
delayMicroseconds(4*SYMBOL);
}
// Software sync
SIG_HIGH;
delayMicroseconds(4550);
SIG_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) {
SIG_LOW;
delayMicroseconds(SYMBOL);
SIG_HIGH;
delayMicroseconds(SYMBOL);
}
else {
SIG_HIGH;
delayMicroseconds(SYMBOL);
SIG_LOW;
delayMicroseconds(SYMBOL);
}
}
SIG_LOW;
delayMicroseconds(30415); // Inter-frame silence
}
void blink_function()
{
//toggle state
int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin
digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state
// Serial.print("blink_function()");
}
void Somfy::blink_blue_light(int time_ms, int delay_ms)
{
int total_time = 0;
int light = 0;
while (total_time < time_ms)
{
if (light == 0)
{
digitalWrite(LED_BUILTIN, LOW);
light = 1;
}
else
{
digitalWrite(LED_BUILTIN, HIGH);
light = 0;
}
delay(delay_ms);
total_time = total_time + delay_ms;
}
// force off
digitalWrite(0, HIGH);
return;
}
void Somfy::sendBit(bool b)
{
if (b) {
digitalWrite(PORT_DIO_TX, HIGH);
delayMicroseconds(310); //275 originally, but tweaked.
digitalWrite(PORT_DIO_TX, LOW);
delayMicroseconds(1340); //1225 originally, but tweaked.
} else {
digitalWrite(PORT_DIO_TX, HIGH);
delayMicroseconds(310); //275 originally, but tweaked.
digitalWrite(PORT_DIO_TX, LOW);
delayMicroseconds(310); //275 originally, but tweaked.
}
}
void Somfy::sendPair(bool b) {
if (b) {
sendBit(true);
sendBit(false);
} else {
sendBit(false);
sendBit(true);
}
}
void Somfy::transmit(unsigned long sender, int interruptor, int blnOn) {
int i;
digitalWrite(PORT_DIO_TX, HIGH);
delayMicroseconds(275);
digitalWrite(PORT_DIO_TX, LOW);
delayMicroseconds(9900); // first lock
digitalWrite(PORT_DIO_TX, HIGH); // high again
delayMicroseconds(275); // wait
digitalWrite(PORT_DIO_TX, LOW); // second lock
delayMicroseconds(2675);
digitalWrite(PORT_DIO_TX, HIGH);
// Emiter ID
for(i = 0; i < 26; i++) {
sendPair( (sender >> (25 - i)) & 0b1);
}
// 26th bit -- grouped command
sendPair(false);
// 27th bit -- On or off
sendPair(blnOn);
// 4 last bits -- reactor code 0000 -&gt; 0 -- 0001 -&gt; 1
for (i = 0; i < 4; i++) {
sendPair( (interruptor >> (3 - i)) & 1 );
}
digitalWrite(PORT_DIO_TX, HIGH); // lock - end of data
delayMicroseconds(275); // wait
digitalWrite(PORT_DIO_TX, LOW); // lock - end of signal
}
int Somfy::add_to_database(String device_id,const char* mqtt_topic, const char* description, const char* type, const char* device_group)
{
if (wifiClient.connect(HOST, 80))
{
String url = "/somfy_rts/mqtt_web_hook_public.php?service=add_device&device_id=" + device_id +
"&topic=" + urlencode(mqtt_topic) + "&description=" + urlencode(description) + "&type=" + type +
"&device_group=" + urlencode(device_group)+ "&mqtt_info=" + urlencode(mqtt_password);
wifiClient.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + HOST + "\r\n" +
"Connection: close\r\n\r\n");
String line = wifiClient.readStringUntil('\r');
// Serial.println(line);
// Read all the lines of the reply from server and print them to Serial
while(wifiClient.available())
{
line = wifiClient.readStringUntil('\r');
// Serial.print(line);
}
Serial.print("The device \"");
Serial.print(description);
Serial.println("\" was added/updated to the database.");
return 1;
}
else
{
Serial.print("An error occurs while adding the device \"");
Serial.print(description);
Serial.print("\"");
return 0;
}
}
int Somfy::clean_database(String device_id)
{
Serial.print("Clean the device ");
Serial.println(device_id);
if (wifiClient.connect(HOST, 80))
{
String url = "/somfy_rts/mqtt_web_hook_public.php?service=clear_devices&device_id=" + device_id +
"&mqtt_info=" + urlencode(mqtt_password);
wifiClient.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + HOST + "\r\n" +
"Connection: close\r\n\r\n");
String line = wifiClient.readStringUntil('\r');
// Serial.println(line);
// Read all the lines of the reply from server and print them to Serial
while(wifiClient.available())
{
line = wifiClient.readStringUntil('\r');
// Serial.print(line);
}
Serial.print("The device \"");
Serial.print(device_id);
Serial.println("\" was deleted from the database.");
return 1;
}
else
{
Serial.print("An error occurs while cleaning the device \"");
Serial.print(device_id);
Serial.print("\"");
return 0;
}
}
String Somfy::urlencode(String str)
{
String encodedString="";
char c;
char code0;
char code1;
char code2;
for (int i =0; i < str.length(); i++){
c=str.charAt(i);
if (c == ' '){
encodedString+= '+';
} else if (isalnum(c)){
encodedString+=c;
} else{
code1=(c & 0xf)+'0';
if ((c & 0xf) >9){
code1=(c & 0xf) - 10 + 'A';
}
c=(c>>4)&0xf;
code0=c+'0';
if (c > 9){
code0=c - 10 + 'A';
}
code2='\0';
encodedString+='%';
encodedString+=code0;
encodedString+=code1;
//encodedString+=code2;
}
yield();
}
return encodedString;
}
unsigned char Somfy::h2int(char c)
{
if (c >= '0' && c <='9'){
return((unsigned char)c - '0');
}
if (c >= 'a' && c <='f'){
return((unsigned char)c - 'a' + 10);
}
if (c >= 'A' && c <='F'){
return((unsigned char)c - 'A' + 10);
}
return(0);
}
void Somfy::loopSerial() {
// Send Remote Control Request by Serial
if (Serial.available() > 0) {
digitalWrite(BUILTIN_LED, LOW);
publish_timer.start();
REMOTE currentRemote;
char inByte[2];
Serial.readBytes(inByte, 2);
byte idx = inByte[1]-48;
Serial.print((char)inByte[0]);
char command = (char)inByte[0];
Serial.print(command);
int nb_remotes = sizeof remotes/sizeof remotes[0];
for (int i=0; i<nb_remotes; i++) {
if (i == idx) {
currentRemote = remotes[idx];
}
}
Serial.println(idx);
if ( command == 'u' ) {
Serial.println("Monte"); // Somfy is a French company, after all.
BuildFrame(frame, HAUT, currentRemote);
}
else if ( command == 's' ) {
Serial.println("Stop");
BuildFrame(frame, STOP, currentRemote);
}
else if ( command == 'd' ) {
Serial.println("Descend");
BuildFrame(frame, BAS, currentRemote);
}
else if ( command == 'p' ) {
Serial.println("Prog");
BuildFrame(frame, PROG, currentRemote);
}
Serial.println("");
SendCommand(frame, 2);
for ( int i = 0; i<2; i++ ) {
SendCommand(frame, 7);
}
}
}