mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2025-12-12 18:42:10 +01:00
My Favorite Processing
* My button long presses detected from remotes for setting the my button. * Allow setting of rolling code. * Tuning for position setter and reading of hw sync bytes for repeats.
This commit is contained in:
parent
d8038aebce
commit
765e8f3fd0
11 changed files with 605 additions and 87 deletions
5
MQTT.cpp
5
MQTT.cpp
|
|
@ -86,6 +86,10 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
|
|||
else
|
||||
shade->sendCommand(somfy_commands::My);
|
||||
}
|
||||
else if(strncmp(command, "mypos", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
shade->setMyPosition(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool MQTTClass::connect() {
|
||||
|
|
@ -106,6 +110,7 @@ bool MQTTClass::connect() {
|
|||
somfy.publish();
|
||||
this->subscribe("shades/+/target/set");
|
||||
this->subscribe("shades/+/direction/set");
|
||||
this->subscribe("shades/+/mypos/set");
|
||||
mqttClient.setCallback(MQTTClass::receive);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ bool Network::connect() {
|
|||
this->connectStart = millis();
|
||||
Serial.print("Set hostname to:");
|
||||
Serial.println(WiFi.getHostname());
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
WiFi.mode(WIFI_STA);
|
||||
delay(100);
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase);
|
||||
|
|
|
|||
335
Somfy.cpp
335
Somfy.cpp
|
|
@ -23,6 +23,8 @@ uint8_t rxmode = 0; // Indicates whether the radio is in receive mode. Just to
|
|||
#define RECEIVE_ATTR
|
||||
#endif
|
||||
|
||||
#define SETMY_REPEATS 15
|
||||
|
||||
int sort_asc(const void *cmp1, const void *cmp2) {
|
||||
int a = *((uint8_t *)cmp1);
|
||||
int b = *((uint8_t *)cmp2);
|
||||
|
|
@ -120,36 +122,6 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
|||
}
|
||||
|
||||
if (this->valid) {
|
||||
/*
|
||||
Serial.println(" KEY 1 2 3 4 5 6 ");
|
||||
Serial.println("--------------------------------");
|
||||
Serial.print("ENC ");
|
||||
for(byte i = 0; i < 7; i++) {
|
||||
if(frame[i] < 10)
|
||||
Serial.print(" ");
|
||||
else if(frame[i] < 100)
|
||||
Serial.print(" ");
|
||||
Serial.print(frame[i]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print("DEC ");
|
||||
for(byte i = 0; i < 7; i++) {
|
||||
if(decoded[i] < 10)
|
||||
Serial.print(" ");
|
||||
else if(decoded[i] < 100)
|
||||
Serial.print(" ");
|
||||
Serial.print(decoded[i]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print("VALID:");
|
||||
Serial.print(this->valid ? "true" : "false");
|
||||
Serial.print(" ENCCS:");
|
||||
Serial.print(checksum);
|
||||
Serial.print(" DECCS:");
|
||||
Serial.println(this->checksum);
|
||||
*/
|
||||
Serial.print("KEY:");
|
||||
Serial.print(this->encKey);
|
||||
Serial.print(" ADDR:");
|
||||
|
|
@ -157,7 +129,9 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
|||
Serial.print(" CMD:");
|
||||
Serial.print(translateSomfyCommand(this->cmd));
|
||||
Serial.print(" RCODE:");
|
||||
Serial.println(this->rollingCode);
|
||||
Serial.print(this->rollingCode);
|
||||
Serial.print(" HWSYNC:");
|
||||
Serial.println(this->hwsync);
|
||||
}
|
||||
else {
|
||||
Serial.print("INVALID FRAME ");
|
||||
|
|
@ -196,7 +170,7 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
|||
void somfy_frame_t::encodeFrame(byte *frame) {
|
||||
const byte btn = static_cast<byte>(cmd);
|
||||
frame[0] = this->encKey; // Encryption key. Doesn't matter much
|
||||
frame[1] = btn << 4; // Which button did you press? The 4 LSB will be the checksum
|
||||
frame[1] = btn << 4; // Which button did you press? The 4 LSB will be the checksum
|
||||
frame[2] = this->rollingCode >> 8; // Rolling code (big endian)
|
||||
frame[3] = this->rollingCode; // Rolling code
|
||||
frame[4] = this->remoteAddress >> 16; // Remote address
|
||||
|
|
@ -231,6 +205,27 @@ void somfy_frame_t::print() {
|
|||
Serial.print(" CS:");
|
||||
Serial.println(this->checksum);
|
||||
}
|
||||
bool somfy_frame_t::isRepeat(somfy_frame_t &frame) { return this->remoteAddress == frame.remoteAddress && this->cmd == frame.cmd && this->rollingCode == frame.rollingCode; }
|
||||
void somfy_frame_t::copy(somfy_frame_t &frame) {
|
||||
if(this->isRepeat(frame)) {
|
||||
this->repeats++;
|
||||
this->rssi = frame.rssi;
|
||||
this->lqi = frame.lqi;
|
||||
}
|
||||
else {
|
||||
this->valid = frame.valid;
|
||||
this->processed = frame.processed;
|
||||
this->rssi = frame.rssi;
|
||||
this->lqi = frame.lqi;
|
||||
this->cmd = frame.cmd;
|
||||
this->remoteAddress = frame.remoteAddress;
|
||||
this->rollingCode = frame.rollingCode;
|
||||
this->encKey = frame.encKey;
|
||||
this->checksum = frame.checksum;
|
||||
this->hwsync = frame.hwsync;
|
||||
this->repeats = frame.repeats;
|
||||
}
|
||||
}
|
||||
void SomfyShadeController::end() { this->transceiver.disableReceive(); }
|
||||
SomfyShadeController::SomfyShadeController() {
|
||||
memset(this->m_shadeIds, 255, sizeof(this->m_shadeIds));
|
||||
|
|
@ -251,17 +246,19 @@ SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) {
|
|||
}
|
||||
bool SomfyShadeController::begin() {
|
||||
// Load up all the configuration data.
|
||||
Serial.printf("sizeof(SomfyShade) = %d\n", sizeof(SomfyShade));
|
||||
//Serial.printf("sizeof(SomfyShade) = %d\n", sizeof(SomfyShade));
|
||||
pref.begin("Shades");
|
||||
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
//this->transceiver.begin();
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
#ifdef DEBUG_SOMFY
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(i != 0) Serial.print(",");
|
||||
Serial.print(this->m_shadeIds[i]);
|
||||
if(i != 0) DEBUG_SOMFY.print(",");
|
||||
DEBUG_SOMFY.print(this->m_shadeIds[i]);
|
||||
}
|
||||
Serial.println();
|
||||
DEBUG_SOMFY.println();
|
||||
#endif
|
||||
|
||||
uint8_t id = 0;
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
|
|
@ -274,16 +271,18 @@ bool SomfyShadeController::begin() {
|
|||
}
|
||||
shade->load();
|
||||
}
|
||||
#ifdef DEBUG_SOMFY
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
Serial.print(this->shades[i].getShadeId());
|
||||
Serial.print(":");
|
||||
Serial.print(this->m_shadeIds[i]);
|
||||
if(i < SOMFY_MAX_SHADES - 1) Serial.print(",");
|
||||
DEBUG_SOMFY.print(this->shades[i].getShadeId());
|
||||
DEBUG_SOMFY.print(":");
|
||||
DEBUG_SOMFY.print(this->m_shadeIds[i]);
|
||||
if(i < SOMFY_MAX_SHADES - 1) DEBUG_SOMFY.print(",");
|
||||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
Serial.println();
|
||||
this->transceiver.begin();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -344,7 +343,6 @@ bool SomfyShade::unlinkRemote(uint32_t address) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SomfyShade::checkMovement() {
|
||||
int8_t currDir = this->direction;
|
||||
uint8_t currPos = this->position;
|
||||
|
|
@ -392,10 +390,11 @@ void SomfyShade::checkMovement() {
|
|||
Serial.print("% target ");
|
||||
Serial.print(this->target);
|
||||
Serial.println("%");
|
||||
this->sendCommand(somfy_commands::My);
|
||||
if(!this->seekingMyPos) this->sendCommand(somfy_commands::My);
|
||||
else this->direction = 0;
|
||||
this->seekingPos = false;
|
||||
this->seekingMyPos = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else if(this->direction < 0) {
|
||||
|
|
@ -432,24 +431,43 @@ void SomfyShade::checkMovement() {
|
|||
Serial.print("% target ");
|
||||
Serial.print(this->target);
|
||||
Serial.println("%");
|
||||
this->sendCommand(somfy_commands::My);
|
||||
if(!this->seekingMyPos) this->sendCommand(somfy_commands::My);
|
||||
else this->direction = 0;
|
||||
this->seekingMyPos = false;
|
||||
this->seekingPos = false;
|
||||
}
|
||||
}
|
||||
if(currDir != this->direction && this->direction == 0) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position: ");
|
||||
Serial.println(this->currentPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.end();
|
||||
if(this->settingMyPos) {
|
||||
delay(200);
|
||||
// Set this position before sending the command. If you don't the processFrame function
|
||||
// will send the shade back to its original My position.
|
||||
if(this->myPos == this->position) this->myPos = 255;
|
||||
else this->myPos = this->position;
|
||||
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
this->settingMyPos = false;
|
||||
this->seekingMyPos = false;
|
||||
Serial.print("Committing My Position: ");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
|
||||
pref.begin(shadeKey);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
if(currDir != this->direction || currPos != this->position) {
|
||||
// We need to emit on the socket that our state has changed.
|
||||
this->position = floor(this->currentPos * 100.0);
|
||||
this->emitState();
|
||||
}
|
||||
if(currDir != this->direction && this->direction == 0) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position:");
|
||||
Serial.println(this->currentPos);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
void SomfyShade::load() {
|
||||
char shadeKey[15];
|
||||
|
|
@ -468,6 +486,7 @@ void SomfyShade::load() {
|
|||
this->currentPos = pref.getFloat("currentPos", 0);
|
||||
this->position = (uint8_t)floor(this->currentPos * 100);
|
||||
this->target = this->position;
|
||||
this->myPos = pref.getUShort("myPos", this->myPos);
|
||||
pref.getBytes("linkedAddr", linkedAddresses, sizeof(linkedAddresses));
|
||||
pref.end();
|
||||
Serial.print("shadeId:");
|
||||
|
|
@ -477,14 +496,17 @@ void SomfyShade::load() {
|
|||
Serial.print(" address:");
|
||||
Serial.print(this->getRemoteAddress());
|
||||
Serial.print(" position:");
|
||||
Serial.println(this->position);
|
||||
Serial.print(this->position);
|
||||
Serial.print(" myPos:");
|
||||
Serial.println(this->myPos);
|
||||
pref.begin("ShadeCodes");
|
||||
this->lastRollingCode = pref.getUShort(this->m_remotePrefId, 0);
|
||||
for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) {
|
||||
SomfyLinkedRemote &lremote = this->linkedRemotes[j];
|
||||
lremote.setRemoteAddress(linkedAddresses[j]);
|
||||
pref.begin("ShadeCodes");
|
||||
lremote.lastRollingCode = pref.getUShort(lremote.getRemotePrefId(), 0);
|
||||
pref.end();
|
||||
}
|
||||
pref.end();
|
||||
|
||||
}
|
||||
void SomfyShade::publish() {
|
||||
|
|
@ -504,12 +526,15 @@ void SomfyShade::publish() {
|
|||
mqtt.publish(topic, this->target);
|
||||
snprintf(topic, sizeof(topic), "shades/%u/lastRollingCode", this->shadeId);
|
||||
mqtt.publish(topic, this->lastRollingCode);
|
||||
snprintf(topic, sizeof(topic), "shades/%u/mypos", this->shadeId);
|
||||
mqtt.publish(topic, this->myPos);
|
||||
|
||||
}
|
||||
}
|
||||
void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
|
||||
void SomfyShade::emitState(uint8_t num, const char *evt) {
|
||||
char buf[220];
|
||||
snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d}", this->shadeId, this->getRemoteAddress(), this->name, this->direction, this->position, this->target);
|
||||
snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d}", this->shadeId, this->getRemoteAddress(), this->name, this->direction, this->position, this->target, this->myPos);
|
||||
if(num >= 255) sockEmit.sendToClients(evt, buf);
|
||||
else sockEmit.sendToClient(num, evt, buf);
|
||||
if(mqtt.connected()) {
|
||||
|
|
@ -522,9 +547,54 @@ void SomfyShade::emitState(uint8_t num, const char *evt) {
|
|||
mqtt.publish(topic, this->target);
|
||||
snprintf(topic, sizeof(topic), "shades/%u/lastRollingCode", this->shadeId);
|
||||
mqtt.publish(topic, this->lastRollingCode);
|
||||
snprintf(topic, sizeof(topic), "shades/%u/mypos", this->shadeId);
|
||||
mqtt.publish(topic, this->myPos);
|
||||
}
|
||||
}
|
||||
void SomfyShade::processWaitingFrame() {
|
||||
if(this->shadeId == 255) {
|
||||
this->lastFrame.await = 0;
|
||||
return;
|
||||
}
|
||||
if(this->lastFrame.processed) return;
|
||||
if(this->lastFrame.await > 0 && (millis() > this->lastFrame.await || this->lastFrame.repeats >= SETMY_REPEATS)) {
|
||||
switch(this->lastFrame.cmd) {
|
||||
case somfy_commands::My:
|
||||
if(this->lastFrame.repeats >= SETMY_REPEATS && this->direction == 0) {
|
||||
if(this->myPos == this->position) // We are clearing it.
|
||||
this->myPos = 255;
|
||||
else
|
||||
this->myPos = this->position;
|
||||
Serial.print(this->name);
|
||||
Serial.print(" MY POSITION SET TO:");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
this->lastFrame.processed = true;
|
||||
this->emitState();
|
||||
}
|
||||
else if(this->myPos >= 0 && this->myPos <= 100) {
|
||||
int8_t dir = 0;
|
||||
if(myPos < this->position) dir = -1;
|
||||
else if(myPos > this->position) dir = 1;
|
||||
if(dir != 0) this->seekingMyPos = true;
|
||||
this->seekingPos = true;
|
||||
this->target = this->myPos;
|
||||
this->setMovement(dir);
|
||||
this->lastFrame.processed = true;
|
||||
}
|
||||
if(this->lastFrame.repeats > SETMY_REPEATS + 2) this->lastFrame.processed = true;
|
||||
if(this->lastFrame.processed) {
|
||||
Serial.print(this->name);
|
||||
Serial.print(" Processing MY after ");
|
||||
Serial.print(this->lastFrame.repeats);
|
||||
Serial.println(" repeats");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
|
||||
if(this->shadeId == 255) return;
|
||||
bool hasRemote = this->getRemoteAddress() == frame.remoteAddress;
|
||||
if(!hasRemote) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
|
|
@ -536,8 +606,9 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
|
|||
}
|
||||
}
|
||||
if(!hasRemote) return;
|
||||
this->lastFrame.copy(frame);
|
||||
int8_t dir = 0;
|
||||
// If the frame came from the radio it cannot be seeing a position. This means that the target will be set.
|
||||
// If the frame came from the radio it cannot be seeking a position. This means that the target will be set.
|
||||
if(!internal) this->seekingPos = false;
|
||||
// At this point we are not processing the combo buttons
|
||||
// will need to see what the shade does when you press both.
|
||||
|
|
@ -545,10 +616,37 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
|
|||
case somfy_commands::Up:
|
||||
dir = -1;
|
||||
if(!internal) this->target = 0;
|
||||
this->lastFrame.processed = true;
|
||||
break;
|
||||
case somfy_commands::Down:
|
||||
dir = 1;
|
||||
if(!internal) this->target = 100;
|
||||
this->lastFrame.processed = true;
|
||||
break;
|
||||
case somfy_commands::My:
|
||||
dir = 0;
|
||||
if(this->direction == 0) {
|
||||
if(!internal) {
|
||||
this->lastFrame.await = millis() + 500;
|
||||
}
|
||||
else if(myPos >= 0 && this->myPos <= 100) {
|
||||
this->lastFrame.processed = true;
|
||||
// In this instance we will be moving to the my position. This
|
||||
// will be up or down or not.
|
||||
if(this->myPos < this->position) dir = -1;
|
||||
else if(this->myPos > this->position) dir = 1;
|
||||
if(dir != 0) {
|
||||
Serial.println("Start moving to My Position");
|
||||
this->seekingMyPos = true;
|
||||
}
|
||||
this->seekingPos = true;
|
||||
this->target = this->myPos;
|
||||
}
|
||||
}
|
||||
else
|
||||
// This will occur if the shade needs to be stopped or there
|
||||
// is no my position set.
|
||||
this->lastFrame.processed = true;
|
||||
break;
|
||||
default:
|
||||
dir = 0;
|
||||
|
|
@ -563,7 +661,15 @@ void SomfyShade::setMovement(int8_t dir) {
|
|||
this->startPos = this->currentPos;
|
||||
this->moveStart = 0;
|
||||
this->direction = dir;
|
||||
this->emitState();
|
||||
if(currDir != dir) {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
Serial.print("Writing current shade position:");
|
||||
Serial.println(this->currentPos, 4);
|
||||
pref.begin(shadeKey);
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.end();
|
||||
}
|
||||
}
|
||||
else if(this->direction != dir) {
|
||||
this->moveStart = millis();
|
||||
|
|
@ -575,6 +681,53 @@ void SomfyShade::setMovement(int8_t dir) {
|
|||
this->emitState();
|
||||
}
|
||||
}
|
||||
void SomfyShade::setMyPosition(uint8_t target) {
|
||||
if(this->direction != 0) return; // Don't do this if it is moving.
|
||||
Serial.println("setMyPosition called");
|
||||
if(target != this->position) {
|
||||
this->settingMyPos = true;
|
||||
Serial.println("Moving to set My Position");
|
||||
if(target == this->myPos)
|
||||
// Setting the My Position to the same position will toggle it off. So lets send a my
|
||||
// command instead of an up/down. This will ensure we get the thing cleared.
|
||||
this->moveToMyPosition();
|
||||
else
|
||||
this->moveToTarget(target);
|
||||
}
|
||||
else {
|
||||
this->sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
if(target == this->myPos)
|
||||
this->myPos = 255;
|
||||
else
|
||||
this->myPos = target;
|
||||
Serial.print("Writing my shade position:");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
pref.begin(shadeKey);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
pref.end();
|
||||
this->emitState();
|
||||
}
|
||||
}
|
||||
void SomfyShade::moveToMyPosition() {
|
||||
if(this->direction != 0 || this->myPos > 100 || this->myPos < 0) return;
|
||||
if(this->position == this->myPos) return; // Nothing to see here since we are already here.
|
||||
Serial.print("Seeking my Position:");
|
||||
Serial.print(this->myPos);
|
||||
Serial.println("%");
|
||||
this->seekingMyPos = true;
|
||||
this->target = this->myPos;
|
||||
Serial.print("Moving to ");
|
||||
Serial.print(this->target);
|
||||
Serial.print("% from ");
|
||||
Serial.print(this->position);
|
||||
Serial.print("% using ");
|
||||
Serial.println(translateSomfyCommand(somfy_commands::My));
|
||||
this->seekingPos = true;
|
||||
SomfyRemote::sendCommand(somfy_commands::My);
|
||||
}
|
||||
void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) {
|
||||
if(cmd == somfy_commands::Up) {
|
||||
this->target = 0;
|
||||
|
|
@ -585,8 +738,14 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) {
|
|||
this->seekingPos = false;
|
||||
}
|
||||
else if(cmd == somfy_commands::My) {
|
||||
this->target = this->position;
|
||||
this->seekingPos = false;
|
||||
if(this->direction == 0 && this->myPos >= 0 && this->myPos <= 100) {
|
||||
this->moveToMyPosition();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
this->target = this->position;
|
||||
this->seekingPos = false;
|
||||
}
|
||||
}
|
||||
SomfyRemote::sendCommand(cmd, repeat);
|
||||
}
|
||||
|
|
@ -608,7 +767,6 @@ void SomfyShade::moveToTarget(uint8_t target) {
|
|||
else this->seekingPos = false;
|
||||
SomfyRemote::sendCommand(cmd);
|
||||
}
|
||||
|
||||
bool SomfyShade::save() {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
|
|
@ -619,6 +777,7 @@ bool SomfyShade::save() {
|
|||
pref.putUShort("downTime", this->downTime);
|
||||
pref.putULong("remoteAddress", this->getRemoteAddress());
|
||||
pref.putFloat("currentPos", this->currentPos);
|
||||
pref.putUShort("myPos", this->myPos);
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(linkedAddresses));
|
||||
uint8_t j = 0;
|
||||
|
|
@ -664,6 +823,8 @@ bool SomfyShade::toJSON(JsonObject &obj) {
|
|||
obj["lastRollingCode"] = this->lastRollingCode;
|
||||
obj["position"] = this->position;
|
||||
obj["target"] = this->target;
|
||||
obj["myPos"] = this->myPos;
|
||||
obj["direction"] = this->direction;
|
||||
SomfyRemote::toJSON(obj);
|
||||
JsonArray arr = obj.createNestedArray("linkedRemotes");
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
|
|
@ -685,7 +846,11 @@ void SomfyRemote::setRemoteAddress(uint32_t address) { this->m_remoteAddress = a
|
|||
uint32_t SomfyRemote::getRemoteAddress() { return this->m_remoteAddress; }
|
||||
void SomfyShadeController::processFrame(somfy_frame_t &frame, bool internal) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++)
|
||||
this->shades[i].processFrame(frame, internal);
|
||||
if(this->shades[i].getShadeId() != 255) this->shades[i].processFrame(frame, internal);
|
||||
}
|
||||
void SomfyShadeController::processWaitingFrame() {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++)
|
||||
if(this->shades[i].getShadeId() != 255) this->shades[i].processWaitingFrame();
|
||||
}
|
||||
void SomfyShadeController::emitState(uint8_t num) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
|
|
@ -797,7 +962,6 @@ SomfyShade *SomfyShadeController::addShade() {
|
|||
}
|
||||
return shade;
|
||||
}
|
||||
|
||||
void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) {
|
||||
somfy_frame_t frame;
|
||||
frame.rollingCode = this->getNextRollingCode();
|
||||
|
|
@ -826,7 +990,6 @@ void SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat) {
|
|||
}
|
||||
this->transceiver.endTransmit();
|
||||
}
|
||||
|
||||
bool SomfyShadeController::deleteShade(uint8_t shadeId) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
if(this->shades[i].getShadeId() == shadeId) {
|
||||
|
|
@ -846,7 +1009,6 @@ bool SomfyShadeController::deleteShade(uint8_t shadeId) {
|
|||
pref.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t SomfyRemote::getNextRollingCode() {
|
||||
pref.begin("ShadeCodes");
|
||||
uint16_t code = pref.getUShort(this->m_remotePrefId, 0);
|
||||
|
|
@ -857,10 +1019,12 @@ uint16_t SomfyRemote::getNextRollingCode() {
|
|||
return code;
|
||||
}
|
||||
uint16_t SomfyRemote::setRollingCode(uint16_t code) {
|
||||
pref.begin("ShadeCodes");
|
||||
pref.putUShort(this->m_remotePrefId, code);
|
||||
pref.end();
|
||||
this->lastRollingCode = code;
|
||||
if(this->lastRollingCode != code) {
|
||||
pref.begin("ShadeCodes");
|
||||
pref.putUShort(this->m_remotePrefId, code);
|
||||
pref.end();
|
||||
this->lastRollingCode = code;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) {
|
||||
|
|
@ -908,7 +1072,6 @@ bool SomfyShadeController::toJSON(JsonArray &arr) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SomfyShadeController::loop() {
|
||||
this->transceiver.loop();
|
||||
for(uint8_t i; i < SOMFY_MAX_SHADES; i++) {
|
||||
|
|
@ -951,6 +1114,7 @@ static struct somfy_rx_t
|
|||
} somfy_rx;
|
||||
uint8_t receive_buffer[10]; // 80 bits
|
||||
bool packet_received = false;
|
||||
uint8_t m_hwsync = 0;
|
||||
void Transceiver::sendFrame(byte *frame, uint8_t sync) {
|
||||
if(!this->config.enabled) return;
|
||||
uint32_t pin = 1 << this->config.TXPin;
|
||||
|
|
@ -997,7 +1161,6 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync) {
|
|||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(30415);
|
||||
}
|
||||
|
||||
void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||
static unsigned long last_time = 0;
|
||||
const long time = micros();
|
||||
|
|
@ -1012,15 +1175,18 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
|||
case waiting_synchro:
|
||||
if (duration > tempo_synchro_hw_min && duration < tempo_synchro_hw_max) {
|
||||
// We have found a hardware sync bit. There should be at least 4 of these.
|
||||
// The original code sets gpio 2 on and off but only when in debug mode. I suspect this was to flash an LED
|
||||
// to see the tempo.
|
||||
++somfy_rx.cpt_synchro_hw;
|
||||
}
|
||||
else if (duration > tempo_synchro_sw_min && duration < tempo_synchro_sw_max && somfy_rx.cpt_synchro_hw >= 4) {
|
||||
// If we have a full hardware sync then we should look for the software sync. If we have a software sync
|
||||
// bit and enough hardware sync bits then we should start receiving data.
|
||||
memset(&somfy_rx, 0x00, sizeof(somfy_rx));
|
||||
//memset(&somfy_rx, 0x00, sizeof(somfy_rx));
|
||||
memset(somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
|
||||
somfy_rx.previous_bit = 0x00;
|
||||
somfy_rx.waiting_half_symbol = false;
|
||||
somfy_rx.cpt_bits = 0;
|
||||
somfy_rx.status = receiving_data;
|
||||
|
||||
}
|
||||
else {
|
||||
// Reset and start looking for hardware sync again.
|
||||
|
|
@ -1047,7 +1213,11 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
|||
}
|
||||
else {
|
||||
// Start over we are not within our parameters for bit timing.
|
||||
memset(&somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
|
||||
somfy_rx.cpt_synchro_hw = 0;
|
||||
somfy_rx.previous_bit = 0x00;
|
||||
somfy_rx.waiting_half_symbol = false;
|
||||
somfy_rx.cpt_bits = 0;
|
||||
somfy_rx.status = waiting_synchro;
|
||||
}
|
||||
break;
|
||||
|
|
@ -1055,17 +1225,21 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
|||
break;
|
||||
}
|
||||
if (somfy_rx.status == receiving_data && somfy_rx.cpt_bits == bit_length) {
|
||||
// The original code posted the task so that it would pick it up immediately.
|
||||
// we don't need any of that so we will simply catch it on the next loop.
|
||||
//task_post_high(DataReady_taskid, (task_param_t)0);
|
||||
memcpy(receive_buffer, somfy_rx.payload, sizeof(receive_buffer));
|
||||
packet_received = true;
|
||||
m_hwsync = somfy_rx.cpt_synchro_hw;
|
||||
memset(&somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
|
||||
somfy_rx.cpt_synchro_hw = 0;
|
||||
somfy_rx.previous_bit = 0x00;
|
||||
somfy_rx.waiting_half_symbol = false;
|
||||
somfy_rx.cpt_bits = 0;
|
||||
somfy_rx.status = waiting_synchro;
|
||||
}
|
||||
}
|
||||
bool Transceiver::receive() {
|
||||
if (packet_received) {
|
||||
packet_received = false;
|
||||
this->frame.hwsync = m_hwsync;
|
||||
this->frame.rssi = ELECHOUSE_cc1101.getRssi();
|
||||
this->frame.decodeFrame(receive_buffer);
|
||||
//this->frame.lqi = ELECHOUSE_cc1101.getLqi();
|
||||
|
|
@ -1337,6 +1511,9 @@ void Transceiver::loop() {
|
|||
snprintf(buf, sizeof(buf), "{\"encKey\":%d,\"address\":%d,\"rcode\":%d,\"command\":\"%s\",\"rssi\":%d}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd), this->frame.rssi);
|
||||
sockEmit.sendToClients("remoteFrame", buf);
|
||||
}
|
||||
else {
|
||||
somfy.processWaitingFrame();
|
||||
}
|
||||
}
|
||||
somfy_frame_t& Transceiver::lastFrame() { return this->frame; }
|
||||
void Transceiver::beginTransmit() {
|
||||
|
|
|
|||
17
Somfy.h
17
Somfy.h
|
|
@ -20,6 +20,8 @@ String translateSomfyCommand(const somfy_commands cmd);
|
|||
somfy_commands translateSomfyCommand(const String& string);
|
||||
|
||||
typedef struct somfy_frame_t {
|
||||
bool valid = false;
|
||||
bool processed = false;
|
||||
int rssi = 0;
|
||||
byte lqi = 0x0;
|
||||
somfy_commands cmd;
|
||||
|
|
@ -27,10 +29,14 @@ typedef struct somfy_frame_t {
|
|||
uint16_t rollingCode = 0;
|
||||
uint8_t encKey = 0xA7;
|
||||
uint8_t checksum = 0;
|
||||
bool valid = false;
|
||||
uint8_t hwsync = 0;
|
||||
uint8_t repeats = 0;
|
||||
uint32_t await = 0;
|
||||
void print();
|
||||
void encodeFrame(byte *frame);
|
||||
void decodeFrame(byte* frame);
|
||||
bool isRepeat(somfy_frame_t &f);
|
||||
void copy(somfy_frame_t &f);
|
||||
};
|
||||
|
||||
class SomfyRemote {
|
||||
|
|
@ -60,13 +66,18 @@ class SomfyShade : public SomfyRemote {
|
|||
uint64_t moveStart = 0;
|
||||
float startPos = 0.0;
|
||||
bool seekingPos = false;
|
||||
bool seekingMyPos = false;
|
||||
bool settingMyPos = false;
|
||||
uint32_t awaitMy = 0;
|
||||
public:
|
||||
void load();
|
||||
somfy_frame_t lastFrame;
|
||||
float currentPos = 0.0;
|
||||
//uint16_t movement = 0;
|
||||
int8_t direction = 0; // 0 = stopped, 1=down, -1=up.
|
||||
uint8_t position = 0;
|
||||
uint8_t target = 0;
|
||||
uint8_t myPos = 255;
|
||||
SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES];
|
||||
bool paired = false;
|
||||
bool fromJSON(JsonObject &obj);
|
||||
|
|
@ -87,6 +98,9 @@ class SomfyShade : public SomfyRemote {
|
|||
bool unlinkRemote(uint32_t remoteAddress);
|
||||
void emitState(const char *evt = "shadeState");
|
||||
void emitState(uint8_t num, const char *evt = "shadeState");
|
||||
void setMyPosition(uint8_t target);
|
||||
void moveToMyPosition();
|
||||
void processWaitingFrame();
|
||||
void publish();
|
||||
};
|
||||
|
||||
|
|
@ -207,6 +221,7 @@ class SomfyShadeController {
|
|||
void processFrame(somfy_frame_t &frame, bool internal = false);
|
||||
void emitState(uint8_t num = 255);
|
||||
void publish();
|
||||
void processWaitingFrame();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
2
Utils.h
2
Utils.h
|
|
@ -11,6 +11,8 @@
|
|||
#define D0 0
|
||||
#endif
|
||||
|
||||
#define DEBUG_SOMFY Serial
|
||||
|
||||
static void SETCHARPROP(char *prop, const char *value, size_t size) {strncpy(prop, value, size); prop[size - 1] = '\0';}
|
||||
namespace util {
|
||||
// Createa a custom to_string function. C++ can be annoying
|
||||
|
|
|
|||
124
Web.cpp
124
Web.cpp
|
|
@ -41,6 +41,9 @@ void Web::sendCORSHeaders() {
|
|||
//server.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
|
||||
//server.sendHeader(F("Access-Control-Allow-Headers"), F("*"));
|
||||
}
|
||||
void Web::sendCacheHeaders(uint32_t seconds) {
|
||||
server.sendHeader(F("Cache-Control"), F("public, max-age=604800, immutable"));
|
||||
}
|
||||
void Web::end() {
|
||||
//server.end();
|
||||
}
|
||||
|
|
@ -174,11 +177,11 @@ void Web::begin() {
|
|||
apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
}
|
||||
});
|
||||
|
||||
server.on("/upnp.xml", []() {
|
||||
SSDP.schema(server.client());
|
||||
});
|
||||
server.on("/", []() {
|
||||
webServer.sendCacheHeaders(604800);
|
||||
webServer.sendCORSHeaders();
|
||||
int statusCode = 200;
|
||||
// Load the index html page from the data directory.
|
||||
|
|
@ -204,6 +207,7 @@ void Web::begin() {
|
|||
file.close();
|
||||
});
|
||||
server.on("/main.css", []() {
|
||||
webServer.sendCacheHeaders(604800);
|
||||
webServer.sendCORSHeaders();
|
||||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file main.css");
|
||||
|
|
@ -216,6 +220,7 @@ void Web::begin() {
|
|||
file.close();
|
||||
});
|
||||
server.on("/icons.css", []() {
|
||||
webServer.sendCacheHeaders(604800);
|
||||
webServer.sendCORSHeaders();
|
||||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file icons.css");
|
||||
|
|
@ -227,6 +232,20 @@ void Web::begin() {
|
|||
server.streamFile(file, "text/css");
|
||||
file.close();
|
||||
});
|
||||
server.on("/favicon.png", []() {
|
||||
webServer.sendCacheHeaders(604800);
|
||||
webServer.sendCORSHeaders();
|
||||
|
||||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file favicon.png");
|
||||
File file = LittleFS.open("/favicon.png", "r");
|
||||
if (!file) {
|
||||
Serial.println("Error opening data/favicon.png");
|
||||
server.send(500, _encoding_text, "Unable to open data/icons.css");
|
||||
}
|
||||
server.streamFile(file, "image/png");
|
||||
file.close();
|
||||
});
|
||||
server.onNotFound([]() {
|
||||
Serial.print("Request 404:");
|
||||
HTTPMethod method = server.method();
|
||||
|
|
@ -505,6 +524,109 @@ void Web::begin() {
|
|||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
}
|
||||
});
|
||||
server.on("/setMyPosition", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
uint8_t shadeId = 255;
|
||||
uint8_t target = 255;
|
||||
|
||||
if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
if (server.hasArg("shadeId")) {
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
if(server.hasArg("target")) target = atoi(server.arg("target").c_str());
|
||||
}
|
||||
else if (server.hasArg("plain")) {
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
case DeserializationError::NoMemory:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}"));
|
||||
break;
|
||||
default:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}"));
|
||||
if(obj.containsKey("target")) {
|
||||
target = obj["target"].as<uint8_t>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
|
||||
}
|
||||
SomfyShade* shade = somfy.getShadeById(shadeId);
|
||||
if (shade) {
|
||||
// Send the command to the shade.
|
||||
if(target == 255) target = shade->myPos;
|
||||
if(target >= 0 && target <= 100)
|
||||
shade->setMyPosition(target);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
}
|
||||
});
|
||||
server.on("/setRollingCode", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
uint8_t shadeId = 255;
|
||||
uint16_t rollingCode = 0;
|
||||
if (server.hasArg("plain")) {
|
||||
// Its coming in the body.
|
||||
StaticJsonDocument<129> doc;
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
case DeserializationError::NoMemory:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}"));
|
||||
break;
|
||||
default:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
if(obj.containsKey("rollingCode")) rollingCode = obj["rollingCode"];
|
||||
}
|
||||
}
|
||||
else if (server.hasArg("shadeId")) {
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
rollingCode = atoi(server.arg("rollingCode").c_str());
|
||||
}
|
||||
SomfyShade* shade = nullptr;
|
||||
if (shadeId != 255) shade = somfy.getShadeById(shadeId);
|
||||
if (!shade) {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to set rolling code\"}"));
|
||||
}
|
||||
else {
|
||||
shade->setRollingCode(rollingCode);
|
||||
StaticJsonDocument<256> doc;
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
}
|
||||
});
|
||||
server.on("/pairShade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
|
|
|
|||
1
Web.h
1
Web.h
|
|
@ -3,6 +3,7 @@
|
|||
class Web {
|
||||
public:
|
||||
void sendCORSHeaders();
|
||||
void sendCacheHeaders(uint32_t seconds=604800);
|
||||
void startup();
|
||||
void begin();
|
||||
void loop();
|
||||
|
|
|
|||
BIN
data/favicon.png
Normal file
BIN
data/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
207
data/index.html
207
data/index.html
|
|
@ -5,6 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="main.css" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css" type="text/css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<script>
|
||||
class General {
|
||||
async init() {
|
||||
|
|
@ -211,6 +212,7 @@
|
|||
}
|
||||
somfy.closeEditShade();
|
||||
somfy.closeConfigTransceiver();
|
||||
|
||||
};
|
||||
};
|
||||
var general = new General();
|
||||
|
|
@ -427,6 +429,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
btnDown = null;
|
||||
btnTimer = null;
|
||||
setShadesList(shades) {
|
||||
let divCfg = '';
|
||||
let divCtl = '';
|
||||
|
|
@ -440,19 +444,157 @@
|
|||
divCfg += `<div class="button-outline" onclick="somfy.deleteShade(${shade.shadeId});"><i class="icss-trash"></i></div>`;
|
||||
divCfg += '</div>';
|
||||
|
||||
divCtl += `<div class="somfyShadeCtl" data-shadeId="${shade.shadeId}" data-remoteaddress="${shade.remoteAddress}" data-position="${shade.position}" data-target="${shade.target}">`;
|
||||
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}">`;
|
||||
divCtl += `<div style="display:inline-block;padding:7px;font-size:48px;vertical-align:middle;margin-top:-5px;" data-shadeid="${shade.shadeId}">`;
|
||||
divCtl += `<i class="somfy-shade-icon icss-window-shade" data-shadeid="${shade.shadeId}" style="--shade-position:${shade.position}%;vertical-align:top;" onclick="event.stopPropagation(); console.log(event); somfy.openSetPosition(${shade.shadeId});"></i></div >`;
|
||||
divCtl += `<span class="shadectl-name" style="font-size:1.5em;color:silver;display:inline-block;vertical-align:middle;white-space:nowrap;width:50px;">${shade.name}</span>`;
|
||||
divCtl += `<div style="display:inline-block;vertical-align:middle;width:50px;">`;
|
||||
divCtl += `<span class="shadectl-name" style="font-size:1.5em;color:silver;display:block;vertical-align:middle;white-space:nowrap;width:50px;">${shade.name}</span>`;
|
||||
divCtl += `<span style="white-space:nowrap;font-size:12px;"><label style="color:silver;">My: </label><span id="spanMyPos">${shade.myPos !== 255 ? shade.myPos + '%' : '---'}</span>`
|
||||
divCtl += '</div>'
|
||||
|
||||
divCtl += `<div class="shadectl-buttons" style="float:right;">`;
|
||||
divCtl += `<div class="button-outline" onclick="somfy.sendCommand(${shade.shadeId}, 'up');" style="display:inline-block;padding:7px;cursor:pointer;"><i class="icss-somfy-up"></i></div>`;
|
||||
divCtl += `<div class="button-outline" onclick="somfy.sendCommand(${shade.shadeId}, 'my');" style="display:inline-block;font-size:2em;padding:10px;cursor:pointer;"><span>my</span></div>`;
|
||||
divCtl += `<div class="button-outline my-button" data-shadeid="${shade.shadeId}" style="display:inline-block;font-size:2em;padding:10px;cursor:pointer;"><span>my</span></div>`;
|
||||
divCtl += `<div class="button-outline" onclick="somfy.sendCommand(${shade.shadeId}, 'down');" style="display:inline-block;padding:7px;cursor:pointer;"><i class="icss-somfy-down" style="margin-top:-4px;"></i></div>`;
|
||||
divCtl += '</div></div>';
|
||||
}
|
||||
document.getElementById('divShadeList').innerHTML = divCfg;
|
||||
document.getElementById('divShadeControls').innerHTML = divCtl;
|
||||
let shadeControls = document.getElementById('divShadeControls');
|
||||
shadeControls.innerHTML = divCtl;
|
||||
// Attach the timer for setting the My Position for the shade.
|
||||
let btns = shadeControls.querySelectorAll('div.my-button');
|
||||
for (let i = 0; i < btns.length; i++) {
|
||||
btns[i].addEventListener('mouseup', (event) => {
|
||||
console.log(this);
|
||||
console.log(event);
|
||||
if (this.btnTimer) {
|
||||
clearTimeout(this.btnTimer);
|
||||
this.btnTimer = null;
|
||||
}
|
||||
let shadeId = parseInt(event.currentTarget.getAttribute('data-shadeid'), 10);
|
||||
if (new Date().getTime() - this.btnDown > 2000) {
|
||||
event.preventDefault();
|
||||
}
|
||||
else {
|
||||
this.sendCommand(shadeId, 'my');
|
||||
}
|
||||
|
||||
}, true);
|
||||
btns[i].addEventListener('mousedown', (event) => {
|
||||
console.log(this);
|
||||
console.log(event);
|
||||
let shadeId = parseInt(event.currentTarget.getAttribute('data-shadeid'), 10);
|
||||
let el = event.currentTarget.closest('.somfyShadeCtl');
|
||||
this.btnDown = new Date().getTime();
|
||||
if (parseInt(el.getAttribute('data-direction'), 10) === 0) {
|
||||
this.btnTimer = setTimeout(() => {
|
||||
// Open up the set My Position dialog. We will allow the user to change the position to match
|
||||
// the desired position.
|
||||
this.openSetMyPosition(shadeId);
|
||||
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}, true);
|
||||
btns[i].addEventListener('touchstart', (event) => {
|
||||
let shadeId = parseInt(event.currentTarget.getAttribute('data-shadeid'), 10);
|
||||
let el = event.currentTarget.closest('.somfyShadeCtl');
|
||||
this.btnDown = new Date().getTime();
|
||||
if (parseInt(el.getAttribute('data-direction'), 10) === 0) {
|
||||
this.btnTimer = setTimeout(() => {
|
||||
// Open up the set My Position dialog. We will allow the user to change the position to match
|
||||
// the desired position.
|
||||
this.openSetMyPosition(shadeId);
|
||||
|
||||
}, 2000);
|
||||
}
|
||||
}, true);
|
||||
btns[i].addEventListener('touchend', (event) => {
|
||||
console.log(this);
|
||||
console.log(event);
|
||||
if (this.btnTimer) {
|
||||
clearTimeout(this.btnTimer);
|
||||
this.btnTimer = null;
|
||||
}
|
||||
let shadeId = parseInt(event.currentTarget.getAttribute('data-shadeid'), 10);
|
||||
if (new Date().getTime() - this.btnDown > 2000) {
|
||||
event.preventDefault();
|
||||
}
|
||||
else {
|
||||
this.sendCommand(shadeId, 'my');
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
closeShadePositioners() {
|
||||
let ctls = document.querySelectorAll('.shade-positioner');
|
||||
for (let i = 0; i < ctls.length; i++) {
|
||||
console.log('Closing shade positioner');
|
||||
ctls[i].remove();
|
||||
}
|
||||
}
|
||||
openSetMyPosition(shadeId) {
|
||||
if (typeof shadeId === 'undefined') {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
let shade = document.querySelector(`div.somfyShadeCtl[data-shadeid="${shadeId}"]`);
|
||||
if (shade) {
|
||||
this.closeShadePositioners();
|
||||
let currPos = parseInt(shade.getAttribute('data-position'), 10);
|
||||
let myPos = parseInt(shade.getAttribute('data-mypos'), 10);
|
||||
let elname = shade.querySelector(`.shadectl-name`);
|
||||
let shadeName = elname.innerHTML;
|
||||
let html = `<div class="shade-name">${shadeName}</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 += `<label for="slidShadeTarget"><span>Target Position </span><span><span id="spanShadeTarget" class="shade-target">${currPos}</span><span>%</span></span></label>`;
|
||||
html += `<hr></hr>`;
|
||||
html += '<div style="text-align:right;width:100%;">'
|
||||
if (myPos !== 255)
|
||||
html += `<div style="float:left;text-align:left;cursor:pointer;" onclick="document.getElementById('slidShadeTarget').value = ${myPos}; document.getElementById('slidShadeTarget').dispatchEvent(new Event('change'));"><span>Current: </span><span>${myPos}</span><span>%</span></div>`
|
||||
if (myPos === currPos)
|
||||
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 += `</div></div>`;
|
||||
let div = document.createElement('div');
|
||||
div.setAttribute('class', 'shade-positioner shade-my-positioner');
|
||||
div.setAttribute('data-shadeid', shadeId);
|
||||
div.style.height = 'auto';
|
||||
div.innerHTML = html;
|
||||
shade.appendChild(div);
|
||||
let elTarget = div.querySelector('input#slidShadeTarget');
|
||||
let elBtn = div.querySelector('button#btnSetMyPosition');
|
||||
elTarget.addEventListener('change', (event) => {
|
||||
console.log(`Target: ${elTarget.value} myPos: ${myPos}`);
|
||||
div.querySelector('#spanShadeTarget').innerHTML = elTarget.value;
|
||||
if (parseInt(elTarget.value, 10) === myPos) {
|
||||
elBtn.innerHTML = 'Clear My Position';
|
||||
elBtn.style.background = 'orangered';
|
||||
}
|
||||
else {
|
||||
elBtn.innerHTML = 'Set My Position';
|
||||
elBtn.style.background = '';
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
sendShadeMyPosition(shadeId, pos) {
|
||||
console.log(`Sending My Position for shade id ${shadeId} to ${pos}`);
|
||||
let overlay = waitMessage(document.getElementById('divContainer'));
|
||||
putJSON('/setMyPosition', { shadeId: shadeId, target: pos }, (err, response) => {
|
||||
this.closeShadePositioners();
|
||||
overlay.remove();
|
||||
console.log(response);
|
||||
});
|
||||
}
|
||||
setLinkedRemotesList(shade) {
|
||||
let divCfg = '';
|
||||
for (let i = 0; i < shade.linkedRemotes.length; i++) {
|
||||
|
|
@ -501,9 +643,14 @@
|
|||
}
|
||||
let divs = document.querySelectorAll(`.somfyShadeCtl[data-shadeid="${state.shadeId}"]`);
|
||||
for (let i = 0; i < divs.length; i++) {
|
||||
divs[i].setAttribute('data-direction', state.direction);
|
||||
divs[i].setAttribute('data-position', state.position);
|
||||
divs[i].setAttribute('data-target', state.target);
|
||||
divs[i].setAttribute('data-mypos', state.mypos);
|
||||
let span = divs[i].querySelector('#spanMyPos');
|
||||
if (span) span.innerHTML = typeof state.mypos !== 'undefined' && state.mypos !== 255 ? `${state.mypos}%` : '---';
|
||||
}
|
||||
|
||||
};
|
||||
procRemoteFrame(frame) {
|
||||
console.log(frame);
|
||||
|
|
@ -556,6 +703,7 @@
|
|||
document.getElementsByName('shadeName')[0].value = '';
|
||||
document.getElementsByName('shadeAddress')[0].value = shade.remoteAddress;
|
||||
document.getElementById('divLinkedRemoteList').innerHTML = '';
|
||||
document.getElementById('btnSetRollingCode').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -606,6 +754,9 @@
|
|||
if (el) el.remove();
|
||||
el = document.getElementById('divPairing');
|
||||
if (el) el.remove();
|
||||
el = document.getElementById('frmSetRollingCode');
|
||||
if (el) el.remove();
|
||||
|
||||
};
|
||||
saveShade() {
|
||||
let shadeId = parseInt(document.getElementById('spanShadeId').innerText, 10);
|
||||
|
|
@ -652,6 +803,7 @@
|
|||
else {
|
||||
document.getElementById('btnPairShade').style.display = 'inline-block';
|
||||
}
|
||||
document.getElementById('btnSetRollingCode').style.display = '';
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -764,6 +916,48 @@
|
|||
}
|
||||
});
|
||||
};
|
||||
setRollingCode(shadeId, rollingCode) {
|
||||
let dlg = document.getElementById('frmSetRollingCode');
|
||||
let overlay = waitMessage(dlg || document.getElementById('fsSomfySettings'));
|
||||
putJSON('/setRollingCode', { shadeId: shadeId, rollingCode: rollingCode }, (err, shade) => {
|
||||
overlay.remove();
|
||||
if (err) {
|
||||
serviceError(document.getElementById('fsSomfySettings'), err);
|
||||
}
|
||||
else {
|
||||
if (dlg) dlg.remove();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
openSetRollingCode(shadeId) {
|
||||
let overlay = waitMessage(document.getElementById('fsSomfySettings'));
|
||||
getJSON(`/shade?shadeId=${shadeId}`, (err, shade) => {
|
||||
overlay.remove();
|
||||
if (err) {
|
||||
serviceError(document.getElementById('fsSomfySettings'), err);
|
||||
}
|
||||
else {
|
||||
console.log(shade);
|
||||
let div = document.createElement('div');
|
||||
let html = `<form id="frmSetRollingCode"><div id="divRollingCode" class="instructions" data-shadeid="${shadeId}">`;
|
||||
html += '<div style="width:100%;color:red;text-align:center;font-weight:bold;"><span style="background:yellow;padding:10px;display:inline-block;border-radius:5px;background:white;">BEWARE ... WARNING ... DANGER<span></div>';
|
||||
html += '<hr style="width:100%;margin:0px;"></hr>';
|
||||
html += '<p style="font-size:14px;">If this shade is already paired with a motor then changing the rolling code WILL cause it to stop working. Rolling codes are tied to the remote address and the Somfy motor expects these to be sequential.</p>';
|
||||
html += '<p style="font-size:14px;">If you hesitated just a little bit do not press the red button. Green represents safety so press it, wipe the sweat from your brow, and go through the normal pairing process.'
|
||||
html += '<div class="field-group" style="border-radius:5px;background:white;width:50%;margin-left:25%;text-align:center">';
|
||||
html += `<input id="fldNewRollingCode" min="0" max="65535" name="newRollingCode" type="number" length="12" style="text-align:center;font-size:24px;" placeholder="New Code" value="${shade.lastRollingCode}"></input>`;
|
||||
html += '<label for="fldNewRollingCode">Rolling Code</label>';
|
||||
html += '</div>'
|
||||
html += `<div class="button-container">`
|
||||
html += `<button id="btnChangeRollingCode" type="button" style="padding-left:20px;padding-right:20px;display:inline-block;background:orangered;" onclick="somfy.setRollingCode(${shadeId}, parseInt(document.getElementById('fldNewRollingCode').value, 10));">Set Rolling Code</button>`
|
||||
html += `<button id="btnCancel" type="button" style="padding-left:20px;padding-right:20px;display:inline-block;background:lawngreen;color:gray" onclick="document.getElementById('frmSetRollingCode').remove();">Cancel</button>`
|
||||
html += `</div><form>`;
|
||||
div.innerHTML = html;
|
||||
document.getElementById('somfyShade').appendChild(div);
|
||||
}
|
||||
});
|
||||
}
|
||||
pairShade(shadeId) {
|
||||
let div = document.createElement('div');
|
||||
let html = `<div id="divPairing" class="instructions" data-type="link-remote" data-shadeid="${shadeId}">`;
|
||||
|
|
@ -1176,7 +1370,6 @@
|
|||
el.appendChild(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
function promptMessage(el, msg, onYes) {
|
||||
let div = document.createElement('div');
|
||||
div.innerHTML = '<div class="innerError">' + msg + '</div><button id="btnYes" type="button">Yes</button><button type="button" onclick="clearErrors();">No</button></div>';
|
||||
|
|
@ -1514,7 +1707,9 @@
|
|||
<button id="btnUnpairShade" type="button" onclick="somfy.unpairShade(parseInt(document.getElementById('spanShadeId').innerText, 10));" style="display:inline-block;width:47%;">
|
||||
Unpair Shade
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="button-container" style="margin-top:-10px;padding-left:7px;padding-right:7px;">
|
||||
<button id="btnChangeRollingCode" type="button" onclick="somfy.openSetRollingCode(parseInt(document.getElementById('spanShadeId').innerText, 10));">Set Rolling Code</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="divLinkedRemoteList" style="overflow-y:auto;max-height:77px;"></div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue