nodemcu-firmware/app/modules/somfy.c

497 lines
17 KiB
C
Raw Permalink Normal View History

2016-10-14 00:49:58 +02:00
// ***************************************************************************
// Somfy module for ESP8266 with NodeMCU
2019-02-17 19:26:29 +01:00
//
2016-10-14 00:49:58 +02:00
// 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
2019-02-17 19:26:29 +01:00
//
2016-10-14 00:49:58 +02:00
// MIT license, http://opensource.org/licenses/MIT
// ***************************************************************************
//#define NODE_DEBUG
#include <stdint.h>
2016-10-14 00:49:58 +02:00
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "task/task.h"
2016-10-14 00:49:58 +02:00
#include "hw_timer.h"
#include "user_interface.h"
2020-12-28 00:04:32 +01:00
#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
2016-10-14 00:49:58 +02:00
#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))
2020-12-28 00:04:32 +01:00
// ----------------------------------------------------------------------------------------------------//
// ------------------------------- transmitter part ---------------------------------------------------//
// ----------------------------------------------------------------------------------------------------//
2016-10-14 00:49:58 +02:00
static const os_param_t TIMER_OWNER = 0x736f6d66; // "somf"
2020-12-28 00:04:32 +01:00
static task_handle_t SendDone_taskid;
2016-10-14 00:49:58 +02:00
2020-12-28 00:04:32 +01:00
static uint8_t TxPin;
2016-10-14 00:49:58 +02:00
static uint8_t frame[7];
static uint8_t sync;
static uint8_t repeat;
2020-12-28 00:04:32 +01:00
//static uint32_t delay[10] = {9415, 89565, 4*SYMBOL, 4*SYMBOL, 4*SYMBOL, 4550, SYMBOL, SYMBOL, SYMBOL, 30415}; // inc us
2016-10-14 00:49:58 +02:00
// the `delay` array of constants must be in RAM as it is accessed from the timer interrupt
2020-12-28 00:04:32 +01:00
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)
2016-10-14 00:49:58 +02:00
static uint8_t repeatindex;
static uint8_t signalindex;
static uint8_t subindex;
static uint8_t bitcondition;
2020-12-28 00:04:32 +01:00
static int lua_done_ref = LUA_NOREF; // callback when transmission is done
2016-10-14 00:49:58 +02:00
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 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
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_HIGH(TxPin);
2016-10-14 00:49:58 +02:00
signalindex++;
// delayMicroseconds(9415);
break;
} else {
signalindex++; signalindex++; //no break means: go directly to step 3
}
case 1:
//Wake-up pulse & Silence
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_LOW(TxPin);
2016-10-14 00:49:58 +02:00
signalindex++;
// delayMicroseconds(89565);
break;
case 2:
2019-02-17 19:26:29 +01:00
signalindex++;
2016-10-14 00:49:58 +02:00
// 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.
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_HIGH(TxPin);
2016-10-14 00:49:58 +02:00
signalindex++;
// delayMicroseconds(4*SYMBOL);
break;
case 4:
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_LOW(TxPin);
2016-10-14 00:49:58 +02:00
subindex++;
if (subindex < sync) {signalindex--;} else {signalindex++;}
// delayMicroseconds(4*SYMBOL);
break;
case 5:
// Software sync
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_HIGH(TxPin);
2016-10-14 00:49:58 +02:00
signalindex++;
// delayMicroseconds(4550);
break;
case 6:
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_LOW(TxPin);
2016-10-14 00:49:58 +02:00
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) {
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_LOW(TxPin);
2016-10-14 00:49:58 +02:00
}
else {
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_HIGH(TxPin);
2016-10-14 00:49:58 +02:00
}
signalindex++;
// delayMicroseconds(SYMBOL);
break;
case 8:
//Data: bits are sent one by one, starting with the MSB.
if(bitcondition) {
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_HIGH(TxPin);
2016-10-14 00:49:58 +02:00
}
else {
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_LOW(TxPin);
2016-10-14 00:49:58 +02:00
}
2019-02-17 19:26:29 +01:00
2016-10-14 00:49:58 +02:00
if (subindex<56) {
subindex++;
signalindex--;
}
else {
signalindex++;
}
// delayMicroseconds(SYMBOL);
break;
case 9:
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_LOW(TxPin);
2016-10-14 00:49:58 +02:00
signalindex++;
// delayMicroseconds(30415); // Inter-frame silence
break;
case 10:
repeatindex++;
if (repeatindex<repeat) {
2020-12-28 00:04:32 +01:00
DIRECT_WRITE_HIGH(TxPin); //start repeat from step 3, but don't wait as after step 1
2016-10-14 00:49:58 +02:00
signalindex=4; subindex=0; sync=7;
} else {
platform_hw_timer_close(TIMER_OWNER);
if (lua_done_ref != LUA_NOREF) {
2020-12-28 00:04:32 +01:00
task_post_low (SendDone_taskid, (task_param_t)0);
2016-10-14 00:49:58 +02:00
}
}
break;
}
if (signalindex<10) {
platform_hw_timer_arm_ticks(TIMER_OWNER, delay[signalindex-1]);
}
}
2020-12-28 00:04:32 +01:00
// ----------------------------------------------------------------------------------------------------//
// ------------------------------- receiver part ------------------------------------------------------//
// ----------------------------------------------------------------------------------------------------//
#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
}
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);
2016-10-14 00:49:58 +02:00
uint64_t remote = luaL_checkinteger(L, 2);
uint8_t cmd = luaL_checkinteger(L, 3);
uint16_t code = luaL_checkinteger(L, 4);
repeat=luaL_optint( L, 5, 2 );
2020-12-28 00:04:32 +01:00
luaL_argcheck(L, platform_gpio_exists(TxPin), 1, "Invalid pin");
2016-10-14 00:49:58 +02:00
2020-12-28 00:04:32 +01:00
if (lua_type(L, 6) == LUA_TFUNCTION) {
lua_pushvalue (L, 6);
register_lua_cb (L, &lua_done_ref);
2016-10-14 00:49:58 +02:00
} else {
2020-12-28 00:04:32 +01:00
unregister_lua_cb (L, &lua_done_ref);
2016-10-14 00:49:58 +02:00
}
2020-12-28 00:04:32 +01:00
MOD_CHECK_ID(gpio, TxPin);
platform_gpio_mode(TxPin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP);
2016-10-14 00:49:58 +02:00
buildFrame(frame, remote, cmd, code);
2019-02-17 19:26:29 +01:00
2016-10-14 00:49:58 +02:00
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) {
// Failed to init the timer
luaL_error(L, "Unable to initialize timer");
}
platform_hw_timer_set_func(TIMER_OWNER, sendCommand, 0);
sync=2;
signalindex=0; repeatindex=0;
sendCommand(0);
return 0;
}
2020-12-28 00:04:32 +01:00
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)
2020-12-28 00:04:32 +01:00
LROT_FUNCENTRY( sendcommand, somfy_lua_sendcommand )
LROT_FUNCENTRY( listen, somfy_lua_listen )
LROT_NUMENTRY( UP, SOMFY_UP )
LROT_NUMENTRY( DOWN, SOMFY_DOWN )
LROT_NUMENTRY( PROG, SOMFY_PROG )
LROT_NUMENTRY( STOP, SOMFY_STOP )
LROT_END(somfy, NULL, 0)
NODEMCU_MODULE(SOMFY, "somfy", somfy, luaopen_somfy);