diff --git a/app/include/sections.h b/app/include/sections.h index d09c576e..58613c90 100644 --- a/app/include/sections.h +++ b/app/include/sections.h @@ -2,5 +2,6 @@ #define _SECTIONS_H_ #define TEXT_SECTION_ATTR __attribute__((section(".text"))) +#define RAM_CONST_SECTION_ATTR __attribute((section(".data"))) #endif diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 40b6dbb6..f34285f7 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -53,6 +53,7 @@ //#define LUA_USE_MODULES_RTCTIME //#define LUA_USE_MODULES_SIGMA_DELTA //#define LUA_USE_MODULES_SNTP +//#define LUA_USE_MODULES_SOMFY #define LUA_USE_MODULES_SPI //#define LUA_USE_MODULES_STRUCT //#define LUA_USE_MODULES_SWITEC diff --git a/app/modules/somfy.c b/app/modules/somfy.c new file mode 100644 index 00000000..4266aa6e --- /dev/null +++ b/app/modules/somfy.c @@ -0,0 +1,248 @@ +// *************************************************************************** +// Somfy module for ESP8266 with NodeMCU +// +// Written by Lukas Voborsky, @voborsky +// based on https://github.com/Nickduino/Somfy_Remote +// Somfy protocol description: https://pushstack.wordpress.com/somfy-rts-protocol/ +// and discussion: https://forum.arduino.cc/index.php?topic=208346.0 +// +// MIT license, http://opensource.org/licenses/MIT +// *************************************************************************** + +//#define NODE_DEBUG + +#include "os_type.h" +#include "osapi.h" +#include "sections.h" + +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" +#include "hw_timer.h" +#include "user_interface.h" + +#define SYMBOL 640 // symbol width in microseconds +#define SOMFY_UP 0x2 +#define SOMFY_STOP 0x1 +#define SOMFY_DOWN 0x4 +#define SOMFY_PROG 0x8 + +#define DIRECT_WRITE_LOW(pin) (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 0)) +#define DIRECT_WRITE_HIGH(pin) (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 1)) + +static const os_param_t TIMER_OWNER = 0x736f6d66; // "somf" +static task_handle_t done_taskid; + +static uint8_t pin; +static uint8_t frame[7]; +static uint8_t sync; +static uint8_t repeat; + +//static uint32_t delay[10] = {9415, 89565, 4*SYMBOL, 4*SYMBOL, 4*SYMBOL, 4550, SYMBOL, SYMBOL, SYMBOL, 30415}; // in us +// the `delay` array of constants must be in RAM as it is accessed from the timer interrupt +static const RAM_CONST_SECTION_ATTR uint32_t delay[10] = {US_TO_RTC_TIMER_TICKS(9415), US_TO_RTC_TIMER_TICKS(89565), US_TO_RTC_TIMER_TICKS(4*SYMBOL), US_TO_RTC_TIMER_TICKS(4*SYMBOL), US_TO_RTC_TIMER_TICKS(4*SYMBOL), US_TO_RTC_TIMER_TICKS(4550), US_TO_RTC_TIMER_TICKS(SYMBOL), US_TO_RTC_TIMER_TICKS(SYMBOL), US_TO_RTC_TIMER_TICKS(SYMBOL), US_TO_RTC_TIMER_TICKS(30415)}; // in ticks (no need to recalculate) + +static uint8_t repeatindex; +static uint8_t signalindex; +static uint8_t subindex; +static uint8_t bitcondition; + +int lua_done_ref; // callback when transmission is done + +void buildFrame(uint8_t *frame, uint64_t remote, uint8_t button, uint16_t code) { + // NODE_DBG("remote: %x\n", remote); + // NODE_DBG("button: %x\n", button); + // NODE_DBG("rolling code: %x\n", 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 + // frame[7] = 0x80; + // frame[8] = 0x0; + // frame[9] = 0x0; + + // NODE_DBG("Frame:\t\t\t%02x %02x %02x %02x %02x %02x %02x\n", frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]); + // Checksum calculation: a XOR of all the nibbles + uint8_t checksum = 0; + for(uint8_t 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. + // NODE_DBG("With checksum:\t%02x %02x %02x %02x %02x %02x %02x\n", frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]); + + // Obfuscation: a XOR of all the uint8_ts + for(uint8_t i = 1; i < 7; i++) { + frame[i] ^= frame[i-1]; + } + // NODE_DBG("Obfuscated:\t\t%02x %02x %02x %02x %02x %02x %02x\n", frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]); +} + +static void somfy_transmissionDone (task_param_t arg) +{ + lua_State *L = lua_getstate(); + lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref); + luaL_unref (L, LUA_REGISTRYINDEX, lua_done_ref); + lua_done_ref = LUA_NOREF; + lua_call (L, 0, 0); +} + +static void ICACHE_RAM_ATTR sendCommand(os_param_t p) { + (void) p; + // NODE_DBG("%d\t%d\n", signalindex, subindex); + switch (signalindex) { + case 0: + subindex = 0; + if(sync == 2) { // Only with the first frame. + //Wake-up pulse & Silence + DIRECT_WRITE_HIGH(pin); + signalindex++; + // delayMicroseconds(9415); + break; + } else { + signalindex++; signalindex++; //no break means: go directly to step 3 + } + case 1: + //Wake-up pulse & Silence + DIRECT_WRITE_LOW(pin); + signalindex++; + // delayMicroseconds(89565); + break; + case 2: + signalindex++; + // no break means go directly to step 3 + // a "useless" step to allow repeating the hardware sync w/o the silence after wake-up pulse + case 3: + // Hardware sync: two sync for the first frame, seven for the following ones. + DIRECT_WRITE_HIGH(pin); + signalindex++; + // delayMicroseconds(4*SYMBOL); + break; + case 4: + DIRECT_WRITE_LOW(pin); + subindex++; + if (subindex < sync) {signalindex--;} else {signalindex++;} + // delayMicroseconds(4*SYMBOL); + break; + case 5: + // Software sync + DIRECT_WRITE_HIGH(pin); + signalindex++; + // delayMicroseconds(4550); + break; + case 6: + DIRECT_WRITE_LOW(pin); + signalindex++; + subindex=0; + // delayMicroseconds(SYMBOL); + break; + case 7: + //Data: bits are sent one by one, starting with the MSB. + bitcondition = ((frame[subindex/8] >> (7 - (subindex%8))) & 1) == 1; + if(bitcondition) { + DIRECT_WRITE_LOW(pin); + } + else { + DIRECT_WRITE_HIGH(pin); + } + signalindex++; + // delayMicroseconds(SYMBOL); + break; + case 8: + //Data: bits are sent one by one, starting with the MSB. + if(bitcondition) { + DIRECT_WRITE_HIGH(pin); + } + else { + DIRECT_WRITE_LOW(pin); + } + + if (subindex<56) { + subindex++; + signalindex--; + } + else { + signalindex++; + } + // delayMicroseconds(SYMBOL); + break; + case 9: + DIRECT_WRITE_LOW(pin); + signalindex++; + // delayMicroseconds(30415); // Inter-frame silence + break; + case 10: + repeatindex++; + if (repeatindex cfg.rc + savenow = savenow or not config[remote] or config[remote].rc > cfg.rc + 10 + end + savelater = savelater and not savenow + if savenow then + print("Saving config now!") + writeconfighard() + end + if savelater then + print("Saving config later") + tmr.alarm(tmr_cache, 65000, tmr.ALARM_SINGLE, writeconfighard) + end +end + +--======================================================================================================-- +function down(remote, cb, par) + par = par or {} + print("down: ".. remote) + config[remote].rc=config[remote].rc+1 + somfy.sendcommand(pin, config[remote].address, somfy.DOWN, config[remote].rc, 16, function() wait(100, cb, par) end) + writeconfig() +end + +function up(remote, cb, par) + par = par or {} + print("up: ".. remote) + config[remote].rc=config[remote].rc+1 + somfy.sendcommand(pin, config[remote].address, somfy.UP, config[remote].rc, 16, function() wait(100, cb, par) end) + writeconfig() +end + +function downStep(remote, cb, par) + par = par or {} + print("downStep: ".. remote) + config[remote].rc=config[remote].rc+1 + somfy.sendcommand(pin, config[remote].address, somfy.DOWN, config[remote].rc, 2, function() wait(300, cb, par) end) + writeconfig() +end + +function upStep(remote, cb, par) + par = par or {} + print("upStep: ".. remote) + config[remote].rc=config[remote].rc+1 + somfy.sendcommand(pin, config[remote].address, somfy.UP, config[remote].rc, 2, function() wait(300, cb, par) end) + writeconfig() +end + +function wait(ms, cb, par) + par = par or {} + print("wait: ".. ms) + if cb then tmr.alarm(tmr_delay, ms, tmr.ALARM_SINGLE, function () cb(unpack(par)) end) end +end + + +--======================================================================================================-- +if not config then readconfig() end +if #config == 0 then -- somfy.cfg does not exist + config = cjson.decode([[{"window1":{"rc":1,"address":123},"window2":{"rc":1,"address":124}}]]) + config_saved = deepcopy(config) +end +down('window1', + wait, {60000, + up, {'window1', + wait, {9000, + downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1', downStep, {'window1' +}}}}}}}}}})