GPIO motor support #165 Publish only changes #167

This commit is contained in:
Robert Strouse 2023-10-05 14:52:54 -07:00
parent 1cb9746cc8
commit 4d49a048f4
11 changed files with 737 additions and 301 deletions

View file

@ -7,9 +7,9 @@
extern Preferences pref; extern Preferences pref;
#define SHADE_HDR_VER 14 #define SHADE_HDR_VER 15
#define SHADE_HDR_SIZE 56 #define SHADE_HDR_SIZE 56
#define SHADE_REC_SIZE 256 #define SHADE_REC_SIZE 264
#define GROUP_REC_SIZE 184 #define GROUP_REC_SIZE 184
#define TRANS_REC_SIZE 74 #define TRANS_REC_SIZE 74
@ -657,6 +657,11 @@ bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) {
if(this->header.version >= 12) shade->repeats = this->readUInt8(1); if(this->header.version >= 12) shade->repeats = this->readUInt8(1);
if(this->header.version >= 13) shade->sortOrder = this->readUInt8(shade->getShadeId() - 1); if(this->header.version >= 13) shade->sortOrder = this->readUInt8(shade->getShadeId() - 1);
else shade->sortOrder = shade->getShadeId() - 1; else shade->sortOrder = shade->getShadeId() - 1;
if(this->header.version > 14) {
shade->gpioUp = this->readUInt8(shade->gpioUp);
shade->gpioDown = this->readUInt8(shade->gpioDown);
}
if(shade->getShadeId() == 255) shade->clear(); if(shade->getShadeId() == 255) shade->clear();
else if(shade->tiltType == tilt_types::tiltonly) { else if(shade->tiltType == tilt_types::tiltonly) {
shade->myPos = shade->currentPos = shade->target = 100.0f; shade->myPos = shade->currentPos = shade->target = 100.0f;
@ -758,7 +763,9 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
this->writeBool(shade->flipCommands); this->writeBool(shade->flipCommands);
this->writeBool(shade->flipPosition); this->writeBool(shade->flipPosition);
this->writeUInt8(shade->repeats); this->writeUInt8(shade->repeats);
this->writeUInt8(shade->sortOrder, CFG_REC_END); this->writeUInt8(shade->sortOrder);
this->writeUInt8(shade->gpioUp);
this->writeUInt8(shade->gpioDown, CFG_REC_END);
return true; return true;
} }
bool ShadeConfigFile::writeSettingsRecord() { bool ShadeConfigFile::writeSettingsRecord() {

View file

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

View file

@ -186,9 +186,13 @@ bool MQTTClass::connect() {
snprintf(this->clientId, sizeof(this->clientId), "client-%08x%08x", (uint32_t)((mac >> 32) & 0xFFFFFFFF), (uint32_t)(mac & 0xFFFFFFFF)); snprintf(this->clientId, sizeof(this->clientId), "client-%08x%08x", (uint32_t)((mac >> 32) & 0xFFFFFFFF), (uint32_t)(mac & 0xFFFFFFFF));
if(strlen(settings.MQTT.protocol) > 0 && strlen(settings.MQTT.hostname) > 0) { if(strlen(settings.MQTT.protocol) > 0 && strlen(settings.MQTT.hostname) > 0) {
mqttClient.setServer(settings.MQTT.hostname, settings.MQTT.port); mqttClient.setServer(settings.MQTT.hostname, settings.MQTT.port);
if(mqttClient.connect(this->clientId, settings.MQTT.username, settings.MQTT.password)) { char lwtTopic[128] = "status";
if(strlen(settings.MQTT.rootTopic) > 0)
snprintf(lwtTopic, sizeof(lwtTopic), "%s/status", settings.MQTT.rootTopic);
if(mqttClient.connect(this->clientId, settings.MQTT.username, settings.MQTT.password, lwtTopic, 0, true, "offline")) {
Serial.print("Successfully connected MQTT client "); Serial.print("Successfully connected MQTT client ");
Serial.println(this->clientId); Serial.println(this->clientId);
this->publish("status", "online", true);
somfy.publish(); somfy.publish();
this->subscribe("shades/+/target/set"); this->subscribe("shades/+/target/set");
this->subscribe("shades/+/tiltTarget/set"); this->subscribe("shades/+/tiltTarget/set");

706
Somfy.cpp

File diff suppressed because it is too large Load diff

32
Somfy.h
View file

@ -16,15 +16,17 @@
#define SOMFY_NO_WIND_TIMEOUT MINS_TO_MILLIS(12) #define SOMFY_NO_WIND_TIMEOUT MINS_TO_MILLIS(12)
#define SOMFY_NO_WIND_REMOTE_TIMEOUT SECS_TO_MILLIS(30) #define SOMFY_NO_WIND_REMOTE_TIMEOUT SECS_TO_MILLIS(30)
struct appver_t { struct appver_t {
uint8_t major; uint8_t major;
uint8_t minor; uint8_t minor;
uint8_t build; uint8_t build;
}; };
enum class radio_proto : byte { enum class radio_proto : byte { // Ordinal byte 0-255
RTS = 0x00, RTS = 0x00,
RTW = 0x01, RTW = 0x01,
RTV = 0x02 RTV = 0x02,
GPIO = 0x08
}; };
enum class somfy_commands : byte { enum class somfy_commands : byte {
Unknown0 = 0x0, Unknown0 = 0x0,
@ -183,6 +185,8 @@ class SomfyRemote {
uint32_t m_remoteAddress = 0; uint32_t m_remoteAddress = 0;
public: public:
radio_proto proto = radio_proto::RTS; radio_proto proto = radio_proto::RTS;
uint8_t gpioUp = 0;
uint8_t gpioDown = 0;
somfy_frame_t lastFrame; somfy_frame_t lastFrame;
bool flipCommands = false; bool flipCommands = false;
uint16_t lastRollingCode = 0; uint16_t lastRollingCode = 0;
@ -204,6 +208,7 @@ class SomfyRemote {
virtual void sendCommand(somfy_commands cmd, uint8_t repeat); virtual void sendCommand(somfy_commands cmd, uint8_t repeat);
void sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat); void sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat);
void repeatFrame(uint8_t repeat); void repeatFrame(uint8_t repeat);
virtual uint16_t p_lastRollingCode(uint16_t code);
somfy_commands transformCommand(somfy_commands cmd); somfy_commands transformCommand(somfy_commands cmd);
}; };
class SomfyLinkedRemote : public SomfyRemote { class SomfyLinkedRemote : public SomfyRemote {
@ -231,6 +236,7 @@ class SomfyShade : public SomfyRemote {
bool settingTiltPos = false; bool settingTiltPos = false;
uint32_t awaitMy = 0; uint32_t awaitMy = 0;
public: public:
int8_t gpioDir = 0;
int8_t sortOrder = 0; int8_t sortOrder = 0;
bool flipPosition = false; bool flipPosition = false;
shade_types shadeType = shade_types::roller; shade_types shadeType = shade_types::roller;
@ -249,8 +255,9 @@ class SomfyShade : public SomfyRemote {
float myTiltPos = -1.0f; float myTiltPos = -1.0f;
SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES]; SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES];
bool paired = false; bool paired = false;
int8_t validateJSON(JsonObject &obj);
bool toJSONRef(JsonObject &obj); bool toJSONRef(JsonObject &obj);
bool fromJSON(JsonObject &obj); int8_t fromJSON(JsonObject &obj);
bool toJSON(JsonObject &obj) override; bool toJSON(JsonObject &obj) override;
char name[21] = ""; char name[21] = "";
void setShadeId(uint8_t id) { shadeId = id; } void setShadeId(uint8_t id) { shadeId = id; }
@ -289,8 +296,27 @@ class SomfyShade : public SomfyRemote {
void commitShadePosition(); void commitShadePosition();
void commitTiltPosition(); void commitTiltPosition();
void commitMyPosition(); void commitMyPosition();
void setGPIOs();
void clear(); void clear();
int8_t transformPosition(float fpos); int8_t transformPosition(float fpos);
// State Setters
int8_t p_direction(int8_t dir);
int8_t p_tiltDirection(int8_t dir);
float p_target(float target);
float p_tiltTarget(float target);
float p_myPos(float pos);
float p_myTiltPos(float pos);
bool p_flag(somfy_flags_t flag, bool val);
bool p_sunFlag(bool val);
bool p_sunny(bool val);
bool p_windy(bool val);
uint16_t p_lastRollingCode(uint16_t code);
bool publish(const char *topic, uint8_t val, bool retain = false);
bool publish(const char *topic, int8_t val, bool retain = false);
bool publish(const char *topic, uint32_t val, bool retain = false);
bool publish(const char *topic, uint16_t val, bool retain = false);
bool publish(const char *topic, bool val, bool retain = false);
}; };
class SomfyGroup : public SomfyRemote { class SomfyGroup : public SomfyRemote {
protected: protected:

Binary file not shown.

Binary file not shown.

42
Web.cpp
View file

@ -593,13 +593,19 @@ void Web::handleShade(WebServer &server) {
if (obj.containsKey("shadeId")) { if (obj.containsKey("shadeId")) {
SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); SomfyShade* shade = somfy.getShadeById(obj["shadeId"]);
if (shade) { if (shade) {
shade->fromJSON(obj); uint8_t err = shade->fromJSON(obj);
shade->save(); if(err == 0) {
DynamicJsonDocument sdoc(2048); shade->save();
JsonObject sobj = sdoc.to<JsonObject>(); DynamicJsonDocument sdoc(2048);
shade->toJSON(sobj); JsonObject sobj = sdoc.to<JsonObject>();
serializeJson(sdoc, g_content); shade->toJSON(sobj);
server.send(200, _encoding_json, g_content); serializeJson(sdoc, g_content);
server.send(200, _encoding_json, g_content);
}
else {
snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err);
server.send(500, _encoding_json, g_content);
}
} }
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}"));
} }
@ -1192,13 +1198,19 @@ void Web::begin() {
if (obj.containsKey("shadeId")) { if (obj.containsKey("shadeId")) {
SomfyShade* shade = somfy.getShadeById(obj["shadeId"]); SomfyShade* shade = somfy.getShadeById(obj["shadeId"]);
if (shade) { if (shade) {
shade->fromJSON(obj); int8_t err = shade->fromJSON(obj);
shade->save(); if(err == 0) {
DynamicJsonDocument sdoc(512); shade->save();
JsonObject sobj = sdoc.to<JsonObject>(); DynamicJsonDocument sdoc(512);
shade->toJSON(sobj); JsonObject sobj = sdoc.to<JsonObject>();
serializeJson(sdoc, g_content); shade->toJSON(sobj);
server.send(200, _encoding_json, g_content); serializeJson(sdoc, g_content);
server.send(200, _encoding_json, g_content);
}
else {
snprintf(g_content, sizeof(g_content), "{\"status\":\"DATA\",\"desc\":\"Data Error.\", \"code\":%d}", err);
server.send(500, _encoding_json, g_content);
}
} }
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}")); else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade Id not found.\"}"));
} }
@ -1207,7 +1219,7 @@ void Web::begin() {
} }
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.\"}"));
} }
}); });
server.on("/saveGroup", []() { server.on("/saveGroup", []() {
webServer.sendCORSHeaders(server); webServer.sendCORSHeaders(server);
if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; }

View file

@ -289,20 +289,33 @@
<div style="display:inline-block;float:right;position:relative;"><span id="spanShadeId">*</span>/<span id="spanMaxShades">5</span></div> <div style="display:inline-block;float:right;position:relative;"><span id="spanShadeId">*</span>/<span id="spanMaxShades">5</span></div>
<div class="field-group" style="padding:0px;"> <div class="field-group" style="padding:0px;">
<div class="field-group" style="margin-top:-18px;display:inline-block;width:77px;"> <div class="field-group" style="margin-top:-18px;display:inline-block;width:77px;">
<select id="selShadeProto" name="proto" data-bind="proto" data-datatype="int" style="width:100%;"> <select id="selShadeProto" name="proto" data-bind="proto" data-datatype="int" style="width:100%;" onchange="somfy.onShadeProtoChanged(this);">
<option value="0">RTS</option> <option value="0">RTS</option>
<option value="1">RTW</option> <option value="1">RTW</option>
<option value="2">RTV</option> <option value="2">RTV</option>
<option value="8">GPIO</option>
</select> </select>
<label for="selShadeProto">Protocol</label> <label for="selShadeProto">Protocol</label>
</div> </div>
<div class="field-group" style="margin-top:-18px;display:inline-block;width:77px;"> <div id="divShadeBitLength" class="field-group" style="margin-top:-18px;width:77px;">
<select id="selShadeBitLength" name="bitLength" data-bind="bitLength" data-datatype="int" style="width:100%;" onchange="somfy.onShadeBitLengthChanged(this);"> <select id="selShadeBitLength" name="bitLength" data-bind="bitLength" data-datatype="int" style="width:100%;" onchange="somfy.onShadeBitLengthChanged(this);">
<option value="56">56-BIT</option> <option value="56">56-BIT</option>
<option value="80">80-BIT</option> <option value="80">80-BIT</option>
</select> </select>
<label for="selShadeBitLength">Bit Length</label> <label for="selShadeBitLength">Bit Length</label>
</div> </div>
<div id="divGPIOControl" class=" field-group">
<div class="field-group" style="width:70px;display:inline-block;">
<select id="selShadeGPIOUp" data-bind="gpioUp" data-datatype="int" style="width:70px;">
</select>
<label for="selShadeGPIOUp">UP</label>
</div>
<div class="field-group" style="width:70px;display:inline-block;">
<select id="selShadeGPIODown" data-bind="gpioDown" data-datatype="int" style="width:70px;">
</select>
<label for="selShadeGPIODown">Down</label>
</div>
</div>
</div> </div>
<div> <div>
<div class="field-group" style="width:127px;display:inline-block;margin-top:-10px;float:left;"> <div class="field-group" style="width:127px;display:inline-block;margin-top:-10px;float:left;">
@ -322,13 +335,13 @@
</select> </select>
<label for="selShadeType">Type</label> <label for="selShadeType">Type</label>
</div> </div>
<div class="field-group" style="margin-top:-15px;"> <div id="divShadeAddress" class="field-group" style="margin-top:-15px;">
<input id="fldShadeAddress" name="shadeAddress" data-bind="remoteAddress" data-datatype="int" type="number" length=5 placeholder="Address" style="width:100%;text-align:right;"> <input id="fldShadeAddress" name="shadeAddress" data-bind="remoteAddress" data-datatype="int" type="number" length=5 placeholder="Address" style="width:100%;text-align:right;">
<label for="fldShadeAddress">Remote Address</label> <label for="fldShadeAddress">Remote Address</label>
</div> </div>
</div> </div>
</div> </div>
<div id="divSomfyButtons" class="shadectl-buttons" style="float:right;margin-top:10px;position:relative"> <div id="divSomfyButtons" class="shadectl-buttons" style="float:right;margin-top:-18px;position:relative">
<div style="display:inline-block;margin-right:7px;position:relative;font-size:48px;"><i id="icoShade" class="somfy-shade-icon icss-window-shade" data-shadeid="0" style="--shade-position:0%;vertical-align:middle;"><span class="icss-panel-left"></span><span class="icss-panel-right"></span></i><i class="icss-window-tilt" data-tiltposition="0" style="display:none;"></i></div> <div style="display:inline-block;margin-right:7px;position:relative;font-size:48px;"><i id="icoShade" class="somfy-shade-icon icss-window-shade" data-shadeid="0" style="--shade-position:0%;vertical-align:middle;"><span class="icss-panel-left"></span><span class="icss-panel-right"></span></i><i class="icss-window-tilt" data-tiltposition="0" style="display:none;"></i></div>
<div class="button-outline" data-cmd="up" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'up');"><i class="icss-somfy-up"></i></div> <div class="button-outline" data-cmd="up" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'up');"><i class="icss-somfy-up"></i></div>
<div class="button-outline" data-cmd="my" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'my');" style="font-size: 2em; padding: 10px;"><span>my</span></div> <div class="button-outline" data-cmd="my" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'my');" style="font-size: 2em; padding: 10px;"><span>my</span></div>

View file

@ -1,3 +1,8 @@
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" }
]
document.oncontextmenu = (event) => { document.oncontextmenu = (event) => {
if (event.target && event.target.tagName.toLowerCase() === 'input' && (event.target.type.toLowerCase() === 'text' || event.target.type.toLowerCase() === 'password')) if (event.target && event.target.tagName.toLowerCase() === 'input' && (event.target.type.toLowerCase() === 'text' || event.target.type.toLowerCase() === 'password'))
return; return;
@ -874,6 +879,7 @@ class UIBinder {
return div; return div;
} }
serviceError(el, err) { serviceError(el, err) {
let title = 'Service Error'
if (arguments.length === 1) { if (arguments.length === 1) {
err = el; err = el;
el = document.getElementById('divContainer'); el = document.getElementById('divContainer');
@ -896,10 +902,17 @@ class UIBinder {
} }
} }
else if (typeof err !== 'undefined') { else if (typeof err !== 'undefined') {
if (typeof err.desc === 'string') msg = typeof err.desc !== 'undefined' ? err.desc : err.message; if (typeof err.desc === 'string') {
msg = typeof err.desc !== 'undefined' ? err.desc : err.message;
if (typeof err.code === 'number') {
let e = errors.find(x => x.code === err.code) || { code: err.code, desc: 'Unspecified error' };
msg = e.desc;
title = err.desc;
}
}
} }
console.log(err); console.log(err);
let div = this.errorMessage(`${err.htmlError || 500}:Service Error`); let div = this.errorMessage(`${err.htmlError || 500}:${title}`);
let sub = div.querySelector('.sub-message'); let sub = div.querySelector('.sub-message');
sub.innerHTML = `<div><label>Service:</label>${err.service}</div><div style="font-size:22px;">${msg}</div>`; sub.innerHTML = `<div><label>Service:</label>${err.service}</div><div style="font-size:22px;">${msg}</div>`;
return div; return div;
@ -1846,6 +1859,8 @@ class Somfy {
ui.toElement(document.getElementById('divTransceiverSettings'), { ui.toElement(document.getElementById('divTransceiverSettings'), {
transceiver: { config: { proto: 0, SCKPin: 18, CSNPin: 5, MOSIPin: 23, MISOPin: 19, TXPin: 12, RXPin: 13, frequency: 433.42, rxBandwidth: 97.96, type:56, deviation: 11.43, txPower: 10, enabled: false } } transceiver: { config: { proto: 0, SCKPin: 18, CSNPin: 5, MOSIPin: 23, MISOPin: 19, TXPin: 12, RXPin: 13, frequency: 433.42, rxBandwidth: 97.96, type:56, deviation: 11.43, txPower: 10, enabled: false } }
}); });
this.loadPins('out', document.getElementById('selShadeGPIOUp'));
this.loadPins('out', document.getElementById('selShadeGPIODown'));
this.initialized = true; this.initialized = true;
} }
async loadSomfy() { async loadSomfy() {
@ -2231,56 +2246,131 @@ class Somfy {
setListDraggable(list, itemclass, onChanged) { setListDraggable(list, itemclass, onChanged) {
let items = list.querySelectorAll(itemclass); let items = list.querySelectorAll(itemclass);
let changed = false; let changed = false;
[].forEach.call(items, (item) => { let timerStart = null;
item.addEventListener('dragstart', function(e) { let dragDiv = null;
console.log({ evt: 'dragStart', e: e, this: this }); let fnDragStart = function(e) {
//console.log({ evt: 'dragStart', e: e, this: this });
if (typeof e.dataTransfer !== 'undefined') {
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML); e.dataTransfer.setData('text/html', this.innerHTML);
e.stopPropagation();
this.style.opacity = '0.4'; this.style.opacity = '0.4';
this.classList.add('dragging'); this.classList.add('dragging');
}); }
item.addEventListener('dragenter', function (e) { else {
this.classList.add('over'); timerStart = setTimeout(() => {
}); this.style.opacity = '0.4';
item.addEventListener('dragover', function(e) { dragDiv = document.createElement('div');
e.preventDefault(); dragDiv.innerHTML = this.innerHTML;
e.dataTransfer.dropEffect = 'move'; dragDiv.style.position = 'absolute';
return false; dragDiv.classList.add('somfyShade');
}); dragDiv.style.left = `${this.offsetLeft}px`;
item.addEventListener('dragleave', function(e) { dragDiv.style.width = `${this.clientWidth}px`;
this.classList.remove('over'); dragDiv.style.top = `${this.offsetTop}px`;
}); dragDiv.style.border = 'dotted 1px silver';
item.addEventListener('drop', function(e) { //dragDiv.style.background = 'gainsboro';
// Shift around the items. list.appendChild(dragDiv);
console.log({ evt: 'drop', e: e, this: this }); this.classList.add('dragging');
let elDrag = list.querySelector('.dragging'); timerStart = null;
if (elDrag !== this) { }, 1000);
let curr = 0, end = 0; }
for (let i = 0; i < items.length; i++) { e.stopPropagation();
if (this === items[i]) end = i; };
if (elDrag === items[i]) curr = i; let fnDragEnter = function (e) {
} //console.log({ evt: 'dragEnter', e: e, this: this });
console.log({ drag: elDrag, curr: curr, end: end, before: curr < end }); this.classList.add('over');
if (curr !== end) { };
this.before(elDrag); let fnDragOver = function (e) {
changed = true; //console.log({ evt: 'dragOver', e: e, this: this });
if (timerStart) {
clearTimeout(timerStart);
timerStart = null;
return;
}
e.preventDefault();
if (typeof e.dataTransfer !== 'undefined') e.dataTransfer.dropEffect = 'move';
else if (dragDiv) {
let rc = list.getBoundingClientRect();
let pageY = e.targetTouches[0].pageY;
let y = pageY - rc.top;
if (y < 0) y = 0;
else if (y > rc.height) y = rc.height;
dragDiv.style.top = `${y}px`;
// Now lets calculate which element we are over.
let ndx = -1;
for (let i = 0; i < items.length; i++) {
let irc = items[i].getBoundingClientRect();
if (pageY <= irc.bottom - (irc.height / 2)) {
ndx = i;
break;
} }
} }
let over = items[ndx];
if (ndx < 0) [].forEach.call(items, (item) => { item.classList.remove('over') });
}); else if (!over.classList.contains['over']) {
item.addEventListener('dragend', function (e) { [].forEach.call(items, (item) => { item.classList.remove('over') });
over.classList.add('over');
[].forEach.call(items, (item) => { item.classList.remove('over') });
this.style.opacity = '1';
//overCounter = 0;
this.classList.remove('dragging');
if (changed && typeof onChanged === 'function') {
onChanged(list);
} }
//console.log(e); }
}); return false;
};
let fnDragLeave = function (e) {
console.log({ evt: 'dragLeave', e: e, this: this });
this.classList.remove('over');
};
let fnDrop = function(e) {
// Shift around the items.
console.log({ evt: 'drop', e: e, this: this });
let elDrag = list.querySelector('.dragging');
if (elDrag !== this) {
let curr = 0, end = 0;
for (let i = 0; i < items.length; i++) {
if (this === items[i]) end = i;
if (elDrag === items[i]) curr = i;
}
if (curr !== end) {
this.before(elDrag);
changed = true;
}
}
};
let fnDragEnd = function(e) {
console.log({ evt: 'dragEnd', e: e, this: this });
let elOver = list.querySelector('.over');
[].forEach.call(items, (item) => { item.classList.remove('over') });
this.style.opacity = '1';
//overCounter = 0;
if (timerStart) {
clearTimeout(timerStart);
timerStart = null;
}
if (dragDiv) {
dragDiv.remove();
dragDiv = null;
if (elOver && typeof elOver !== 'undefined') fnDrop.call(elOver, e);
}
if (changed && typeof onChanged === 'function') {
onChanged(list);
}
this.classList.remove('dragging');
};
[].forEach.call(items, (item) => {
if (firmware.isMobile()) {
item.addEventListener('touchstart', fnDragStart);
//item.addEventListener('touchenter', fnDragEnter);
item.addEventListener('touchmove', fnDragOver);
item.addEventListener('touchleave', fnDragLeave);
item.addEventListener('drop', fnDrop);
item.addEventListener('touchend', fnDragEnd);
}
else {
item.addEventListener('dragstart', fnDragStart);
item.addEventListener('dragenter', fnDragEnter);
item.addEventListener('dragover', fnDragOver);
item.addEventListener('dragleave', fnDragLeave);
item.addEventListener('drop', fnDrop);
item.addEventListener('dragend', fnDragEnd);
}
}); });
} }
setGroupsList(groups) { setGroupsList(groups) {
@ -2573,7 +2663,7 @@ class Somfy {
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 >= 0 ? `${state.mypos}%` : '---'; if (span) span.innerHTML = typeof state.myPos !== 'undefined' && state.myPos >= 0 ? `${state.myPos}%` : '---';
span = divs[i].querySelector('#spanMyTiltPos'); span = divs[i].querySelector('#spanMyTiltPos');
if (span) span.innerHTML = typeof state.myTiltPos !== 'undefined' && state.myTiltPos >= 0 ? `${state.myTiltPos}%` : '---'; if (span) span.innerHTML = typeof state.myTiltPos !== 'undefined' && state.myTiltPos >= 0 ? `${state.myTiltPos}%` : '---';
} }
@ -2820,6 +2910,9 @@ class Somfy {
document.getElementById('somfyShade').setAttribute('data-bitlength', el.value); document.getElementById('somfyShade').setAttribute('data-bitlength', el.value);
//document.getElementById('divStepSettings').style.display = parseInt(el.value, 10) === 80 ? '' : 'none'; //document.getElementById('divStepSettings').style.display = parseInt(el.value, 10) === 80 ? '' : 'none';
} }
onShadeProtoChanged(el) {
document.getElementById('somfyShade').setAttribute('data-proto', el.value);
}
openEditShade(shadeId) { openEditShade(shadeId) {
if (typeof shadeId === 'undefined') { if (typeof shadeId === 'undefined') {
getJSONSync('/getNextShade', (err, shade) => { getJSONSync('/getNextShade', (err, shade) => {
@ -2833,7 +2926,6 @@ class Somfy {
document.getElementById('selShadeBitLength').value = 56; document.getElementById('selShadeBitLength').value = 56;
document.getElementById('cbFlipCommands').value = false; document.getElementById('cbFlipCommands').value = false;
document.getElementById('cbFlipPosition').value = false; document.getElementById('cbFlipPosition').value = false;
if (err) { if (err) {
ui.serviceError(err); ui.serviceError(err);
} }
@ -2921,6 +3013,7 @@ class Somfy {
ico.style.setProperty('--tilt-position', `${shade.flipPosition ? 100 - shade.tiltPosition : shade.tiltPosition}%`); ico.style.setProperty('--tilt-position', `${shade.flipPosition ? 100 - shade.tiltPosition : shade.tiltPosition}%`);
ico.setAttribute('data-shadeid', shade.shadeId); ico.setAttribute('data-shadeid', shade.shadeId);
somfy.onShadeBitLengthChanged(document.getElementById('selShadeBitLength')); somfy.onShadeBitLengthChanged(document.getElementById('selShadeBitLength'));
somfy.onShadeProtoChanged(document.getElementById('selShadeProto'));
document.getElementById('btnSetRollingCode').style.display = 'inline-block'; document.getElementById('btnSetRollingCode').style.display = 'inline-block';
if (shade.paired) { if (shade.paired) {
document.getElementById('btnUnpairShade').style.display = 'inline-block'; document.getElementById('btnUnpairShade').style.display = 'inline-block';
@ -3017,11 +3110,20 @@ class Somfy {
ui.errorMessage(document.getElementById('divSomfySettings'), 'Down Time must be a value between 0 and 4,294,967,295 milliseconds. This is the travel time to go from full open to full closed.'); ui.errorMessage(document.getElementById('divSomfySettings'), 'Down Time must be a value between 0 and 4,294,967,295 milliseconds. This is the travel time to go from full open to full closed.');
valid = false; valid = false;
} }
if (obj.proto === 8) {
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 (valid) { if (valid) {
if (isNaN(shadeId) || shadeId >= 255) { if (isNaN(shadeId) || shadeId >= 255) {
// We are adding. // We are adding.
putJSONSync('/addShade', obj, (err, shade) => { putJSONSync('/addShade', obj, (err, shade) => {
if (err) ui.serviceError(err); if (err) {
ui.serviceError(err);
console.log(err);
}
else { else {
console.log(shade); console.log(shade);
document.getElementById('spanShadeId').innerText = shade.shadeId; document.getElementById('spanShadeId').innerText = shade.shadeId;

View file

@ -135,6 +135,25 @@
.shadectl-buttons:not([data-shadetype="5"]):not([data-shadetype="9"]) > .button-outline[data-cmd="toggle"] { .shadectl-buttons:not([data-shadetype="5"]):not([data-shadetype="9"]) > .button-outline[data-cmd="toggle"] {
display: none; display: none;
} }
#somfyShade #divGPIOControl {
display: none;
}
#somfyShade #divShadeBitLength {
display:inline-block;
}
#somfyShade[data-proto="8"] #divGPIOControl {
display: inline-block;
width: auto;
margin-top: -18px;
}
#somfyShade[data-proto="8"] #divShadeBitLength {
display: none;
}
#divGPIOControl {
text-align:center;
margin-left:7px;
}
#somfyShade[data-proto="8"] #divStepSettings,
#somfyShade[data-bitlength="56"] #divStepSettings, #somfyShade[data-bitlength="56"] #divStepSettings,
#somfyShade[data-shadetype="5"] #divStepSettings, #somfyShade[data-shadetype="5"] #divStepSettings,
#somfyShade[data-shadetype="6"] #divStepSettings, #somfyShade[data-shadetype="6"] #divStepSettings,