From d7cb7a6f061ab32f343d7339d06be7897729e018 Mon Sep 17 00:00:00 2001 From: vsky Date: Sat, 28 May 2016 00:09:47 +0200 Subject: [PATCH 1/6] Somfy/TELIS driver --- app/include/user_modules.h | 1 + app/modules/somfy.c | 246 +++++++++++++++++++++++++++++++++++++ docs/en/modules/somfy.md | 42 +++++++ lua_examples/somfy.lua | 145 ++++++++++++++++++++++ 4 files changed, 434 insertions(+) create mode 100644 app/modules/somfy.c create mode 100644 docs/en/modules/somfy.md create mode 100644 lua_examples/somfy.lua diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 40b6dbb673..f34285f78d 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 0000000000..d8c6b13765 --- /dev/null +++ b/app/modules/somfy.c @@ -0,0 +1,246 @@ +// *************************************************************************** +// 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 "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 +static const __attribute__((section(".data.delay"))) 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 recalulate) + +//static uint32_t* pdelay; //pointer to store delay array in ram + +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); + lua_call (L, 0, 0); + luaL_unref (L, LUA_REGISTRYINDEX, lua_done_ref); +} + +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' +}}}}}}}}}}) From f9dae2871369be6030d2efe6dab50945e59304eb Mon Sep 17 00:00:00 2001 From: vsky Date: Mon, 3 Oct 2016 22:55:29 +0200 Subject: [PATCH 2/6] enhanced documentation --- docs/en/modules/somfy.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/modules/somfy.md b/docs/en/modules/somfy.md index 8bf09aee50..10a0b77ecc 100644 --- a/docs/en/modules/somfy.md +++ b/docs/en/modules/somfy.md @@ -5,9 +5,11 @@ This module provides a simple interface to control Somfy blinds via an RF transmitter (433.42 MHz). It is based on [Nickduino Somfy Remote Arduino skecth](https://github.com/Nickduino/Somfy_Remote). +The hardware used is the standard [433 MHz RF transmitter](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2050601.m570.l1313.TR0.TRC0.H0.X433Mhz+RF+transmitter+and+receiver+link+kit+for+Arduino%2FARM%2FMCU+WL.TRS0&_nkw=433Mhz+RF+transmitter+and+receiver+link+kit+for+Arduino%2FARM%2FMCU+WL&_sacat=0) (on eBay usually sold also with the receiver). Unfortunately these chips are usually transmitting at he frequency of 433.92MHz so the crystal resonator should be replaced with the [433.42 MHz resonator](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2047675.m570.l1313.TR0.TRC0.H0.X433.42MHz+Resonator+Crystals+DIP-3+TO-39.TRS0&_nkw=433.42MHz+Resonator+Crystals+DIP-3+TO-39&_sacat=0) though some reporting that it is working even with the original crystal. + To understand details of the Somfy protocol please refer to [Somfy RTS protocol](https://pushstack.wordpress.com/somfy-rts-protocol/) and also discussion [here](https://forum.arduino.cc/index.php?topic=208346.0). -The module is using hardware timer so it is not compatible with other NodeMCU modules using the hardware timer, i.e. `sigma delta`, `pcm`, `perf`, or `pwm` modules. +The module is using hardware timer so it cannot be used at the same time with other NodeMCU modules using the hardware timer, i.e. `sigma delta`, `pcm`, `perf`, or `pwm` modules. ## somfy.sendcommand() From 3fa490d20a639440f7c2171188e18b6b329f191a Mon Sep 17 00:00:00 2001 From: vsky Date: Tue, 4 Oct 2016 16:44:01 +0200 Subject: [PATCH 3/6] doc update --- docs/en/modules/somfy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/modules/somfy.md b/docs/en/modules/somfy.md index 10a0b77ecc..07fe0a41de 100644 --- a/docs/en/modules/somfy.md +++ b/docs/en/modules/somfy.md @@ -5,7 +5,7 @@ This module provides a simple interface to control Somfy blinds via an RF transmitter (433.42 MHz). It is based on [Nickduino Somfy Remote Arduino skecth](https://github.com/Nickduino/Somfy_Remote). -The hardware used is the standard [433 MHz RF transmitter](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2050601.m570.l1313.TR0.TRC0.H0.X433Mhz+RF+transmitter+and+receiver+link+kit+for+Arduino%2FARM%2FMCU+WL.TRS0&_nkw=433Mhz+RF+transmitter+and+receiver+link+kit+for+Arduino%2FARM%2FMCU+WL&_sacat=0) (on eBay usually sold also with the receiver). Unfortunately these chips are usually transmitting at he frequency of 433.92MHz so the crystal resonator should be replaced with the [433.42 MHz resonator](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2047675.m570.l1313.TR0.TRC0.H0.X433.42MHz+Resonator+Crystals+DIP-3+TO-39.TRS0&_nkw=433.42MHz+Resonator+Crystals+DIP-3+TO-39&_sacat=0) though some reporting that it is working even with the original crystal. +The hardware used is the standard 433 MHz RF transmitter. Unfortunately these chips are usually transmitting at he frequency of 433.92MHz so the crystal resonator should be replaced with the 433.42 MHz resonator though some reporting that it is working even with the original crystal. To understand details of the Somfy protocol please refer to [Somfy RTS protocol](https://pushstack.wordpress.com/somfy-rts-protocol/) and also discussion [here](https://forum.arduino.cc/index.php?topic=208346.0). From 83315d8692d09b368f76ef3086acfe022a906462 Mon Sep 17 00:00:00 2001 From: vsky Date: Tue, 4 Oct 2016 21:26:22 +0200 Subject: [PATCH 4/6] RAM_CONST_SECTION_ATTR --- app/include/sections.h | 1 + app/modules/somfy.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/include/sections.h b/app/include/sections.h index d09c576e7b..58613c90c0 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/modules/somfy.c b/app/modules/somfy.c index d8c6b13765..d29ec10659 100644 --- a/app/modules/somfy.c +++ b/app/modules/somfy.c @@ -13,6 +13,7 @@ #include "os_type.h" #include "osapi.h" +#include "sections.h" #include "module.h" #include "lauxlib.h" @@ -39,9 +40,8 @@ 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 -static const __attribute__((section(".data.delay"))) 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 recalulate) - -//static uint32_t* pdelay; //pointer to store delay array in ram +// 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; From a943f9ccff8da5d978b7fb9894f184bda8a7b638 Mon Sep 17 00:00:00 2001 From: vsky Date: Thu, 6 Oct 2016 22:46:33 +0200 Subject: [PATCH 5/6] callback fix --- app/modules/somfy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/modules/somfy.c b/app/modules/somfy.c index d29ec10659..3c97c84987 100644 --- a/app/modules/somfy.c +++ b/app/modules/somfy.c @@ -88,8 +88,9 @@ static void somfy_transmissionDone (task_param_t arg) { lua_State *L = lua_getstate(); lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref); - lua_call (L, 0, 0); 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) { From c2ffe79279a411fbb8a3eb4beeb35109060db41f Mon Sep 17 00:00:00 2001 From: vsky Date: Fri, 7 Oct 2016 21:37:36 +0200 Subject: [PATCH 6/6] possible memleak fix --- app/modules/somfy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/somfy.c b/app/modules/somfy.c index 3c97c84987..4266aa6ea2 100644 --- a/app/modules/somfy.c +++ b/app/modules/somfy.c @@ -207,6 +207,7 @@ static int somfy_lua_sendcommand(lua_State* L) { // pin, remote, command, rollin luaL_argcheck(L, platform_gpio_exists(pin), 1, "Invalid pin"); + luaL_unref(L, LUA_REGISTRYINDEX, lua_done_ref); if (!lua_isnoneornil(L, 6)) { lua_pushvalue(L, 6); lua_done_ref = luaL_ref(L, LUA_REGISTRYINDEX);