v1.5.0 updates

* Support for integrated tilt types
* Increased linked remotes to 7 #25.
* Fixed issue #25 by increasing JSON buffer
* Changed software sync detection for linked remotes to better match on sync #26
This commit is contained in:
Robert Strouse 2023-04-20 14:29:13 -07:00
parent aec9989d8d
commit 5a16977ea9
13 changed files with 497 additions and 383 deletions

View file

@ -6,9 +6,9 @@
extern Preferences pref; extern Preferences pref;
#define SHADE_HDR_VER 2 #define SHADE_HDR_VER 5
#define SHADE_HDR_SIZE 16 #define SHADE_HDR_SIZE 16
#define SHADE_REC_SIZE 180 #define SHADE_REC_SIZE 222
bool ConfigFile::begin(const char* filename, bool readOnly) { bool ConfigFile::begin(const char* filename, bool readOnly) {
this->file = LittleFS.open(filename, readOnly ? "r" : "w"); this->file = LittleFS.open(filename, readOnly ? "r" : "w");
@ -281,7 +281,11 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
shade->shadeType = static_cast<shade_types>(this->readUInt8(0)); shade->shadeType = static_cast<shade_types>(this->readUInt8(0));
shade->setRemoteAddress(this->readUInt32(0)); shade->setRemoteAddress(this->readUInt32(0));
this->readString(shade->name, sizeof(shade->name)); this->readString(shade->name, sizeof(shade->name));
shade->hasTilt = this->readBool(false); if(this->header.version < 3)
shade->tiltType = this->readBool(false) ? tilt_types::none : tilt_types::tiltmotor;
else
shade->tiltType = static_cast<tilt_types>(this->readUInt8(0));
if(this->header.version > 1) { if(this->header.version > 1) {
shade->bitLength = this->readUInt8(56); shade->bitLength = this->readUInt8(56);
} }
@ -292,16 +296,32 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
SomfyLinkedRemote *rem = &shade->linkedRemotes[j]; SomfyLinkedRemote *rem = &shade->linkedRemotes[j];
rem->setRemoteAddress(this->readUInt32(0)); rem->setRemoteAddress(this->readUInt32(0));
if(rem->getRemoteAddress() != 0) rem->lastRollingCode = pref.getUShort(rem->getRemotePrefId(), 0); if(rem->getRemoteAddress() != 0) rem->lastRollingCode = pref.getUShort(rem->getRemotePrefId(), 0);
if(this->header.version < 5 && j == 4) break; // Prior to version 5 we only supported 5 linked remotes.
} }
shade->lastRollingCode = this->readUInt16(0); shade->lastRollingCode = this->readUInt16(0);
if(shade->getRemoteAddress() != 0) shade->lastRollingCode = max(pref.getUShort(shade->getRemotePrefId(), shade->lastRollingCode), shade->lastRollingCode); if(shade->getRemoteAddress() != 0) shade->lastRollingCode = max(pref.getUShort(shade->getRemotePrefId(), shade->lastRollingCode), shade->lastRollingCode);
shade->myPos = this->readUInt8(255); if(this->header.version < 4)
shade->myPos = static_cast<float>(this->readUInt8(255));
else {
shade->myPos = this->readFloat(-1);
shade->myTiltPos = this->readFloat(-1);
}
if(shade->myPos > 100 || shade->myPos < 0) shade->myPos = -1;
if(shade->myTiltPos > 100 || shade->myTiltPos < 0) shade->myTiltPos = -1;
shade->currentPos = this->readFloat(0); shade->currentPos = this->readFloat(0);
shade->currentTiltPos = this->readFloat(0); shade->currentTiltPos = this->readFloat(0);
shade->tiltPosition = (uint8_t)floor(shade->currentTiltPos * 100); if(shade->tiltType == tilt_types::none || shade->shadeType != shade_types::blind) {
shade->position = (uint8_t)floor(shade->currentPos * 100); shade->myTiltPos = -1;
shade->target = shade->position; shade->currentTiltPos = 0;
shade->tiltTarget = shade->tiltPosition; shade->tiltType = tilt_types::none;
}
if(this->header.version < 3) {
shade->currentPos = shade->currentPos * 100;
shade->currentTiltPos = shade->currentTiltPos * 100;
}
shade->target = floor(shade->currentPos);
shade->tiltTarget = floor(shade->currentTiltPos);
} }
pref.end(); pref.end();
if(opened) { if(opened) {
@ -311,12 +331,17 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
return true; return true;
} }
bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) { bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
if(shade->tiltType == tilt_types::none || shade->shadeType != shade_types::blind) {
shade->myTiltPos = -1;
shade->currentTiltPos = 0;
shade->tiltType = tilt_types::none;
}
this->writeUInt8(shade->getShadeId()); this->writeUInt8(shade->getShadeId());
this->writeBool(shade->paired); this->writeBool(shade->paired);
this->writeUInt8(static_cast<uint8_t>(shade->shadeType)); this->writeUInt8(static_cast<uint8_t>(shade->shadeType));
this->writeUInt32(shade->getRemoteAddress()); this->writeUInt32(shade->getRemoteAddress());
this->writeString(shade->name, sizeof(shade->name)); this->writeString(shade->name, sizeof(shade->name));
this->writeBool(shade->hasTilt); this->writeUInt8(static_cast<uint8_t>(shade->tiltType));
this->writeUInt8(shade->bitLength); this->writeUInt8(shade->bitLength);
this->writeUInt32(shade->upTime); this->writeUInt32(shade->upTime);
this->writeUInt32(shade->downTime); this->writeUInt32(shade->downTime);
@ -326,7 +351,8 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
this->writeUInt32(rem->getRemoteAddress()); this->writeUInt32(rem->getRemoteAddress());
} }
this->writeUInt16(shade->lastRollingCode); this->writeUInt16(shade->lastRollingCode);
this->writeUInt8(shade->myPos); this->writeFloat(shade->myPos, 5);
this->writeFloat(shade->myTiltPos, 5);
this->writeFloat(shade->currentPos, 5); this->writeFloat(shade->currentPos, 5);
this->writeFloat(shade->currentTiltPos, 5, CFG_REC_END); this->writeFloat(shade->currentTiltPos, 5, CFG_REC_END);
return true; return true;

View file

@ -3,7 +3,7 @@
#ifndef configsettings_h #ifndef configsettings_h
#define configsettings_h #define configsettings_h
#define FW_VERSION "v1.4.7" #define FW_VERSION "v1.5.0"
enum DeviceStatus { enum DeviceStatus {
DS_OK = 0, DS_OK = 0,
DS_ERROR = 1, DS_ERROR = 1,

View file

@ -368,6 +368,7 @@ bool Network::openSoftAP() {
Serial.println(); Serial.println();
Serial.println("Turning the HotSpot On"); Serial.println("Turning the HotSpot On");
WiFi.disconnect(true); WiFi.disconnect(true);
WiFi.hostname("ESPSomfy RTS");
WiFi.mode(WIFI_AP_STA); WiFi.mode(WIFI_AP_STA);
delay(100); delay(100);
WiFi.softAP("ESPSomfy RTS", ""); WiFi.softAP("ESPSomfy RTS", "");
@ -375,7 +376,7 @@ bool Network::openSoftAP() {
Serial.println(); Serial.println();
Serial.print("SoftAP IP: "); Serial.print("SoftAP IP: ");
Serial.println(WiFi.softAPIP()); Serial.println(WiFi.softAPIP());
pinMode(D0, INPUT_PULLUP); //pinMode(D0, INPUT_PULLUP);
long startTime = millis(); long startTime = millis();
int c = 0; int c = 0;

637
Somfy.cpp
View file

@ -514,16 +514,29 @@ bool SomfyShade::unlinkRemote(uint32_t address) {
} }
return false; return false;
} }
bool SomfyShade::isAtTarget() { return this->currentPos == this->target && this->currentTiltPos == this->tiltTarget; }
void SomfyShade::checkMovement() { void SomfyShade::checkMovement() {
// We are checking movement for essentially 3 types of motors.
// If this is an integrated tilt we need to first tilt in the direction we are moving then move. We know
// what needs to be done by the tilt type. Set a tilt first flag to indicate whether we should be tilting or
// moving. If this is only a tilt action then the regular tilt action should operate fine.
int8_t currDir = this->direction; int8_t currDir = this->direction;
uint8_t currPos = this->position;
int8_t currTiltDir = this->tiltDirection; int8_t currTiltDir = this->tiltDirection;
uint8_t currTiltPos = this->tiltPosition; this->direction = this->currentPos == this->target ? 0 : this->currentPos > this->target ? -1 : 1;
bool tilt_first = this->tiltType == tilt_types::integrated && ((this->direction == -1 && this->currentTiltPos != 0.0f) || (this->direction == 1 && this->currentTiltPos != 100.0f));
this->tiltDirection = this->currentTiltPos == this->tiltTarget ? 0 : this->currentTiltPos > this->tiltTarget ? -1 : 1;
if(tilt_first) {
this->tiltDirection = this->direction;
this->direction = 0;
}
else if(this->direction != 0) this->tiltDirection = 0;
uint8_t currPos = floor(this->currentPos);
uint8_t currTiltPos = floor(this->currentTiltPos);
if(this->direction > 0) { if(!tilt_first && this->direction > 0) {
if(this->downTime == 0) { if(this->downTime == 0) {
this->direction = 0; this->direction = 0;
this->currentPos = 1; this->currentPos = 100.0;
} }
else { else {
// The shade is moving down so we need to calculate its position through the down position. // The shade is moving down so we need to calculate its position through the down position.
@ -531,7 +544,7 @@ void SomfyShade::checkMovement() {
// The starting posion is a float value from 0-1 that indicates how much the shade is open. So // The starting posion is a float value from 0-1 that indicates how much the shade is open. So
// if we take the starting position * the total down time then this will tell us how many ms it // if we take the starting position * the total down time then this will tell us how many ms it
// has moved in the down position. // has moved in the down position.
int32_t msFrom0 = (int32_t)floor(this->startPos * this->downTime); int32_t msFrom0 = (int32_t)floor((this->startPos/100) * this->downTime);
// So if the start position is .1 it is 10% closed so we have a 1000ms (1sec) of time to account for // So if the start position is .1 it is 10% closed so we have a 1000ms (1sec) of time to account for
// before we add any more time. // before we add any more time.
@ -540,7 +553,7 @@ void SomfyShade::checkMovement() {
// don't have any rounding errors make sure that it is not greater than the max down time. // don't have any rounding errors make sure that it is not greater than the max down time.
msFrom0 = min((int32_t)this->downTime, msFrom0); msFrom0 = min((int32_t)this->downTime, msFrom0);
if(msFrom0 >= this->downTime) { if(msFrom0 >= this->downTime) {
this->currentPos = 1.0; this->currentPos = 100.0f;
this->direction = 0; this->direction = 0;
} }
else { else {
@ -548,30 +561,33 @@ void SomfyShade::checkMovement() {
// a ratio of how much time has travelled over the total time to go 100%. // a ratio of how much time has travelled over the total time to go 100%.
// We should now have the number of ms it will take to reach the shade fully close. // We should now have the number of ms it will take to reach the shade fully close.
this->currentPos = min(max((float)0.0, (float)msFrom0 / (float)this->downTime), (float)1.0); this->currentPos = (min(max((float)0.0, (float)msFrom0 / (float)this->downTime), (float)1.0)) * 100;
// If the current position is >= 1 then we are at the bottom of the shade. // If the current position is >= 1 then we are at the bottom of the shade.
if(this->currentPos >= 1) { if(this->currentPos >= 100) {
this->direction = 0; this->direction = 0;
this->currentPos = 1.0; this->currentPos = 100.0;
} }
} }
this->position = floor(this->currentPos * 100); }
if(this->seekingPos && this->position >= this->target) { if(this->currentPos >= this->target) {
Serial.print("Stopping Shade:"); this->currentPos = this->target;
Serial.print(this->name); // If we need to stop the shade do this before we indicate that we are
Serial.print(" at "); // not moving otherwise the my function will kick in.
Serial.print(this->position); if(this->settingPos) {
Serial.print("% target "); if(!isAtTarget()) {
Serial.print(this->target); // We now need to move the tilt to the position we requested.
Serial.println("%"); this->moveToTiltTarget(this->tiltTarget);
if(!this->seekingFixedPos) this->sendCommand(somfy_commands::My); }
else this->direction = 0; else
this->seekingPos = false; SomfyRemote::sendCommand(somfy_commands::My);
this->seekingFixedPos = false;
} }
this->direction = 0;
this->tiltStart = millis();
this->startTiltPos = this->currentTiltPos;
if(this->isAtTarget()) this->commitShadePosition();
} }
} }
else if(this->direction < 0) { else if(!tilt_first && this->direction < 0) {
if(this->upTime == 0) { if(this->upTime == 0) {
this->direction = 0; this->direction = 0;
this->currentPos = 0; this->currentPos = 0;
@ -581,7 +597,7 @@ void SomfyShade::checkMovement() {
// often move slower in the up position so since we are using a relative position the up time // often move slower in the up position so since we are using a relative position the up time
// can be calculated. // can be calculated.
// 10000ms from 100 to 0; // 10000ms from 100 to 0;
int32_t msFrom100 = (int32_t)this->upTime - (int32_t)floor(this->startPos * this->upTime); int32_t msFrom100 = (int32_t)this->upTime - (int32_t)floor((this->startPos/100) * this->upTime);
msFrom100 += (millis() - this->moveStart); msFrom100 += (millis() - this->moveStart);
msFrom100 = min((int32_t)this->upTime, msFrom100); msFrom100 = min((int32_t)this->upTime, msFrom100);
if(msFrom100 >= this->upTime) { if(msFrom100 >= this->upTime) {
@ -589,115 +605,131 @@ void SomfyShade::checkMovement() {
this->direction = 0; this->direction = 0;
} }
// We should now have the number of ms it will take to reach the shade fully open. // We should now have the number of ms it will take to reach the shade fully open.
this->currentPos = (float)1.0 - min(max((float)0.0, (float)msFrom100 / (float)this->upTime), (float)1.0); this->currentPos = ((float)1.0 - min(max((float)0.0, (float)msFrom100 / (float)this->upTime), (float)1.0)) * 100;
// If we are at the top of the shade then set the movement to 0. // If we are at the top of the shade then set the movement to 0.
if(this->currentPos <= 0.0) { if(this->currentPos <= 0.0) {
this->direction = 0; this->direction = 0;
this->currentPos = 0; this->currentPos = 0;
} }
} }
this->position = floor(this->currentPos * 100); if(this->currentPos <= this->target) {
if(this->seekingPos && this->position <= this->target) { this->currentPos = this->target;
Serial.print("Stopping Shade:"); // If we need to stop the shade do this before we indicate that we are
Serial.print(this->name); // not moving otherwise the my function will kick in.
Serial.print(" at "); if(this->settingPos) {
Serial.print(this->position); if(!isAtTarget()) {
Serial.print("% target "); // We now need to move the tilt to the position we requested.
Serial.print(this->target); this->moveToTiltTarget(this->tiltTarget);
Serial.println("%"); }
if(!this->seekingFixedPos) this->sendCommand(somfy_commands::My); else
else this->direction = 0; SomfyRemote::sendCommand(somfy_commands::My);
this->seekingFixedPos = false; }
this->seekingPos = false; this->direction = 0;
this->tiltStart = millis();
this->startTiltPos = this->currentTiltPos;
if(this->isAtTarget()) this->commitShadePosition();
} }
} }
if(this->tiltDirection > 0) { if(this->tiltDirection > 0) {
int32_t msFrom0 = (int32_t)floor(this->startTiltPos * this->tiltTime); if(tilt_first) this->moveStart = millis();
int32_t msFrom0 = (int32_t)floor((this->startTiltPos/100) * this->tiltTime);
msFrom0 += (millis() - this->tiltStart); msFrom0 += (millis() - this->tiltStart);
msFrom0 = min((int32_t)this->tiltTime, msFrom0); msFrom0 = min((int32_t)this->tiltTime, msFrom0);
if(msFrom0 >= this->tiltTime) { if(msFrom0 >= this->tiltTime) {
this->currentTiltPos = 1.0; this->currentTiltPos = 100.0f;
this->tiltDirection = 0; this->tiltDirection = 0;
} }
else { else {
this->currentTiltPos = min(max((float)0.0, (float)msFrom0 / (float)this->tiltTime), (float)1.0); this->currentTiltPos = (min(max((float)0.0, (float)msFrom0 / (float)this->tiltTime), (float)1.0)) * 100;
if(this->currentTiltPos >= 1) { if(this->currentTiltPos >= 100) {
this->tiltDirection = 0; this->tiltDirection = 0;
this->currentTiltPos = 1.0; this->currentTiltPos = 100.0f;
} }
} }
this->tiltPosition = floor(this->currentTiltPos * 100); if(tilt_first) {
if(this->seekingTiltPos && this->tiltPosition >= this->tiltTarget) { if(this->currentTiltPos >= 100.0f) {
Serial.print("Stopping Shade Tilt:"); this->currentTiltPos = 100.0f;
Serial.print(this->name); this->moveStart = millis();
Serial.print(" at "); this->startPos = this->currentPos;
Serial.print(this->tiltPosition); this->tiltDirection = 0;
Serial.print("% target "); }
Serial.print(this->tiltTarget); }
Serial.println("%"); else if(this->currentTiltPos >= this->tiltTarget) {
this->sendCommand(somfy_commands::My); this->currentTiltPos = this->tiltTarget;
// If we need to stop the shade do this before we indicate that we are
// not moving otherwise the my function will kick in.
if(this->settingTiltPos) SomfyRemote::sendCommand(somfy_commands::My);
this->tiltDirection = 0; this->tiltDirection = 0;
this->seekingTiltPos = false; this->settingTiltPos = false;
if(this->isAtTarget()) this->commitShadePosition();
} }
} }
else if(this->tiltDirection < 0) { else if(this->tiltDirection < 0) {
if(tilt_first) this->moveStart = millis();
if(this->tiltTime == 0) { if(this->tiltTime == 0) {
this->tiltDirection = 0; this->tiltDirection = 0;
this->currentTiltPos = 0; this->currentTiltPos = 0;
} }
else { else {
int32_t msFrom100 = (int32_t)this->tiltTime - (int32_t)floor(this->startTiltPos * this->tiltTime); int32_t msFrom100 = (int32_t)this->tiltTime - (int32_t)floor((this->startTiltPos/100) * this->tiltTime);
msFrom100 += (millis() - this->tiltStart); msFrom100 += (millis() - this->tiltStart);
msFrom100 = min((int32_t)this->tiltTime, msFrom100); msFrom100 = min((int32_t)this->tiltTime, msFrom100);
if(msFrom100 >= this->tiltTime) { if(msFrom100 >= this->tiltTime) {
this->currentTiltPos = 0.0; this->currentTiltPos = 0.0f;
this->tiltDirection = 0; this->tiltDirection = 0;
} }
this->currentTiltPos = (float)1.0 - min(max((float)0.0, (float)msFrom100 / (float)this->tiltTime), (float)1.0); this->currentTiltPos = ((float)1.0 - min(max((float)0.0, (float)msFrom100 / (float)this->tiltTime), (float)1.0)) * 100;
// If we are at the top of the shade then set the movement to 0. // If we are at the top of the shade then set the movement to 0.
if(this->currentTiltPos <= 0.0) { if(this->currentTiltPos <= 0.0f) {
this->tiltDirection = 0; this->tiltDirection = 0;
this->currentTiltPos = 0; this->currentTiltPos = 0.0f;
} }
} }
this->tiltPosition = floor(this->currentTiltPos * 100); if(tilt_first) {
if(this->seekingTiltPos && this->tiltPosition <= this->tiltTarget) { if(this->currentTiltPos <= 0.0f) {
Serial.print("Stopping Shade Tilt:"); this->currentTiltPos = 0.0f;
Serial.print(this->name); this->moveStart = millis();
Serial.print(" at "); this->startPos = this->currentPos;
Serial.print(this->tiltPosition); this->tiltDirection = 0;
Serial.print("% target "); Serial.println("Processed tilt first UP");
Serial.print(this->tiltTarget); }
Serial.println("%"); }
this->sendCommand(somfy_commands::My); else if(this->currentTiltPos <= this->tiltTarget) {
this->currentTiltPos = this->tiltTarget;
// If we need to stop the shade do this before we indicate that we are
// not moving otherwise the my function will kick in.
if(this->settingTiltPos) SomfyRemote::sendCommand(somfy_commands::My);
this->tiltDirection = 0; this->tiltDirection = 0;
this->seekingTiltPos = false; this->settingTiltPos = false;
Serial.println("Stopping at tilt position");
if(this->isAtTarget()) this->commitShadePosition();
} }
} }
if(currDir != this->direction && this->direction == 0) { if(this->settingMyPos && this->isAtTarget()) {
this->commitShadePosition(); delay(200);
if(this->settingMyPos) { // Set this position before sending the command. If you don't the processFrame function
delay(200); // will send the shade back to its original My position.
// Set this position before sending the command. If you don't the processFrame function if(this->tiltType != tilt_types::none) {
// will send the shade back to its original My position. if(this->myTiltPos == this->currentTiltPos && this->myPos == this->currentPos) this->myPos = this->myTiltPos = -1;
if(this->myPos == this->position) this->myPos = 255; else {
else this->myPos = this->position; this->myPos = this->currentPos;
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS); this->myTiltPos = this->currentTiltPos;
this->settingMyPos = false; }
this->seekingFixedPos = false;
this->commitMyPosition();
} }
} else {
if(currTiltDir != this->tiltDirection && this->tiltDirection == 0) { this->myTiltPos = -1;
this->commitTiltPosition(); if(this->myPos == this->currentPos) this->myPos = -1;
} else this->myPos = this->currentPos;
if(currDir != this->direction || currPos != this->position || currTiltDir != this->tiltDirection || currTiltPos != this->tiltPosition) { }
// We need to emit on the socket that our state has changed. SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
this->position = floor(this->currentPos * 100.0); this->settingMyPos = false;
this->tiltPosition = floor(this->currentTiltPos * 100.0); this->commitMyPosition();
this->emitState();
}
else if(currDir != this->direction || currPos != floor(this->currentPos) || currTiltDir != this->tiltDirection || currTiltPos != floor(this->currentTiltPos)) {
// We need to emit on the socket that our state has changed.
this->emitState(); this->emitState();
} }
} }
void SomfyShade::load() { void SomfyShade::load() {
char shadeKey[15]; char shadeKey[15];
@ -732,14 +764,12 @@ void SomfyShade::load() {
} }
this->setRemoteAddress(pref.getUInt("remoteAddress", 0)); this->setRemoteAddress(pref.getUInt("remoteAddress", 0));
this->currentPos = pref.getFloat("currentPos", 0); this->currentPos = pref.getFloat("currentPos", 0);
this->position = (uint8_t)floor(this->currentPos * 100); this->target = floor(this->currentPos);
this->target = this->position; this->myPos = static_cast<float>(pref.getUShort("myPos", this->myPos));
this->myPos = pref.getUShort("myPos", this->myPos); this->tiltType = pref.getBool("hasTilt", false) ? tilt_types::none : tilt_types::tiltmotor;
this->hasTilt = pref.getBool("hasTilt", false);
this->shadeType = static_cast<shade_types>(pref.getChar("shadeType", static_cast<uint8_t>(this->shadeType))); this->shadeType = static_cast<shade_types>(pref.getChar("shadeType", static_cast<uint8_t>(this->shadeType)));
this->currentTiltPos = pref.getFloat("currentTiltPos", 0); this->currentTiltPos = pref.getFloat("currentTiltPos", 0);
this->tiltPosition = (uint8_t)floor(this->currentTiltPos * 100); this->tiltTarget = floor(this->currentTiltPos);
this->tiltTarget = this->tiltPosition;
pref.getBytes("linkedAddr", linkedAddresses, sizeof(linkedAddresses)); pref.getBytes("linkedAddr", linkedAddresses, sizeof(linkedAddresses));
pref.end(); pref.end();
Serial.print("shadeId:"); Serial.print("shadeId:");
@ -749,7 +779,7 @@ void SomfyShade::load() {
Serial.print(" address:"); Serial.print(" address:");
Serial.print(this->getRemoteAddress()); Serial.print(this->getRemoteAddress());
Serial.print(" position:"); Serial.print(" position:");
Serial.print(this->position); Serial.print(this->currentPos);
Serial.print(" myPos:"); Serial.print(" myPos:");
Serial.println(this->myPos); Serial.println(this->myPos);
pref.begin("ShadeCodes"); pref.begin("ShadeCodes");
@ -771,57 +801,61 @@ void SomfyShade::publish() {
snprintf(topic, sizeof(topic), "shades/%u/remoteAddress", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/remoteAddress", this->shadeId);
mqtt.publish(topic, this->getRemoteAddress()); mqtt.publish(topic, this->getRemoteAddress());
snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId);
mqtt.publish(topic, this->position); mqtt.publish(topic, static_cast<uint8_t>(floor(this->currentPos)));
snprintf(topic, sizeof(topic), "shades/%u/direction", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/direction", this->shadeId);
mqtt.publish(topic, this->direction); mqtt.publish(topic, this->direction);
snprintf(topic, sizeof(topic), "shades/%u/target", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/target", this->shadeId);
mqtt.publish(topic, this->target); mqtt.publish(topic, static_cast<uint8_t>(floor(this->target)));
snprintf(topic, sizeof(topic), "shades/%u/lastRollingCode", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/lastRollingCode", this->shadeId);
mqtt.publish(topic, this->lastRollingCode); mqtt.publish(topic, this->lastRollingCode);
snprintf(topic, sizeof(topic), "shades/%u/mypos", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/mypos", this->shadeId);
mqtt.publish(topic, this->hasTilt ? "true" : "false"); mqtt.publish(topic, static_cast<uint8_t>(floor(this->myPos)));
snprintf(topic, sizeof(topic), "shades/%u/shadeType", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/shadeType", this->shadeId);
mqtt.publish(topic, static_cast<uint8_t>(this->shadeType)); mqtt.publish(topic, static_cast<uint8_t>(this->shadeType));
if(this->hasTilt) { snprintf(topic, sizeof(topic), "shades/%u/tiltType", this->shadeId);
mqtt.publish(topic, static_cast<uint8_t>(this->tiltType));
if(this->tiltType != tilt_types::none) {
snprintf(topic, sizeof(topic), "shades/%u/tiltDirection", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/tiltDirection", this->shadeId);
mqtt.publish(topic, this->tiltDirection); mqtt.publish(topic, this->tiltDirection);
snprintf(topic, sizeof(topic), "shades/%u/tiltPosition", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/tiltPosition", this->shadeId);
mqtt.publish(topic, this->tiltPosition); mqtt.publish(topic, static_cast<uint8_t>(floor(this->currentTiltPos)));
snprintf(topic, sizeof(topic), "shades/%u/tiltTarget", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/tiltTarget", this->shadeId);
mqtt.publish(topic, this->tiltTarget); mqtt.publish(topic, static_cast<uint8_t>(floor(this->tiltTarget)));
} }
} }
} }
void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); } void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
void SomfyShade::emitState(uint8_t num, const char *evt) { void SomfyShade::emitState(uint8_t num, const char *evt) {
char buf[320]; char buf[320];
if(this->hasTilt) if(this->tiltType != tilt_types::none)
snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"hasTilt\":%s,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d}", snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"myTiltPos\":%d,\"tiltType\":%u,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d}",
this->shadeId, static_cast<uint8_t>(this->shadeType), this->getRemoteAddress(), this->name, this->direction, this->position, this->target, this->myPos, this->hasTilt ? "true" : "false", this->tiltDirection, this->tiltTarget, this->tiltPosition); this->shadeId, static_cast<uint8_t>(this->shadeType), this->getRemoteAddress(), this->name, this->direction, static_cast<uint8_t>(floor(this->currentPos)), static_cast<uint8_t>(floor(this->target)), static_cast<int8_t>(floor(this->myPos)), static_cast<int8_t>(this->myTiltPos), static_cast<uint8_t>(this->tiltType), this->tiltDirection, static_cast<uint8_t>(floor(this->tiltTarget)), static_cast<uint8_t>(floor(this->currentTiltPos)));
else else
snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"hasTilt\":%s}", snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"tiltType\":%u}",
this->shadeId, static_cast<uint8_t>(this->shadeType), this->getRemoteAddress(), this->name, this->direction, this->position, this->target, this->myPos, this->hasTilt ? "true" : "false"); this->shadeId, static_cast<uint8_t>(this->shadeType), this->getRemoteAddress(), this->name, this->direction, static_cast<uint8_t>(floor(this->currentPos)), static_cast<uint8_t>(floor(this->target)), static_cast<int8_t>(floor(this->myPos)), static_cast<uint8_t>(this->tiltType));
if(num >= 255) sockEmit.sendToClients(evt, buf); if(num >= 255) sockEmit.sendToClients(evt, buf);
else sockEmit.sendToClient(num, evt, buf); else sockEmit.sendToClient(num, evt, buf);
if(mqtt.connected()) { if(mqtt.connected()) {
char topic[32]; char topic[32];
snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId);
mqtt.publish(topic, this->position); mqtt.publish(topic, static_cast<uint8_t>(floor(this->currentPos)));
snprintf(topic, sizeof(topic), "shades/%u/direction", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/direction", this->shadeId);
mqtt.publish(topic, this->direction); mqtt.publish(topic, this->direction);
snprintf(topic, sizeof(topic), "shades/%u/target", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/target", this->shadeId);
mqtt.publish(topic, this->target); mqtt.publish(topic, static_cast<uint8_t>(floor(this->target)));
snprintf(topic, sizeof(topic), "shades/%u/lastRollingCode", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/lastRollingCode", this->shadeId);
mqtt.publish(topic, this->lastRollingCode); mqtt.publish(topic, this->lastRollingCode);
snprintf(topic, sizeof(topic), "shades/%u/mypos", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/mypos", this->shadeId);
mqtt.publish(topic, this->myPos); mqtt.publish(topic, static_cast<int8_t>(floor(this->myPos)));
snprintf(topic, sizeof(topic), "shades/%u/hasTilt", this->hasTilt); snprintf(topic, sizeof(topic), "shades/%u/tiltType", this->shadeId);
mqtt.publish(topic, this->hasTilt ? "true" : "false"); mqtt.publish(topic, static_cast<uint8_t>(this->tiltType));
if(this->hasTilt) { if(this->tiltType != tilt_types::none) {
snprintf(topic, sizeof(topic), "shades/%u/myTiltPos", this->shadeId);
mqtt.publish(topic, static_cast<int8_t>(floor(this->myTiltPos)));
snprintf(topic, sizeof(topic), "shades/%u/tiltPosition", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/tiltPosition", this->shadeId);
mqtt.publish(topic, this->tiltPosition); mqtt.publish(topic, static_cast<uint8_t>(floor(this->currentTiltPos)));
snprintf(topic, sizeof(topic), "shades/%u/tiltTarget", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/tiltTarget", this->shadeId);
mqtt.publish(topic, this->tiltTarget); mqtt.publish(topic, static_cast<uint8_t>(floor(this->tiltTarget)));
} }
} }
} }
@ -837,30 +871,25 @@ void SomfyShade::processWaitingFrame() {
case somfy_commands::StepUp: case somfy_commands::StepUp:
this->lastFrame.processed = true; this->lastFrame.processed = true;
// Simply move the shade up by 1%. // Simply move the shade up by 1%.
if(this->position > 0) { if(this->currentPos > 0) {
this->seekingFixedPos = true; this->target = floor(this->currentPos) - 1;
this->seekingPos = true;
this->target = this->position - 1;
this->setMovement(-1); this->setMovement(-1);
} }
break; break;
case somfy_commands::StepDown: case somfy_commands::StepDown:
this->lastFrame.processed = true; this->lastFrame.processed = true;
// Simply move the shade down by 1%. // Simply move the shade down by 1%.
if(this->position < 100) { if(this->currentPos < 100) {
this->seekingFixedPos = true; this->target = floor(this->currentPos) + 1;
this->seekingPos = true;
this->target = this->position + 1;
this->setMovement(1); this->setMovement(1);
} }
break; break;
case somfy_commands::Down: case somfy_commands::Down:
case somfy_commands::Up: case somfy_commands::Up:
if(this->hasTilt) { // Theoretically this should get here unless it does have a tilt. if(this->tiltType == tilt_types::tiltmotor) { // Theoretically this should get here unless it does have a tilt motor.
if(this->lastFrame.repeats >= TILT_REPEATS) { if(this->lastFrame.repeats >= TILT_REPEATS) {
int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1; int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1;
this->seekingTiltPos = false; this->tiltTarget = dir > 0 ? 100.0f : 0.0f;
this->tiltTarget = dir > 0 ? 100 : 0;
this->setTiltMovement(dir); this->setTiltMovement(dir);
this->lastFrame.processed = true; this->lastFrame.processed = true;
Serial.print(this->name); Serial.print(this->name);
@ -872,7 +901,6 @@ void SomfyShade::processWaitingFrame() {
} }
else { else {
int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1; int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1;
this->seekingPos = false;
this->target = dir > 0 ? 100 : 0; this->target = dir > 0 ? 100 : 0;
this->setMovement(dir); this->setMovement(dir);
this->lastFrame.processed = true; this->lastFrame.processed = true;
@ -882,24 +910,29 @@ void SomfyShade::processWaitingFrame() {
break; break;
case somfy_commands::My: case somfy_commands::My:
if(this->lastFrame.repeats >= SETMY_REPEATS && this->isIdle()) { if(this->lastFrame.repeats >= SETMY_REPEATS && this->isIdle()) {
if(this->myPos == this->position) // We are clearing it. if(floor(this->myPos) == floor(this->currentPos)) {
this->myPos = 255; // We are clearing it.
else this->myPos = -1;
this->myPos = this->position; this->myTiltPos = -1;
}
else {
this->myPos = this->currentPos;
this->myTiltPos = this->currentTiltPos;
}
this->commitMyPosition(); this->commitMyPosition();
this->lastFrame.processed = true; this->lastFrame.processed = true;
this->emitState(); this->emitState();
} }
else if(this->myPos >= 0 && this->myPos <= 100) { else if(this->isIdle()) {
int8_t dir = 0; if(this->myPos >= 0.0f && this->myPos <= 100.0f) this->target = this->myPos;
if(myPos < this->position) dir = -1; if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->tiltTarget = this->myTiltPos;
else if(myPos > this->position) dir = 1; this->setMovement(0);
if(dir != 0) this->seekingFixedPos = true;
this->seekingPos = true;
this->target = this->myPos;
this->setMovement(dir);
this->lastFrame.processed = true; this->lastFrame.processed = true;
} }
else {
this->target = this->currentPos;
this->tiltTarget = this->currentTiltPos;
}
if(this->lastFrame.repeats > SETMY_REPEATS + 2) this->lastFrame.processed = true; if(this->lastFrame.repeats > SETMY_REPEATS + 2) this->lastFrame.processed = true;
if(this->lastFrame.processed) { if(this->lastFrame.processed) {
Serial.print(this->name); Serial.print(this->name);
@ -912,6 +945,9 @@ void SomfyShade::processWaitingFrame() {
} }
} }
void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
// The reason why we are processing all frames here is so
// any linked remotes that may happen to be on the same ESPSomfy RTS
// device can trigger the appropriate actions.
if(this->shadeId == 255) return; if(this->shadeId == 255) return;
bool hasRemote = this->getRemoteAddress() == frame.remoteAddress; bool hasRemote = this->getRemoteAddress() == frame.remoteAddress;
if(!hasRemote) { if(!hasRemote) {
@ -926,82 +962,64 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
if(!hasRemote) return; if(!hasRemote) return;
this->lastFrame.copy(frame); this->lastFrame.copy(frame);
int8_t dir = 0; int8_t dir = 0;
// If the frame came from the radio it cannot be seeking a position. This means that the target will be set. int8_t tiltDir = 0;
if(!internal) this->seekingTiltPos = this->seekingPos = false; this->moveStart = this->tiltStart = millis();
this->startPos = this->currentPos;
this->startTiltPos = this->currentTiltPos;
// If the command is coming from a remote then we are aborting all these positioning operations.
if(!internal) this->settingMyPos = this->settingPos = this->settingTiltPos = false;
// At this point we are not processing the combo buttons // At this point we are not processing the combo buttons
// will need to see what the shade does when you press both. // will need to see what the shade does when you press both.
switch(frame.cmd) { switch(frame.cmd) {
case somfy_commands::Up: case somfy_commands::Up:
if(this->hasTilt) { if(this->tiltType == tilt_types::tiltmotor) {
// Wait another half seccond just in case we are potentially processing a tilt. // Wait another half second just in case we are potentially processing a tilt.
if(!internal) this->lastFrame.await = millis() + 500; if(!internal) this->lastFrame.await = millis() + 500;
else if(this->lastFrame.repeats >= TILT_REPEATS) { else this->lastFrame.processed = true;
// This is an internal tilt command.
this->setTiltMovement(-1);
this->lastFrame.processed = true;
return;
}
else {
dir = -1;
if(!internal) this->target = 0;
this->lastFrame.processed = true;
}
} }
else { else {
dir = -1; // If from a remote we will simply be going up.
if(!internal) this->target = 0; if(!internal) this->target = this->tiltTarget = 0.0f;
this->lastFrame.processed = true; this->lastFrame.processed = true;
} }
break; break;
case somfy_commands::Down: case somfy_commands::Down:
if(this->hasTilt) { if(this->tiltType == tilt_types::tiltmotor) {
// Wait another half seccond just in case we are potentially processing a tilt. // Wait another half seccond just in case we are potentially processing a tilt.
if(!internal) this->lastFrame.await = millis() + 500; if(!internal) this->lastFrame.await = millis() + 500;
else if(this->lastFrame.repeats >= TILT_REPEATS) { else this->lastFrame.processed = true;
// This is an internal tilt command.
this->setTiltMovement(1);
this->lastFrame.processed = true;
return;
}
else {
dir = 1;
if(!internal) this->target = 100;
this->lastFrame.processed = true;
}
} }
else { else {
dir = 1;
if(!internal) this->target = 100;
this->lastFrame.processed = true; this->lastFrame.processed = true;
if(!internal) {
this->target = 100.0f;
if(this->tiltType != tilt_types::none) this->tiltTarget = 100.0f;
}
} }
break; break;
case somfy_commands::My: case somfy_commands::My:
dir = 0;
if(this->isIdle()) { if(this->isIdle()) {
if(!internal) { if(!internal) {
// This frame is coming from a remote. We are potentially setting // This frame is coming from a remote. We are potentially setting
// the my position. // the my position.
this->lastFrame.await = millis() + 500; this->lastFrame.await = millis() + 500;
} }
else if(myPos >= 0 && this->myPos <= 100) { else {
this->lastFrame.processed = true; this->lastFrame.processed = true;
// In this instance we will be moving to the my position. This if(!internal) {
// will be up or down or not. if(this->myTiltPos >= 0.0f && this->myTiltPos >= 100.0f) this->tiltTarget = this->myTiltPos;
if(this->myPos < this->position) dir = -1; if(this->myPos >= 0.0f && this->myPos <= 100.0f) this->target = this->myPos;
else if(this->myPos > this->position) dir = 1;
if(dir != 0) {
Serial.println("Start moving to My Position");
this->seekingFixedPos = true;
} }
this->seekingPos = true;
this->target = this->myPos;
} }
} }
else else {
// This will occur if the shade needs to be stopped or there
// is no my position set.
this->lastFrame.processed = true; this->lastFrame.processed = true;
if(!internal) {
this->target = this->currentPos;
this->tiltTarget = this->currentTiltPos;
}
}
break; break;
case somfy_commands::StepUp: case somfy_commands::StepUp:
if(!internal) { if(!internal) {
@ -1010,10 +1028,8 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
else { else {
this->lastFrame.processed = true; this->lastFrame.processed = true;
// Simply move the shade up by 1%. // Simply move the shade up by 1%.
if(this->position > 0) { if(this->currentPos > 0) {
this->seekingFixedPos = true; this->target = this->currentPos - 0.5f;
this->seekingPos = true;
this->target = this->position - 1;
dir = -1; dir = -1;
} }
} }
@ -1025,10 +1041,8 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
else { else {
this->lastFrame.processed = true; this->lastFrame.processed = true;
// Simply move the shade down by 1%. // Simply move the shade down by 1%.
if(this->position < 100) { if(this->currentPos < 100) {
this->seekingFixedPos = true; this->target = this->currentPos + 0.5f;
this->seekingPos = true;
this->target = this->position + 1;
dir = 1; dir = 1;
} }
} }
@ -1037,7 +1051,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
dir = 0; dir = 0;
break; break;
} }
if(dir == 0 && this->hasTilt && this->tiltDirection != 0) this->setTiltMovement(0); //if(dir == 0 && this->tiltType == tilt_types::tiltmotor && this->tiltDirection != 0) this->setTiltMovement(0);
this->setMovement(dir); this->setMovement(dir);
} }
void SomfyShade::setTiltMovement(int8_t dir) { void SomfyShade::setTiltMovement(int8_t dir) {
@ -1051,154 +1065,187 @@ void SomfyShade::setTiltMovement(int8_t dir) {
this->commitTiltPosition(); this->commitTiltPosition();
} }
} }
else if(this->direction != dir) { else if(this->tiltDirection != dir) {
this->tiltStart = millis(); this->tiltStart = millis();
this->startTiltPos = this->currentTiltPos; this->startTiltPos = this->currentTiltPos;
this->tiltDirection = dir; this->tiltDirection = dir;
} }
if(this->tiltDirection != currDir) { if(this->tiltDirection != currDir) {
this->tiltPosition = floor(this->currentTiltPos * 100.0);
this->emitState(); this->emitState();
} }
} }
void SomfyShade::setMovement(int8_t dir) { void SomfyShade::setMovement(int8_t dir) {
int8_t currDir = this->direction; int8_t currDir = this->direction;
int8_t currTiltDir = this->tiltDirection;
if(dir == 0) { if(dir == 0) {
// The shade is stopped. if(currDir != dir || currTiltDir != dir) this->commitShadePosition();
this->startPos = this->currentPos;
this->moveStart = 0;
this->direction = dir;
if(currDir != dir) {
this->commitShadePosition();
}
} }
else if(this->direction != dir) { else {
this->moveStart = millis(); this->tiltStart = this->moveStart = millis();
this->startPos = this->currentPos; this->startPos = this->currentPos;
this->direction = dir; this->startTiltPos = this->currentTiltPos;
} }
if(this->direction != currDir) { if(this->direction != currDir || currTiltDir != this->tiltDirection) {
this->position = floor(this->currentPos * 100.0);
this->emitState(); this->emitState();
} }
} }
void SomfyShade::setMyPosition(uint8_t target) { void SomfyShade::setMyPosition(int8_t pos, int8_t tilt) {
if(this->direction != 0) return; // Don't do this if it is moving. if(this->direction != 0) return; // Don't do this if it is moving.
Serial.println("setMyPosition called"); if(this->tiltType != tilt_types::none) {
if(target != this->position) { if(tilt < 0) tilt = 0;
this->settingMyPos = true; if(pos != floor(this->currentPos) || tilt != floor(currentTiltPos)) {
Serial.println("Moving to set My Position"); this->settingMyPos = true;
if(target == this->myPos) if(pos == floor(this->myPos) && tilt == floor(this->myTiltPos))
// Setting the My Position to the same position will toggle it off. So lets send a my this->moveToMyPosition();
// command instead of an up/down. This will ensure we get the thing cleared. else
this->moveToMyPosition(); this->moveToTarget(pos, tilt);
else }
this->moveToTarget(target); else if(pos == floor(this->myPos) && tilt == floor(this->myTiltPos)) {
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
this->myPos = this->myTiltPos = -1;
this->commitMyPosition();
this->emitState();
}
else {
this->myPos = this->currentPos;
this->myTiltPos = this->currentTiltPos;
}
this->commitMyPosition();
this->emitState();
} }
else { else {
this->sendCommand(somfy_commands::My, SETMY_REPEATS); if(pos != floor(this->currentPos)) {
if(target == this->myPos) this->settingMyPos = true;
this->myPos = 255; if(pos == floor(this->myPos))
else this->moveToMyPosition();
this->myPos = target; else
this->commitMyPosition(); this->moveToTarget(pos);
this->emitState(); }
else if(pos == floor(this->myPos)) {
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
this->myPos = this->myTiltPos = -1;
this->commitMyPosition();
this->emitState();
}
else {
this->myPos = currentPos;
this->myTiltPos = -1;
this->commitMyPosition();
this->emitState();
}
} }
} }
void SomfyShade::moveToMyPosition() { void SomfyShade::moveToMyPosition() {
if(this->direction != 0 || this->myPos > 100 || this->myPos < 0) return; if(!this->isIdle()) return;
if(this->position == this->myPos) return; // Nothing to see here since we are already here. if(this->currentPos == this->myPos) return; // Nothing to see here since we are already here.
Serial.print("Seeking my Position:"); Serial.print("Seeking my Position:");
Serial.print(this->myPos); Serial.print(this->myPos);
Serial.println("%"); Serial.print("% ");
this->seekingFixedPos = true; this->target = floor(this->myPos);
this->target = this->myPos;
Serial.print("Moving to "); Serial.print("Moving to ");
Serial.print(this->target); Serial.print(this->target);
Serial.print("% from "); Serial.print("% from ");
Serial.print(this->position); Serial.print(this->currentPos);
Serial.print("% using "); Serial.print("% using ");
Serial.println(translateSomfyCommand(somfy_commands::My)); Serial.print(translateSomfyCommand(somfy_commands::My));
this->seekingPos = true; Serial.println(this->direction);
if(this->myPos >= 0.0f && this->myPos <= 100.0f) this->target = this->myPos;
if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->tiltTarget = this->myTiltPos;
SomfyRemote::sendCommand(somfy_commands::My); SomfyRemote::sendCommand(somfy_commands::My);
} }
void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) { void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) {
// This sendCommand function will always be called externally. sendCommand at the remote level
// is expected to be called internally when the motor needs commanded.
if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type;
if(cmd == somfy_commands::Up) { if(cmd == somfy_commands::Up) {
this->target = 0; SomfyRemote::sendCommand(cmd, repeat);
this->seekingPos = false; this->target = 0.0f;
if(this->tiltType == tilt_types::integrated) this->tiltTarget = 0.0f;
} }
else if(cmd == somfy_commands::Down) { else if(cmd == somfy_commands::Down) {
this->target = 100; SomfyRemote::sendCommand(cmd, repeat);
this->seekingPos = false; this->target = 100.0f;
if(this->tiltType == tilt_types::integrated) this->tiltTarget = 100.0f;
} }
else if(cmd == somfy_commands::My) { else if(cmd == somfy_commands::My) {
if(this->isIdle() && this->myPos >= 0 && this->myPos <= 100) { if(this->isIdle()) {
this->moveToMyPosition(); this->moveToMyPosition();
return; return;
} }
else { else {
this->target = this->position; SomfyRemote::sendCommand(cmd, repeat);
this->seekingPos = false; this->target = this->currentPos;
this->tiltTarget = this->currentTiltPos;
} }
} }
SomfyRemote::sendCommand(cmd, repeat);
} }
void SomfyShade::sendTiltCommand(somfy_commands cmd) { void SomfyShade::sendTiltCommand(somfy_commands cmd) {
if(cmd == somfy_commands::Up) { if(cmd == somfy_commands::Up) {
this->tiltTarget = 0; SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : 1);
this->seekingTiltPos = false; this->tiltTarget = 0.0f;
SomfyRemote::sendCommand(cmd, TILT_REPEATS);
} }
else if(cmd == somfy_commands::Down) { else if(cmd == somfy_commands::Down) {
this->tiltTarget = 100; SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : 1);
this->seekingTiltPos = false; this->tiltTarget = 100.0f;
SomfyRemote::sendCommand(cmd, TILT_REPEATS);
} }
else if(cmd == somfy_commands::My) { else if(cmd == somfy_commands::My) {
this->tiltTarget = this->tiltPosition; SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : 1);
this->seekingTiltPos = false; this->tiltTarget = this->currentTiltPos;
SomfyRemote::sendCommand(cmd);
} }
} }
void SomfyShade::moveToTiltTarget(uint8_t target) { void SomfyShade::moveToTiltTarget(float target) {
int8_t newDir = 0; int8_t newDir = 0;
somfy_commands cmd = somfy_commands::My; somfy_commands cmd = somfy_commands::My;
if(target < this->tiltPosition) if(target < this->currentTiltPos)
cmd = somfy_commands::Up; cmd = somfy_commands::Up;
else if(target > this->tiltPosition) else if(target > this->currentTiltPos)
cmd = somfy_commands::Down; cmd = somfy_commands::Down;
Serial.print("Moving Tilt to "); if(target > 0.0f && target < 100.0f) {
Serial.print(target); if(cmd != somfy_commands::My) {
Serial.print("% from "); Serial.print("Moving Tilt to ");
Serial.print(this->tiltPosition); Serial.print(target);
Serial.print("% using "); Serial.print("% from ");
Serial.println(translateSomfyCommand(cmd)); Serial.print(this->currentTiltPos);
this->tiltTarget = target; Serial.print("% using ");
if(target > 0 && target < 100) this->seekingTiltPos = true; Serial.println(translateSomfyCommand(cmd));
else this->seekingTiltPos = false; SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : 1);
if(cmd != somfy_commands::My) }
SomfyRemote::sendCommand(cmd, TILT_REPEATS); else
else SomfyRemote::sendCommand(cmd);
SomfyRemote::sendCommand(cmd); this->tiltTarget = target;
}
this->settingTiltPos = true;
} }
void SomfyShade::moveToTarget(uint8_t target) { void SomfyShade::moveToTarget(float pos, float tilt) {
int8_t newDir = 0; int8_t newDir = 0;
somfy_commands cmd = somfy_commands::My; somfy_commands cmd = somfy_commands::My;
if(target < this->position) if(pos < this->currentPos)
cmd = somfy_commands::Up; cmd = somfy_commands::Up;
else if(target > this->position) else if(pos > this->currentPos)
cmd = somfy_commands::Down;
else if(tilt >= 0 && tilt < this->currentTiltPos)
cmd = somfy_commands::Up;
else if(tilt >= 0 && tilt > this->currentTiltPos)
cmd = somfy_commands::Down; cmd = somfy_commands::Down;
Serial.print("Moving to "); Serial.print("Moving to ");
Serial.print(target); Serial.print(pos);
Serial.print("% from "); Serial.print("% from ");
Serial.print(this->position); Serial.print(this->currentPos);
if(tilt >= 0) {
Serial.print(" tilt ");
Serial.print(tilt);
Serial.print("% from ");
Serial.print(this->currentTiltPos);
}
Serial.print("% using "); Serial.print("% using ");
Serial.println(translateSomfyCommand(cmd)); Serial.println(translateSomfyCommand(cmd));
this->target = target;
if(target > 0 && target != 100) this->seekingPos = true;
else this->seekingPos = false;
SomfyRemote::sendCommand(cmd); SomfyRemote::sendCommand(cmd);
this->target = pos;
this->settingPos = true;
if(tilt >= 0) {
this->tiltTarget = tilt;
this->settingTiltPos = true;
}
} }
bool SomfyShade::save() { bool SomfyShade::save() {
if(somfy.useNVS()) { if(somfy.useNVS()) {
@ -1209,7 +1256,7 @@ bool SomfyShade::save() {
pref.putChar("shadeType", static_cast<uint8_t>(this->shadeType)); pref.putChar("shadeType", static_cast<uint8_t>(this->shadeType));
pref.putUInt("remoteAddress", this->getRemoteAddress()); pref.putUInt("remoteAddress", this->getRemoteAddress());
pref.putString("name", this->name); pref.putString("name", this->name);
pref.putBool("hasTilt", this->hasTilt); pref.putBool("hasTilt", this->tiltType != tilt_types::none);
pref.putBool("paired", this->paired); pref.putBool("paired", this->paired);
pref.putUInt("upTime", this->upTime); pref.putUInt("upTime", this->upTime);
pref.putUInt("downTime", this->downTime); pref.putUInt("downTime", this->downTime);
@ -1237,7 +1284,7 @@ bool SomfyShade::fromJSON(JsonObject &obj) {
if(obj.containsKey("downTime")) this->downTime = obj["downTime"]; if(obj.containsKey("downTime")) this->downTime = obj["downTime"];
if(obj.containsKey("remoteAddress")) this->setRemoteAddress(obj["remoteAddress"]); if(obj.containsKey("remoteAddress")) this->setRemoteAddress(obj["remoteAddress"]);
if(obj.containsKey("tiltTime")) this->tiltTime = obj["tiltTime"]; if(obj.containsKey("tiltTime")) this->tiltTime = obj["tiltTime"];
if(obj.containsKey("hasTilt")) this->hasTilt = obj["hasTilt"]; if(obj.containsKey("hasTilt")) this->tiltType = static_cast<bool>(obj["hasTilt"]) ? tilt_types::none : tilt_types::tiltmotor;
if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"]; if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"];
if(obj.containsKey("shadeType")) { if(obj.containsKey("shadeType")) {
if(obj["shadeType"].is<const char *>()) { if(obj["shadeType"].is<const char *>()) {
@ -1252,6 +1299,21 @@ bool SomfyShade::fromJSON(JsonObject &obj) {
this->shadeType = static_cast<shade_types>(obj["shadeType"].as<uint8_t>()); this->shadeType = static_cast<shade_types>(obj["shadeType"].as<uint8_t>());
} }
} }
if(obj.containsKey("tiltType")) {
if(obj["tiltType"].is<const char *>()) {
if(strncmp(obj["tiltType"].as<const char *>(), "none", 4) == 0)
this->tiltType = tilt_types::none;
else if(strncmp(obj["tiltType"].as<const char *>(), "tiltmotor", 9) == 0)
this->tiltType = tilt_types::tiltmotor;
else if(strncmp(obj["tiltType"].as<const char *>(), "integ", 5) == 0)
this->tiltType = tilt_types::integrated;
else if(strncmp(obj["tiltType"].as<const char *>(), "tiltonly", 8) == 0)
this->tiltType = tilt_types::tiltonly;
}
else {
this->tiltType = static_cast<tilt_types>(obj["tiltType"].as<uint8_t>());
}
}
if(obj.containsKey("linkedAddresses")) { if(obj.containsKey("linkedAddresses")) {
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
memset(linkedAddresses, 0x00, sizeof(linkedAddresses)); memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
@ -1277,17 +1339,18 @@ bool SomfyShade::toJSON(JsonObject &obj) {
obj["upTime"] = this->upTime; obj["upTime"] = this->upTime;
obj["downTime"] = this->downTime; obj["downTime"] = this->downTime;
obj["paired"] = this->paired; obj["paired"] = this->paired;
obj["remotePrefId"] = this->getRemotePrefId(); //obj["remotePrefId"] = this->getRemotePrefId();
obj["lastRollingCode"] = this->lastRollingCode; obj["lastRollingCode"] = this->lastRollingCode;
obj["position"] = this->position; obj["position"] = static_cast<uint8_t>(floor(this->currentPos));
obj["tiltPosition"] = this->tiltPosition; obj["tiltPosition"] = static_cast<uint8_t>(floor(this->currentTiltPos));
obj["tiltDirection"] = this->tiltDirection; obj["tiltDirection"] = this->tiltDirection;
obj["tiltTime"] = this->tiltTime; obj["tiltTime"] = this->tiltTime;
obj["tiltTarget"] = this->tiltTarget; obj["tiltTarget"] = static_cast<uint8_t>(floor(this->tiltTarget));
obj["target"] = this->target; obj["target"] = this->target;
obj["myPos"] = this->myPos; obj["myPos"] = static_cast<int8_t>(floor(this->myPos));
obj["myTiltPos"] = static_cast<int8_t>(floor(this->myTiltPos));
obj["direction"] = this->direction; obj["direction"] = this->direction;
obj["hasTilt"] = this->hasTilt; obj["tiltType"] = static_cast<uint8_t>(this->tiltType);
obj["tiltTime"] = this->tiltTime; obj["tiltTime"] = this->tiltTime;
obj["shadeType"] = static_cast<uint8_t>(this->shadeType); obj["shadeType"] = static_cast<uint8_t>(this->shadeType);
obj["bitLength"] = this->bitLength; obj["bitLength"] = this->bitLength;
@ -1303,7 +1366,7 @@ bool SomfyShade::toJSON(JsonObject &obj) {
return true; return true;
} }
bool SomfyRemote::toJSON(JsonObject &obj) { bool SomfyRemote::toJSON(JsonObject &obj) {
obj["remotePrefId"] = this->getRemotePrefId(); //obj["remotePrefId"] = this->getRemotePrefId();
obj["remoteAddress"] = this->getRemoteAddress(); obj["remoteAddress"] = this->getRemoteAddress();
obj["lastRollingCode"] = this->lastRollingCode; obj["lastRollingCode"] = this->lastRollingCode;
return true; return true;
@ -1598,8 +1661,8 @@ static const uint32_t tempo_wakeup_silence_min = 89565 * TOLERANCE_MIN;
static const uint32_t tempo_wakeup_silence_max = 89565 * TOLERANCE_MAX; static const uint32_t tempo_wakeup_silence_max = 89565 * TOLERANCE_MAX;
static const uint32_t tempo_synchro_hw_min = SYMBOL * 4 * TOLERANCE_MIN; 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 tempo_synchro_hw_max = SYMBOL * 4 * TOLERANCE_MAX;
static const uint32_t tempo_synchro_sw_min = 4550 * TOLERANCE_MIN; static const uint32_t tempo_synchro_sw_min = 4850 * TOLERANCE_MIN;
static const uint32_t tempo_synchro_sw_max = 4550 * TOLERANCE_MAX; static const uint32_t tempo_synchro_sw_max = 4850 * TOLERANCE_MAX;
static const uint32_t tempo_half_symbol_min = SYMBOL * TOLERANCE_MIN; 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_half_symbol_max = SYMBOL * TOLERANCE_MAX;
static const uint32_t tempo_symbol_min = SYMBOL * 2 * TOLERANCE_MIN; static const uint32_t tempo_symbol_min = SYMBOL * 2 * TOLERANCE_MIN;

44
Somfy.h
View file

@ -2,7 +2,7 @@
#define SOMFY_H #define SOMFY_H
#define SOMFY_MAX_SHADES 32 #define SOMFY_MAX_SHADES 32
#define SOMFY_MAX_LINKED_REMOTES 5 #define SOMFY_MAX_LINKED_REMOTES 7
typedef struct appver_t { typedef struct appver_t {
uint8_t major; uint8_t major;
@ -35,7 +35,12 @@ enum class shade_types : byte {
blind = 0x01, blind = 0x01,
drapery = 0x02 drapery = 0x02
}; };
enum class tilt_types : byte {
none = 0x00,
tiltmotor = 0x01,
integrated = 0x02,
tiltonly = 0x03,
};
String translateSomfyCommand(const somfy_commands cmd); String translateSomfyCommand(const somfy_commands cmd);
somfy_commands translateSomfyCommand(const String& string); somfy_commands translateSomfyCommand(const String& string);
@ -97,7 +102,7 @@ class SomfyRemote {
// confirmed. The address is actually 24bits // confirmed. The address is actually 24bits
// and the rolling code is 16 bits. // and the rolling code is 16 bits.
protected: protected:
char m_remotePrefId[10] = ""; char m_remotePrefId[11] = "";
uint32_t m_remoteAddress = 0; uint32_t m_remoteAddress = 0;
public: public:
uint8_t bitLength = 0; uint8_t bitLength = 0;
@ -119,28 +124,26 @@ class SomfyShade : public SomfyRemote {
uint8_t shadeId = 255; uint8_t shadeId = 255;
uint64_t moveStart = 0; uint64_t moveStart = 0;
uint64_t tiltStart = 0; uint64_t tiltStart = 0;
float startPos = 0.0; float startPos = 0.0f;
float startTiltPos = 0.00; float startTiltPos = 0.0f;
bool seekingPos = false;
bool seekingTiltPos = false;
bool seekingFixedPos = false;
bool settingMyPos = false; bool settingMyPos = false;
bool settingPos = false;
bool settingTiltPos = false;
uint32_t awaitMy = 0; uint32_t awaitMy = 0;
public: public:
shade_types shadeType = shade_types::roller; shade_types shadeType = shade_types::roller;
bool hasTilt = false; tilt_types tiltType = tilt_types::none;
void load(); void load();
somfy_frame_t lastFrame; somfy_frame_t lastFrame;
float currentPos = 0.0; float currentPos = 0.0f;
float currentTiltPos = 0.0; float currentTiltPos = 0.0f;
//uint16_t movement = 0; //uint16_t movement = 0;
int8_t direction = 0; // 0 = stopped, 1=down, -1=up. int8_t direction = 0; // 0 = stopped, 1=down, -1=up.
int8_t tiltDirection = 0; // 0=stopped, 1=clockwise, -1=counter clockwise int8_t tiltDirection = 0; // 0=stopped, 1=clockwise, -1=counter clockwise
uint8_t tiltPosition = 0; float target = 0.0;
uint8_t position = 0; float tiltTarget = 0.0;
uint8_t target = 0; float myPos = -1.0;
uint8_t tiltTarget = 0; float myTiltPos = -1.0;
uint8_t myPos = 255;
SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES]; SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES];
bool paired = false; bool paired = false;
bool fromJSON(JsonObject &obj); bool fromJSON(JsonObject &obj);
@ -157,16 +160,17 @@ class SomfyShade : public SomfyRemote {
void processFrame(somfy_frame_t &frame, bool internal = false); void processFrame(somfy_frame_t &frame, bool internal = false);
void setTiltMovement(int8_t dir); void setTiltMovement(int8_t dir);
void setMovement(int8_t dir); void setMovement(int8_t dir);
void setTarget(uint8_t target); void setTarget(float target);
void moveToTarget(uint8_t target); bool isAtTarget();
void moveToTiltTarget(uint8_t target); void moveToTarget(float pos, float tilt = -1.0f);
void moveToTiltTarget(float target);
void sendTiltCommand(somfy_commands cmd); void sendTiltCommand(somfy_commands cmd);
void sendCommand(somfy_commands cmd, uint8_t repeat = 1); void sendCommand(somfy_commands cmd, uint8_t repeat = 1);
bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0); bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0);
bool unlinkRemote(uint32_t remoteAddress); bool unlinkRemote(uint32_t remoteAddress);
void emitState(const char *evt = "shadeState"); void emitState(const char *evt = "shadeState");
void emitState(uint8_t num, const char *evt = "shadeState"); void emitState(uint8_t num, const char *evt = "shadeState");
void setMyPosition(uint8_t target); void setMyPosition(int8_t pos, int8_t tilt = -1);
void moveToMyPosition(); void moveToMyPosition();
void processWaitingFrame(); void processWaitingFrame();
void publish(); void publish();

Binary file not shown.

Binary file not shown.

View file

@ -1,15 +1,6 @@
#ifndef utils_h #ifndef utils_h
#define utils_h #define utils_h
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
#ifndef D3
#define D3 3
#endif
#ifndef D0
#define D0 0
#endif
#define DEBUG_SOMFY Serial #define DEBUG_SOMFY Serial

22
Web.cpp
View file

@ -541,7 +541,7 @@ void Web::begin() {
int shadeId = atoi(server.arg("shadeId").c_str()); int shadeId = atoi(server.arg("shadeId").c_str());
SomfyShade* shade = somfy.getShadeById(shadeId); SomfyShade* shade = somfy.getShadeById(shadeId);
if (shade) { if (shade) {
DynamicJsonDocument doc(512); DynamicJsonDocument doc(2048);
JsonObject obj = doc.to<JsonObject>(); JsonObject obj = doc.to<JsonObject>();
shade->toJSON(obj); shade->toJSON(obj);
serializeJson(doc, g_content); serializeJson(doc, g_content);
@ -579,7 +579,7 @@ void Web::begin() {
if (shade) { if (shade) {
shade->fromJSON(obj); shade->fromJSON(obj);
shade->save(); shade->save();
DynamicJsonDocument sdoc(512); DynamicJsonDocument sdoc(2048);
JsonObject sobj = sdoc.to<JsonObject>(); JsonObject sobj = sdoc.to<JsonObject>();
shade->toJSON(sobj); shade->toJSON(sobj);
serializeJson(sdoc, g_content); serializeJson(sdoc, g_content);
@ -771,12 +771,13 @@ void Web::begin() {
webServer.sendCORSHeaders(); webServer.sendCORSHeaders();
HTTPMethod method = server.method(); HTTPMethod method = server.method();
uint8_t shadeId = 255; uint8_t shadeId = 255;
uint8_t target = 255; int8_t pos = -1;
int8_t tilt = -1;
if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) { if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
if (server.hasArg("shadeId")) { if (server.hasArg("shadeId")) {
shadeId = atoi(server.arg("shadeId").c_str()); shadeId = atoi(server.arg("shadeId").c_str());
if(server.hasArg("target")) target = atoi(server.arg("target").c_str()); if(server.hasArg("pos")) pos = atoi(server.arg("pos").c_str());
if(server.hasArg("tilt")) tilt = atoi(server.arg("tilt").c_str());
} }
else if (server.hasArg("plain")) { else if (server.hasArg("plain")) {
DynamicJsonDocument doc(256); DynamicJsonDocument doc(256);
@ -799,9 +800,8 @@ void Web::begin() {
JsonObject obj = doc.as<JsonObject>(); JsonObject obj = doc.as<JsonObject>();
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}")); else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}"));
if(obj.containsKey("target")) { if(obj.containsKey("pos")) pos = obj["pos"].as<int8_t>();
target = obj["target"].as<uint8_t>(); if(obj.containsKey("tilt")) tilt = obj["tilt"].as<int8_t>();
}
} }
} }
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}")); else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
@ -809,9 +809,9 @@ void Web::begin() {
SomfyShade* shade = somfy.getShadeById(shadeId); SomfyShade* shade = somfy.getShadeById(shadeId);
if (shade) { if (shade) {
// Send the command to the shade. // Send the command to the shade.
if(target == 255) target = shade->myPos; if(tilt < 0) tilt = shade->myPos;
if(target >= 0 && target <= 100) if(pos >= 0 && pos <= 100)
shade->setMyPosition(target); shade->setMyPosition(pos, tilt);
DynamicJsonDocument sdoc(512); DynamicJsonDocument sdoc(512);
JsonObject sobj = sdoc.to<JsonObject>(); JsonObject sobj = sdoc.to<JsonObject>();
shade->toJSON(sobj); shade->toJSON(sobj);

View file

@ -1 +1 @@
1.4.7 1.5.0

View file

@ -3,10 +3,10 @@
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="main.css?v=1.4.7" type="text/css" /> <link rel="stylesheet" href="main.css?v=1.5.0" type="text/css" />
<link rel="stylesheet" href="icons.css?v=1.4.7" type="text/css" /> <link rel="stylesheet" href="icons.css?v=1.5.0" type="text/css" />
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
<script type="text/javascript" src="index.js?v=1.4.7"></script> <script type="text/javascript" src="index.js?v=1.5.0"></script>
</head> </head>
<body> <body>
<div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;"> <div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;">
@ -263,8 +263,12 @@
</div> </div>
<div id="divTiltSettings" style="display:none;"> <div id="divTiltSettings" style="display:none;">
<div class="field-group" style="display:inline-block; margin-right:17px;width:127px;vertical-align:middle;"> <div class="field-group" style="display:inline-block; margin-right:17px;width:127px;vertical-align:middle;">
<input id="cbHasTilt" type="checkbox" onchange="somfy.onShadeTypeChanged(this);" /> <select id="selTiltType" name="tiltType" style="width:100%;" onchange="somfy.onShadeTypeChanged(this);">
<label for="cbHasTilt" style="display:inline-block;cursor:pointer;">Has Tilt</label> <option value="0">None</option>
<option value="1">Tilt Motor</option>
<option value="2">Integrated</option>
</select>
<label for="selTiltType" style="cursor:pointer;">Tilt Type</label>
</div> </div>
<div class="field-group" style="display:inline-block;width:127px;vertical-align:middle;"> <div class="field-group" style="display:inline-block;width:127px;vertical-align:middle;">
<input id="fldTiltTime" name="shadeTiltTime" type="number" length=5 placeholder="milliseconds" style="width:100%;text-align:right;" /> <input id="fldTiltTime" name="shadeTiltTime" type="number" length=5 placeholder="milliseconds" style="width:100%;text-align:right;" />
@ -288,7 +292,7 @@
</button> </button>
</div> </div>
<hr /> <hr />
<div id="divLinkedRemoteList" style="overflow-y:auto;max-height:77px;padding-top:2px;padding-bottom:2px;"></div> <div id="divLinkedRemoteList" style="overflow-y:auto;max-height:177px;padding-top:2px;padding-bottom:2px;"></div>
<div class="button-container"> <div class="button-container">
<button id="btnLinkRemote" type="button" onclick="somfy.linkRemote(parseInt(document.getElementById('spanShadeId').innerText, 10));"> <button id="btnLinkRemote" type="button" onclick="somfy.linkRemote(parseInt(document.getElementById('spanShadeId').innerText, 10));">
Link Remote Link Remote

View file

@ -378,7 +378,7 @@ async function reopenSocket() {
await initSockets(); await initSockets();
} }
class General { class General {
appVersion = 'v1.4.7'; appVersion = 'v1.5.0';
reloadApp = false; reloadApp = false;
async init() { async init() {
this.setAppVersion(); this.setAppVersion();
@ -1040,7 +1040,7 @@ class Somfy {
let divCtl = ''; let divCtl = '';
for (let i = 0; i < shades.length; i++) { for (let i = 0; i < shades.length; i++) {
let shade = shades[i]; let shade = shades[i];
divCfg += `<div class="somfyShade" data-shadeid="${shade.shadeId}" data-remoteaddress="${shade.remoteAddress}" data-tilt="${shade.hasTilt}" data-shadetype="${shade.shadeType}">`; divCfg += `<div class="somfyShade" data-shadeid="${shade.shadeId}" data-remoteaddress="${shade.remoteAddress}" data-tilt="${shade.tiltType}" data-shadetype="${shade.shadeType}">`;
divCfg += `<div class="button-outline" onclick="somfy.openEditShade(${shade.shadeId});"><i class="icss-edit"></i></div>`; divCfg += `<div class="button-outline" onclick="somfy.openEditShade(${shade.shadeId});"><i class="icss-edit"></i></div>`;
//divCfg += `<i class="shade-icon" data-position="${shade.position || 0}%"></i>`; //divCfg += `<i class="shade-icon" data-position="${shade.position || 0}%"></i>`;
divCfg += `<span class="shade-name">${shade.name}</span>`; divCfg += `<span class="shade-name">${shade.name}</span>`;
@ -1048,8 +1048,8 @@ class Somfy {
divCfg += `<div class="button-outline" onclick="somfy.deleteShade(${shade.shadeId});"><i class="icss-trash"></i></div>`; divCfg += `<div class="button-outline" onclick="somfy.deleteShade(${shade.shadeId});"><i class="icss-trash"></i></div>`;
divCfg += '</div>'; divCfg += '</div>';
divCtl += `<div class="somfyShadeCtl" data-shadeId="${shade.shadeId}" data-direction="${shade.direction}" data-remoteaddress="${shade.remoteAddress}" data-position="${shade.position}" data-target="${shade.target}" data-mypos="${shade.myPos}" data-shadetype="${shade.shadeType}" data-tilt="${shade.hasTilt}"`; divCtl += `<div class="somfyShadeCtl" data-shadeId="${shade.shadeId}" data-direction="${shade.direction}" data-remoteaddress="${shade.remoteAddress}" data-position="${shade.position}" data-target="${shade.target}" data-mypos="${shade.myPos}" data-mytiltpos="${shade.myTiltPos} data-shadetype="${shade.shadeType}" data-tilt="${shade.tiltType}"`;
if (shade.hasTilt) { if (shade.tiltType !== 0) {
divCtl += ` data-tiltposition="${shade.tiltPosition}" data-tiltdirection="${shade.tiltDirection}" data-tilttarget="${shade.tiltTarget}"` divCtl += ` data-tiltposition="${shade.tiltPosition}" data-tiltdirection="${shade.tiltDirection}" data-tilttarget="${shade.tiltTarget}"`
} }
divCtl += `><div class="shade-icon" data-shadeid="${shade.shadeId}" onclick="event.stopPropagation(); console.log(event); somfy.openSetPosition(${shade.shadeId});">`; divCtl += `><div class="shade-icon" data-shadeid="${shade.shadeId}" onclick="event.stopPropagation(); console.log(event); somfy.openSetPosition(${shade.shadeId});">`;
@ -1063,10 +1063,11 @@ class Somfy {
break; break;
} }
divCtl += `" data-shadeid="${shade.shadeId}" style="--shade-position:${shade.position}%;vertical-align: top;"></i>`; divCtl += `" data-shadeid="${shade.shadeId}" style="--shade-position:${shade.position}%;vertical-align: top;"></i>`;
divCtl += shade.hasTilt ? `<i class="icss-window-tilt" data-shadeid="${shade.shadeId}" data-tiltposition="${shade.tiltPosition}"></i></div>` : '</div>'; divCtl += shade.tiltType !== 0 ? `<i class="icss-window-tilt" data-shadeid="${shade.shadeId}" data-tiltposition="${shade.tiltPosition}"></i></div>` : '</div>';
divCtl += `<div class="shade-name">`; divCtl += `<div class="shade-name">`;
divCtl += `<span class="shadectl-name">${shade.name}</span>`; divCtl += `<span class="shadectl-name">${shade.name}</span>`;
divCtl += `<span class="shadectl-mypos"><label>My: </label><span id="spanMyPos">${shade.myPos !== 255 ? shade.myPos + '%' : '---'}</span>` divCtl += `<span class="shadectl-mypos"><label>My: </label><span id="spanMyPos">${shade.myPos > 0 ? shade.myPos + '%' : '---'}</span>`
if (shade.myTiltPos > 0) divCtl += `<label> Tilt: </label><span id="spanMyTiltPos">${shade.myTiltPos > 0 ? shade.myTiltPos + '%' : '---'}</span>`
divCtl += '</div>' divCtl += '</div>'
divCtl += `<div class="shadectl-buttons">`; divCtl += `<div class="shadectl-buttons">`;
@ -1163,29 +1164,33 @@ class Somfy {
} }
} }
openSetMyPosition(shadeId) { openSetMyPosition(shadeId) {
if (typeof shadeId === 'undefined') { if (typeof shadeId === 'undefined') return;
return;
}
else { else {
let shade = document.querySelector(`div.somfyShadeCtl[data-shadeid="${shadeId}"]`); let shade = document.querySelector(`div.somfyShadeCtl[data-shadeid="${shadeId}"]`);
if (shade) { if (shade) {
this.closeShadePositioners(); this.closeShadePositioners();
let currPos = parseInt(shade.getAttribute('data-position'), 10); let currPos = parseInt(shade.getAttribute('data-position'), 10);
let currTiltPos = parseInt(shade.getAttribute('data-tiltposition'), 10);
let myPos = parseInt(shade.getAttribute('data-mypos'), 10); let myPos = parseInt(shade.getAttribute('data-mypos'), 10);
let tiltType = parseInt(shade.getAttribute('data-tilt'), 10);
let myTiltPos = parseInt(shade.getAttribute('data-mytiltpos'), 10);
let elname = shade.querySelector(`.shadectl-name`); let elname = shade.querySelector(`.shadectl-name`);
let shadeName = elname.innerHTML; let shadeName = elname.innerHTML;
let html = `<div class="shade-name">${shadeName}`; let html = `<div class="shade-name"><span>${shadeName}</span><div style="float:right;vertical-align:top;cursor:pointer;font-size:12px;margin-top:4px;">`;
if (myPos !== 255) if (myPos >= 0)
html += `<div style="float:right;vertical-align:top;cursor:pointer;font-size:14px;margin-top:4px;" onclick="document.getElementById('slidShadeTarget').value = ${myPos}; document.getElementById('slidShadeTarget').dispatchEvent(new Event('change'));"><span>Current: </span><span>${myPos}</span><span>%</span></div>` html += `<div onclick="document.getElementById('slidShadeTarget').value = ${myPos}; document.getElementById('slidShadeTarget').dispatchEvent(new Event('change'));"><span style="display:inline-block;width:47px;">Current:</span><span>${myPos}</span><span>%</span></div>`;
html += `</div >`; if (myTiltPos >= 0 && tiltType > 0)
html += `<div onclick="document.getElementById('slidShadeTiltTarget').value = ${myTiltPos}; document.getElementById('slidShadeTarget').dispatchEvent(new Event('change'));"><span style="display:inline-block;width:47px;">Tilt:</span><span>${myTiltPos}</span><span>%</span></div>`;
html += `</div></div >`;
html += `<input id="slidShadeTarget" name="shadeTarget" type="range" min="0" max="100" step="1" value="${currPos}" oninput="document.getElementById('spanShadeTarget').innerHTML = this.value;" />`; html += `<input id="slidShadeTarget" name="shadeTarget" type="range" min="0" max="100" step="1" value="${currPos}" oninput="document.getElementById('spanShadeTarget').innerHTML = this.value;" />`;
html += `<label for="slidShadeTarget"><span>Target Position </span><span><span id="spanShadeTarget" class="shade-target">${currPos}</span><span>%</span></span></label>`; html += `<label for="slidShadeTarget"><span>Target Position </span><span><span id="spanShadeTarget" class="shade-target">${currPos}</span><span>%</span></span></label>`;
html += '<div id="divTiltTarget" style="display:none;">';
html += `<input id="slidShadeTiltTarget" name="shadeTiltTarget" type="range" min="0" max="100" step="1" value="${currTiltPos}" oninput="document.getElementById('spanShadeTiltTarget').innerHTML = this.value;" />`;
html += `<label for="slidShadeTiltTarget"><span>Target Tilt </span><span><span id="spanShadeTiltTarget" class="shade-target">${currTiltPos}</span><span>%</span></span></label>`;
html += '</div>'
html += `<hr></hr>`; html += `<hr></hr>`;
html += '<div style="text-align:right;width:100%;">' html += '<div style="text-align:right;width:100%;">'
if (myPos === currPos) html += `<button id="btnSetMyPosition" type="button" style="width:auto;display:inline-block;padding-left:10px;padding-right:10px;margin-top:0px;margin-bottom:10px;margin-right:7px;">Set My Position</button>`;
html += `<button id="btnSetMyPosition" type="button" onclick="somfy.sendShadeMyPosition(${shadeId}, document.getElementById('slidShadeTarget').value);" style="background:orangered;width:auto;display:inline-block;padding-left:10px;padding-right:10px;margin-top:0px;margin-bottom:10px;margin-right:7px;">Clear My Position</button>`;
else
html += `<button id="btnSetMyPosition" type="button" onclick="somfy.sendShadeMyPosition(${shadeId}, document.getElementById('slidShadeTarget').value);" style="width:auto;display:inline-block;padding-left:10px;padding-right:10px;margin-top:0px;margin-bottom:10px;margin-right:7px;">Set My Position</button>`;
html += `<button id="btnCancel" type="button" onclick="somfy.closeShadePositioners();" style="width:auto;display:inline-block;padding-left:10px;padding-right:10px;margin-top:0px;margin-bottom:10px;">Cancel</button>`; html += `<button id="btnCancel" type="button" onclick="somfy.closeShadePositioners();" style="width:auto;display:inline-block;padding-left:10px;padding-right:10px;margin-top:0px;margin-bottom:10px;">Cancel</button>`;
html += `</div></div>`; html += `</div></div>`;
let div = document.createElement('div'); let div = document.createElement('div');
@ -1195,11 +1200,13 @@ class Somfy {
div.innerHTML = html; div.innerHTML = html;
shade.appendChild(div); shade.appendChild(div);
let elTarget = div.querySelector('input#slidShadeTarget'); let elTarget = div.querySelector('input#slidShadeTarget');
let elTiltTarget = div.querySelector('input#slidShadeTiltTarget');
let elBtn = div.querySelector('button#btnSetMyPosition'); let elBtn = div.querySelector('button#btnSetMyPosition');
elTarget.addEventListener('change', (event) => { if (tiltType > 0) div.querySelector('div#divTiltTarget').style.display = '';
console.log(`Target: ${elTarget.value} myPos: ${myPos}`); let fnProcessChange = () => {
div.querySelector('#spanShadeTarget').innerHTML = elTarget.value; let pos = parseInt(elTarget.value, 10);
if (parseInt(elTarget.value, 10) === myPos) { let tilt = parseInt(elTiltTarget.value, 10);
if (pos === myPos && tilt === myTiltPos) {
elBtn.innerHTML = 'Clear My Position'; elBtn.innerHTML = 'Clear My Position';
elBtn.style.background = 'orangered'; elBtn.style.background = 'orangered';
} }
@ -1207,15 +1214,26 @@ class Somfy {
elBtn.innerHTML = 'Set My Position'; elBtn.innerHTML = 'Set My Position';
elBtn.style.background = ''; elBtn.style.background = '';
} }
}); document.getElementById('spanShadeTiltTarget').innerHTML = tilt;
document.getElementById('spanShadeTarget').innerHTML = pos;
};
let fnSetMyPosition = () => {
let pos = parseInt(elTarget.value, 10);
let tilt = parseInt(elTiltTarget.value, 10);
somfy.sendShadeMyPosition(shadeId, pos, tilt);
};
fnProcessChange();
elTarget.addEventListener('change', (event) => { fnProcessChange(); });
elTiltTarget.addEventListener('change', (event) => { fnProcessChange(); });
elBtn.addEventListener('click', (event) => { fnSetMyPosition(); });
} }
} }
} }
sendShadeMyPosition(shadeId, pos) { sendShadeMyPosition(shadeId, pos, tilt) {
console.log(`Sending My Position for shade id ${shadeId} to ${pos}`); console.log(`Sending My Position for shade id ${shadeId} to ${pos} and ${tilt}`);
let overlay = waitMessage(document.getElementById('divContainer')); let overlay = waitMessage(document.getElementById('divContainer'));
putJSON('/setMyPosition', { shadeId: shadeId, target: parseInt(pos, 10) }, (err, response) => { putJSON('/setMyPosition', { shadeId: shadeId, pos: pos, tilt: tilt }, (err, response) => {
this.closeShadePositioners(); this.closeShadePositioners();
overlay.remove(); overlay.remove();
console.log(response); console.log(response);
@ -1243,7 +1261,7 @@ class Somfy {
case 9: case 9:
case 10: case 10:
case 11: case 11:
continue; if(type !== 'inout') continue;
break; break;
case 37: case 37:
case 38: case 38:
@ -1267,7 +1285,7 @@ class Somfy {
for (let i = 0; i < icons.length; i++) { for (let i = 0; i < icons.length; i++) {
icons[i].style.setProperty('--shade-position', `${state.position}%`); icons[i].style.setProperty('--shade-position', `${state.position}%`);
} }
if (state.hasTilt) { if (state.tiltType !== 0) {
let tilts = document.querySelectorAll(`.icss-window-tilt[data-shadeid="${state.shadeId}"]`); let tilts = document.querySelectorAll(`.icss-window-tilt[data-shadeid="${state.shadeId}"]`);
for (let i = 0; i < tilts.length; i++) { for (let i = 0; i < tilts.length; i++) {
tilts[i].setAttribute('data-tiltposition', `${state.tiltPosition}`); tilts[i].setAttribute('data-tiltposition', `${state.tiltPosition}`);
@ -1279,13 +1297,17 @@ class Somfy {
divs[i].setAttribute('data-position', state.position); divs[i].setAttribute('data-position', state.position);
divs[i].setAttribute('data-target', state.target); divs[i].setAttribute('data-target', state.target);
divs[i].setAttribute('data-mypos', state.mypos); divs[i].setAttribute('data-mypos', state.mypos);
if (state.hasTilt) { if (typeof state.myTiltPos !== 'undefined') divs[i].setAttribute('data-mytiltpos', state.myTiltPos);
else divs[i].setAttribute('data-mytiltpos', -1);
if (state.tiltType !== 0) {
divs[i].setAttribute('data-tiltdirection', state.tiltDirection); divs[i].setAttribute('data-tiltdirection', state.tiltDirection);
divs[i].setAttribute('data-tiltposition', state.tiltPosition); divs[i].setAttribute('data-tiltposition', state.tiltPosition);
divs[i].setAttribute('data-tilttarget', state.tiltTarget); divs[i].setAttribute('data-tilttarget', state.tiltTarget);
} }
let span = divs[i].querySelector('#spanMyPos'); let span = divs[i].querySelector('#spanMyPos');
if (span) span.innerHTML = typeof state.mypos !== 'undefined' && state.mypos !== 255 ? `${state.mypos}%` : '---'; if (span) span.innerHTML = typeof state.mypos !== 'undefined' && state.mypos >= 0 ? `${state.mypos}%` : '---';
span = divs[i].querySelector('#spanMyTiltPos');
if (span) span.innerHTML = typeof state.myTiltPos !== 'undefined' && state.myTiltPos >= 0 ? `${state.myTiltPos}%` : '---';
} }
}; };
@ -1377,7 +1399,7 @@ class Somfy {
}; };
onShadeTypeChanged(el) { onShadeTypeChanged(el) {
let sel = document.getElementById('selShadeType'); let sel = document.getElementById('selShadeType');
let tilt = document.getElementById('cbHasTilt').checked; let tilt = parseInt(document.getElementById('selTiltType').value, 10);
let ico = document.getElementById('icoShade'); let ico = document.getElementById('icoShade');
switch (parseInt(sel.value, 10)) { switch (parseInt(sel.value, 10)) {
case 1: case 1:
@ -1450,7 +1472,7 @@ class Somfy {
document.getElementsByName('shadeUpTime')[0].value = shade.upTime; document.getElementsByName('shadeUpTime')[0].value = shade.upTime;
document.getElementsByName('shadeDownTime')[0].value = shade.downTime; document.getElementsByName('shadeDownTime')[0].value = shade.downTime;
document.getElementById('fldTiltTime').value = shade.tiltTime; document.getElementById('fldTiltTime').value = shade.tiltTime;
document.getElementById('cbHasTilt').checked = shade.hasTilt; document.getElementById('selTiltType').value = shade.tiltType;
this.onShadeTypeChanged(document.getElementById('selShadeType')); this.onShadeTypeChanged(document.getElementById('selShadeType'));
let ico = document.getElementById('icoShade'); let ico = document.getElementById('icoShade');
switch (shade.shadeType) { switch (shade.shadeType) {
@ -1460,7 +1482,7 @@ class Somfy {
break; break;
} }
let tilt = ico.parentElement.querySelector('i.icss-window-tilt'); let tilt = ico.parentElement.querySelector('i.icss-window-tilt');
tilt.style.display = shade.hasTilt ? '' : 'none'; tilt.style.display = shade.tiltType !== 0 ? '' : 'none';
tilt.setAttribute('data-tiltposition', shade.tiltPosition); tilt.setAttribute('data-tiltposition', shade.tiltPosition);
ico.style.setProperty('--shade-position', `${shade.position}%`); ico.style.setProperty('--shade-position', `${shade.position}%`);
ico.style.setProperty('--tilt-position', `${shade.tiltPosition}%`); ico.style.setProperty('--tilt-position', `${shade.tiltPosition}%`);
@ -1503,9 +1525,9 @@ class Somfy {
bitLength: parseInt(document.getElementById('selShadeBitLength').value, 10) || 56 bitLength: parseInt(document.getElementById('selShadeBitLength').value, 10) || 56
}; };
if (obj.shadeType == 1) { if (obj.shadeType == 1) {
obj.hasTilt = document.getElementById('cbHasTilt').checked; obj.tiltType = parseInt(document.getElementById('selTiltType').value, 10);
} }
else obj.hasTilt = false; else obj.tiltType = 0;
let valid = true; let valid = true;
if (valid && (isNaN(obj.remoteAddress) || obj.remoteAddress < 1 || obj.remoteAddress > 16777215)) { if (valid && (isNaN(obj.remoteAddress) || obj.remoteAddress < 1 || obj.remoteAddress > 16777215)) {
errorMessage(document.getElementById('fsSomfySettings'), 'The remote address must be a number between 1 and 16777215. This number must be unique for all shades.'); errorMessage(document.getElementById('fsSomfySettings'), 'The remote address must be a number between 1 and 16777215. This number must be unique for all shades.');

View file

@ -488,6 +488,7 @@ div.waitoverlay > .lds-roller {
} }
.somfyShade { .somfyShade {
text-align:center; text-align:center;
padding-bottom:4px;
} }
.button-outline { .button-outline {
background-color: #00bcd4; background-color: #00bcd4;
@ -501,12 +502,13 @@ div.waitoverlay > .lds-roller {
} }
.shade-name { .shade-name {
text-align:left; text-align:left;
width: 124px; width: 170px;
padding-left: 2px; padding-left: 2px;
padding-right: 2px; padding-right: 2px;
display: inline-block; display: inline-block;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow:hidden; overflow:hidden;
vertical-align:middle;
} }
.shade-address { .shade-address {
width:77px; width:77px;
@ -515,6 +517,7 @@ div.waitoverlay > .lds-roller {
text-overflow:ellipsis; text-overflow:ellipsis;
overflow:hidden; overflow:hidden;
display:inline-block; display:inline-block;
vertical-align:middle;
} }
.progress-bar { .progress-bar {
display:block; display:block;