Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Somfy/TELIS driver #1521

Merged
merged 6 commits into from
Oct 13, 2016
Merged

Somfy/TELIS driver #1521

merged 6 commits into from
Oct 13, 2016

Conversation

vsky279
Copy link
Contributor

@vsky279 vsky279 commented Oct 2, 2016

  • This PR is for the dev branch rather than for master.
  • This PR is compliant with the other contributing guidelines as well (if not, please describe why).
  • I have thoroughly tested my contribution.
  • The code changes are reflected in the documentation at docs/en/*.

Possibility to control Somfy blinds with ESP8266 using an RF transmitter (433.42 MHz) connected to GPIO pin.

The module is quite simple. It uses hw timer for precision timing (similar implementation to gpio.serout()). The PR includes also Lua example.


Somfy module

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.

To understand details of the Somfy protocol please refer to Somfy RTS protocol and also discussion here.

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.

somfy.sendcommand()

Builds an frame defined by Somfy protocol and sends it to the RF transmitter.

Syntax

somfy.sendcommand(pin, remote_address, command, rolling_code, repeat_count, call_back)

Parameters

  • pin GPIO pin the RF transmitter is connected to.
  • remote_address address of the remote control. The device to be controlled is programmed with the addresses of the remote controls it should listen to.
  • 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.
  • 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.

My original remote is 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 scrumbling alhorithm 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.

Returns

nil

Example

To start with controlling your Somfy blinds you need to

  • Choose an arbitrary remote address (different from your existing remote) - 123 in this example
  • Choose a starting point for the rolling code. Any unsigned int works, 1 is a good start
  • Long-press the program button of your existing remote control until your blind goes up and down slightly
  • execute somfy.sendcommand(4, 123, somfy.PROG, 1, 2) - the blinds will react and your ESP8266 remote control is now registered
  • running somfy.sendcommand(4, 123, somfy.DOWN, 2, 16) - fully closes the blinds

For more elaborated example please refer to somfy.lua.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your module is compatible -- the other modules just can't be used at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I've changed it to:

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.

| :----- | :-------------------- | :---------- | :------ |
| 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).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a reference to the device that you need -- the transmitter appears to be an odd frequency....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've added:

The hardware used is the standard 433 MHz RF transmitter (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 though some reporting that it is working even with the original crystal.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a comment to say that this must be in RAM as it is accessed from the timer interrupt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd even be inclined to add this to sections.h and use that instead:
#define RAM_CONST_SECTION_ATTR __attribute((section((".data")))

Since it's a static in a module there's nothing to be gained from giving it a separately named section I believe, and it would add a bit of clarity to use a #define instead.

static void somfy_transmissionDone (task_param_t arg)
{
lua_State *L = lua_getstate();
lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a subtle race condition here -- if lua_done_ref is not nil when the transmission ends, the task will be queued. But it might be made null by another call to send command in the mean time.

Worse you need to set the lua_done_ref to LUA_NOREF before doing the lua_call so that if the callback tries to send another command it will all still work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Philip, I am not sure I got the point. If I modify the code as follows

static void somfy_transmissionDone (task_param_t arg)
{
    lua_State *L = lua_getstate();
    lua_rawgeti (L, LUA_REGISTRYINDEX, lua_done_ref);
    lua_done_ref = LUA_NOREF;
    lua_call (L, 0, 0);
    luaL_unref (L, LUA_REGISTRYINDEX, lua_done_ref);
}

Will the luaL_unref correctly unreference the callback function (when lua_done_ref is LUA_NOREF at the moment)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it will indeed not. What you want to do is luaL_unref() as soon as you've done the lua_rawgeti(), or well, anywhere before you lua_call() at least. So something like

    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);

This way, the callback function is able to issue another somfy command successfully (since lua_done_ref is LUA_NOREF at that point), and the callback itself can become available for gc as soon as the lua_call() returns. While the callback is on the stack (after lua_rawgeti()), the stack itself holds a reference on it and thus ensures it doesn't get gc'd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Johny. It's fixed now.

I guess we have the same issue in gpio.c. According to the same logic it should look like:

static void seroutasync_done (task_param_t arg)
{
  lua_State *L = lua_getstate();
  luaM_freearray(L, serout.delay_table, serout.tablelen, uint32);
  if (serout.lua_done_ref != LUA_REFNIL) { // we're here so serout.lua_done_ref != LUA_NOREF
    lua_rawgeti (L, LUA_REGISTRYINDEX, serout.lua_done_ref);
    luaL_unref (L, LUA_REGISTRYINDEX, serout.lua_done_ref);
    serout.lua_done_ref = LUA_NOREF;
    if (lua_pcall(L, 0, 0, 0)) {
      // Uncaught Error. Print instead of sudden reset
      luaL_error(L, "error: %s", lua_tostring(L, -1));
    }
  }
}

Can it be fixed in this PR or should there be another one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it another one, once this is settled.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't normally have links to hardware resources unless they're "stable" links to official data sheets. So, I'd like to see those ebay links removed/replaced.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I guess there is no official data sheet. So I leave there just the "433 MHz RF transmitter". Everybody should be able to google it.


if (!lua_isnoneornil(L, 6)) {
lua_pushvalue(L, 6);
lua_done_ref = luaL_ref(L, LUA_REGISTRYINDEX);
Copy link
Member

@pjsg pjsg Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to do luaL_unref(L, LUA_REGISTRYINDEX, lua_done_ref) first (just in case there is still a reference) (actually in both cases -- so do it a few more lines up). Doing an unref on a LUA_NOREF is perfectly OK

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le voila. To be fixed in the gpio.c too.

@marcelstoer marcelstoer added this to the Post-1.5.4.1-II milestone Oct 13, 2016
@marcelstoer
Copy link
Member

AFAICS there are no pending issues anymore with this. @devsaurus @pjsg if you agree then please merge.

@marcelstoer marcelstoer merged commit 880bd98 into nodemcu:dev Oct 13, 2016
marcelstoer added a commit that referenced this pull request Dec 1, 2016
* add u8g.fb_rle display

* move comm drivers to u8g_glue.c

* disable fb_rle per default

* implement file.size for spiffs (#1516)

Another bug squashed!

* Fix start-up race between UART & start_lua. (#1522)

Input during startup (especially while doing initial filesystem format)
ran the risk of filling up the task queue, preventing the start_lua task
from being queued, and hence NodeMCU would not start up that time.

* Reimplemented esp_init_data_default.

To work around the pesky "rf_cal[0] !=0x05" hang when booting on a chip
which doesn't have esp_init_data written to it.

It is no longer possible to do the writing of the esp_init_data_default
from within nodemcu_init(), as the SDK now hangs long before it gets
there.  As such, I've had to reimplement this in our user_start_trampoline
and get it all done before the SDK has a chance to look for the init data.
It's unfortunate that we have to spend IRAM on this, but I see no better
alternative at this point.

* Replace hardcoded init data with generated data from SDK

The esp_init_data_default.bin is now extracted from the SDK (and its
patch file, if present), and the contents are automatically embedded
into user_main.o.

* Rework flashing instructions

Clarifies issues around SDK init data and hopefully clears up some
confusion, when paired with the esp_init_data_default changes in
NodeMCU.

* Fix typo

* Fixes the gpio.serout problem from #1534 (#1535)

* Fix some issues in gpio.serout
* Minor cleanup

* fix dereferencing NULL pointer in vfs_errno() (#1539)

* add map ids for flash sizes 32m-c2, 64m, 128m in user_rf_cal_sector_set() (#1529)

* Somfy/TELIS driver (#1521)

* Reduced LUAL_BUFFERSIZE to 256. Should free up some stack (#1530)

* avoid task queue overrun for serial input (#1540)

Thank you.

* Increase irom0_0_seg size for PR build

* Improve reliability of FS detection. (#1528)

* Version to make filesystem detection more reliable
* Improve bad fs detection

* Version of printf that doesn't suffer from buffer overflows (#1564)

* Small improvement to http client (#1558)

* Remove luaL_buffer from file_g_read() (#1541)

* remove luaL_buffer from file_g_read()
- avoid memory leak when function gets terminated by lua_error
- skip scanning for end_char when reading until EOF
* attempt to free memory in any case

* Change HTTP failures from debug to error messages (#1568)

* Change HTTP failures from debug to error messages

* Add tag to HTTP error messages

* Create macro for error msg and improve dbg msg

* Add ssd1306_128x32 for U8G (#1571)

* Update CONTRIBUTING.md

* Add support to mix ws2812.buffer objects.  (#1575)

* Add load/dump/mix/power operations on the buffer object
* Calculate the pixel value in mix and then clip to the range.
* Fixed the two wrong userdata types
* Added a couple more useful methods
* Add support for shifting a piece of the buffer.
* Fix a minor bug with offset shifts

* Update to the wifi module (#1497)

* Removed inline documentation for several functions and update comments
Since documentation is now part of the repository, the inline
documentation just adds to the already huge wifi.c

* Wifi module: add new functionality, update documentation

Functions Added:
wifi.getdefaultmode(): returns default wifi opmode
wifi.sta.apchange(): select alternate cached AP
wifi.sta.apinfo(): get cached AP list 
wifi.sta.aplimit(): set cached AP limit
wifi.sta.getapindex(): get index of currently configured AP
wifi.sta.getdefaultconfig(): get default station configuration
wifi.ap.getdefaultconfig(): get default AP configuration

functions modified:
wifi.setmode: saving mode to flash is now optional
wifi.sta.config: now accepts table as an argument and save config to
flash is now optional
wifi.sta.getconfig: added option to return table
wifi.ap.config: save config to flash is now optional
wifi.ap.getconfig: added option to return table

Documentation changes:
- Modified documentation to reflect above changes
- Removed unnecessary inline documentation from `wifi.c` 
- Updated documentation for `wifi.sta.disconnect`to address issue #1480 
- Fixed inaccurate documentation for function `wifi.sleeptype`
- Added more details to `wifi.nullmodesleep()`

* Move function `wifi.sleeptype()` to `wifi.sta.sleeptype()`

* Fixed problem where wifi.x.getconfig() returned invalid strings when
ssid or password were set to maximum length.

* fix error in documentation for `wifi.sta.getapindex`

* Renamed some wifi functions
wifi.sta.apinfo -> getapinfo
wifi.sta.aplimit -> setaplimit 
wifi.sta.apchange -> changeap

also organized the wifi_station_map array

* Make the MQTT PING functionality work better. (#1557)

Deal with flow control stopped case

* Implement object model for files (#1532)

* Eus channelfix (#1583)

Squashed commits included:

Bug fixes and final implementation
- Added Content-Length: 0 to all headers
- Endpoint name checks not using trailing space so cache-busting techniques can be used (i.e., append a nonce to the URL)
- Track when connecting so APList scan doesn't take place during (which changes the channel)
- More debugging output added to assist in tracking down some issues

Added /status.json endpoint for phone apps/XHR to get JSON response

Station Status caching for wifi channel workaround + AJAX/CORS
- During checkstation poll, cache the last station status
- Shut down the station if status = 2,3,4 and channel is different than SoftAP
- Add Access-Control-Allow-Origin: * to endpoint responses used by a service
- Add a /setwifi GET endpoint for phone apps/XHR to use (same parameters as /update endpoint). Returns a JSON response containing chip id and status code.
- Add handler for OPTIONS verb (needed for CORS support)

Wi-Fi Channel Issue Workaround
- Do a site survey upon startup, set SoftAP channel to the strongest rssi's channel
- Compare successful station connect channel to SoftAP's. If different, then defer the Lua success callback to the end. Shut down Station and start the SoftAP back up with original channel.
- After the 10 second shutdown timer fires, check to see if success callback was already called. If not, then call it while starting the Station back up.

HTTP Response and DNS enhancements
- If DNS's UDP buffer fills up, keep going as non-fatal. It's UDP and not guaranteed anyways. I've seen this occur when connecting a PC to the SoftAP and every open program tries to phone home at the same time, overwhelming the EUS DNS server.
- Support for detecting/handling pre-gzipped `enduser_setup.html` (and `http_html_backup`) payload. Nice for keeping the size of the `state->http_payload_data` as small as possible (also makes minimization not as critical)
- Corrected misuse of HTTP 401 response status (changed one occurrence to 400/Bad Request, and changed another to 405/Method Not Allowed)

* Normalized formatting (tabs-to-spaces)
* Added documentation
* Corrected misuse of strlen for binary (gzip) data.
* Added NULL check after malloc

* fix vfs_lseek() result checking in enduser_setup and clarify SPIFFS_lseek() return value (#1570)

* Fix link

* Overhaul flashing docs once again (#1587)

* Add chapter about determine flash size plus small fixes
* Rewrite esptool.py chapter, move flash size chapter to end

* i2c - allow slave stretching SCL (just loop and check) (#1589)

* Add note on dev board usage of SPI bus 0 (#1591)

* Turn SPI busses note to admonition note

* support for custom websocket headers (#1573)

Looks good to me. Thank you.

Also:
 - allow for '\0's in received messages

* add client:config for setting websocket headers

Also:
 - headers are case-insensitive now

* fix docs

* fix typo

* remove unnecessary luaL_argcheck calls

* replace os_sprintf with simple string copy

* Handle error condition in file.read() (#1599)

* handle error condition in file.read()

* simplify loop initialization

* Fix macro as suggested in #1548

* Extract and hoist net receive callbacks

This is done to avoid the accidental upval binding

* Fix typo at rtctime.md

rtctime.dsleep -> rtctime.dsleep_aligned
@marcelstoer
Copy link
Member

It's only now that I realized that the Somfy entry was missing in mkdocs.yml. Sorry about that. I'll fix it right away and push it also to master. No one ever missed the Somfy chapter in the docs...not cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants