Browse Source

* Support for ITead's Sonoff Dual

* Support for Electrodragon's ESP Relay Board
* Support for multi-relay boards
* Changed relay MQTT topics
* Changed relay API entry points
* Removed non-secure api entry points
fastled
Xose Pérez 8 years ago
parent
commit
11434301e0
17 changed files with 490 additions and 122 deletions
  1. +1
    -1
      code/data/fsversion
  2. BIN
      code/data/index.html.gz
  3. +3
    -0
      code/html/custom.css
  4. +47
    -15
      code/html/custom.js
  5. +1
    -1
      code/html/fsversion
  6. +22
    -3
      code/html/index.html
  7. +16
    -0
      code/platformio.custom.ini
  8. +44
    -2
      code/platformio.ini
  9. +50
    -4
      code/src/button.ino
  10. +4
    -1
      code/src/config/general.h
  11. +29
    -7
      code/src/config/hardware.h
  12. +2
    -2
      code/src/config/version.h
  13. +16
    -3
      code/src/fauxmo.ino
  14. +18
    -17
      code/src/main.ino
  15. +15
    -10
      code/src/mqtt.ino
  16. +164
    -25
      code/src/relay.ino
  17. +58
    -31
      code/src/web.ino

+ 1
- 1
code/data/fsversion View File

@ -1 +1 @@
1.0.3
1.1.0

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


+ 3
- 0
code/html/custom.css View File

@ -61,3 +61,6 @@ div.hint {
.module {
display: none;
}
#relayTemplate {
display: none;
}

+ 47
- 15
code/html/custom.js View File

@ -22,7 +22,8 @@ function doReconnect() {
}
function doToggle(element, value) {
websock.send(JSON.stringify({'action': value ? 'on' : 'off'}));
var relayID = parseInt(element.attr("data"));
websock.send(JSON.stringify({'action': value ? 'on' : 'off', 'relayID': relayID}));
return false;
}
@ -57,6 +58,31 @@ function toggleMenu() {
$("#menuLink").toggleClass('active');
}
function createRelays(count) {
var current = $("#relays > div").length;
if (current > 0) return;
var template = $("#relayTemplate .pure-g")[0];
for (var relayID=0; relayID<count; relayID++) {
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("data", relayID);
});
if (count > 1) $(".relay_id", line).html(" " + relayID);
line.appendTo("#relays");
$(":checkbox", line).iphoneStyle({
onChange: doToggle,
resizeContainer: true,
resizeHandle: true,
checkedLabel: 'ON',
uncheckedLabel: 'OFF'
});
}
}
function processData(data) {
// title
@ -84,6 +110,26 @@ function processData(data) {
return;
}
// Relay status
if (key == "relayStatus") {
var relays = data.relayStatus;
createRelays(relays.length);
for (var relayID in relays) {
var element = $(".relayStatus[data=" + relayID + "]");
if (element.length > 0) {
element
.prop("checked", relays[relayID])
.iphoneStyle("refresh");
}
}
return;
}
// Messages
if (key == "message") {
window.alert(data.message);
@ -166,20 +212,6 @@ function init() {
$(".button-apikey").on('click', doGenerateAPIKey);
$(".pure-menu-link").on('click', showPanel);
$("input[name='relayStatus']")
.iphoneStyle({
onChange: doToggle
});
$("input[type='checkbox']")
.iphoneStyle({
resizeContainer: true,
resizeHandle: true,
checkedLabel: 'ON',
uncheckedLabel: 'OFF'
})
.iphoneStyle("refresh");
$.ajax({
'method': 'GET',
'url': '/auth'


+ 1
- 1
code/html/fsversion View File

@ -1 +1 @@
1.0.3
1.1.0

+ 22
- 3
code/html/index.html View File

@ -132,11 +132,17 @@
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powActivePower" readonly />
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label for="relayStatus">Relay Status</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="relayStatus" /></div>
<div id="relays">
</div>
<div id="relayTemplate">
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label >Relay<span class="relay_id"></span> Status</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" class="relayStatus" data="0" /></div>
</div>
</div>
</fieldset>
</form>
@ -176,6 +182,19 @@
<div class="pure-u-1 pure-u-md-3-4 hint">Here you can define what will be the status of the relay after a reboot.</div>
</div>
<div class="pure-g module module-multirelay">
<label class="pure-u-1 pure-u-md-1-4" for="relaySync">Relay sync mode</label>
<div class="pure-u-1 pure-u-md-3-4">
<select name="relaySync" class="pure-u-1-2" tabindex="2">
<option value="0">No synchonization</a>
<option value="1">Zero or one relays active</a>
<option value="2">One and just one relay active</a>
</select>
</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Define how the different relays should be synchronized.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass">Admin password</label>
<input name="adminPass" class="pure-u-1 pure-u-md-3-4" type="text" tabindex="3" />


+ 16
- 0
code/platformio.custom.ini View File

@ -65,6 +65,14 @@ build_flags = -g -Wl,-Tesp8266.flash.1m256.ld -DDEBUG_PORT=Serial -DSONOFF_POW
[env:sonoff-pow-debug-ota]
include = env:sonoff-pow-debug,ota
[env:sonoff-dual-debug]
include = common
board = esp12e
build_flags = -g -Wl,-Tesp8266.flash.1m256.ld -DDEBUG_PORT=Serial -DSONOFF_DUAL
[env:sonoff-dual-debug-ota]
include = env:sonoff-dual-debug,ota
[env:slampher-debug]
include = common
board = esp01_1m
@ -81,6 +89,14 @@ build_flags = -g -Wl,-Tesp8266.flash.1m256.ld -DDEBUG_PORT=Serial -DS20
[env:s20-debug-ota]
include = env:s20-debug,ota
[env:electrodragon-debug]
include = common
board = esp12e
build_flags = -g -Wl,-Tesp8266.flash.1m256.ld -DDEBUG_PORT=Serial -DESP_RELAY_BOARD
[env:electrodragon-debug-ota]
include = env:electrodragon-debug,ota
# ------------------------------------------------------------------------------
[env:ac-device]


+ 44
- 2
code/platformio.ini View File

@ -32,7 +32,7 @@ board = nodemcuv2
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial -DNOWSAUTH
[env:node-debug-ota]
platform = espressif8266
@ -41,7 +41,7 @@ board = nodemcuv2
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial -DNOWSAUTH
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
@ -88,6 +88,27 @@ upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[env:sonoff-dual-debug]
platform = espressif8266
framework = arduino
board = esp01_1m
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -Wl,-Tesp8266.flash.1m256.ld -DSONOFF_DUAL
[env:sonoff-dual-debug-ota]
platform = espressif8266
framework = arduino
board = esp01_1m
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -Wl,-Tesp8266.flash.1m256.ld -DSONOFF_DUAL
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[env:slampher-debug]
platform = espressif8266
framework = arduino
@ -130,6 +151,27 @@ upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[env:electrodragon-debug]
platform = espressif8266
framework = arduino
board = esp12e
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -DDEBUG_PORT=Serial -DDEBUG_PORT=Serial -DESP_RELAY_BOARD
[env:electrodragon-debug-ota]
platform = espressif8266
framework = arduino
board = esp12e
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -DDEBUG_PORT=Serial -DDEBUG_PORT=Serial -DESP_RELAY_BOARD
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
# ------------------------------------------------------------------------------
[env:ac-device]


+ 50
- 4
code/src/button.ino View File

@ -7,14 +7,58 @@ Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <DebounceEvent.h>
DebounceEvent button1 = false;
// -----------------------------------------------------------------------------
// BUTTON
// -----------------------------------------------------------------------------
#ifdef SONOFF_DUAL
void buttonSetup() {}
void buttonLoop() {
if (Serial.available() >= 4) {
unsigned char value;
if (Serial.read() == 0xA0) {
if (Serial.read() == 0x04) {
value = Serial.read();
if (Serial.read() == 0xA1) {
// RELAYs and BUTTONs are synchonized in the SIL F330
// The on-board BUTTON2 should toggle RELAY0 value
// Since we are not passing back RELAY2 value
// (in the relayStatus method) it will only be present
// here if it has actually been pressed
if ((value & 4) == 4) value = value ^ 1;
// Otherwise check if any of the other two BUTTONs
// (in the header) has been pressent, but we should
// ensure that we only toggle one of them to avoid
// the synchronization going mad
// This loop is generic for any PSB-04 module
for (unsigned int i=0; i<relayCount(); i++) {
bool status = (value & (1 << i)) > 0;
// relayStatus returns true if the status has changed
if (relayStatus(i, status)) break;
}
}
}
}
}
}
#else
#include <DebounceEvent.h>
DebounceEvent button1 = false;
void buttonSetup() {
button1 = DebounceEvent(BUTTON_PIN);
}
@ -26,3 +70,5 @@ void buttonLoop() {
if (button1.getEvent() == EVENT_LONG_CLICK) ESP.reset();
}
}
#endif

+ 4
- 1
code/src/config/general.h View File

@ -15,6 +15,9 @@
// 0 means OFF, 1 ON and 2 whatever was before
#define RELAY_MODE 1
// 0 means ANY, 1 zero or one and 2 one and only one
#define RELAY_SYNC 0
// -----------------------------------------------------------------------------
// WIFI & WEB
// -----------------------------------------------------------------------------
@ -45,7 +48,7 @@
#define MQTT_QOS 0
#define MQTT_KEEPALIVE 30
#define MQTT_RECONNECT_DELAY 10000
#define MQTT_STATUS_TOPIC ""
#define MQTT_RELAY_TOPIC "/relay/%d"
#define MQTT_IP_TOPIC "/ip"
#define MQTT_VERSION_TOPIC "/version"
#define MQTT_FSVERSION_TOPIC "/fsversion"


+ 29
- 7
code/src/config/hardware.h View File

@ -7,7 +7,7 @@
#define MANUFACTURER "NODEMCU"
#define DEVICE "LOLIN"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 2
#define LED_PIN_INVERSE 0
@ -20,7 +20,7 @@
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 13
#define LED_PIN_INVERSE 0
@ -29,7 +29,7 @@
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_TH"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 13
#define LED_PIN_INVERSE 0
@ -38,17 +38,29 @@
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_POW"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 15
#define LED_PIN_INVERSE 1
#define ENABLE_POW 1
#elif defined(SONOFF_DUAL)
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_DUAL"
#define BUTTON_PIN 0
#define RELAY1_PIN 12
#define RELAY2_PIN 13
#define LED_PIN 15
#define LED_PIN_INVERSE 1
#undef SERIAL_BAUDRATE
#define SERIAL_BAUDRATE 19230
#elif defined(SLAMPHER)
#define MANUFACTURER "ITEAD"
#define DEVICE "SLAMPHER"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 13
#define LED_PIN_INVERSE 0
@ -57,10 +69,20 @@
#define MANUFACTURER "ITEAD"
#define DEVICE "S20"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 13
#define LED_PIN_INVERSE 0
#elif defined(ESP_RELAY_BOARD)
#define MANUFACTURER "ELECTRODRAGON"
#define DEVICE "ESP_RELAY_BOARD"
#define BUTTON_PIN 2
#define RELAY1_PIN 12
#define RELAY2_PIN 13
#define LED_PIN 16
#define LED_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// ESPurna board (still beta)
// -----------------------------------------------------------------------------
@ -70,7 +92,7 @@
#define MANUFACTURER "TINKERMAN"
#define DEVICE "ESPURNA"
#define BUTTON_PIN 0
#define RELAY_PIN 12
#define RELAY1_PIN 12
#define LED_PIN 13
#define LED_PIN_INVERSE 0


+ 2
- 2
code/src/config/version.h View File

@ -1,4 +1,4 @@
#define APP_NAME "Espurna"
#define APP_VERSION "1.0.3"
#define APP_NAME "ESPurna"
#define APP_VERSION "1.1.0"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"

+ 16
- 3
code/src/fauxmo.ino View File

@ -23,10 +23,23 @@ void fauxmoConfigure() {
void fauxmoSetup() {
fauxmoConfigure();
fauxmo.addDevice(getSetting("hostname", HOSTNAME).c_str());
fauxmo.onMessage([](const char * name, bool state) {
unsigned int relays = relayCount();
String hostname = getSetting("hostname", HOSTNAME);
if (relays == 1) {
fauxmo.addDevice(hostname.c_str());
} else {
for (unsigned int i=0; i<relays; i++) {
fauxmo.addDevice((hostname + "_" + i).c_str());
}
}
fauxmo.onMessage([relays](const char * name, bool state) {
DEBUG_MSG("[FAUXMO] %s state: %s\n", name, state ? "ON" : "OFF");
relayStatus(0, state);
unsigned int id = 0;
if (relays > 1) {
id = name[strlen(name)-1] - '0';
if (id >= relays) id = 0;
}
relayStatus(id, state);
});
}


+ 18
- 17
code/src/main.ino View File

@ -64,7 +64,7 @@ void showStatus() {
}
void hardwareSetup() {
Serial.begin(115200);
Serial.begin(SERIAL_BAUDRATE);
pinMode(LED_PIN, OUTPUT);
SPIFFS.begin();
}
@ -105,24 +105,23 @@ void hardwareLoop() {
void welcome() {
delay(2000);
Serial.printf("%s %s\n", (char *) APP_NAME, (char *) APP_VERSION);
Serial.printf("%s\n%s\n\n", (char *) APP_AUTHOR, (char *) APP_WEBSITE);
//Serial.printf("Device: %s\n", (char *) getIdentifier().c_str());
Serial.printf("ChipID: %06X\n", ESP.getChipId());
Serial.printf("Last reset reason: %s\n", (char *) ESP.getResetReason().c_str());
Serial.printf("Memory size: %d bytes\n", ESP.getFlashChipSize());
Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
DEBUG_MSG("%s %s\n", (char *) APP_NAME, (char *) APP_VERSION);
DEBUG_MSG("%s\n%s\n\n", (char *) APP_AUTHOR, (char *) APP_WEBSITE);
//DEBUG_MSG("Device: %s\n", (char *) getIdentifier().c_str());
DEBUG_MSG("ChipID: %06X\n", ESP.getChipId());
DEBUG_MSG("Last reset reason: %s\n", (char *) ESP.getResetReason().c_str());
DEBUG_MSG("Memory size: %d bytes\n", ESP.getFlashChipSize());
DEBUG_MSG("Free heap: %d bytes\n", ESP.getFreeHeap());
FSInfo fs_info;
if (SPIFFS.info(fs_info)) {
Serial.printf("File system total size: %d bytes\n", fs_info.totalBytes);
Serial.printf(" used size : %d bytes\n", fs_info.usedBytes);
Serial.printf(" block size: %d bytes\n", fs_info.blockSize);
Serial.printf(" page size : %d bytes\n", fs_info.pageSize);
Serial.printf(" max files : %d\n", fs_info.maxOpenFiles);
Serial.printf(" max length: %d\n", fs_info.maxPathLength);
DEBUG_MSG("File system total size: %d bytes\n", fs_info.totalBytes);
DEBUG_MSG(" used size : %d bytes\n", fs_info.usedBytes);
DEBUG_MSG(" block size: %d bytes\n", fs_info.blockSize);
DEBUG_MSG(" page size : %d bytes\n", fs_info.pageSize);
DEBUG_MSG(" max files : %d\n", fs_info.maxOpenFiles);
DEBUG_MSG(" max length: %d\n", fs_info.maxPathLength);
}
Serial.println();
Serial.println();
DEBUG_MSG("\n\n");
}
@ -174,12 +173,14 @@ void loop() {
hardwareLoop();
buttonLoop();
settingsLoop();
wifiLoop();
otaLoop();
mqttLoop();
ntpLoop();
#ifndef SONOFF_DUAL
settingsLoop();
#endif
#if ENABLE_NOFUSS
nofussLoop();
#endif


+ 15
- 10
code/src/mqtt.ino View File

@ -43,6 +43,8 @@ void mqttSend(char * topic, char * message) {
void _mqttOnConnect(bool sessionPresent) {
char buffer[50];
DEBUG_MSG("[MQTT] Connected!\n");
// Send status via webSocket
@ -54,16 +56,16 @@ void _mqttOnConnect(bool sessionPresent) {
// Say hello and report our IP and VERSION
mqttSend((char *) MQTT_IP_TOPIC, (char *) getIP().c_str());
mqttSend((char *) MQTT_VERSION_TOPIC, (char *) APP_VERSION);
char buffer[10];
getFSVersion(buffer);
mqttSend((char *) MQTT_FSVERSION_TOPIC, buffer);
// Publish current relay status
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) (relayStatus(0) ? "1" : "0"));
relayMQTT();
// Subscribe to topic
DEBUG_MSG("[MQTT] Subscribing to %s\n", (char *) mqttTopic.c_str());
mqtt.subscribe(mqttTopic.c_str(), MQTT_QOS);
// Subscribe to relay topics
sprintf(buffer, "%s/relay/#", mqttTopic.c_str());
DEBUG_MSG("[MQTT] Subscribing to %s\n", buffer);
mqtt.subscribe(buffer, MQTT_QOS);
}
@ -78,8 +80,7 @@ void _mqttOnMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
static bool isFirstMessage = true;
payload[len] = '\0';
DEBUG_MSG("[MQTT] Received %s %s\n", topic, payload);
DEBUG_MSG("[MQTT] Received %s %c\n", topic, payload[0]);
// If relayMode is not SAME avoid responding to a retained message
if (isFirstMessage) {
@ -88,17 +89,21 @@ void _mqttOnMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
if (relayMode != 2) return;
}
// Get relay ID
unsigned int relayID = topic[strlen(topic)-1] - '0';
if (relayID >= relayCount()) relayID = 0;
// Action to perform
if ((char)payload[0] == '0') {
isCallbackMessage = true;
relayStatus(0, false);
relayStatus(relayID, false);
}
if ((char)payload[0] == '1') {
isCallbackMessage = true;
relayStatus(0, true);
relayStatus(relayID, true);
}
if ((char)payload[0] == '2') {
relayToggle(0);
relayToggle(relayID);
}
isCallbackMessage = false;


+ 164
- 25
code/src/relay.ino View File

@ -8,56 +8,195 @@ Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <EEPROM.h>
#include <ArduinoJson.h>
#include <vector>
std::vector<unsigned char> _relays;
bool recursive = false;
#ifdef SONOFF_DUAL
unsigned char dualRelayStatus = 0;
#endif
#define RELAY_MODE_OFF 0
#define RELAY_MODE_ON 1
#define RELAY_MODE_SAME 2
#define RELAY_SYNC_ANY 0
#define RELAY_SYNC_NONE_OR_ONE 1
#define RELAY_SYNC_ONE 2
// -----------------------------------------------------------------------------
// RELAY
// -----------------------------------------------------------------------------
void _relayOn(unsigned char id) {
void relayMQTT(unsigned char id) {
char buffer[10];
sprintf(buffer, MQTT_RELAY_TOPIC, id);
mqttSend(buffer, (char *) (relayStatus(id) ? "1" : "0"));
}
if (!digitalRead(RELAY_PIN)) {
DEBUG_MSG("[RELAY] ON\n");
digitalWrite(RELAY_PIN, HIGH);
EEPROM.write(0, 1);
EEPROM.commit();
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) "1");
void relayMQTT() {
for (unsigned int i=0; i < _relays.size(); i++) {
relayMQTT(i);
}
}
wsSend((char *) "{\"relayStatus\": true}");
String relayString() {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
relay.add(relayStatus(i));
}
String output;
root.printTo(output);
return output;
}
void _relayOff(unsigned char id) {
void relayWS() {
String output = relayString();
wsSend((char *) output.c_str());
}
if (digitalRead(RELAY_PIN)) {
DEBUG_MSG("[RELAY] OFF\n");
digitalWrite(RELAY_PIN, LOW);
EEPROM.write(0, 0);
EEPROM.commit();
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) "0");
void relaySave() {
unsigned char bit = 1;
unsigned char mask = 0;
for (unsigned int i=0; i < _relays.size(); i++) {
if (relayStatus(i)) mask += bit;
bit += bit;
}
EEPROM.write(0, mask);
EEPROM.commit();
}
wsSend((char *) "{\"relayStatus\": false}");
void relayRetrieve() {
recursive = true;
unsigned char bit = 1;
unsigned char mask = EEPROM.read(0);
for (unsigned int i=0; i < _relays.size(); i++) {
relayStatus(i, ((mask & bit) == bit));
bit += bit;
}
recursive = false;
}
bool relayStatus(unsigned char id) {
#ifdef SONOFF_DUAL
return ((dualRelayStatus & (1 << id)) > 0);
#else
return (digitalRead(_relays[id]) == HIGH);
#endif
}
void relayStatus(unsigned char id, bool status) {
status ? _relayOn(id) : _relayOff(id);
void relaySync(unsigned char id) {
if (_relays.size() > 1) {
recursive = true;
byte relaySync = getSetting("relaySync", String(RELAY_SYNC)).toInt();
bool status = relayStatus(id);
// If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
if (status) {
if (relaySync != RELAY_SYNC_ANY) {
for (unsigned short i=0; i<_relays.size(); i++) {
if (i != id) relayStatus(i, false);
}
}
// If ONLY_ONE and setting OFF we should set ON the other one
} else {
if (relaySync == RELAY_SYNC_ONE) {
unsigned char i = (id + 1) % _relays.size();
relayStatus(i, true);
}
}
recursive = false;
}
}
bool relayStatus(unsigned char id) {
return (digitalRead(RELAY_PIN) == HIGH);
bool relayStatus(unsigned char id, bool status) {
bool changed = false;
if (relayStatus(id) != status) {
DEBUG_MSG("[RELAY] %d => %s\n", id, status ? "ON" : "OFF");
changed = true;
#ifdef SONOFF_DUAL
dualRelayStatus ^= (1 << id);
Serial.flush();
Serial.write(0xA0);
Serial.write(0x04);
Serial.write(dualRelayStatus);
Serial.write(0xA1);
Serial.flush();
#else
digitalWrite(_relays[id], status);
#endif
if (!recursive) {
relaySync(id);
relaySave();
}
}
relayMQTT(id);
if (!recursive) relayWS();
return changed;
}
void relayToggle(unsigned char id) {
relayStatus(id, !relayStatus(id));
}
unsigned char relayCount() {
return _relays.size();
}
void relaySetup() {
pinMode(RELAY_PIN, OUTPUT);
#ifdef SONOFF_DUAL
// Two dummy relays for the dual
_relays.push_back(0);
_relays.push_back(0);
#else
#ifdef RELAY1_PIN
_relays.push_back(RELAY1_PIN);
#endif
#ifdef RELAY2_PIN
_relays.push_back(RELAY2_PIN);
#endif
#ifdef RELAY3_PIN
_relays.push_back(RELAY3_PIN);
#endif
#ifdef RELAY4_PIN
_relays.push_back(RELAY4_PIN);
#endif
#endif
EEPROM.begin(4096);
byte relayMode = getSetting("relayMode", String(RELAY_MODE)).toInt();
if (relayMode == 0) relayStatus(0, false);
if (relayMode == 1) relayStatus(0, true);
if (relayMode == 2) relayStatus(0, EEPROM.read(0) == 1);
for (unsigned int i=0; i < _relays.size(); i++) {
pinMode(_relays[i], OUTPUT);
if (relayMode == RELAY_MODE_OFF) relayStatus(i, false);
if (relayMode == RELAY_MODE_ON) relayStatus(i, true);
}
if (relayMode == RELAY_MODE_SAME) relayRetrieve();
}

+ 58
- 31
code/src/web.ino View File

@ -54,12 +54,18 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
if (root.containsKey("action")) {
String action = root["action"];
unsigned int relayID = 0;
if (root.containsKey("relayID")) {
String value = root["relayID"];
relayID = value.toInt();
}
DEBUG_MSG("[WEBSOCKET] Requested action: %s\n", action.c_str());
if (action.equals("reset")) ESP.reset();
if (action.equals("reconnect")) wifiDisconnect();
if (action.equals("on")) relayStatus(0, true);
if (action.equals("off")) relayStatus(0, false);
if (action.equals("on")) relayStatus(relayID, true);
if (action.equals("off")) relayStatus(relayID, false);
};
@ -191,14 +197,24 @@ void _wsStart(uint32_t client_id) {
root["hostname"] = getSetting("hostname", HOSTNAME);
root["network"] = getNetwork();
root["ip"] = getIP();
root["mqttStatus"] = mqttConnected();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", String(MQTT_PORT));
root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword");
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
root["relayStatus"] = relayStatus(0);
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
relay.add(relayStatus(relayID));
}
root["relayMode"] = getSetting("relayMode", String(RELAY_MODE));
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", String(RELAY_SYNC));
}
root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1;
root["apiKey"] = getSetting("apiKey");
@ -340,24 +356,6 @@ void _onHome(AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.html");
}
void _onRelayOn(AsyncWebServerRequest *request) {
_logRequest(request);
relayStatus(0, true);
request->send(200, "text/plain", "ON");
};
void _onRelayOff(AsyncWebServerRequest *request) {
_logRequest(request);
relayStatus(0, false);
request->send(200, "text/plain", "OFF");
};
bool _apiAuth(AsyncWebServerRequest *request) {
if (getSetting("apiEnabled").toInt() == 0) {
@ -383,9 +381,34 @@ bool _apiAuth(AsyncWebServerRequest *request) {
}
void _onRelay(AsyncWebServerRequest *request) {
_logRequest(request);
if (!_apiAuth(request)) return;
bool asJson = false;
if (request->hasHeader("Accept")) {
AsyncWebHeader* h = request->getHeader("Accept");
asJson = h->value().equals("application/json");
}
String output;
if (asJson) {
output = relayString();
request->send(200, "application/json", output);
} else {
for (unsigned int i=0; i<relayCount(); i++) {
output += "Relay #" + String(i) + String(": ") + String(relayStatus(i) ? "1" : "0") + "\n";
}
request->send(200, "text/plain", output);
}
};
ArRequestHandlerFunction _onRelayStatusWrapper(unsigned int relayID) {
return [&](AsyncWebServerRequest *request) {
return [relayID](AsyncWebServerRequest *request) {
_logRequest(request);
@ -394,6 +417,8 @@ ArRequestHandlerFunction _onRelayStatusWrapper(unsigned int relayID) {
if (request->method() == HTTP_PUT) {
if (request->hasParam("status", true)) {
AsyncWebParameter* p = request->getParam("status", true);
wsSend((char *) String(relayID).c_str());
wsSend((char *) p->value().c_str());
unsigned int value = p->value().toInt();
if (value == 2) {
relayToggle(relayID);
@ -409,10 +434,10 @@ ArRequestHandlerFunction _onRelayStatusWrapper(unsigned int relayID) {
asJson = h->value().equals("application/json");
}
String output;
if (asJson) {
char buffer[20];
sprintf(buffer, "{\"status\": %d}", relayStatus(relayID) ? 1 : 0);
request->send(200, "application/json", buffer);
output = String("{\"relayStatus\": ") + String(relayStatus(relayID) ? "1" : "0") + "}";
request->send(200, "application/json", output);
} else {
request->send(200, "text/plain", relayStatus(relayID) ? "1" : "0");
}
@ -427,16 +452,18 @@ void webSetup() {
ws.onEvent(_wsEvent);
server.addHandler(&ws);
// Serve home (password protected)
// Serve home (basic authentication protection)
server.on("/", HTTP_GET, _onHome);
server.on("/index.html", HTTP_GET, _onHome);
server.on("/auth", HTTP_GET, _onAuth);
// API entry points (non protected)
server.on("/relay/on", HTTP_GET, _onRelayOn);
server.on("/relay/off", HTTP_GET, _onRelayOff);
server.on("/relay/0/status", HTTP_GET + HTTP_PUT, _onRelayStatusWrapper(0));
//server.on("/relay/1/status", HTTP_GET + HTTP_PUT, _onRelayStatusWrapper(1));
// API entry points (protected with apikey)
for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
char buffer[15];
sprintf(buffer, "/api/relay/%d", relayID);
server.on(buffer, HTTP_GET + HTTP_PUT, _onRelayStatusWrapper(relayID));
}
server.on("/api/relay", HTTP_GET, _onRelay);
// Serve static files
server.serveStatic("/", SPIFFS, "/");


Loading…
Cancel
Save