This commit is contained in:
Frédéric Metrich 2026-07-05 20:17:28 +02:00 committed by GitHub
commit 95ad4967aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 4484 additions and 2891 deletions

13
.gitignore vendored
View file

@ -8,3 +8,16 @@ SomfyController.ino.XIAO_ESP32S3.bin
SomfyController.ino.esp32c3.bin SomfyController.ino.esp32c3.bin
SomfyController.ino.esp32s2.bin SomfyController.ino.esp32s2.bin
.vscode/settings.json .vscode/settings.json
.pio/
.generated_files/
build/
dist/
nbproject/
Makefile*
Package-*
private/
_build/
!cmake/**/user.cmake
cmake/**/.generated/*
cmake/**/CMake*
out/

View file

@ -7,9 +7,9 @@
extern Preferences pref; extern Preferences pref;
#define SHADE_HDR_VER 24 #define SHADE_HDR_VER 25
#define SHADE_HDR_SIZE 76 #define SHADE_HDR_SIZE 76
#define SHADE_REC_SIZE 276 #define SHADE_REC_SIZE 320
#define GROUP_REC_SIZE 200 #define GROUP_REC_SIZE 200
#define TRANS_REC_SIZE 74 #define TRANS_REC_SIZE 74
#define ROOM_REC_SIZE 29 #define ROOM_REC_SIZE 29
@ -861,6 +861,12 @@ bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) {
if(shade->proto == radio_proto::GP_Remote) if(shade->proto == radio_proto::GP_Remote)
pinMode(shade->gpioMy, OUTPUT); pinMode(shade->gpioMy, OUTPUT);
if(this->header.version >= 19) shade->roomId = this->readUInt8(0); if(this->header.version >= 19) shade->roomId = this->readUInt8(0);
if(this->header.version >= 25) {
shade->upMidTime = this->readUInt32(0);
shade->downMidTime = this->readUInt32(0);
shade->descendTime = this->readUInt32(0);
shade->upSlatTime = this->readUInt32(0);
}
if(this->file.position() != startPos + this->header.shadeRecordSize) { if(this->file.position() != startPos + this->header.shadeRecordSize) {
Serial.println("Reading to end of shade record"); Serial.println("Reading to end of shade record");
this->seekChar(CFG_REC_END); this->seekChar(CFG_REC_END);
@ -997,7 +1003,11 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
this->writeUInt8(shade->gpioDown); this->writeUInt8(shade->gpioDown);
this->writeUInt8(shade->gpioMy); this->writeUInt8(shade->gpioMy);
this->writeUInt8(shade->gpioFlags); this->writeUInt8(shade->gpioFlags);
this->writeUInt8(shade->roomId, CFG_REC_END); this->writeUInt8(shade->roomId);
this->writeUInt32(shade->upMidTime);
this->writeUInt32(shade->downMidTime);
this->writeUInt32(shade->descendTime);
this->writeUInt32(shade->upSlatTime, CFG_REC_END);
return true; return true;
} }
bool ShadeConfigFile::writeSettingsRecord() { bool ShadeConfigFile::writeSettingsRecord() {

View file

@ -3,7 +3,7 @@
#ifndef configsettings_h #ifndef configsettings_h
#define configsettings_h #define configsettings_h
#include "WResp.h" #include "WResp.h"
#define FW_VERSION "v2.4.7" #define FW_VERSION "v3.0.0"
enum class conn_types_t : byte { enum class conn_types_t : byte {
unset = 0x00, unset = 0x00,
wifi = 0x01, wifi = 0x01,

View file

@ -69,6 +69,49 @@ While the interface that comes with the ESPSomfy RTS is a huge improvement, the
You can find the documentation for the interfaces in the [Integrations](https://github.com/rstrouse/ESPSomfy-RTS/wiki/Integrations) wiki. Plenty of stuff there for you folks that make red nodes and stuff. You can find the documentation for the interfaces in the [Integrations](https://github.com/rstrouse/ESPSomfy-RTS/wiki/Integrations) wiki. Plenty of stuff there for you folks that make red nodes and stuff.
## Accurate Position Estimation for Roller Shades
### The problem
ESPSomfy RTS estimates shade position by interpolating elapsed travel time against the configured Up/Down time. This works well for simple motors, but roller shades wind their material around a drum. As the shade travels, the drum radius grows (when opening) or shrinks (when closing), so the linear speed of the fabric changes even when the motor turns at a constant angular speed. The result is that the reported percentage drifts noticeably from the real position — the shade is at 50% in Home Assistant but clearly not in the middle of the window.
### Non-linear calibration fields
Two optional fields can be set per shade to correct for this:
| Field | Description |
|---|---|
| **Up mid-time** | Elapsed time (ms) from fully closed until the shade reaches the 50% open position |
| **Down mid-time** | Elapsed time (ms) from fully open until the shade reaches the 50% closed position |
When either field is non-zero, ESPSomfy RTS fits a logarithmic model (α ratio solved via Newton's method) to the measured mid-point. All position estimates then use this model instead of linear interpolation. Set both fields to 0 to revert to the original linear behaviour.
**How to measure:** start a full travel, watch the shade, and record the time at which it crosses the halfway point of the window opening. A stopwatch is sufficient.
### Two-phase model for roller shutters with blade tilt
Roller shutters that tilt their blades go through two distinct motion phases:
- **Closing:** the shade descends (blades unrolling) then the blades tilt to fully close — a short linear phase at the end
- **Opening:** the blades open first (linear phase), then the shade ascends
Two additional fields model these phases:
| Field | Description |
|---|---|
| **Descend time** | Elapsed time (ms) from fully open until the shade touches the floor (blades horizontal, before tilt) |
| **Up slat time** | Elapsed time (ms) from fully closed until the blades are fully expanded (before the shade starts to rise) |
The non-linear model is applied only to the pure travel portion (descent or ascent), excluding the linear blade-tilt phases at each end. As with the mid-time fields, set to 0 to disable.
**How to measure:** start a full travel from the opposite end-stop and watch for the physical transition — bottom of shade touching the sill (for descend time) or blades visibly fully open (for up slat time). Both can be measured with a stopwatch from movement start.
### The special 1% position
The boundary between the two phases — blades fully expanded so that light passes between them, shade resting on the floor — is represented internally as **1%**. Positions from 100% down to 1% correspond to the blade-tilt range; positions from 1% down to 0% correspond to the shade travel range. A consequence of this is that there is no intermediate state between blades fully expanded (letting some light through) and blades fully closed (in contact with each other, no light): the shade is either in the tilt zone (≥ 1%) or in the travel zone (< 1%). In practice this is not a real-world limitation, since stopping the blades at a partially-expanded position is rarely useful, but it is worth being aware of.
All four fields are persisted to NVS, included in the backup file, and are restored on firmware update.
## Sources for this Project ## Sources for this Project
I spent some time reading about a myriad of topics but in the end the primary source for this project comes from https://pushstack.wordpress.com/somfy-rts-protocol/. The work done on pushstack regarding the protocol timing made this feasible without burning a bunch of time measuring pulses. I spent some time reading about a myriad of topics but in the end the primary source for this project comes from https://pushstack.wordpress.com/somfy-rts-protocol/. The work done on pushstack regarding the protocol timing made this feasible without burning a bunch of time measuring pulses.

6302
Somfy.cpp

File diff suppressed because it is too large Load diff

View file

@ -314,6 +314,13 @@ class SomfyShade : public SomfyRemote {
uint32_t upTime = 10000; uint32_t upTime = 10000;
uint32_t downTime = 10000; uint32_t downTime = 10000;
uint32_t tiltTime = 7000; uint32_t tiltTime = 7000;
uint32_t upMidTime = 0; // Timestamp (ms from fully closed) at 50% when opening; 0 = linear
uint32_t downMidTime = 0; // Timestamp (ms from fully open) at 50% when closing; 0 = linear
uint32_t descendTime = 0; // Timestamp (ms from fully open) when slats touch floor, blades expanded; 0 = disabled
uint32_t upSlatTime = 0; // Timestamp (ms from fully closed) when blades are expanded during opening; 0 = disabled
float alphaUp = 0.0f; // Derived from (upMidTime-upSlatTime)/(upTime-upSlatTime); 0 = use linear
float alphaDown = 0.0f; // Derived from downMidTime/descendTime; 0 = use linear
void computeAlphas();
uint16_t stepSize = 100; uint16_t stepSize = 100;
bool save(); bool save();
bool isIdle(); bool isIdle();

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
2.4.7 3.0.0

File diff suppressed because it is too large Load diff

View file

@ -1270,7 +1270,7 @@ var security = new Security();
class General { class General {
initialized = false; initialized = false;
appVersion = 'v2.4.7'; appVersion = 'v3.0.0';
reloadApp = false; reloadApp = false;
init() { init() {
if (this.initialized) return; if (this.initialized) return;