diff --git a/code/espurna/board.cpp b/code/espurna/board.cpp index fdb3af1d..c1095921 100644 --- a/code/espurna/board.cpp +++ b/code/espurna/board.cpp @@ -177,6 +177,9 @@ PROGMEM const char espurna_webui[] = #if WEBUI_IMAGE == WEBUI_IMAGE_THERMOSTAT "THERMOSTAT" #endif + #if WEBUI_IMAGE == WEBUI_IMAGE_CURTAIN + "CURTAIN" + #endif #if WEBUI_IMAGE == WEBUI_IMAGE_FULL "FULL" #endif diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 6b0c64b2..f5f3c241 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -4595,6 +4595,8 @@ #define MANUFACTURER "KINGART" #define DEVICE "CURTAIN_SWITCH" + #define CURTAIN_SUPPORT 1 + // LEDs #define LED1_PIN 13 #define LED1_PIN_INVERSE 1 diff --git a/code/espurna/config/webui.h b/code/espurna/config/webui.h index 2407e850..b01894f7 100644 --- a/code/espurna/config/webui.h +++ b/code/espurna/config/webui.h @@ -11,6 +11,7 @@ #define WEBUI_IMAGE_RFM69 8 #define WEBUI_IMAGE_LIGHTFOX 16 #define WEBUI_IMAGE_THERMOSTAT 32 +#define WEBUI_IMAGE_CURTAIN 64 #define WEBUI_IMAGE_FULL 15 #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE @@ -65,6 +66,15 @@ #endif #endif +#if CURTAIN_SUPPORT == 1 + #ifndef WEBUI_IMAGE + #define WEBUI_IMAGE WEBUI_IMAGE_CURTAIN + #else + #undef WEBUI_IMAGE + #define WEBUI_IMAGE WEBUI_IMAGE_FULL + #endif +#endif + #ifndef WEBUI_IMAGE #define WEBUI_IMAGE WEBUI_IMAGE_SMALL #endif diff --git a/code/espurna/curtain_kingart.cpp b/code/espurna/curtain_kingart.cpp index d46398e1..73e81d97 100644 --- a/code/espurna/curtain_kingart.cpp +++ b/code/espurna/curtain_kingart.cpp @@ -5,6 +5,9 @@ KingArt Cover/Shutter/Blind/Curtain support for ESPURNA Based on xdrv_19_ps16dz.dimmer.ino, PS_16_DZ dimmer support for Tasmota Copyright (C) 2019 by Albert Weterings +Based on curtain_kingart.ino Albert Weterings +Copyright (C) 2020 - Eric Chauvet + */ #include "curtain_kingart.h" @@ -13,6 +16,8 @@ Copyright (C) 2019 by Albert Weterings #include "ntp.h" #include "mqtt.h" +#include "settings.h" +#include "ws.h" #ifndef KINGART_CURTAIN_PORT #define KINGART_CURTAIN_PORT Serial // Hardware serial port by default @@ -24,21 +29,157 @@ Copyright (C) 2019 by Albert Weterings #define KINGART_CURTAIN_TERMINATION '\e' // Termination character after each message #define KINGART_CURTAIN_BAUDRATE 19200 // Serial speed is fixed for the device - + +// --> Let see after if we define a curtain generic switch, use these for now +#define CURTAIN_BUTTON_UNKNOWN -1 +#define CURTAIN_BUTTON_PAUSE 0 +#define CURTAIN_BUTTON_OPEN 1 +#define CURTAIN_BUTTON_CLOSE 2 + +#define CURTAIN_POSITION_UNKNOWN -1 +// <-- + char _KACurtainBuffer[KINGART_CURTAIN_BUFFER_SIZE]; bool _KACurtainNewData = false; + +// Status vars - for curtain move detection : +int _curtain_position = CURTAIN_POSITION_UNKNOWN; +int _curtain_last_position = CURTAIN_POSITION_UNKNOWN; +int _curtain_button = CURTAIN_BUTTON_UNKNOWN; +int _curtain_last_button = CURTAIN_BUTTON_UNKNOWN; +unsigned long last_uptime = 0; + +int _curtain_position_set = CURTAIN_POSITION_UNKNOWN; //Last position asked to be set (not the real position, the real query - updated when the curtain stops moving) +bool _curtain_waiting_ack = false; //Avoid too fast MQTT commands +bool _curtain_ignore_next_position = false; //Avoid a bug (see (*1) +bool _curtain_initial_position_set = false; //Flag to detect if we manage to set the curtain back to its position before power off or reboot + +// Calculated behaviour depending on KA switch, MQTT and UI actions +bool _curtain_moving = true; + +//Enable more traces, true as a default and stopped when curtain is setup. +bool _debug_flag = true; + +#if WEB_SUPPORT +bool _curtain_report_ws = true; //This will init curtain control and flag the web ui update +#endif // WEB_SUPPORT + +//------------------------------------------------------------------------------ +void curtainUpdateUI() { +#if WEB_SUPPORT + _curtain_report_ws = true; +#endif // WEB_SUPPORT +} + +//------------------------------------------------------------------------------ +int setButtonFromSwitchText(String & text) { + if(text == "on") + return CURTAIN_BUTTON_OPEN; + else if(text == "off") + return CURTAIN_BUTTON_CLOSE; + else if(text == "pause") + return CURTAIN_BUTTON_PAUSE; + else + return CURTAIN_BUTTON_UNKNOWN; +} // ----------------------------------------------------------------------------- // Private // ----------------------------------------------------------------------------- +//------------------------------------------------------------------------------ +//This check that wa got latest and new stats from the AT+RESULT message +bool _KAValidStatus() { + return _curtain_button != CURTAIN_BUTTON_UNKNOWN && + _curtain_last_button != CURTAIN_BUTTON_UNKNOWN && + _curtain_position != CURTAIN_POSITION_UNKNOWN && + _curtain_last_position != CURTAIN_POSITION_UNKNOWN; +} + + +//------------------------------------------------------------------------------ +//We consider that the curtain is moving. A timer is set to get the position of the curtain sending AT+START messages in the loop() +void _KASetMoving() { + last_uptime = millis() + 1000; //Let the returned curtain position to be refreshed to know of the curtain is still moving + _curtain_moving = true; +} + +//------------------------------------------------------------------------------ +//Send a buffer to serial void _KACurtainSend(const char * tx_buffer) { KINGART_CURTAIN_PORT.print(tx_buffer); KINGART_CURTAIN_PORT.print(KINGART_CURTAIN_TERMINATION); KINGART_CURTAIN_PORT.flush(); + if(_debug_flag) DEBUG_MSG_P(PSTR("[KAUART] Send : %s\n"), tx_buffer); } - -void _KACurtainReceiveUART() { + +//------------------------------------------------------------------------------ +//Send a formatted message to MCU +void _KACurtainSet(int button, int position = CURTAIN_POSITION_UNKNOWN) { + + if(_curtain_waiting_ack) { + DEBUG_MSG_P(PSTR("[KAUART] MCU BUSY : Request ignored!\n")); + return; + } + + char tx_buffer[80] = {0}; + if(button != CURTAIN_BUTTON_UNKNOWN && position != CURTAIN_POSITION_UNKNOWN) { + snprintf_P( + tx_buffer, sizeof(tx_buffer), + PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"switch\":\"%s\",\"setclose\":%d"), + now(), millis() % 1000, + (button == CURTAIN_BUTTON_PAUSE ? "pause" : (button == CURTAIN_BUTTON_OPEN ? "on" : "off")), position + ); + } else if(button == CURTAIN_BUTTON_UNKNOWN) { + snprintf_P( + tx_buffer, sizeof(tx_buffer), + PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"setclose\":%d"), + now(), millis() % 1000, + position + ); + } else { + snprintf_P( + tx_buffer, sizeof(tx_buffer), + PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"switch\":\"%s\""), + now(), millis() % 1000, + (button == CURTAIN_BUTTON_PAUSE ? "pause" : (button == CURTAIN_BUTTON_OPEN ? "on" : "off")) + ); + } + _KACurtainSend(tx_buffer); + _curtain_waiting_ack = true; +} + +//------------------------------------------------------------------------------ +//Stop moving will set the real curtain position to the GUI/MQTT +void _KAStopMoving() { + _curtain_moving = false; + if( _curtain_position != CURTAIN_POSITION_UNKNOWN) + _curtain_position_set = _curtain_position; + else if( _curtain_last_position != CURTAIN_POSITION_UNKNOWN) + _curtain_position_set = _curtain_last_position; + + if(_curtain_initial_position_set == false) {//The curtain stopped moving for the first time, set the position back to + int init_position = getSetting("curtainInitialBehaviour", 0); + DEBUG_MSG_P(PSTR("[CURTAIN] curtainInitialBehaviour : %d, curtainInitialPosition : %d\n"), init_position, getSetting("curtainInitialPosition", 100)); + if (init_position == 1) + _KACurtainSet(CURTAIN_BUTTON_CLOSE); + else if (init_position == 2) + _KACurtainSet(CURTAIN_BUTTON_OPEN); + else if (init_position == 3) { + int pos = getSetting("curtainInitialPosition", 100); //Set closed if we do not have initial position set. + if (_curtain_position_set != pos) { + _KACurtainSet(CURTAIN_BUTTON_UNKNOWN, pos); + } else + DEBUG_MSG_P(PSTR("[CURTAIN] No need to update bootup position\n")); + } + _curtain_initial_position_set = true; + _debug_flag = false; //Disable debug - user has could ask for it + } +} + +//------------------------------------------------------------------------------ +//Receive a buffer from serial +bool _KACurtainReceiveUART() { static unsigned char ndx = 0; while (KINGART_CURTAIN_PORT.available() > 0 && !_KACurtainNewData) { char rc = KINGART_CURTAIN_PORT.read(); @@ -51,8 +192,15 @@ void _KACurtainReceiveUART() { ndx = 0; } } + if(_KACurtainNewData) { + if(_debug_flag) DEBUG_MSG_P(PSTR("[KAUART] Received : %s\n"), _KACurtainBuffer); + _KACurtainNewData = false; + return true; + } + return false; } + /* Buttons on the device will move Cover/Shutter/Blind/Curtain up/open or down/close On the end of every movement the unit reports the last action and posiston over MQTT topic {hostname}/curtain @@ -87,108 +235,248 @@ To configure the device: - Press up/down for 5 seconds to bring device into AP mode. After pressing up/down again, device will restart in normal mode. */ +//------------------------------------------------------------------------------ void _KACurtainResult() { - if (_KACurtainNewData) { - // Need to send confiramtion to the N76E003AT20 that message is received - _KACurtainSend("AT+SEND=ok"); + // Need to send confirmation to the N76E003AT20 that message is received + // ECH : TODO Check this is the case every time + _KACurtainSend("AT+SEND=ok"); - // We don't handle "setclose" any other way, simply redirect payload value - const String buffer(_KACurtainBuffer); - #if MQTT_SUPPORT - int setclose_idx = buffer.indexOf("setclose"); - if (setclose_idx > 0) { - auto position = buffer.substring(setclose_idx + strlen("setclose") + 2, buffer.length()); - int leftovers = position.indexOf(','); - if (leftovers > 0) { - position = position.substring(0, leftovers); - } - mqttSend(MQTT_TOPIC_CURTAIN, position.c_str()); + //Init receive stats : The buffer which may contain : "setclose":INT(0-100) or "switch":["on","off","pause"] + const String buffer(_KACurtainBuffer); + _curtain_button = CURTAIN_BUTTON_UNKNOWN; + _curtain_position = CURTAIN_POSITION_UNKNOWN; + + + if(buffer.indexOf("AT+RESULT") == 0) { //AT+RESULT is an acquitment of our command (MQTT or GUI) + //Set the status on what we kown + if( _curtain_last_button == CURTAIN_BUTTON_OPEN && _curtain_last_position == 0 || + _curtain_last_button == CURTAIN_BUTTON_CLOSE && _curtain_last_position == 100 || + _curtain_last_button == CURTAIN_BUTTON_PAUSE) //The curtain is max opened, closed or pause + _KAStopMoving(); + else { //Else it is probably moving + _KASetMoving(); + /* + (*1) ATTENTION THERE : + Send immediatly a AT+START - we need to purge the first response. + It will return us the right direction of the switch but the position + we set instead of the real on. We take care of the switch response but + we ignore the position. + */ + _KACurtainSend("AT+START"); + _curtain_ignore_next_position = true; + } + //Time to update UI + curtainUpdateUI(); + _curtain_waiting_ack = false; + return; + } else if(buffer.indexOf("AT+UPDATE") == 0) { //AT+UPDATE is a response from the switch itself or AT+SEND query + // Get switch status from MCU + int switch_idx = buffer.indexOf("switch"); + if (switch_idx > 0) { + String switch_text = buffer.substring(switch_idx + strlen("switch") + 3, buffer.length()); + int leftovers = switch_text.indexOf('"'); + if (leftovers > 0) { //We must find leftover as it is text + switch_text = switch_text.substring(0, leftovers); + _curtain_button = setButtonFromSwitchText(switch_text); } - #endif // MQTT_SUPPORT - - // Handle configuration button presses - if (buffer.indexOf("enterESPTOUCH") > 0) { - wifiStartAP(); - } else if (buffer.indexOf("exitESPTOUCH") > 0) { - deferredReset(100, CUSTOM_RESET_HARDWARE); + + } + // Get position from MCU + int setclose_idx = buffer.indexOf("setclose"); + if (setclose_idx > 0) { + String position = buffer.substring(setclose_idx + strlen("setclose") + 2, buffer.length()); + int leftovers = position.indexOf(','); + if (leftovers > 0) // Not found if finishing by setclose + position = position.substring(0, leftovers); + if(_curtain_ignore_next_position) // (*1) + _curtain_ignore_next_position = false; + else + _curtain_position = position.toInt(); } + } else { + DEBUG_MSG_P(PSTR("[KAUART] ERROR : Unknown message : %s\n"), _KACurtainBuffer); + } + + //Check if curtain is moving or not + if( _curtain_button == CURTAIN_BUTTON_PAUSE ) { //This is returned from MCU and tells us than last status is pause or full opened or closed + _KAStopMoving(); + } else if(_curtain_moving ) { + if(_KAValidStatus()) { + if(_curtain_last_button != _curtain_button) //Direction change? Reset the timer to know + _KASetMoving(); + else if(_curtain_last_position == _curtain_position) //Same direction, same position - curtain is not moving anymore + _KAStopMoving(); + } + } else //Not paused, not moving, and we received an AT+UPDATE -> This means that we are moving + _KASetMoving(); - _KACurtainNewData = false; + //Update last position and transmit to MQTT (GUI is at the end) + if(_curtain_position != CURTAIN_POSITION_UNKNOWN && _curtain_last_position != _curtain_position) { + _curtain_last_position = _curtain_position; + if(_curtain_initial_position_set) //Update initial position - TDOD : maybe only when move is finished to avoid to write too frequently + setSetting("curtainInitialPosition", _curtain_last_position); //Remeber last position in case of power loss + #if MQTT_SUPPORT + const String pos = String(_curtain_last_position); + mqttSend(MQTT_TOPIC_CURTAIN, pos.c_str()); + #endif // MQTT_SUPPORT } -} - -// %d = now() / time_t / NTP timestamp in seconds -// %03u = millis() / uint32_t / we need last 3 digits -// %s = char strings for various actions - -// Tell N76E003AT20 to stop moving and report current position -void _KACurtainPause(const char * message) { - char tx_buffer[80] = {0}; - snprintf_P( - tx_buffer, sizeof(tx_buffer), - PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"switch\":\"%s\""), - now(), millis() % 1000, - message - ); - _KACurtainSend(tx_buffer); -} - -// Tell N76E003AT20 to go to position X (based on X N76E003AT20 decides to go up or down) -void _KACurtainSetclose(const char * message) { - char tx_buffer[80] = {0}; - snprintf_P( - tx_buffer, sizeof(tx_buffer), - PSTR("AT+UPDATE=\"sequence\":\"%d%03u\",\"switch\":\"%s\",\"setclose\":%s"), - now(), millis() % 1000, - "off", message - ); - _KACurtainSend(tx_buffer); -} - -#if MQTT_SUPPORT -void _KACurtainActionSelect(const char * message) { - if (strcmp(message, "pause") == 0) { - _KACurtainPause(message); - } else { - _KACurtainSetclose(message); + //Reset last button to make the algorithm work and set last button state + if(!_curtain_moving) + _curtain_last_button = CURTAIN_BUTTON_UNKNOWN; + else if (_curtain_button != CURTAIN_BUTTON_UNKNOWN) + _curtain_last_button = _curtain_button; + + // Handle configuration button presses + if (buffer.indexOf("enterESPTOUCH") > 0) { + wifiStartAP(); + } else if (buffer.indexOf("exitESPTOUCH") > 0) { + deferredReset(100, CUSTOM_RESET_HARDWARE); + } else { //In any other case, update as it could be a move action + curtainUpdateUI(); } } -void _KACurtainCallback(unsigned int type, const char * topic, char * payload) { +// ----------------------------------------------------------------------------- +// MQTT Support +// ----------------------------------------------------------------------------- + +#if MQTT_SUPPORT + +//------------------------------------------------------------------------------ +void _curtainMQTTCallback(unsigned int type, const char * topic, char * payload) { if (type == MQTT_CONNECT_EVENT) { mqttSubscribe(MQTT_TOPIC_CURTAIN); - } - if (type == MQTT_MESSAGE_EVENT) { + } else if (type == MQTT_MESSAGE_EVENT) { // Match topic const String t = mqttMagnitude(const_cast(topic)); if (t.equals(MQTT_TOPIC_CURTAIN)) { - _KACurtainActionSelect(payload); + if (strcmp(payload, "pause") == 0) + _KACurtainSet(CURTAIN_BUTTON_PAUSE); + else if (strcmp(payload, "on") == 0) + _KACurtainSet(CURTAIN_BUTTON_OPEN); + else if (strcmp(payload, "off") == 0) + _KACurtainSet(CURTAIN_BUTTON_CLOSE); + else { + _curtain_position_set = t.toInt(); + _KACurtainSet(CURTAIN_BUTTON_UNKNOWN, _curtain_position_set); + + } } } } #endif // MQTT_SUPPORT - + +// ----------------------------------------------------------------------------- +// WEB Support +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +//------------------------------------------------------------------------------ +void _curtainWebSocketOnConnected(JsonObject& root) { + root["curtainType"] = getSetting("curtainType", "0"); + root["curtainInitialBehaviour"] = getSetting("curtainInitialBehaviour", "0"); +} + +//------------------------------------------------------------------------------ +bool _curtainWebSocketOnKeyCheck(const char * key, JsonVariant& value) { + if (strncmp(key, "curtain", strlen("curtain")) == 0) return true; + return false; +} + +//------------------------------------------------------------------------------ +void _curtainWebSocketUpdate(JsonObject& root) { + JsonObject& state = root.createNestedObject("curtainState"); + state["curtainGet"] = _curtain_last_position; + if(_curtain_position_set == CURTAIN_POSITION_UNKNOWN) + _curtain_position_set = _curtain_last_position; + state["curtainSet"] = _curtain_position_set; + state["curtainButton"] = _curtain_last_button; + state["curtainMoving"] = _curtain_moving ? "Moving" : "Idle"; + state["curtainType"] = getSetting("curtainType", "0"); +} + +//------------------------------------------------------------------------------ +void _curtainWebSocketStatus(JsonObject& root) { + root["curtainVisible"] = 1; + _curtainWebSocketUpdate(root); +} + +//------------------------------------------------------------------------------ +void _curtainWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { + + if (strcmp(action, "curtainAction") == 0) { + if (data.containsKey("position")) { + _curtain_position_set = data["position"].as(); + _KACurtainSet(CURTAIN_BUTTON_UNKNOWN, _curtain_position_set); + } else if(data.containsKey("button")){ + _curtain_last_button = data["button"].as(); + _KACurtainSet(_curtain_last_button); + } + } else if (strcmp(action, "dbgcmd") == 0) { //Directly send our buffer to the KA serial + if(data["command"] == "debug") { + _debug_flag = true; + } else + _KACurtainSend(data["command"]); + } +} + +void _curtainWebSocketOnVisible(JsonObject& root) { + root["curtainVisible"] = 1; +} + +#endif //WEB_SUPPORT + // ----------------------------------------------------------------------------- // SETUP & LOOP // ----------------------------------------------------------------------------- - + +//------------------------------------------------------------------------------ void _KACurtainLoop() { - _KACurtainReceiveUART(); - _KACurtainResult(); + + if(_KACurtainReceiveUART()) + _KACurtainResult(); + else if(_curtain_moving) { //When curtain move and no messages, get position every 600ms with AT+START + unsigned long uptime = millis(); + long diff = uptime - last_uptime; + if(diff >= 600) { + _KACurtainSend("AT+START"); + last_uptime = uptime; + } + } + +#if WEB_SUPPORT + if (_curtain_report_ws) { //Launch a websocket update + wsPost(_curtainWebSocketUpdate); + _curtain_report_ws = false; + } + #endif } +//------------------------------------------------------------------------------ void kingartCurtainSetup() { // Init port to receive and send messages KINGART_CURTAIN_PORT.begin(KINGART_CURTAIN_BAUDRATE); - + +#if MQTT_SUPPORT // Register MQTT callback only when supported - #if MQTT_SUPPORT - mqttRegister(_KACurtainCallback); - #endif // MQTT_SUPPORT + mqttRegister(_curtainMQTTCallback); +#endif // MQTT_SUPPORT + + +#if WEB_SUPPORT + // Websockets + wsRegister() + .onVisible(_curtainWebSocketOnVisible) + .onConnected(_curtainWebSocketOnConnected) + .onKeyCheck(_curtainWebSocketOnKeyCheck) + .onAction(_curtainWebSocketOnAction) + .onData(_curtainWebSocketUpdate); +#endif // Register loop to poll the UART for new messages espurnaRegisterLoop(_KACurtainLoop); diff --git a/code/espurna/web.cpp b/code/espurna/web.cpp index e4991743..0dc38755 100644 --- a/code/espurna/web.cpp +++ b/code/espurna/web.cpp @@ -30,6 +30,8 @@ Copyright (C) 2016-2019 by Xose PĂ©rez #include "static/index.lightfox.html.gz.h" #elif WEBUI_IMAGE == WEBUI_IMAGE_THERMOSTAT #include "static/index.thermostat.html.gz.h" +#elif WEBUI_IMAGE == WEBUI_IMAGE_CURTAIN + #include "static/index.curtain.html.gz.h" #elif WEBUI_IMAGE == WEBUI_IMAGE_FULL #include "static/index.all.html.gz.h" #endif diff --git a/code/gulpfile.js b/code/gulpfile.js index 714ca018..89fce351 100644 --- a/code/gulpfile.js +++ b/code/gulpfile.js @@ -118,7 +118,8 @@ var buildWebUI = function(module) { 'rfbridge': false, 'rfm69': false, 'thermostat': false, - 'lightfox': false + 'lightfox': false, + 'curtain': false }; // Note: only build these when specified as module arg @@ -218,6 +219,10 @@ gulp.task('webui_thermostat', function() { return buildWebUI('thermostat'); }); +gulp.task('webui_curtain', function() { + return buildWebUI('curtain'); +}); + gulp.task('webui_all', function() { return buildWebUI('all'); }); @@ -231,6 +236,7 @@ gulp.task('webui', 'webui_rfm69', 'webui_lightfox', 'webui_thermostat', + 'webui_curtain', 'webui_all' ) ); diff --git a/code/html/custom.css b/code/html/custom.css index 75636cc8..76d211ad 100644 --- a/code/html/custom.css +++ b/code/html/custom.css @@ -130,6 +130,31 @@ div.state { color: #0F0; } +/* ----------------------------------------------------------------------------- + Curtains + -------------------------------------------------------------------------- */ + + .curtain-div { + text-align: center; +} + +.curtain-roller { + width: 300px; + height: 200px; + display: inline-block; +} + +.curtain-button{ + margin-left: 10px; + margin-right: 10px; + text-align: center; + +} + +.reverse-range { + direction: rtl; +} + /* ----------------------------------------------------------------------------- Buttons -------------------------------------------------------------------------- */ @@ -202,6 +227,9 @@ div.state { background: rgb(255, 128, 0); /* orange */ } +.button-curtain-open, +.button-curtain-pause, +.button-curtain-close, .button-generate-password { background: rgb(66, 184, 221); /* blue */ } diff --git a/code/html/custom.js b/code/html/custom.js index 0ed38770..c5b85f7b 100644 --- a/code/html/custom.js +++ b/code/html/custom.js @@ -1272,6 +1272,49 @@ function initMagnitudes(data) { } +// ----------------------------------------------------------------------------- +// Curtains +// ----------------------------------------------------------------------------- + + + +//Create the controls for one curtain. It is called when curtain is updated (so created the first time) +//Let this there as we plan to have more than one curtain per switch +function initCurtain(data) { + + var current = $("#curtains > div").length; + if (current > 0) { return; } + + // add curtain template (prepare multi switches) + var template = $("#curtainTemplate").children(); + var line = $(template).clone(); + // init curtain button + $(line).find(".button-curtain-open").on("click", function() { + sendAction("curtainAction", {button: 1}); + $(this).css('background', 'red'); + }); + $(line).find(".button-curtain-pause").on("click", function() { + sendAction("curtainAction", {button: 0}); + $(this).css('background', 'red'); + }); + $(line).find(".button-curtain-close").on("click", function() { + sendAction("curtainAction", {button: 2}); + $(this).css('background', 'red'); + }); + line.appendTo("#curtains"); + + // init curtain slider + $("#curtainSet").on("change", function() { + var value = $(this).val(); + var parent = $(this).parents(".pure-g"); + $("span", parent).html(value); + sendAction("curtainAction", {position: value}); + }); + +} + + + // ----------------------------------------------------------------------------- // Lights // ----------------------------------------------------------------------------- @@ -1691,6 +1734,49 @@ function processData(data) { if (key == "rpnNames") return; + // --------------------------------------------------------------------- + // Curtains + // --------------------------------------------------------------------- + + + + if ("curtainState" === key) { + initCurtain(); + switch(value.curtainType) { + case '0': //Roller + default: + $("#curtainGetPicture").css('background', 'linear-gradient(180deg, black ' + value.curtainGet + '%, #a0d6ff ' + value.curtainGet + '%)'); + break; + case '1': //One side left to right + $("#curtainGetPicture").css('background', 'linear-gradient(90deg, black ' + value.curtainGet + '%, #a0d6ff ' + value.curtainGet + '%)'); + break; + case '2': //One side right to left + $("#curtainGetPicture").css('background', 'linear-gradient(270deg, black ' + value.curtainGet + '%, #a0d6ff ' + value.curtainGet + '%)'); + break; + case '3': //Two sides + $("#curtainGetPicture").css('background', 'linear-gradient(90deg, black ' + value.curtainGet/2 + '%, #a0d6ff ' + value.curtainGet/2 + '% ' + (100 - value.curtainGet/2) + '%, black ' + (100 - value.curtainGet/2) + '%)'); + break; + } + $("#curtainSet").val(value.curtainSet); //Update sliders + if(value.curtainMoving == "Idle") { //When idle, all buttons are off (blue) + $("button.curtain-button").css('background', 'rgb(66, 184, 221)'); + } else { //If moving, adapt the color depending on the button active + if(value.curtainButton == 1) { + $("button.button-curtain-close").css('background', 'rgb(66, 184, 221)'); //Close back to blue + $("button.button-curtain-open").css('background', 'rgb(192, 0, 0)'); + } + else if(value.curtainButton == 0) + $("button.button-curtain-pause").css('background', 'rgb(192, 0, 0)'); + else if(value.curtainButton == 2) { + $("button.button-curtain-open").css('background', 'rgb(66, 184, 221)'); //Open back to blue + $("button.button-curtain-close").css('background', 'rgb(192, 0, 0)'); + + } + } + return; + } + + // --------------------------------------------------------------------- // Lights // --------------------------------------------------------------------- diff --git a/code/html/index.html b/code/html/index.html index 9addcad9..ee95bbc2 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -96,6 +96,12 @@ GENERAL + +
  • + CURTAIN +
  • + +
  • THERMOSTAT @@ -228,6 +234,10 @@
    + +
    + +
    @@ -484,6 +494,55 @@ + + +
    +
    + +
    +

    CURTAIN

    +

    Curtain configuration

    +
    + +
    + +
    + + General + +
    + + +
    +
    Define your curtain type. It adapts the graphical view in status menu.
    +
    + +
    + + +
    +
    Define the initial position of the curtain after a reboot or power loss.
    +
    + +
    + +
    + +
    +
    +
    + +
    @@ -972,6 +1031,9 @@
    This is the root topic for this device. The {hostname} and {mac} placeholders will be replaced by the device hostname and MAC address.
    - <root>/relay/#/set Send a 0 or a 1 as a payload to this topic to switch it on or off. You can also send a 2 to toggle its current state. Replace # with the switch ID (starting from 0). If the board has only one switch it will be 0.
    + + - <root>/curtain/set Set the curtain opening value (0-100), 0 means closed, 100 opened. "on", "off", "pause" pilots buttons.
    + - <root>/rgb/set Set the color using this topic, your can either send an "#RRGGBB" value or "RRR,GGG,BBB" (0-255 each).
    - <root>/hsv/set Set the color using hue (0-360), saturation (0-100) and value (0-100) values, comma separated.
    @@ -2070,6 +2132,29 @@
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + +
    +
    + +