Add dry contact support for IO Remote and Relays #165

This commit is contained in:
Robert Strouse 2023-10-10 11:41:21 -07:00
parent 87995bf707
commit bb36ed0f82
9 changed files with 194 additions and 122 deletions

View file

@ -492,6 +492,15 @@ bool EthernetSettings::toJSON(JsonObject &obj) {
obj["MDIOPin"] = this->MDIOPin;
return true;
}
bool EthernetSettings::usesPin(uint8_t pin) {
if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true;
else if(this->CLKMode == 2 && pin == 16) return true;
else if(this->CLKMode == 3 && pin == 17) return true;
else if(this->PWRPin == pin) return true;
else if(this->MDCPin == pin) return true;
else if(this->MDIOPin == pin) return true;
return false;
}
bool EthernetSettings::save() {
pref.begin("ETH");
pref.clear();

View file

@ -76,6 +76,7 @@ class EthernetSettings: BaseSettings {
bool load();
bool save();
void print();
bool usesPin(uint8_t pin);
};
class IPSettings: BaseSettings {
public:

141
Somfy.cpp
View file

@ -806,24 +806,32 @@ void SomfyShade::setGPIOs() {
int8_t dir = this->direction;
if(dir == 0 && this->tiltType == tilt_types::integrated)
dir = this->tiltDirection;
if(this->shadeType == shade_types::drycontact) {
digitalWrite(this->gpioDown, this->currentPos == 100 ? HIGH : LOW);
this->gpioDir = this->currentPos == 100 ? 1 : -1;
}
else {
switch(dir) {
case -1:
digitalWrite(this->gpioDown, LOW);
digitalWrite(this->gpioUp, HIGH);
if(dir != this->gpioDir) Serial.printf("UP: true, DOWN: false\n");
this->gpioDir = dir;
break;
case 1:
digitalWrite(this->gpioUp, LOW);
digitalWrite(this->gpioDown, HIGH);
if(dir != this->gpioDir) Serial.printf("UP: false, DOWN: true\n");
this->gpioDir = dir;
break;
default:
digitalWrite(this->gpioUp, LOW);
digitalWrite(this->gpioDown, LOW);
if(dir != this->gpioDir) Serial.printf("UP: false, DOWN: false\n");
this->gpioDir = dir;
break;
}
this->gpioDir = dir;
}
}
else if(this->proto == radio_proto::GP_Remote) {
if(millis() > this->gpioRelease) {
@ -834,49 +842,62 @@ void SomfyShade::setGPIOs() {
}
}
}
void SomfyRemote::triggerGPIOs(somfy_frame_t &frame) {
void SomfyRemote::triggerGPIOs(somfy_frame_t &frame) { }
void SomfyShade::triggerGPIOs(somfy_frame_t &frame) {
if(this->proto == radio_proto::GP_Remote) {
int8_t dir = 0;
switch(frame.cmd) {
case somfy_commands::My:
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) {
digitalWrite(this->gpioUp, LOW);
digitalWrite(this->gpioDown, LOW);
digitalWrite(this->gpioMy, HIGH);
dir = 0;
if(dir != this->gpioDir) Serial.printf("UP: false, DOWN: false, MY: true\n");
}
break;
case somfy_commands::Up:
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) {
digitalWrite(this->gpioMy, LOW);
digitalWrite(this->gpioDown, LOW);
digitalWrite(this->gpioUp, HIGH);
dir = -1;
Serial.printf("UP: true, DOWN: false, MY: false\n");
}
break;
case somfy_commands::Toggle:
case somfy_commands::Down:
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) {
digitalWrite(this->gpioMy, LOW);
digitalWrite(this->gpioUp, LOW);
}
digitalWrite(this->gpioDown, HIGH);
dir = 1;
Serial.printf("UP: false, DOWN: true, MY: false\n");
break;
case somfy_commands::MyUp:
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) {
digitalWrite(this->gpioDown, LOW);
digitalWrite(this->gpioMy, HIGH);
digitalWrite(this->gpioUp, HIGH);
Serial.printf("UP: true, DOWN: false, MY: true\n");
}
break;
case somfy_commands::MyDown:
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) {
digitalWrite(this->gpioUp, LOW);
digitalWrite(this->gpioMy, HIGH);
digitalWrite(this->gpioDown, HIGH);
Serial.printf("UP: true, DOWN: false, MY: true\n");
Serial.printf("UP: false, DOWN: true, MY: true\n");
}
break;
case somfy_commands::MyUpDown:
if(this->shadeType != shade_types::drycontact && this->shadeType != shade_types::garage1) {
digitalWrite(this->gpioUp, HIGH);
digitalWrite(this->gpioMy, HIGH);
digitalWrite(this->gpioDown, HIGH);
Serial.printf("UP: true, DOWN: true, MY: true\n");
}
break;
}
this->gpioRelease = millis() + (frame.repeats * 200);
@ -2492,8 +2513,50 @@ bool SomfyShade::save() {
return true;
}
bool SomfyGroup::save() { somfy.commit(); return true; }
bool SomfyShade::usesPin(uint8_t pin) {
if(this->proto != radio_proto::GP_Remote && this->proto != radio_proto::GP_Relay) return false;
if(this->gpioDown == pin) return true;
else if(this->shadeType == shade_types::drycontact)
return this->gpioDown == pin;
else if(this->shadeType == shade_types::garage1) {
if(this->proto == radio_proto::GP_Relay && this->gpioUp == pin) return true;
}
else {
if(this->gpioUp == pin) return true;
else if(this->proto == radio_proto::GP_Remote && this->gpioMy == pin) return true;
}
return false;
}
int8_t SomfyShade::validateJSON(JsonObject &obj) {
int8_t ret = 0;
shade_types type = this->shadeType;
if(obj.containsKey("shadeType")) {
if(obj["shadeType"].is<const char *>()) {
if(strncmp(obj["shadeType"].as<const char *>(), "roller", 7) == 0)
type = shade_types::roller;
else if(strncmp(obj["shadeType"].as<const char *>(), "ldrapery", 9) == 0)
type = shade_types::ldrapery;
else if(strncmp(obj["shadeType"].as<const char *>(), "rdrapery", 9) == 0)
type = shade_types::rdrapery;
else if(strncmp(obj["shadeType"].as<const char *>(), "cdrapery", 9) == 0)
type = shade_types::cdrapery;
else if(strncmp(obj["shadeType"].as<const char *>(), "garage1", 7) == 0)
type = shade_types::garage1;
else if(strncmp(obj["shadeType"].as<const char *>(), "garage3", 7) == 0)
type = shade_types::garage3;
else if(strncmp(obj["shadeType"].as<const char *>(), "blind", 5) == 0)
type = shade_types::blind;
else if(strncmp(obj["shadeType"].as<const char *>(), "awning", 7) == 0)
type = shade_types::awning;
else if(strncmp(obj["shadeType"].as<const char *>(), "shutter", 8) == 0)
type = shade_types::shutter;
else if(strncmp(obj["shadeType"].as<const char *>(), "drycontact", 11) == 0)
type = shade_types::drycontact;
}
else {
this->shadeType = static_cast<shade_types>(obj["shadeType"].as<uint8_t>());
}
}
if(obj.containsKey("proto")) {
radio_proto proto = this->proto;
if(proto == radio_proto::GP_Relay || proto == radio_proto::GP_Remote) {
@ -2502,64 +2565,30 @@ int8_t SomfyShade::validateJSON(JsonObject &obj) {
uint8_t upPin = obj.containsKey("gpioUp") ? obj["gpioUp"].as<uint8_t>() : this->gpioUp;
uint8_t downPin = obj.containsKey("gpioDown") ? obj["gpioDown"].as<uint8_t>() : this->gpioDown;
uint8_t myPin = obj.containsKey("gpioMy") ? obj["gpioMy"].as<uint8_t>() : this->gpioMy;
if(type == shade_types::drycontact || (type == shade_types::garage1 && proto == radio_proto::GP_Remote)) upPin = myPin = 255;
if(proto == radio_proto::GP_Relay) myPin = 255;
if(somfy.transceiver.config.enabled) {
if(somfy.transceiver.config.SCKPin == upPin || somfy.transceiver.config.SCKPin == downPin ||
somfy.transceiver.config.TXPin == upPin || somfy.transceiver.config.TXPin == downPin ||
somfy.transceiver.config.RXPin == upPin || somfy.transceiver.config.RXPin == downPin ||
somfy.transceiver.config.MOSIPin == upPin || somfy.transceiver.config.MOSIPin == downPin ||
somfy.transceiver.config.MISOPin == upPin || somfy.transceiver.config.MISOPin == downPin ||
somfy.transceiver.config.CSNPin == upPin || somfy.transceiver.config.CSNPin == downPin)
ret = -10; // Pin in use with transceiver.
else if(proto == radio_proto::GP_Remote) {
if(somfy.transceiver.config.SCKPin == myPin ||
somfy.transceiver.config.TXPin == myPin ||
somfy.transceiver.config.RXPin == myPin ||
somfy.transceiver.config.MOSIPin == myPin ||
somfy.transceiver.config.MISOPin == myPin ||
somfy.transceiver.config.CSNPin == myPin)
ret = -10; // Pin in use with transceiver.
}
if((upPin != 255 && somfy.transceiver.usesPin(upPin)) ||
(downPin != 255 && somfy.transceiver.usesPin(downPin)) ||
(myPin != 255 && somfy.transceiver.usesPin(myPin)))
ret = -10;
}
if(settings.connType == conn_types::ethernet || settings.connType == conn_types::ethernetpref) {
if((settings.Ethernet.CLKMode == 0 || settings.Ethernet.CLKMode == 1) && (upPin == 0 || downPin == 0))
ret = -11; // Pin in use with ethernet.
else if(proto == radio_proto::GP_Remote && ((settings.Ethernet.CLKMode == 0 || settings.Ethernet.CLKMode == 1) && myPin == 0))
ret = -11; // Pin in use with ethernet.
else if((settings.Ethernet.CLKMode == 2 && (upPin == 16 || downPin == 16)) ||
(settings.Ethernet.CLKMode == 3 && (upPin == 17 || downPin == 17)))
ret = -11;
else if(proto == radio_proto::GP_Remote && (settings.Ethernet.CLKMode == 2 && myPin == 16 || settings.Ethernet.CLKMode == 3 && myPin == 17))
ret = -11;
else if(settings.Ethernet.PWRPin == upPin || settings.Ethernet.PWRPin == downPin ||
settings.Ethernet.MDCPin == upPin || settings.Ethernet.MDCPin == downPin ||
settings.Ethernet.MDIOPin == upPin || settings.Ethernet.MDIOPin == downPin)
ret = -11;
else if(proto == radio_proto::GP_Remote && (settings.Ethernet.PWRPin == myPin ||
settings.Ethernet.MDCPin == myPin || settings.Ethernet.MDIOPin == myPin))
if((upPin != 255 && settings.Ethernet.usesPin(upPin)) ||
(downPin != 255 && somfy.transceiver.usesPin(downPin)) ||
(myPin != 255 && somfy.transceiver.usesPin(myPin)))
ret = -11;
}
if(ret == 0) {
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
SomfyShade *shade = &somfy.shades[i];
if(shade->getShadeId() == this->getShadeId() || shade->getShadeId() == 255) continue;
if(shade->proto == radio_proto::GP_Relay || shade->proto == radio_proto::GP_Remote) {
if(shade->gpioUp == upPin || shade->gpioDown == upPin || shade->gpioDown == upPin || shade->gpioDown == downPin) {
if((upPin != 255 && shade->usesPin(upPin)) ||
(downPin != 255 && shade->usesPin(downPin)) ||
(myPin != 255 && shade->usesPin(myPin))){
ret = -12;
break;
}
else if(proto == radio_proto::GP_Remote && (shade->gpioUp == myPin || shade->gpioDown == myPin)) {
ret = -12;
break;
}
else if(shade->proto == radio_proto::GP_Remote && (shade->gpioMy == upPin || shade->gpioMy == downPin)) {
ret = -12;
break;
}
else if(shade->proto == radio_proto::GP_Remote && proto == radio_proto::GP_Remote && (shade->gpioMy == myPin)) {
ret = -12;
break;
}
}
}
}
}
@ -2600,6 +2629,8 @@ int8_t SomfyShade::fromJSON(JsonObject &obj) {
this->shadeType = shade_types::awning;
else if(strncmp(obj["shadeType"].as<const char *>(), "shutter", 8) == 0)
this->shadeType = shade_types::shutter;
else if(strncmp(obj["shadeType"].as<const char *>(), "drycontact", 11) == 0)
this->shadeType = shade_types::drycontact;
}
else {
this->shadeType = static_cast<shade_types>(obj["shadeType"].as<uint8_t>());
@ -3708,6 +3739,18 @@ bool Transceiver::fromJSON(JsonObject& obj) {
}
return true;
}
bool Transceiver::usesPin(uint8_t pin) {
if(this->config.enabled) {
if(this->config.SCKPin == pin ||
this->config.TXPin == pin ||
this->config.RXPin == pin ||
this->config.MOSIPin == pin ||
this->config.MISOPin == pin ||
this->config.CSNPin == pin)
return true;
}
return false;
}
bool Transceiver::save() {
this->config.save();
this->config.apply();

View file

@ -214,7 +214,7 @@ class SomfyRemote {
void repeatFrame(uint8_t repeat);
virtual uint16_t p_lastRollingCode(uint16_t code);
somfy_commands transformCommand(somfy_commands cmd);
void triggerGPIOs(somfy_frame_t &frame);
virtual void triggerGPIOs(somfy_frame_t &frame);
};
class SomfyLinkedRemote : public SomfyRemote {
@ -304,7 +304,8 @@ class SomfyShade : public SomfyRemote {
void clear();
int8_t transformPosition(float fpos);
void setGPIOs();
void triggerGPIOs(somfy_frame_t &frame);
bool usesPin(uint8_t pin);
// State Setters
int8_t p_direction(int8_t dir);
int8_t p_tiltDirection(int8_t dir);
@ -450,6 +451,7 @@ class Transceiver {
void endFrequencyScan();
void processFrequencyScan(bool received = false);
void emitFrequencyScan(uint8_t num = 255);
bool usesPin(uint8_t pin);
};
class SomfyShadeController {
protected:

Binary file not shown.

Binary file not shown.

View file

@ -293,8 +293,8 @@
<option value="0">RTS</option>
<option value="1">RTW</option>
<option value="2">RTV</option>
<option value="8">Relay</option>
<option value="9">Remote</option>
<option value="8">IO-Relay</option>
<option value="9">IO-Remote</option>
</select>
<label for="selShadeProto">Protocol</label>
</div>
@ -306,12 +306,12 @@
<label for="selShadeBitLength">Bit Length</label>
</div>
<div id="divGPIOControl" class="field-group">
<div class="field-group" style="">
<div id="divGPIOUp" class="field-group" style="">
<select id="selShadeGPIOUp" data-bind="gpioUp" data-datatype="int" style="width:70px;">
</select>
<label for="selShadeGPIOUp">UP</label>
</div>
<div class="field-group" style="">
<div id="divGPIODown" class="field-group" style="">
<select id="selShadeGPIODown" data-bind="gpioDown" data-datatype="int" style="width:70px;">
</select>
<label for="selShadeGPIODown">Down</label>

View file

@ -1,7 +1,7 @@
var errors = [
{ code: -10, desc: "Pin setting in use for Transceiver" },
{ code: -11, desc: "Pin setting in use for Ethernet Adapter" },
{ code: -12, desc: "Pin setting in use on another motor" }
{ code: -10, desc: "Pin setting in use for Transceiver. Output pins cannot be re-used." },
{ code: -11, desc: "Pin setting in use for Ethernet Adapter. Output pins cannot be re-used." },
{ code: -12, desc: "Pin setting in use on another motor. Output pins cannot be re-used." }
]
document.oncontextmenu = (event) => {
if (event.target && event.target.tagName.toLowerCase() === 'input' && (event.target.type.toLowerCase() === 'text' || event.target.type.toLowerCase() === 'password'))
@ -3112,16 +3112,26 @@ class Somfy {
valid = false;
}
if (obj.proto === 8 || obj.proto === 9) {
switch (obj.shadeType) {
case 5: // Garage 1-button
if (obj.proto !== 9 && obj.gpioUp === obj.gpioDown) {
ui.errorMessage(document.getElementById('divSomfySettings'), 'For GPIO controlled motors the up and down GPIO selections must be unique.');
valid = false;
}
break;
case 9: // Dry contact.
break;
default:
if (obj.gpioUp === obj.gpioDown) {
ui.errorMessage(document.getElementById('divSomfySettings'), 'For GPIO controlled motors the up and down GPIO selections must be unique.');
valid = false;
}
}
if (obj.proto === 9) {
if (obj.gpioMy === obj.gpioUp || obj.gpioMy === obj.gpioDown) {
else if (obj.proto === 9 && (obj.gpioMy === obj.gpioUp || obj.gpioMy === obj.gpioDown)) {
ui.errorMessage(document.getElementById('divSomfySettings'), 'For GPIO controlled motors the up and down and my GPIO selections must be unique.');
valid = false;
}
break;
}
}
if (valid) {
if (isNaN(shadeId) || shadeId >= 255) {

View file

@ -167,7 +167,14 @@
#somfyShade[data-shadetype="6"] #divStepSettings {
display: none;
}
#somfyShade[data-proto="9"][data-shadetype="5"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="5"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="5"] #divGPIOMy,
#somfyShade[data-proto="9"][data-shadetype="9"] #divGPIOUp,
#somfyShade[data-proto="9"][data-shadetype="9"] #divGPIOMy,
#somfyShade[data-proto="8"][data-shadetype="9"] #divGPIOUp {
display: none;
}
.group-draggable,
.shade-draggable {
height: 32px;