somfy.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. // ***************************************************************************
  2. // Somfy module for ESP8266 with NodeMCU
  3. //
  4. // Written by Lukas Voborsky, @voborsky
  5. // based on https://github.com/Nickduino/Somfy_Remote
  6. // Somfy protocol description: https://pushstack.wordpress.com/somfy-rts-protocol/
  7. // and discussion: https://forum.arduino.cc/index.php?topic=208346.0
  8. //
  9. // MIT license, http://opensource.org/licenses/MIT
  10. // ***************************************************************************
  11. //#define NODE_DEBUG
  12. #include <stdint.h>
  13. #include "module.h"
  14. #include "lauxlib.h"
  15. #include "platform.h"
  16. #include "task/task.h"
  17. #include "hw_timer.h"
  18. #include "user_interface.h"
  19. #ifdef LUA_USE_MODULES_SOMFY
  20. #if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
  21. #error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using SOMFY module
  22. #endif
  23. #endif
  24. #ifdef NODE_DEBUG
  25. #define PULLUP PLATFORM_GPIO_PULLUP
  26. #define OUTPUT PLATFORM_GPIO_OUTPUT
  27. #define HIGH PLATFORM_GPIO_HIGH
  28. #define LOW PLATFORM_GPIO_LOW
  29. #define MODE_TP1 platform_gpio_mode( 3, OUTPUT, PULLUP ); // GPIO 00
  30. #define SET_TP1 platform_gpio_write(3, HIGH);
  31. #define CLR_TP1 platform_gpio_write(3, LOW);
  32. #define WAIT os_delay_us(1);
  33. #else
  34. #define MODE_TP1
  35. #define SET_TP1
  36. #define CLR_TP1
  37. #define WAIT
  38. #endif
  39. #define SYMBOL 640 // symbol width in microseconds
  40. #define SOMFY_UP 0x2
  41. #define SOMFY_STOP 0x1
  42. #define SOMFY_DOWN 0x4
  43. #define SOMFY_PROG 0x8
  44. #define DIRECT_WRITE_LOW(pin) (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 0))
  45. #define DIRECT_WRITE_HIGH(pin) (GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), 1))
  46. // ----------------------------------------------------------------------------------------------------//
  47. // ------------------------------- transmitter part ---------------------------------------------------//
  48. // ----------------------------------------------------------------------------------------------------//
  49. static const os_param_t TIMER_OWNER = 0x736f6d66; // "somf"
  50. static task_handle_t SendDone_taskid;
  51. static uint8_t TxPin;
  52. static uint8_t frame[7];
  53. static uint8_t sync;
  54. static uint8_t repeat;
  55. //static uint32_t delay[10] = {9415, 89565, 4*SYMBOL, 4*SYMBOL, 4*SYMBOL, 4550, SYMBOL, SYMBOL, SYMBOL, 30415}; // inc us
  56. // the `delay` array of constants must be in RAM as it is accessed from the timer interrupt
  57. 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)
  58. static uint8_t repeatindex;
  59. static uint8_t signalindex;
  60. static uint8_t subindex;
  61. static uint8_t bitcondition;
  62. static int lua_done_ref = LUA_NOREF; // callback when transmission is done
  63. void buildFrame(uint8_t *frame, uint64_t remote, uint8_t button, uint16_t code) {
  64. // NODE_DBG("remote: %x\n", remote);
  65. // NODE_DBG("button: %x\n", button);
  66. // NODE_DBG("rolling code: %x\n", code);
  67. frame[0] = 0xA7; // Encryption key. Doesn't matter much
  68. frame[1] = button << 4; // Which button did you press? The 4 LSB will be the checksum
  69. frame[2] = code >> 8; // Rolling code (big endian)
  70. frame[3] = code; // Rolling code
  71. frame[4] = remote >> 16; // Remote address
  72. frame[5] = remote >> 8; // Remote address
  73. frame[6] = remote; // Remote address
  74. // frame[7] = 0x80;
  75. // frame[8] = 0x0;
  76. // frame[9] = 0x0;
  77. // 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]);
  78. // Checksum calculation: a XOR of all the nibbles
  79. uint8_t checksum = 0;
  80. for(uint8_t i = 0; i < 7; i++) {
  81. checksum = checksum ^ frame[i] ^ (frame[i] >> 4);
  82. }
  83. checksum &= 0b1111; // We keep the last 4 bits only
  84. //Checksum integration
  85. frame[1] |= checksum; // If a XOR of all the nibbles is equal to 0, the blinds will consider the checksum ok.
  86. // 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]);
  87. // Obfuscation: a XOR of all the uint8_ts
  88. for(uint8_t i = 1; i < 7; i++) {
  89. frame[i] ^= frame[i-1];
  90. }
  91. // 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]);
  92. }
  93. static void ICACHE_RAM_ATTR sendCommand(os_param_t p) {
  94. (void) p;
  95. // NODE_DBG("%d\t%d\n", signalindex, subindex);
  96. switch (signalindex) {
  97. case 0:
  98. subindex = 0;
  99. if(sync == 2) { // Only with the first frame.
  100. //Wake-up pulse & Silence
  101. DIRECT_WRITE_HIGH(TxPin);
  102. signalindex++;
  103. // delayMicroseconds(9415);
  104. break;
  105. } else {
  106. signalindex++; signalindex++; //no break means: go directly to step 3
  107. }
  108. case 1:
  109. //Wake-up pulse & Silence
  110. DIRECT_WRITE_LOW(TxPin);
  111. signalindex++;
  112. // delayMicroseconds(89565);
  113. break;
  114. case 2:
  115. signalindex++;
  116. // no break means go directly to step 3
  117. // a "useless" step to allow repeating the hardware sync w/o the silence after wake-up pulse
  118. case 3:
  119. // Hardware sync: two sync for the first frame, seven for the following ones.
  120. DIRECT_WRITE_HIGH(TxPin);
  121. signalindex++;
  122. // delayMicroseconds(4*SYMBOL);
  123. break;
  124. case 4:
  125. DIRECT_WRITE_LOW(TxPin);
  126. subindex++;
  127. if (subindex < sync) {signalindex--;} else {signalindex++;}
  128. // delayMicroseconds(4*SYMBOL);
  129. break;
  130. case 5:
  131. // Software sync
  132. DIRECT_WRITE_HIGH(TxPin);
  133. signalindex++;
  134. // delayMicroseconds(4550);
  135. break;
  136. case 6:
  137. DIRECT_WRITE_LOW(TxPin);
  138. signalindex++;
  139. subindex=0;
  140. // delayMicroseconds(SYMBOL);
  141. break;
  142. case 7:
  143. //Data: bits are sent one by one, starting with the MSB.
  144. bitcondition = ((frame[subindex/8] >> (7 - (subindex%8))) & 1) == 1;
  145. if(bitcondition) {
  146. DIRECT_WRITE_LOW(TxPin);
  147. }
  148. else {
  149. DIRECT_WRITE_HIGH(TxPin);
  150. }
  151. signalindex++;
  152. // delayMicroseconds(SYMBOL);
  153. break;
  154. case 8:
  155. //Data: bits are sent one by one, starting with the MSB.
  156. if(bitcondition) {
  157. DIRECT_WRITE_HIGH(TxPin);
  158. }
  159. else {
  160. DIRECT_WRITE_LOW(TxPin);
  161. }
  162. if (subindex<56) {
  163. subindex++;
  164. signalindex--;
  165. }
  166. else {
  167. signalindex++;
  168. }
  169. // delayMicroseconds(SYMBOL);
  170. break;
  171. case 9:
  172. DIRECT_WRITE_LOW(TxPin);
  173. signalindex++;
  174. // delayMicroseconds(30415); // Inter-frame silence
  175. break;
  176. case 10:
  177. repeatindex++;
  178. if (repeatindex<repeat) {
  179. DIRECT_WRITE_HIGH(TxPin); //start repeat from step 3, but don't wait as after step 1
  180. signalindex=4; subindex=0; sync=7;
  181. } else {
  182. platform_hw_timer_close(TIMER_OWNER);
  183. if (lua_done_ref != LUA_NOREF) {
  184. task_post_low (SendDone_taskid, (task_param_t)0);
  185. }
  186. }
  187. break;
  188. }
  189. if (signalindex<10) {
  190. platform_hw_timer_arm_ticks(TIMER_OWNER, delay[signalindex-1]);
  191. }
  192. }
  193. // ----------------------------------------------------------------------------------------------------//
  194. // ------------------------------- receiver part ------------------------------------------------------//
  195. // ----------------------------------------------------------------------------------------------------//
  196. #define TOLERANCE_MIN 0.7
  197. #define TOLERANCE_MAX 1.3
  198. static const uint32_t tempo_wakeup_pulse = 9415;
  199. static const uint32_t tempo_wakeup_silence = 89565;
  200. // static const uint32_t tempo_synchro_hw = SYMBOL*4;
  201. static const uint32_t tempo_synchro_hw_min = SYMBOL*4*TOLERANCE_MIN;
  202. static const uint32_t tempo_synchro_hw_max = SYMBOL*4*TOLERANCE_MAX;
  203. // static const uint32_t k_tempo_synchro_sw = 4550;
  204. static const uint32_t tempo_synchro_sw_min = 4550*TOLERANCE_MIN;
  205. static const uint32_t tempo_synchro_sw_max = 4550*TOLERANCE_MAX;
  206. // static const uint32_t tempo_half_symbol = SYMBOL;
  207. static const uint32_t tempo_half_symbol_min = SYMBOL*TOLERANCE_MIN;
  208. static const uint32_t tempo_half_symbol_max = SYMBOL*TOLERANCE_MAX;
  209. // static const uint32_t tempo_symbol = SYMBOL*2;
  210. static const uint32_t tempo_symbol_min = SYMBOL*2*TOLERANCE_MIN;
  211. static const uint32_t tempo_symbol_max = SYMBOL*2*TOLERANCE_MAX;
  212. static const uint32_t tempo_inter_frame_gap = 30415;
  213. static int16_t bitMin = SYMBOL*TOLERANCE_MIN;
  214. typedef enum {
  215. waiting_synchro = 0,
  216. receiving_data = 1,
  217. complete = 2
  218. }
  219. t_status;
  220. static struct SomfyRx_t
  221. {
  222. t_status status;
  223. uint8_t cpt_synchro_hw;
  224. uint8_t cpt_bits;
  225. uint8_t previous_bit;
  226. bool waiting_half_symbol;
  227. uint8_t payload[9];
  228. } SomfyRx;
  229. static task_handle_t DataReady_taskid;
  230. static uint8_t RxPin;
  231. static uint8_t IntBitmask;
  232. static int lua_dataready_ref = LUA_NOREF;
  233. static uint32_t ICACHE_RAM_ATTR InterruptHandler (uint32_t ret_gpio_status) {
  234. // This function really is running at interrupt level with everything
  235. // else masked off. It should take as little time as necessary.
  236. uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
  237. if ((gpio_status & IntBitmask) == 0) {
  238. return ret_gpio_status;
  239. }
  240. GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & IntBitmask);
  241. uint32_t actMicros = system_get_time();
  242. ret_gpio_status &= ~(IntBitmask);
  243. static unsigned long lastMicros = 0;
  244. unsigned long bitMicros;
  245. bitMicros = actMicros-lastMicros;
  246. if ( bitMicros < bitMin ) {
  247. // too short - may be false interrupt due to glitch or false protocol -> ignore
  248. return ret_gpio_status; // abort IRQ
  249. }
  250. lastMicros = actMicros;
  251. switch(SomfyRx.status) {
  252. case waiting_synchro:
  253. if (bitMicros > tempo_synchro_hw_min && bitMicros < tempo_synchro_hw_max) {
  254. SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
  255. ++SomfyRx.cpt_synchro_hw;
  256. CLR_TP1
  257. }
  258. else if (bitMicros > tempo_synchro_sw_min && bitMicros < tempo_synchro_sw_max && SomfyRx.cpt_synchro_hw >= 4) {
  259. SET_TP1 //WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
  260. memset( &SomfyRx, 0, sizeof( SomfyRx) );
  261. SomfyRx.status = receiving_data;
  262. } else {
  263. SomfyRx.cpt_synchro_hw = 0;
  264. }
  265. break;
  266. case receiving_data:
  267. if (bitMicros > tempo_symbol_min && bitMicros < tempo_symbol_max && !SomfyRx.waiting_half_symbol) {
  268. SET_TP1
  269. SomfyRx.previous_bit = 1 - SomfyRx.previous_bit;
  270. SomfyRx.payload[SomfyRx.cpt_bits/8] += SomfyRx.previous_bit << (7 - SomfyRx.cpt_bits%8);
  271. ++SomfyRx.cpt_bits;
  272. } else if (bitMicros > tempo_half_symbol_min && bitMicros < tempo_half_symbol_max) {
  273. SET_TP1 WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
  274. if (SomfyRx.waiting_half_symbol) {
  275. SomfyRx.waiting_half_symbol = false;
  276. SomfyRx.payload[SomfyRx.cpt_bits/8] += SomfyRx.previous_bit << (7 - SomfyRx.cpt_bits%8);
  277. ++SomfyRx.cpt_bits;
  278. } else {
  279. SomfyRx.waiting_half_symbol = true;
  280. }
  281. } else {
  282. SomfyRx.cpt_synchro_hw = 0;
  283. SomfyRx.status = waiting_synchro;
  284. }
  285. CLR_TP1
  286. break;
  287. default:
  288. break;
  289. }
  290. if (SomfyRx.status == receiving_data && SomfyRx.cpt_bits == 80) { //56) { experiment
  291. task_post_high(DataReady_taskid, (task_param_t)0);
  292. SomfyRx.status = waiting_synchro;
  293. }
  294. return ret_gpio_status;
  295. }
  296. static void somfy_decode (os_param_t param, uint8_t prio)
  297. {
  298. #ifdef NODE_DEBUG
  299. NODE_DBG("Payload:\t");
  300. for(uint8_t i = 0; i < 10; i++) {
  301. NODE_DBG("%02x ", SomfyRx.payload[i]);
  302. }
  303. NODE_DBG("\n");
  304. #endif
  305. // Deobfuscation
  306. uint8_t frame[10];
  307. frame[0] = SomfyRx.payload[0];
  308. for(int i = 1; i < 7; ++i) frame[i] = SomfyRx.payload[i] ^ SomfyRx.payload[i-1];
  309. frame[7] = SomfyRx.payload[7] ^ SomfyRx.payload[0];
  310. for(int i = 8; i < 10; ++i) frame[i] = SomfyRx.payload[i] ^ SomfyRx.payload[i-1];
  311. #ifdef NODE_DEBUG
  312. NODE_DBG("Frame:\t");
  313. for(uint8_t i = 0; i < 10; i++) {
  314. NODE_DBG("%02x ", frame[i]);
  315. }
  316. NODE_DBG("\n");
  317. #endif
  318. // Checksum check
  319. uint8_t cksum = 0;
  320. for(int i = 0; i < 7; ++i) cksum = cksum ^ frame[i] ^ (frame[i] >> 4);
  321. cksum = cksum & 0x0F;
  322. if (cksum != 0) {
  323. NODE_DBG("Checksum incorrect!\n");
  324. return;
  325. }
  326. unsigned long rolling_code = (frame[2] << 8) || frame[3];
  327. unsigned long address = ((unsigned long)frame[4] << 16) || (frame[5] << 8) || frame[6];
  328. if (lua_dataready_ref == LUA_NOREF)
  329. return;
  330. lua_State *L = lua_getstate();
  331. lua_rawgeti(L, LUA_REGISTRYINDEX, lua_dataready_ref);
  332. lua_pushinteger(L, address);
  333. lua_pushinteger(L, frame[1] >> 4);
  334. lua_pushinteger(L, rolling_code);
  335. lua_pushlstring(L, frame, 10);
  336. luaL_pcallx(L, 4, 0);
  337. }
  338. // ----------------------------------------------------------------------------------------------------//
  339. // ------------------------------- Lua part -----------------------------------------------------------//
  340. // ----------------------------------------------------------------------------------------------------//
  341. static inline void register_lua_cb(lua_State* L, int* cb_ref){
  342. int ref=luaL_ref(L, LUA_REGISTRYINDEX);
  343. if( *cb_ref != LUA_NOREF){
  344. luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
  345. }
  346. *cb_ref = ref;
  347. }
  348. static inline void unregister_lua_cb(lua_State* L, int* cb_ref){
  349. if(*cb_ref != LUA_NOREF){
  350. luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
  351. *cb_ref = LUA_NOREF;
  352. }
  353. }
  354. int somfy_lua_listen(lua_State* L) { // pin, callback
  355. NODE_DBG("[somfy_lua_listen]\n");
  356. #if LUA_VERSION_NUM == 501
  357. if (lua_isnumber(L, 1) && lua_type(L, 2) == LUA_TFUNCTION) {
  358. #else
  359. if (lua_isinteger(L, 1) && lua_type(L, 2) == LUA_TFUNCTION) {
  360. #endif
  361. RxPin = luaL_checkinteger(L, 1);
  362. luaL_argcheck(L, platform_gpio_exists(RxPin) && RxPin>0, 1, "Invalid interrupt pin");
  363. lua_pushvalue(L, 2);
  364. register_lua_cb(L, &lua_dataready_ref);
  365. memset( &SomfyRx, 0, sizeof( SomfyRx) );
  366. IntBitmask = 1 << pin_num[RxPin];
  367. MODE_TP1
  368. NODE_DBG("[somfy_lua_listen] Enabling interrupt on PIN %d\n", RxPin);
  369. platform_gpio_mode(RxPin, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
  370. NODE_DBG("[somfy_lua_listen] platform_gpio_register_intr_hook - pin: %d, mask: %d\n", RxPin, IntBitmask);
  371. platform_gpio_register_intr_hook(IntBitmask, InterruptHandler);
  372. gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[RxPin]), GPIO_PIN_INTR_ANYEDGE);
  373. #if LUA_VERSION_NUM == 501
  374. } else if ((lua_isnoneornil(L, 1) || lua_isnumber(L, 1)) && lua_isnoneornil(L, 2)) {
  375. #else
  376. } else if ((lua_isnoneornil(L, 1) || lua_isinteger(L, 1)) && lua_isnoneornil(L, 2)) {
  377. #endif
  378. NODE_DBG("[somfy_lua_listen] Desabling interrupt on PIN %d\n", RxPin);
  379. platform_gpio_mode(RxPin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP);
  380. unregister_lua_cb(L, &lua_dataready_ref);
  381. RxPin = 0;
  382. } else {
  383. luaL_error(L, "Invalid parameters");
  384. }
  385. return 0;
  386. }
  387. static void somfy_transmissionDone (task_param_t arg)
  388. {
  389. lua_State *L = lua_getstate();
  390. lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref);
  391. unregister_lua_cb (L, &lua_done_ref);
  392. luaL_pcallx (L, 0, 0);
  393. }
  394. int somfy_lua_sendcommand(lua_State* L) { // pin, remote, command, rolling_code, num_repeat, callback
  395. TxPin = luaL_checkinteger(L, 1);
  396. uint64_t remote = luaL_checkinteger(L, 2);
  397. uint8_t cmd = luaL_checkinteger(L, 3);
  398. uint16_t code = luaL_checkinteger(L, 4);
  399. repeat=luaL_optint( L, 5, 2 );
  400. luaL_argcheck(L, platform_gpio_exists(TxPin), 1, "Invalid pin");
  401. if (lua_type(L, 6) == LUA_TFUNCTION) {
  402. lua_pushvalue (L, 6);
  403. register_lua_cb (L, &lua_done_ref);
  404. } else {
  405. unregister_lua_cb (L, &lua_done_ref);
  406. }
  407. MOD_CHECK_ID(gpio, TxPin);
  408. platform_gpio_mode(TxPin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP);
  409. buildFrame(frame, remote, cmd, code);
  410. if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) {
  411. // Failed to init the timer
  412. luaL_error(L, "Unable to initialize timer");
  413. }
  414. platform_hw_timer_set_func(TIMER_OWNER, sendCommand, 0);
  415. sync=2;
  416. signalindex=0; repeatindex=0;
  417. sendCommand(0);
  418. return 0;
  419. }
  420. int luaopen_somfy( lua_State *L ) {
  421. SendDone_taskid = task_get_id((task_callback_t) somfy_transmissionDone);
  422. DataReady_taskid = task_get_id((task_callback_t) somfy_decode);
  423. return 0;
  424. }
  425. // Module function map
  426. LROT_BEGIN(somfy, NULL, 0)
  427. LROT_FUNCENTRY( sendcommand, somfy_lua_sendcommand )
  428. LROT_FUNCENTRY( listen, somfy_lua_listen )
  429. LROT_NUMENTRY( UP, SOMFY_UP )
  430. LROT_NUMENTRY( DOWN, SOMFY_DOWN )
  431. LROT_NUMENTRY( PROG, SOMFY_PROG )
  432. LROT_NUMENTRY( STOP, SOMFY_STOP )
  433. LROT_END(somfy, NULL, 0)
  434. NODEMCU_MODULE(SOMFY, "somfy", somfy, luaopen_somfy);