Browse Source

Merge branch 'dev' into justwifi2

fastled^2
Xose Pérez 6 years ago
parent
commit
6c87de02a7
17 changed files with 2813 additions and 2632 deletions
  1. +1
    -0
      code/espurna/config/arduino.h
  2. +1
    -1
      code/espurna/config/defaults.h
  3. +2
    -2
      code/espurna/config/general.h
  4. +46
    -0
      code/espurna/config/hardware.h
  5. BIN
      code/espurna/data/index.html.gz
  6. +6
    -2
      code/espurna/homeassistant.ino
  7. +12
    -0
      code/espurna/migrate.ino
  8. +10
    -1
      code/espurna/mqtt.ino
  9. +20
    -6
      code/espurna/relay.ino
  10. +7
    -0
      code/espurna/settings.ino
  11. +2302
    -2300
      code/espurna/static/index.html.gz.h
  12. +1
    -1
      code/espurna/utils.ino
  13. +5
    -5
      code/espurna/web.ino
  14. +65
    -14
      code/espurna/ws.ino
  15. +21
    -10
      code/html/custom.js
  16. +1
    -1
      code/html/index.html
  17. +313
    -289
      code/platformio.ini

+ 1
- 0
code/espurna/config/arduino.h View File

@ -84,6 +84,7 @@
//#define TONBUX_MOSQUITO_KILLER //#define TONBUX_MOSQUITO_KILLER
//#define NEO_COOLCAM_POWER_PLUG_WIFI //#define NEO_COOLCAM_POWER_PLUG_WIFI
//#define ESTINK_WIFI_POWER_STRIP //#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Features (values below are non-default values) // Features (values below are non-default values)


+ 1
- 1
code/espurna/config/defaults.h View File

@ -424,7 +424,7 @@
// General // General
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Default hostname will be ESPURNA_XXXXXX, where XXXXXX is last 3 octets of chipID
// Default hostname will be ESPURNA-XXXXXX, where XXXXXX is last 3 octets of chipID
#ifndef HOSTNAME #ifndef HOSTNAME
#define HOSTNAME "" #define HOSTNAME ""
#endif #endif


+ 2
- 2
code/espurna/config/general.h View File

@ -559,9 +559,9 @@
#ifndef MQTT_AUTOCONNECT #ifndef MQTT_AUTOCONNECT
#define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SERVER_SUPPORT=1 will perform an autodiscover and #define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SERVER_SUPPORT=1 will perform an autodiscover and
// autoconnect to the first MQTT broker found if none defined
#endif #endif
// autoconnect to the first MQTT broker found if none defined
#ifndef MQTT_SERVER #ifndef MQTT_SERVER
#define MQTT_SERVER "" // Default MQTT broker address #define MQTT_SERVER "" // Default MQTT broker address
#endif #endif
@ -591,7 +591,7 @@
#endif #endif
#ifndef MQTT_KEEPALIVE #ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 30 // MQTT keepalive value
#define MQTT_KEEPALIVE 300 // MQTT keepalive value
#endif #endif


+ 46
- 0
code/espurna/config/hardware.h View File

@ -1945,6 +1945,52 @@
#endif #endif
#define DALLAS_PIN 2 #define DALLAS_PIN 2
// -----------------------------------------------------------------------------
// ESP-DIN relay board V1
// https://github.com/pilotak/esp_din
// -----------------------------------------------------------------------------
#elif defined(PILOTAK_ESP_DIN_V1)
// Info
#define MANUFACTURER "PILOTAK"
#define DEVICE "ESP_DIN_V1"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_RELAY 1
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 5
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 15
#define LED1_PIN_INVERSE 0
#define I2C_SDA_PIN 12
#define I2C_SCL_PIN 13
#ifndef DALLAS_SUPPORT
#define DALLAS_SUPPORT 1
#endif
#define DALLAS_PIN 2
#ifndef RF_SUPPORT
#define RF_SUPPORT 1
#endif
#define RF_PIN 14
#ifndef DIGITAL_SUPPORT
#define DIGITAL_SUPPORT 1
#endif
#define DIGITAL_PIN 16
#define DIGITAL_PIN_MODE INPUT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Heltec Touch Relay // Heltec Touch Relay
// https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180408043114&SearchText=esp8266+touch+relay // https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180408043114&SearchText=esp8266+touch+relay


BIN
code/espurna/data/index.html.gz View File


+ 6
- 2
code/espurna/homeassistant.ino View File

@ -255,13 +255,17 @@ void _haInitCommands() {
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) { settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1"); setSetting("haEnabled", "1");
_haConfigure(); _haConfigure();
wsSend(_haWebSocketOnSend);
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) { settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0"); setSetting("haEnabled", "0");
_haConfigure(); _haConfigure();
wsSend(_haWebSocketOnSend);
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
} }


+ 12
- 0
code/espurna/migrate.ino View File

@ -963,6 +963,18 @@ void migrate() {
setSetting("relayGPIO", 0, 12); setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL); setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(PILOTAK_ESP_DIN_V1)
setSetting("board", 76);
setSetting("ledGPIO", 0, 16);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ESTINK_WIFI_POWER_STRIP) #elif defined(ESTINK_WIFI_POWER_STRIP)
setSetting("board", 76); setSetting("board", 76);


+ 10
- 1
code/espurna/mqtt.ino View File

@ -218,7 +218,6 @@ void _mqttConfigure() {
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1); if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
// Placeholders // Placeholders
_mqtt_topic.replace("{identifier}", getSetting("hostname"));
_mqtt_topic.replace("{hostname}", getSetting("hostname")); _mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#"); _mqtt_topic.replace("{magnitude}", "#");
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#"; if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
@ -250,6 +249,14 @@ void _mqttConfigure() {
} }
void _mqttBackwards() {
String mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
if (mqttTopic.indexOf("{identifier}") > 0) {
mqttTopic.replace("{identifier}", "{hostname}");
setSetting("mqttTopic", mqttTopic);
}
}
unsigned long _mqttNextMessageId() { unsigned long _mqttNextMessageId() {
static unsigned long id = 0; static unsigned long id = 0;
@ -741,6 +748,8 @@ void mqttReset() {
void mqttSetup() { void mqttSetup() {
_mqttBackwards();
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"), DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED", MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED", ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",


+ 20
- 6
code/espurna/relay.ino View File

@ -23,6 +23,7 @@ typedef struct {
unsigned long delay_off; // Delay to turn relay OFF unsigned long delay_off; // Delay to turn relay OFF
unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON
unsigned long pulse_ms; // Pulse length in millis unsigned long pulse_ms; // Pulse length in millis
unsigned long pulse_start; // Current pulse start (millis), 0 means no pulse
// Status variables // Status variables
@ -34,10 +35,6 @@ typedef struct {
bool report; // Whether to report to own topic bool report; // Whether to report to own topic
bool group_report; // Whether to report to group topic bool group_report; // Whether to report to group topic
// Helping objects
Ticker pulseTicker; // Holds the pulse back timer
} relay_t; } relay_t;
std::vector<relay_t> _relays; std::vector<relay_t> _relays;
bool _relayRecursive = false; bool _relayRecursive = false;
@ -203,6 +200,21 @@ void _relayProcess(bool mode) {
} }
/**
* Walks the relay vector check if any relay has to pulse back
*/
void _relayPulseCheck() {
unsigned long current_time = millis();
for (unsigned char id = 0; id < _relays.size(); id++) {
if (_relays[id].pulse_start > 0) {
if (current_time - _relays[id].pulse_start > _relays[id].pulse_ms) {
_relays[id].pulse_start = 0;
relayToggle(id);
}
}
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RELAY // RELAY
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -218,10 +230,10 @@ void relayPulse(unsigned char id) {
bool pulseStatus = (mode == RELAY_PULSE_ON); bool pulseStatus = (mode == RELAY_PULSE_ON);
if (pulseStatus == status) { if (pulseStatus == status) {
_relays[id].pulseTicker.detach();
_relays[id].pulse_start = 0;
} else { } else {
DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms); DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms);
_relays[id].pulseTicker.once_ms(ms, relayToggle, id);
_relays[id].pulse_start = millis();
} }
} }
@ -464,6 +476,7 @@ void _relayBoot() {
} }
_relays[i].current_status = !status; _relays[i].current_status = !status;
_relays[i].target_status = status; _relays[i].target_status = status;
_relays[i].pulse_start = 0;
#if RELAY_PROVIDER == RELAY_PROVIDER_STM #if RELAY_PROVIDER == RELAY_PROVIDER_STM
_relays[i].change_time = millis() + 3000 + 1000 * i; _relays[i].change_time = millis() + 3000 + 1000 * i;
#else #else
@ -821,6 +834,7 @@ void _relayInitCommands() {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void _relayLoop() { void _relayLoop() {
_relayPulseCheck();
_relayProcess(false); _relayProcess(false);
_relayProcess(true); _relayProcess(true);
} }


+ 7
- 0
code/espurna/settings.ino View File

@ -269,6 +269,13 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("RESET"), [](Embedis* e) { settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL); deferredReset(100, CUSTOM_RESET_TERMINAL);


+ 2302
- 2300
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 1
- 1
code/espurna/utils.ino View File

@ -11,7 +11,7 @@ Ticker _defer_reset;
String getIdentifier() { String getIdentifier() {
char buffer[20]; char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), APP_NAME, ESP.getChipId());
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%06X"), APP_NAME, ESP.getChipId());
return String(buffer); return String(buffer);
} }


+ 5
- 5
code/espurna/web.ino View File

@ -43,7 +43,7 @@ void _onReset(AsyncWebServerRequest *request) {
void _onGetConfig(AsyncWebServerRequest *request) { void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
AsyncResponseStream *response = request->beginResponseStream("text/json"); AsyncResponseStream *response = request->beginResponseStream("text/json");
@ -64,7 +64,7 @@ void _onGetConfig(AsyncWebServerRequest *request) {
void _onPostConfig(AsyncWebServerRequest *request) { void _onPostConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
request->send(_webConfigSuccess ? 200 : 400); request->send(_webConfigSuccess ? 200 : 400);
} }
@ -112,7 +112,7 @@ void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t i
void _onHome(AsyncWebServerRequest *request) { void _onHome(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (request->header("If-Modified-Since").equals(_last_modified)) { if (request->header("If-Modified-Since").equals(_last_modified)) {
@ -212,7 +212,7 @@ int _onCertificate(void * arg, const char *filename, uint8_t **buf) {
void _onUpgrade(AsyncWebServerRequest *request) { void _onUpgrade(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
char buffer[10]; char buffer[10];
if (!Update.hasError()) { if (!Update.hasError()) {
@ -262,7 +262,7 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool _authenticate(AsyncWebServerRequest *request) {
bool webAuthenticate(AsyncWebServerRequest *request) {
#if USE_PASSWORD #if USE_PASSWORD
String password = getSetting("adminPass", ADMIN_PASS); String password = getSetting("adminPass", ADMIN_PASS);
char httpPassword[password.length() + 1]; char httpPassword[password.length() + 1];


+ 65
- 14
code/espurna/ws.ino View File

@ -27,6 +27,57 @@ std::vector<ws_on_receive_callback_f> _ws_on_receive_callbacks;
// Private methods // Private methods
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
typedef struct {
IPAddress ip;
unsigned long timestamp = 0;
} ws_ticket_t;
ws_ticket_t _ticket[WS_BUFFER_SIZE];
void _onAuth(AsyncWebServerRequest *request) {
webLog(request);
if (!webAuthenticate(request)) return request->requestAuthentication();
IPAddress ip = request->client()->remoteIP();
unsigned long now = millis();
unsigned short index;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if (_ticket[index].ip == ip) break;
if (_ticket[index].timestamp == 0) break;
if (now - _ticket[index].timestamp > WS_TIMEOUT) break;
}
if (index == WS_BUFFER_SIZE) {
request->send(429);
} else {
_ticket[index].ip = ip;
_ticket[index].timestamp = now;
request->send(200, "text/plain", "OK");
}
}
bool _wsAuth(AsyncWebSocketClient * client) {
IPAddress ip = client->remoteIP();
unsigned long now = millis();
unsigned short index = 0;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if ((_ticket[index].ip == ip) && (now - _ticket[index].timestamp < WS_TIMEOUT)) break;
}
if (index == WS_BUFFER_SIZE) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Validation check failed\n"));
wsSend_P(client->id(), PSTR("{\"message\": 10}"));
return false;
}
return true;
}
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT #if MQTT_SUPPORT
void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) { void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}")); if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}"));
@ -205,9 +256,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
if (save) { if (save) {
// Callbacks // Callbacks
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
wsReload();
// This should got to callback as well // This should got to callback as well
// but first change management has to be in place // but first change management has to be in place
@ -317,6 +366,11 @@ void _wsStart(uint32_t client_id) {
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if (type == WS_EVT_CONNECT) { if (type == WS_EVT_CONNECT) {
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
#endif
IPAddress ip = client->remoteIP(); IPAddress ip = client->remoteIP();
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url()); DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
_wsStart(client->id()); _wsStart(client->id());
@ -423,27 +477,24 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
_ws.text(client_id, buffer); _ws.text(client_id, buffer);
} }
void wsConfigure() {
#if USE_PASSWORD
bool auth = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
if (auth) {
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
} else {
_ws.setAuthentication("", "");
}
#endif
// This method being public makes
// _ws_on_after_parse_callbacks strange here,
// it should belong somewhere else.
void wsReload() {
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
} }
void wsSetup() { void wsSetup() {
_ws.onEvent(_wsEvent); _ws.onEvent(_wsEvent);
wsConfigure();
webServer()->addHandler(&_ws); webServer()->addHandler(&_ws);
webServer()->on("/auth", HTTP_GET, _onAuth);
#if MQTT_SUPPORT #if MQTT_SUPPORT
mqttRegister(_wsMQTTCallback); mqttRegister(_wsMQTTCallback);
#endif #endif
wsOnSendRegister(_wsOnStart); wsOnSendRegister(_wsOnStart);
wsOnReceiveRegister(_wsOnReceive); wsOnReceiveRegister(_wsOnReceive);
wsOnAfterParseRegister(wsConfigure);
espurnaRegisterLoop(_wsLoop); espurnaRegisterLoop(_wsLoop);
} }


+ 21
- 10
code/html/custom.js View File

@ -142,7 +142,7 @@ function validateForm(form) {
// http://www.the-art-of-web.com/javascript/validate-password/ // http://www.the-art-of-web.com/javascript/validate-password/
// at least one lowercase and one uppercase letter or number // at least one lowercase and one uppercase letter or number
// at least five characters (letters, numbers or special characters) // at least five characters (letters, numbers or special characters)
var re_password = new RegExp('^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$');
var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
// password // password
var adminPass1 = $("input[name='adminPass']", form).first().val(); var adminPass1 = $("input[name='adminPass']", form).first().val();
@ -1351,7 +1351,7 @@ function hasChanged() {
function initUrls(root) { function initUrls(root) {
var paths = ["ws", "upgrade", "config"];
var paths = ["ws", "upgrade", "config", "auth"];
urls["root"] = root; urls["root"] = root;
paths.forEach(function(path) { paths.forEach(function(path) {
@ -1363,15 +1363,26 @@ function initUrls(root) {
} }
function connectToURL(url) { function connectToURL(url) {
initUrls(url); initUrls(url);
if (websock) { websock.close(); }
websock = new WebSocket(urls.ws.href);
websock.onmessage = function(evt) {
var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
if (data) {
processData(data);
}
};
$.ajax({
'method': 'GET',
'url': urls.auth.href,
'xhrFields': { 'withCredentials': true }
}).done(function(data) {
if (websock) { websock.close(); }
websock = new WebSocket(urls.ws.href);
websock.onmessage = function(evt) {
var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
if (data) {
processData(data);
}
};
}).fail(function() {
// Nothing to do, reload page and retry
});
} }
function connect(host) { function connect(host) {


+ 1
- 1
code/html/index.html View File

@ -1336,7 +1336,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Pulse time (s)</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>Pulse time (s)</label></div>
<div class="pure-u-1 pure-u-lg-1-4"><input name="relayTime" class="pure-u-1" type="number" min="0" step="0.1" max="3600" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input name="relayTime" class="pure-u-1" type="number" min="0" step="0.1" max="86400" /></div>
</div> </div>
<div class="pure-g module module-mqtt"> <div class="pure-g module module-mqtt">
<div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div>


+ 313
- 289
code/platformio.ini
File diff suppressed because it is too large
View File


Loading…
Cancel
Save