Add Somfy receiver (#3320)

This commit is contained in:
Lukáš Voborský 2020-12-28 00:04:32 +01:00 committed by GitHub
parent 0fb2a121c1
commit 0ef609d8f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 334 additions and 58 deletions

View File

@ -12,18 +12,37 @@
//#define NODE_DEBUG //#define NODE_DEBUG
#include <stdint.h> #include <stdint.h>
#include "os_type.h"
#include "osapi.h"
#include "sections.h"
#include "module.h" #include "module.h"
#include "lauxlib.h" #include "lauxlib.h"
#include "lmem.h"
#include "platform.h" #include "platform.h"
#include "task/task.h" #include "task/task.h"
#include "hw_timer.h" #include "hw_timer.h"
#include "user_interface.h" #include "user_interface.h"
#ifdef LUA_USE_MODULES_SOMFY
#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using SOMFY module
#endif
#endif
#ifdef NODE_DEBUG
#define PULLUP PLATFORM_GPIO_PULLUP
#define OUTPUT PLATFORM_GPIO_OUTPUT
#define HIGH PLATFORM_GPIO_HIGH
#define LOW PLATFORM_GPIO_LOW
#define MODE_TP1 platform_gpio_mode( 3, OUTPUT, PULLUP ); // GPIO 00
#define SET_TP1 platform_gpio_write(3, HIGH);
#define CLR_TP1 platform_gpio_write(3, LOW);
#define WAIT os_delay_us(1);
#else
#define MODE_TP1
#define SET_TP1
#define CLR_TP1
#define WAIT
#endif
#define SYMBOL 640 // symbol width in microseconds #define SYMBOL 640 // symbol width in microseconds
#define SOMFY_UP 0x2 #define SOMFY_UP 0x2
#define SOMFY_STOP 0x1 #define SOMFY_STOP 0x1
@ -33,24 +52,27 @@
#define DIRECT_WRITE_LOW(pin) (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 0)) #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)) #define DIRECT_WRITE_HIGH(pin) (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 1))
// ----------------------------------------------------------------------------------------------------//
// ------------------------------- transmitter part ---------------------------------------------------//
// ----------------------------------------------------------------------------------------------------//
static const os_param_t TIMER_OWNER = 0x736f6d66; // "somf" static const os_param_t TIMER_OWNER = 0x736f6d66; // "somf"
static task_handle_t done_taskid; static task_handle_t SendDone_taskid;
static uint8_t pin; static uint8_t TxPin;
static uint8_t frame[7]; static uint8_t frame[7];
static uint8_t sync; static uint8_t sync;
static uint8_t repeat; 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 uint32_t delay[10] = {9415, 89565, 4*SYMBOL, 4*SYMBOL, 4*SYMBOL, 4550, SYMBOL, SYMBOL, SYMBOL, 30415}; // inc us
// the `delay` array of constants must be in RAM as it is accessed from the timer interrupt // 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 const 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 repeatindex;
static uint8_t signalindex; static uint8_t signalindex;
static uint8_t subindex; static uint8_t subindex;
static uint8_t bitcondition; static uint8_t bitcondition;
int lua_done_ref; // callback when transmission is done static int lua_done_ref = LUA_NOREF; // callback when transmission is done
void buildFrame(uint8_t *frame, uint64_t remote, uint8_t button, uint16_t code) { void buildFrame(uint8_t *frame, uint64_t remote, uint8_t button, uint16_t code) {
// NODE_DBG("remote: %x\n", remote); // NODE_DBG("remote: %x\n", remote);
@ -86,15 +108,6 @@ void buildFrame(uint8_t *frame, uint64_t remote, uint8_t button, uint16_t code)
// 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]); // 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;
luaL_pcallx (L, 0, 0);
}
static void ICACHE_RAM_ATTR sendCommand(os_param_t p) { static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
(void) p; (void) p;
// NODE_DBG("%d\t%d\n", signalindex, subindex); // NODE_DBG("%d\t%d\n", signalindex, subindex);
@ -103,7 +116,7 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
subindex = 0; subindex = 0;
if(sync == 2) { // Only with the first frame. if(sync == 2) { // Only with the first frame.
//Wake-up pulse & Silence //Wake-up pulse & Silence
DIRECT_WRITE_HIGH(pin); DIRECT_WRITE_HIGH(TxPin);
signalindex++; signalindex++;
// delayMicroseconds(9415); // delayMicroseconds(9415);
break; break;
@ -112,7 +125,7 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
} }
case 1: case 1:
//Wake-up pulse & Silence //Wake-up pulse & Silence
DIRECT_WRITE_LOW(pin); DIRECT_WRITE_LOW(TxPin);
signalindex++; signalindex++;
// delayMicroseconds(89565); // delayMicroseconds(89565);
break; break;
@ -122,24 +135,24 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
// a "useless" step to allow repeating the hardware sync w/o the silence after wake-up pulse // a "useless" step to allow repeating the hardware sync w/o the silence after wake-up pulse
case 3: case 3:
// Hardware sync: two sync for the first frame, seven for the following ones. // Hardware sync: two sync for the first frame, seven for the following ones.
DIRECT_WRITE_HIGH(pin); DIRECT_WRITE_HIGH(TxPin);
signalindex++; signalindex++;
// delayMicroseconds(4*SYMBOL); // delayMicroseconds(4*SYMBOL);
break; break;
case 4: case 4:
DIRECT_WRITE_LOW(pin); DIRECT_WRITE_LOW(TxPin);
subindex++; subindex++;
if (subindex < sync) {signalindex--;} else {signalindex++;} if (subindex < sync) {signalindex--;} else {signalindex++;}
// delayMicroseconds(4*SYMBOL); // delayMicroseconds(4*SYMBOL);
break; break;
case 5: case 5:
// Software sync // Software sync
DIRECT_WRITE_HIGH(pin); DIRECT_WRITE_HIGH(TxPin);
signalindex++; signalindex++;
// delayMicroseconds(4550); // delayMicroseconds(4550);
break; break;
case 6: case 6:
DIRECT_WRITE_LOW(pin); DIRECT_WRITE_LOW(TxPin);
signalindex++; signalindex++;
subindex=0; subindex=0;
// delayMicroseconds(SYMBOL); // delayMicroseconds(SYMBOL);
@ -148,10 +161,10 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
//Data: bits are sent one by one, starting with the MSB. //Data: bits are sent one by one, starting with the MSB.
bitcondition = ((frame[subindex/8] >> (7 - (subindex%8))) & 1) == 1; bitcondition = ((frame[subindex/8] >> (7 - (subindex%8))) & 1) == 1;
if(bitcondition) { if(bitcondition) {
DIRECT_WRITE_LOW(pin); DIRECT_WRITE_LOW(TxPin);
} }
else { else {
DIRECT_WRITE_HIGH(pin); DIRECT_WRITE_HIGH(TxPin);
} }
signalindex++; signalindex++;
// delayMicroseconds(SYMBOL); // delayMicroseconds(SYMBOL);
@ -159,10 +172,10 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
case 8: case 8:
//Data: bits are sent one by one, starting with the MSB. //Data: bits are sent one by one, starting with the MSB.
if(bitcondition) { if(bitcondition) {
DIRECT_WRITE_HIGH(pin); DIRECT_WRITE_HIGH(TxPin);
} }
else { else {
DIRECT_WRITE_LOW(pin); DIRECT_WRITE_LOW(TxPin);
} }
if (subindex<56) { if (subindex<56) {
@ -175,19 +188,19 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
// delayMicroseconds(SYMBOL); // delayMicroseconds(SYMBOL);
break; break;
case 9: case 9:
DIRECT_WRITE_LOW(pin); DIRECT_WRITE_LOW(TxPin);
signalindex++; signalindex++;
// delayMicroseconds(30415); // Inter-frame silence // delayMicroseconds(30415); // Inter-frame silence
break; break;
case 10: case 10:
repeatindex++; repeatindex++;
if (repeatindex<repeat) { if (repeatindex<repeat) {
DIRECT_WRITE_HIGH(pin); //start repeat from step 3, but don't wait as after step 1 DIRECT_WRITE_HIGH(TxPin); //start repeat from step 3, but don't wait as after step 1
signalindex=4; subindex=0; sync=7; signalindex=4; subindex=0; sync=7;
} else { } else {
platform_hw_timer_close(TIMER_OWNER); platform_hw_timer_close(TIMER_OWNER);
if (lua_done_ref != LUA_NOREF) { if (lua_done_ref != LUA_NOREF) {
task_post_low (done_taskid, (task_param_t)0); task_post_low (SendDone_taskid, (task_param_t)0);
} }
} }
break; break;
@ -197,28 +210,258 @@ static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
} }
} }
static int somfy_lua_sendcommand(lua_State* L) { // pin, remote, command, rolling_code, num_repeat, callback // ----------------------------------------------------------------------------------------------------//
if (!lua_isnumber(L, 4)) { // ------------------------------- receiver part ------------------------------------------------------//
return luaL_error(L, "wrong arg range"); // ----------------------------------------------------------------------------------------------------//
#define TOLERANCE_MIN 0.7
#define TOLERANCE_MAX 1.3
static const uint32_t tempo_wakeup_pulse = 9415;
static const uint32_t tempo_wakeup_silence = 89565;
// static const uint32_t tempo_synchro_hw = SYMBOL*4;
static const uint32_t tempo_synchro_hw_min = SYMBOL*4*TOLERANCE_MIN;
static const uint32_t tempo_synchro_hw_max = SYMBOL*4*TOLERANCE_MAX;
// static const uint32_t k_tempo_synchro_sw = 4550;
static const uint32_t tempo_synchro_sw_min = 4550*TOLERANCE_MIN;
static const uint32_t tempo_synchro_sw_max = 4550*TOLERANCE_MAX;
// static const uint32_t tempo_half_symbol = SYMBOL;
static const uint32_t tempo_half_symbol_min = SYMBOL*TOLERANCE_MIN;
static const uint32_t tempo_half_symbol_max = SYMBOL*TOLERANCE_MAX;
// static const uint32_t tempo_symbol = SYMBOL*2;
static const uint32_t tempo_symbol_min = SYMBOL*2*TOLERANCE_MIN;
static const uint32_t tempo_symbol_max = SYMBOL*2*TOLERANCE_MAX;
static const uint32_t tempo_inter_frame_gap = 30415;
static int16_t bitMin = SYMBOL*TOLERANCE_MIN;
typedef enum {
waiting_synchro = 0,
receiving_data = 1,
complete = 2
} }
pin = luaL_checkinteger(L, 1); t_status;
static struct SomfyRx_t
{
t_status status;
uint8_t cpt_synchro_hw;
uint8_t cpt_bits;
uint8_t previous_bit;
bool waiting_half_symbol;
uint8_t payload[9];
} SomfyRx;
static task_handle_t DataReady_taskid;
static uint8_t RxPin;
static uint8_t IntBitmask;
static int lua_dataready_ref = LUA_NOREF;
static uint32_t ICACHE_RAM_ATTR InterruptHandler (uint32_t ret_gpio_status) {
// This function really is running at interrupt level with everything
// else masked off. It should take as little time as necessary.
uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
if ((gpio_status & IntBitmask) == 0) {
return ret_gpio_status;
}
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & IntBitmask);
uint32_t actMicros = system_get_time();
ret_gpio_status &= ~(IntBitmask);
static unsigned long lastMicros = 0;
unsigned long bitMicros;
bitMicros = actMicros-lastMicros;
if ( bitMicros < bitMin ) {
// too short - may be false interrupt due to glitch or false protocol -> ignore
return ret_gpio_status; // abort IRQ
}
lastMicros = actMicros;
switch(SomfyRx.status) {
case waiting_synchro:
if (bitMicros > tempo_synchro_hw_min && bitMicros < tempo_synchro_hw_max) {
SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
++SomfyRx.cpt_synchro_hw;
CLR_TP1
}
else if (bitMicros > tempo_synchro_sw_min && bitMicros < tempo_synchro_sw_max && SomfyRx.cpt_synchro_hw >= 4) {
SET_TP1 //WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
memset( &SomfyRx, 0, sizeof( SomfyRx) );
SomfyRx.status = receiving_data;
} else {
SomfyRx.cpt_synchro_hw = 0;
}
break;
case receiving_data:
if (bitMicros > tempo_symbol_min && bitMicros < tempo_symbol_max && !SomfyRx.waiting_half_symbol) {
SET_TP1
SomfyRx.previous_bit = 1 - SomfyRx.previous_bit;
SomfyRx.payload[SomfyRx.cpt_bits/8] += SomfyRx.previous_bit << (7 - SomfyRx.cpt_bits%8);
++SomfyRx.cpt_bits;
} else if (bitMicros > tempo_half_symbol_min && bitMicros < tempo_half_symbol_max) {
SET_TP1 WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
if (SomfyRx.waiting_half_symbol) {
SomfyRx.waiting_half_symbol = false;
SomfyRx.payload[SomfyRx.cpt_bits/8] += SomfyRx.previous_bit << (7 - SomfyRx.cpt_bits%8);
++SomfyRx.cpt_bits;
} else {
SomfyRx.waiting_half_symbol = true;
}
} else {
SomfyRx.cpt_synchro_hw = 0;
SomfyRx.status = waiting_synchro;
}
CLR_TP1
break;
default:
break;
}
if (SomfyRx.status == receiving_data && SomfyRx.cpt_bits == 80) { //56) { experiment
task_post_high(DataReady_taskid, (task_param_t)0);
SomfyRx.status = waiting_synchro;
}
return ret_gpio_status;
}
static void somfy_decode (os_param_t param, uint8_t prio)
{
#ifdef NODE_DEBUG
NODE_DBG("Payload:\t");
for(uint8_t i = 0; i < 10; i++) {
NODE_DBG("%02x ", SomfyRx.payload[i]);
}
NODE_DBG("\n");
#endif
// Deobfuscation
uint8_t frame[10];
frame[0] = SomfyRx.payload[0];
for(int i = 1; i < 7; ++i) frame[i] = SomfyRx.payload[i] ^ SomfyRx.payload[i-1];
frame[7] = SomfyRx.payload[7] ^ SomfyRx.payload[0];
for(int i = 8; i < 10; ++i) frame[i] = SomfyRx.payload[i] ^ SomfyRx.payload[i-1];
#ifdef NODE_DEBUG
NODE_DBG("Frame:\t");
for(uint8_t i = 0; i < 10; i++) {
NODE_DBG("%02x ", frame[i]);
}
NODE_DBG("\n");
#endif
// Checksum check
uint8_t cksum = 0;
for(int i = 0; i < 7; ++i) cksum = cksum ^ frame[i] ^ (frame[i] >> 4);
cksum = cksum & 0x0F;
if (cksum != 0) {
NODE_DBG("Checksum incorrect!\n");
return;
}
unsigned long rolling_code = (frame[2] << 8) || frame[3];
unsigned long address = ((unsigned long)frame[4] << 16) || (frame[5] << 8) || frame[6];
if (lua_dataready_ref == LUA_NOREF)
return;
lua_State *L = lua_getstate();
lua_rawgeti(L, LUA_REGISTRYINDEX, lua_dataready_ref);
lua_pushinteger(L, address);
lua_pushinteger(L, frame[1] >> 4);
lua_pushinteger(L, rolling_code);
lua_pushlstring(L, frame, 10);
luaL_pcallx(L, 4, 0);
}
// ----------------------------------------------------------------------------------------------------//
// ------------------------------- Lua part -----------------------------------------------------------//
// ----------------------------------------------------------------------------------------------------//
static inline void register_lua_cb(lua_State* L, int* cb_ref){
int ref=luaL_ref(L, LUA_REGISTRYINDEX);
if( *cb_ref != LUA_NOREF){
luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
}
*cb_ref = ref;
}
static inline void unregister_lua_cb(lua_State* L, int* cb_ref){
if(*cb_ref != LUA_NOREF){
luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
*cb_ref = LUA_NOREF;
}
}
int somfy_lua_listen(lua_State* L) { // pin, callback
NODE_DBG("[somfy_lua_listen]\n");
#if LUA_VERSION_NUM == 501
if (lua_isnumber(L, 1) && lua_type(L, 2) == LUA_TFUNCTION) {
#else
if (lua_isinteger(L, 1) && lua_type(L, 2) == LUA_TFUNCTION) {
#endif
RxPin = luaL_checkinteger(L, 1);
luaL_argcheck(L, platform_gpio_exists(RxPin) && RxPin>0, 1, "Invalid interrupt pin");
lua_pushvalue(L, 2);
register_lua_cb(L, &lua_dataready_ref);
memset( &SomfyRx, 0, sizeof( SomfyRx) );
IntBitmask = 1 << pin_num[RxPin];
MODE_TP1
NODE_DBG("[somfy_lua_listen] Enabling interrupt on PIN %d\n", RxPin);
platform_gpio_mode(RxPin, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
NODE_DBG("[somfy_lua_listen] platform_gpio_register_intr_hook - pin: %d, mask: %d\n", RxPin, IntBitmask);
platform_gpio_register_intr_hook(IntBitmask, InterruptHandler);
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[RxPin]), GPIO_PIN_INTR_ANYEDGE);
#if LUA_VERSION_NUM == 501
} else if ((lua_isnoneornil(L, 1) || lua_isnumber(L, 1)) && lua_isnoneornil(L, 2)) {
#else
} else if ((lua_isnoneornil(L, 1) || lua_isinteger(L, 1)) && lua_isnoneornil(L, 2)) {
#endif
NODE_DBG("[somfy_lua_listen] Desabling interrupt on PIN %d\n", RxPin);
platform_gpio_mode(RxPin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP);
unregister_lua_cb(L, &lua_dataready_ref);
RxPin = 0;
} else {
luaL_error(L, "Invalid parameters");
}
return 0;
}
static void somfy_transmissionDone (task_param_t arg)
{
lua_State *L = lua_getstate();
lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref);
unregister_lua_cb (L, &lua_done_ref);
luaL_pcallx (L, 0, 0);
}
int somfy_lua_sendcommand(lua_State* L) { // pin, remote, command, rolling_code, num_repeat, callback
TxPin = luaL_checkinteger(L, 1);
uint64_t remote = luaL_checkinteger(L, 2); uint64_t remote = luaL_checkinteger(L, 2);
uint8_t cmd = luaL_checkinteger(L, 3); uint8_t cmd = luaL_checkinteger(L, 3);
uint16_t code = luaL_checkinteger(L, 4); uint16_t code = luaL_checkinteger(L, 4);
repeat=luaL_optint( L, 5, 2 ); repeat=luaL_optint( L, 5, 2 );
luaL_argcheck(L, platform_gpio_exists(pin), 1, "Invalid pin"); luaL_argcheck(L, platform_gpio_exists(TxPin), 1, "Invalid pin");
luaL_unref(L, LUA_REGISTRYINDEX, lua_done_ref); if (lua_type(L, 6) == LUA_TFUNCTION) {
if (!lua_isnoneornil(L, 6)) {
lua_pushvalue (L, 6); lua_pushvalue (L, 6);
lua_done_ref = luaL_ref(L, LUA_REGISTRYINDEX); register_lua_cb (L, &lua_done_ref);
} else { } else {
lua_done_ref = LUA_NOREF; unregister_lua_cb (L, &lua_done_ref);
} }
MOD_CHECK_ID(gpio, pin); MOD_CHECK_ID(gpio, TxPin);
platform_gpio_mode(pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP); platform_gpio_mode(TxPin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP);
buildFrame(frame, remote, cmd, code); buildFrame(frame, remote, cmd, code);
@ -233,18 +476,21 @@ static int somfy_lua_sendcommand(lua_State* L) { // pin, remote, command, rollin
return 0; return 0;
} }
int luaopen_somfy( lua_State *L ) {
SendDone_taskid = task_get_id((task_callback_t) somfy_transmissionDone);
DataReady_taskid = task_get_id((task_callback_t) somfy_decode);
return 0;
}
// Module function map
LROT_BEGIN(somfy, NULL, 0) LROT_BEGIN(somfy, NULL, 0)
LROT_FUNCENTRY( sendcommand, somfy_lua_sendcommand )
LROT_FUNCENTRY( listen, somfy_lua_listen )
LROT_NUMENTRY( UP, SOMFY_UP ) LROT_NUMENTRY( UP, SOMFY_UP )
LROT_NUMENTRY( DOWN, SOMFY_DOWN ) LROT_NUMENTRY( DOWN, SOMFY_DOWN )
LROT_NUMENTRY( PROG, SOMFY_PROG ) LROT_NUMENTRY( PROG, SOMFY_PROG )
LROT_NUMENTRY( STOP, SOMFY_STOP ) LROT_NUMENTRY( STOP, SOMFY_STOP )
LROT_FUNCENTRY( sendcommand, somfy_lua_sendcommand )
LROT_END(somfy, NULL, 0) LROT_END(somfy, NULL, 0)
int luaopen_somfy( lua_State *L ) {
done_taskid = task_get_id((task_callback_t) somfy_transmissionDone);
return 0;
}
NODEMCU_MODULE(SOMFY, "somfy", somfy, luaopen_somfy); NODEMCU_MODULE(SOMFY, "somfy", somfy, luaopen_somfy);

View File

@ -3,9 +3,9 @@
| :----- | :-------------------- | :---------- | :------ | | :----- | :-------------------- | :---------- | :------ |
| 2016-09-27 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [somfy.c](../../app/modules/somfy.c)| | 2016-09-27 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [somfy.c](../../app/modules/somfy.c)|
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). 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). It also allows listening to commands transmitted by the Somfy remote control.
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. 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 resonator on this frequency 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). 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).
@ -16,7 +16,7 @@ The module is using hardware timer so it cannot be used at the same time with ot
Builds an frame defined by Somfy protocol and sends it to the RF transmitter. Builds an frame defined by Somfy protocol and sends it to the RF transmitter.
#### Syntax #### Syntax
`somfy.sendcommand(pin, remote_address, command, rolling_code, repeat_count, call_back)` `somfy.sendcommand(pin, remote_address, command, rolling_code, repeat_count, callback)`
#### Parameters #### Parameters
- `pin` GPIO pin the RF transmitter is connected to. - `pin` GPIO pin the RF transmitter is connected to.
@ -24,9 +24,9 @@ Builds an frame defined by Somfy protocol and sends it to the RF transmitter.
- `command` command to be transmitted. Can be one of `somfy.SOMFY_UP`, `somfy.SOMFY_DOWN`, `somfy.SOMFY_PROG`, `somfy.SOMFY_STOP` - `command` command to be transmitted. Can be one of `somfy.SOMFY_UP`, `somfy.SOMFY_DOWN`, `somfy.SOMFY_PROG`, `somfy.SOMFY_STOP`
- `rolling_code` The rolling code is increased every time a button is pressed. The receiver only accepts command if the rolling code is above the last received code and is not to far ahead of the last received code. This window is in the order of a 100 big. The rolling code needs to be stored in the EEPROM (i.e. filesystem) to survive the ESP8266 reset. - `rolling_code` The rolling code is increased every time a button is pressed. The receiver only accepts command if the rolling code is above the last received code and is not to far ahead of the last received code. This window is in the order of a 100 big. The rolling code needs to be stored in the EEPROM (i.e. filesystem) to survive the ESP8266 reset.
- `repeat_count` how many times the command is repeated - `repeat_count` how many times the command is repeated
- `call_back` a function to be called after the command is transmitted. Allows chaining commands to set the blinds to a defined position. - `callback` a function to be called after the command is transmitted. Allows chaining commands to set the blinds to a defined position.
My original remote is [TELIS 4 MODULIS RTS](https://www.somfy.co.uk/products/1810765/telis-4-modulis-rts). This remote is working with the additional info - additional 56 bits that follow data (shortening the Inter-frame gap). It seems that the scrambling algorithm has not been revealed yet. My original remote is [TELIS 4 MODULIS RTS](https://www.somfy.co.uk/products/blinds-and-curtains/buy-products/controls). This remote is working with the additional info - additional 56 bits that follow data (shortening the Inter-frame gap). It seems that the scrambling algorithm has not been revealed yet.
When I send the `somfy.DOWN` command, repeating the frame twice (which seems to be the standard for a short button press), i.e. `repeat_count` equal to 2, the blinds go only 1 step down. This corresponds to the movement of the wheel on the original remote. The down button on the original remote sends also `somfy.DOWN` command but the additional info is different and this makes the blinds go full down. Fortunately it seems that repeating the frame 16 times makes the blinds go fully down. When I send the `somfy.DOWN` command, repeating the frame twice (which seems to be the standard for a short button press), i.e. `repeat_count` equal to 2, the blinds go only 1 step down. This corresponds to the movement of the wheel on the original remote. The down button on the original remote sends also `somfy.DOWN` command but the additional info is different and this makes the blinds go full down. Fortunately it seems that repeating the frame 16 times makes the blinds go fully down.
@ -43,3 +43,32 @@ To start with controlling your Somfy blinds you need to:
- running `somfy.sendcommand(4, 123, somfy.DOWN, 2, 16)` - fully closes the blinds - running `somfy.sendcommand(4, 123, somfy.DOWN, 2, 16)` - fully closes the blinds
For more elaborated example please refer to [`somfy.lua`](../../lua_examples/somfy.lua). For more elaborated example please refer to [`somfy.lua`](../../lua_examples/somfy.lua).
## somfy.listen()
Using RF receiver listens to Somfy commands and triggers a callback when command is identified.
#### Syntax
`somfy.listen(pin, callback)`
#### Parameters
- `pin` GPIO pin the RF receiver is connected to.
- `callback(address, command, rc, frame)` a function called when a Somfy command is identified. Use `nil` to stop listening.
- `address` of the remote controller sending the command
- `command` sent by the remote controller. A number between 0 and 0xf. Can be `somfy.SOMFY_UP`, `somfy.SOMFY_DOWN`, `somfy.SOMFY_PROG`, `somfy.SOMFY_STOP`.
- `rc` rolling code
- `frame` String of 10 characters with the full captured data frame.
#### Returns
nil
#### Example
```Lua
somfy.listen(4, function(address, command, rc, frame)
print(("Address:\t0x%x\nCommand:\t0x%x\nRolling code:\t%d"):format(address, command, rc))
print(("Frame:\t"..("%02x "):rep(#frame)):format(frame:byte(1, -1)))
end)
```
Use `somfy.listen()` or `somfy.listen(4, nil)` to unhook the GPIO and free the callback.

View File

@ -609,7 +609,8 @@ stds.nodemcu_libs = {
PROG = empty, PROG = empty,
STOP = empty, STOP = empty,
UP = empty, UP = empty,
sendcommand = empty sendcommand = empty,
listen = empty
} }
}, },
spi = { spi = {