diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 68fea5dc..bca537b1 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -30,6 +30,7 @@ //#define LED_CONTROLLER //#define H801_LED_CONTROLLER //#define ESPURNA_H +//#define SONOFF_RFBRIDGE //-------------------------------------------------------------------------------- // Features (values below are non-default values) diff --git a/code/espurna/config/debug.h b/code/espurna/config/debug.h index 271a4c25..54178cd3 100644 --- a/code/espurna/config/debug.h +++ b/code/espurna/config/debug.h @@ -1,6 +1,6 @@ #define DEBUG_MESSAGE_MAX_LENGTH 80 -#ifdef SONOFF_DUAL +#if defined(SONOFF_DUAL) #undef DEBUG_PORT #endif diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index d1ad0705..ed327dba 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -24,7 +24,7 @@ // nc -ul 8111 //#define DEBUG_UDP_IP IPAddress(192, 168, 1, 100) -//#define DEBUG_UDP_PORT 8111 +//#define DEBUG_UDP_PORT 8113 //-------------------------------------------------------------------------------- // EEPROM @@ -136,6 +136,7 @@ PROGMEM const char* const custom_reset_string[] = { #define RELAY_PROVIDER_RELAY 0 #define RELAY_PROVIDER_DUAL 1 #define RELAY_PROVIDER_LIGHT 2 +#define RELAY_PROVIDER_RFBRIDGE 3 // Pulse time in milliseconds #define RELAY_PULSE_TIME 1.0 @@ -249,6 +250,9 @@ PROGMEM const char* const custom_reset_string[] = { #define MQTT_TOPIC_HOSTNAME "host" #define MQTT_TOPIC_TIME "time" #define MQTT_TOPIC_ANALOG "analog" +#define MQTT_TOPIC_RFOUT "rfout" +#define MQTT_TOPIC_RFIN "rfin" +#define MQTT_TOPIC_RFLEARN "rflearn" #define MQTT_STATUS_ONLINE "1" // Value for the device ON message #define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will) @@ -345,3 +349,11 @@ PROGMEM const char* const custom_reset_string[] = { // this device should be discoberable and respond to Alexa commands. // Both ENABLE_FAUXMO and fauxmoEnabled should be 1 for Alexa support to work. #define FAUXMO_ENABLED 1 + + +// ----------------------------------------------------------------------------- +// RFBRIDGE +// ----------------------------------------------------------------------------- + +#define RF_SEND_TIMES 4 // How many times to send the message +#define RF_SEND_DELAY 250 // Interval between sendings in ms diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 12b2b48e..d5d06ba8 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -161,6 +161,7 @@ #define SERIAL_BAUDRATE 19230 #undef RELAY_PROVIDER #define RELAY_PROVIDER RELAY_PROVIDER_DUAL + #define DUMMY_RELAY_COUNT 2 #elif defined(SONOFF_4CH) @@ -222,6 +223,21 @@ #define LED1_PIN 13 #define LED1_PIN_INVERSE 1 +#elif defined(SONOFF_RFBRIDGE) + + #define MANUFACTURER "ITEAD" + #define DEVICE "RFBRIDGE" + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + #undef SERIAL_BAUDRATE + #define SERIAL_BAUDRATE 19200 + #undef RELAY_PROVIDER + #define RELAY_PROVIDER RELAY_PROVIDER_RFBRIDGE + #define DUMMY_RELAY_COUNT 6 + #define TRACK_RELAY_STATUS 0 + // ----------------------------------------------------------------------------- // Electrodragon boards // ----------------------------------------------------------------------------- @@ -269,6 +285,7 @@ #define DEVICE "AI_LIGHT" #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192 + #define DUMMY_RELAY_COUNT 1 // ----------------------------------------------------------------------------- // LED Controller @@ -282,6 +299,7 @@ #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_RGB + #define DUMMY_RELAY_COUNT 1 #undef RGBW_INVERSE_LOGIC #undef RGBW_RED_PIN @@ -307,6 +325,7 @@ #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_RGB2W + #define DUMMY_RELAY_COUNT 1 #undef RGBW_INVERSE_LOGIC #undef RGBW_RED_PIN @@ -528,6 +547,11 @@ #define BUTTON_SET_PULLUP 4 #endif +// Does the board track the relay status? +#ifndef TRACK_RELAY_STATUS +#define TRACK_RELAY_STATUS 1 +#endif + // Relay providers #ifndef RELAY_PROVIDER #define RELAY_PROVIDER RELAY_PROVIDER_RELAY diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index e8edfd43..2a5e3de6 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -120,19 +120,22 @@ unsigned char customReset() { } void hardwareSetup() { + EEPROM.begin(4096); + #ifdef DEBUG_PORT DEBUG_PORT.begin(SERIAL_BAUDRATE); if (customReset() == CUSTOM_RESET_HARDWARE) { DEBUG_PORT.setDebugOutput(true); } - #endif - #ifdef SONOFF_DUAL + #elif defined(SERIAL_BAUDRATE) Serial.begin(SERIAL_BAUDRATE); #endif + #if not EMBEDDED_WEB SPIFFS.begin(); #endif + } void hardwareLoop() { @@ -214,6 +217,9 @@ void setup() { mqttSetup(); ntpSetup(); + #ifdef SONOFF_RFBRIDGE + rfbSetup(); + #endif #if ENABLE_I2C i2cSetup(); #endif @@ -267,9 +273,12 @@ void loop() { #if ENABLE_FAUXMO fauxmoLoop(); #endif - #ifndef SONOFF_DUAL + #if !defined(SONOFF_DUAL) & !defined(SONOFF_RFBRIDGE) settingsLoop(); #endif + #ifdef SONOFF_RFBRIDGE + rfbLoop(); + #endif #if ENABLE_NOFUSS nofussLoop(); #endif diff --git a/code/espurna/hardware.ino b/code/espurna/hardware.ino index a34e4466..420ade14 100644 --- a/code/espurna/hardware.ino +++ b/code/espurna/hardware.ino @@ -17,6 +17,7 @@ the migration to future version 2 will be straigh forward. #define RELAY_PROVIDER_RELAY 0 #define RELAY_PROVIDER_DUAL 1 #define RELAY_PROVIDER_LIGHT 2 +#define RELAY_PROVIDER_RFBRIDGE 3 #define LIGHT_PROVIDER_NONE 0 #define LIGHT_PROVIDER_WS2812 1 @@ -278,6 +279,14 @@ void hwUpwardsCompatibility() { setSetting("lightLogic", 1); #endif + #ifdef SONOFF_RFBRIDGE + setSetting("board", 26); + setSetting("ledGPIO", 1, 13); + setSetting("ledLogic", 1, 1); + setSetting("btnGPIO", 1, 0); + setSetting("relayProvider", RELAY_PROVIDER_RFBRIDGE); + #endif + saveSettings(); } diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 8aa9de83..1232a17b 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -216,14 +216,13 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) { char message[len + 1]; strlcpy(message, (char *) payload, len + 1); - DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s"), topic, message); #if MQTT_SKIP_RETAINED if (millis() - mqttConnectedAt < MQTT_SKIP_TIME) { - DEBUG_MSG_P(PSTR(" - SKIPPED\n")); + DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message); return; } #endif - DEBUG_MSG_P(PSTR("\n")); + DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message); // Check system topics String t = mqttSubtopic((char *) topic); diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 0a264f2c..2cc3317a 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -20,6 +20,7 @@ typedef struct { unsigned long delay_off; unsigned int floodWindowStart; unsigned char floodWindowChanges; + bool scheduled; unsigned int scheduledStatusTime; bool scheduledStatus; bool scheduledReport; @@ -40,6 +41,10 @@ void relayProviderStatus(unsigned char id, bool status) { if (id >= _relays.size()) return; + #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE + rfbStatus(id, status); + #endif + #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL _dual_status ^= (1 << id); Serial.flush(); @@ -64,6 +69,10 @@ bool relayProviderStatus(unsigned char id) { if (id >= _relays.size()) return false; + #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE + return _relays[id].scheduledStatus; + #endif + #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL return ((_dual_status & (1 << id)) > 0); #endif @@ -142,7 +151,9 @@ bool relayStatus(unsigned char id, bool status, bool report) { bool changed = false; + #if TRACK_RELAY_STATUS if (relayStatus(id) != status) { + #endif unsigned int currentTime = millis(); unsigned int floodWindowEnd = _relays[id].floodWindowStart + 1000 * RELAY_FLOOD_WINDOW; @@ -169,6 +180,7 @@ bool relayStatus(unsigned char id, bool status, bool report) { } + _relays[id].scheduled = true; _relays[id].scheduledStatus = status; if (report) _relays[id].scheduledReport = true; @@ -178,7 +190,9 @@ bool relayStatus(unsigned char id, bool status, bool report) { changed = true; + #if TRACK_RELAY_STATUS } + #endif return changed; } @@ -400,16 +414,14 @@ void relayInfluxDB(unsigned char id) { void relaySetup() { - #if defined(SONOFF_DUAL) - - // Two dummy relays for the dual - _relays.push_back((relay_t) {0, 0, 0, RELAY1_DELAY_ON, RELAY1_DELAY_OFF}); - _relays.push_back((relay_t) {0, 0, 0, RELAY2_DELAY_ON, RELAY2_DELAY_OFF}); - - #elif defined(AI_LIGHT) | defined(LED_CONTROLLER) | defined(H801_LED_CONTROLLER) + // Dummy relays for AI Light, Magic Home LED Controller, H801, + // Sonoff Dual and Sonoff RF Bridge + #ifdef DUMMY_RELAY_COUNT - // One dummy relay for the AI Thinker Light & Magic Home and H801 led controllers - _relays.push_back((relay_t) {0, 0, 0, RELAY1_DELAY_ON, RELAY1_DELAY_OFF}); + for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) { + _relays.push_back((relay_t) {0, 0}); + _relays[i].scheduled = false; + } #else @@ -454,7 +466,11 @@ void relayLoop(void) { unsigned int currentTime = millis(); bool status = _relays[id].scheduledStatus; + #if TRACK_RELAY_STATUS if (relayStatus(id) != status && currentTime >= _relays[id].scheduledStatusTime) { + #else + if (_relays[id].scheduled && currentTime >= _relays[id].scheduledStatusTime) { + #endif DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF"); @@ -486,6 +502,7 @@ void relayLoop(void) { relayInfluxDB(id); #endif + _relays[id].scheduled = false; _relays[id].scheduledReport = false; } diff --git a/code/espurna/rfbridge.ino b/code/espurna/rfbridge.ino new file mode 100644 index 00000000..da238fd2 --- /dev/null +++ b/code/espurna/rfbridge.ino @@ -0,0 +1,307 @@ +/* + +ITEAD RF BRIDGE MODULE + +Copyright (C) 2017 by Xose PĂ©rez + +*/ + +#ifdef SONOFF_RFBRIDGE + +// ----------------------------------------------------------------------------- +// DEFINITIONS +// ----------------------------------------------------------------------------- + +#define RF_MESSAGE_SIZE 9 +#define RF_CODE_START 0xAA +#define RF_CODE_ACK 0xA0 +#define RF_CODE_LEARN 0xA1 +#define RF_CODE_LEARN_KO 0xA2 +#define RF_CODE_LEARN_OK 0xA3 +#define RF_CODE_RFIN 0xA4 +#define RF_CODE_RFOUT 0xA5 +#define RF_CODE_STOP 0x55 + +// ----------------------------------------------------------------------------- +// GLOBALS TO THE MODULE +// ----------------------------------------------------------------------------- + +unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0}; +unsigned char _uartpos = 0; +unsigned char _learnId = 0; +bool _learnStatus = true; +bool _rfbin = false; + +// ----------------------------------------------------------------------------- +// PRIVATES +// ----------------------------------------------------------------------------- + +void _rfbAck() { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n")); + Serial.println(); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_ACK); + Serial.write(RF_CODE_STOP); + Serial.flush(); + Serial.println(); +} + +void _rfbLearn() { + + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n")); + Serial.println(); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_LEARN); + Serial.write(RF_CODE_STOP); + Serial.flush(); + Serial.println(); + + char wsb[100]; + sprintf_P(wsb, PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0); + wsSend(wsb); + +} + +void _rfbSend(byte * message) { + Serial.println(); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_RFOUT); + for (unsigned char j=0; j0) { + unsigned long start = millis(); + while (millis() - start < RF_SEND_DELAY) delay(1); + } + _rfbSend(message); + } + +} + +void _rfbDecode() { + + byte action = _uartbuf[0]; + char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0}; + DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action); + + if (action == RF_CODE_LEARN_KO) { + _rfbAck(); + DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n")); + wsSend("{\"action\": \"rfbTimeout\"}"); + } + + if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) { + _rfbToChar(&_uartbuf[1], buffer); + mqttSend(MQTT_TOPIC_RFIN, buffer); + _rfbAck(); + } + + if (action == RF_CODE_LEARN_OK) { + + DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n")); + rfbStore(_learnId, _learnStatus, buffer); + + // Websocket update + char wsb[100]; + sprintf_P(wsb, PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer); + wsSend(wsb); + + } + + if (action == RF_CODE_RFIN) { + + DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer); + + // Look for the code + unsigned char id, status; + bool found = false; + for (id=0; id= relayCount()) { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId); + return; + } + _learnStatus = (char)payload[0] != '0'; + _rfbLearn(); + + } + + if (t.equals(MQTT_TOPIC_RFOUT)) { + byte message[RF_MESSAGE_SIZE]; + if (_rfbToArray(payload, message)) { + _rfbSend(message, 1); + } + } + + } + +} + +// ----------------------------------------------------------------------------- +// PUBLIC +// ----------------------------------------------------------------------------- + +void rfbStore(unsigned char id, bool status, const char * code) { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code); + char key[8] = {0}; + sprintf(key, "rfb%d%s", id, status ? "on" : "off"); + setSetting(key, code); +} + +String rfbRetrieve(unsigned char id, bool status) { + char key[8] = {0}; + sprintf(key, "rfb%d%s", id, status ? "on" : "off"); + return getSetting(key); +} + +void rfbStatus(unsigned char id, bool status) { + String value = rfbRetrieve(id, status); + if (value.length() > 0) { + bool same = _rfbSameOnOff(id); + byte message[RF_MESSAGE_SIZE]; + _rfbToArray(value.c_str(), message); + unsigned char times = RF_SEND_TIMES; + if (same) times = _rfbin ? 0 : 1; + _rfbSend(message, times); + } +} + +void rfbLearn(unsigned char id, bool status) { + _learnId = id; + _learnStatus = status; + _rfbLearn(); +} + +void rfbForget(unsigned char id, bool status) { + + char key[8] = {0}; + sprintf(key, "rfb%d%s", id, status ? "on" : "off"); + delSetting(key); + + // Websocket update + char wsb[100]; + sprintf_P(wsb, PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0); + wsSend(wsb); + +} + +// ----------------------------------------------------------------------------- +// SETUP & LOOP +// ----------------------------------------------------------------------------- + +void rfbSetup() { + mqttRegister(_rfbMqttCallback); +} + +void rfbLoop() { + _rfbReceive(); +} + +#endif diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 80d49506..ce9ae9f6 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -92,6 +92,21 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { ESP.restart(); } + #ifdef SONOFF_RFBRIDGE + if (action.equals("rfblearn") && root.containsKey("data")) { + JsonObject& data = root["data"]; + rfbLearn(data["id"], data["status"]); + } + if (action.equals("rfbforget") && root.containsKey("data")) { + JsonObject& data = root["data"]; + rfbForget(data["id"], data["status"]); + } + if (action.equals("rfbsend") && root.containsKey("data")) { + JsonObject& data = root["data"]; + rfbStore(data["id"], data["status"], data["data"].as()); + } + #endif + if (action.equals("restore") && root.containsKey("data")) { JsonObject& data = root["data"]; @@ -129,7 +144,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { if (data.containsKey("status")) { - bool state = (strcmp(data["status"], "1") == 0); + bool status = (strcmp(data["status"], "1") == 0); unsigned int relayID = 0; if (data.containsKey("id")) { @@ -137,7 +152,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { relayID = value.toInt(); } - relayStatus(relayID, state); + relayStatus(relayID, status); } @@ -565,6 +580,20 @@ void _wsStart(uint32_t client_id) { root["powPowerFactor"] = String(getPowerFactor(), 2); #endif + #ifdef SONOFF_RFBRIDGE + root["rfbVisible"] = 1; + root["rfbCount"] = relayCount(); + JsonArray& rfb = root.createNestedArray("rfb"); + for (byte id=0; id fieldset").length; + + var template = $("#rfbNodeTemplate").children(); + var line = $(template).clone(); + var status = true; + $("span", line).html(numNodes+1); + $(line).find("input").each(function() { + $(this).attr("data_id", numNodes); + $(this).attr("data_status", status ? 1 : 0); + status = !status; + }); + $(line).find(".button-rfb-learn").on('click', rfbLearn); + $(line).find(".button-rfb-forget").on('click', rfbForget); + $(line).find(".button-rfb-send").on('click', rfbSend); + line.appendTo("#rfbNodes"); + + return line; +} + +function rfbLearn() { + var parent = $(this).parents(".pure-g"); + var input = $("input", parent); + websock.send(JSON.stringify({'action': 'rfblearn', 'data' : {'id' : input.attr("data_id"), 'status': input.attr("data_status")}})); +} + +function rfbForget() { + var parent = $(this).parents(".pure-g"); + var input = $("input", parent); + websock.send(JSON.stringify({'action': 'rfbforget', 'data' : {'id' : input.attr("data_id"), 'status': input.attr("data_status")}})); +} + +function rfbSend() { + var parent = $(this).parents(".pure-g"); + var input = $("input", parent); + websock.send(JSON.stringify({'action': 'rfbsend', 'data' : {'id' : input.attr("data_id"), 'status': input.attr("data_status"), 'data': input.val()}})); +} + function forgetCredentials() { $.ajax({ 'method': 'GET', @@ -337,10 +376,33 @@ function processData(data) { }, 1000); } + if (data.action == "rfbLearn") { + // Nothing to do? + } + + if (data.action == "rfbTimeout") { + // Nothing to do? + } + return; } + if (key == "rfbCount") { + for (var i=0; iPOWER +
  • + RFBRIDGE +
  • +
  • ADMIN
  • @@ -717,12 +721,57 @@ +
    + +
    +

    RFBRIDGE

    +

    + Sonoff 433 RF Bridge Configuration

    + To learn a new code click LEARN, the Sonoff RFBridge will beep, then press a button on the remote, the RFBridge will then double beep and the new code should show up. If the device double beeps but the code does not update it has not been properly learnt. Keep trying.

    + Modify or create new codes manually (all codes must be 18 characters long) and then click SAVE to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.

    + Delete any code clicking the FORGET button. +

    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +  Switch   + +
    + + +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    + +
    + +
    +
    diff --git a/code/platformio.ini b/code/platformio.ini index 4a00daea..1c491bb3 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -262,6 +262,27 @@ upload_speed = 115200 upload_port = "192.168.4.1" upload_flags = --auth=fibonacci --port 8266 +[env:rfbridge-debug] +platform = espressif8266 +framework = arduino +board = esp01_1m +board_flash_mode = dout +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +build_flags = ${common.build_flags_1m} -DSONOFF_RFBRIDGE + +[env:rfbridge-debug-ota] +platform = espressif8266 +framework = arduino +board = esp01_1m +board_flash_mode = dout +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +build_flags = ${common.build_flags_1m} -DSONOFF_RFBRIDGE +upload_speed = 115200 +upload_port = "192.168.4.1" +upload_flags = --auth=Algernon1 --port 8266 + [env:1ch-inching-debug] platform = espressif8266 framework = arduino