// ------------------------------------------------------------- // // 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> 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 -> 0 -- 0001 -> 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