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 e14e4561..7fdd6534 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -23,8 +23,8 @@ // To receive the message son the destination computer use nc: // nc -ul 8111 -#define DEBUG_UDP_IP IPAddress(192, 168, 1, 100) -#define DEBUG_UDP_PORT 8113 +//#define DEBUG_UDP_IP IPAddress(192, 168, 1, 100) +//#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 @@ -250,6 +251,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" // Lights #define MQTT_TOPIC_CHANNEL "channel" @@ -351,3 +355,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 b896b157..fa38822d 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) @@ -230,9 +231,25 @@ #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 #define LIGHT_CH1_PIN 12 #define LIGHT_CH1_INVERSE 0 +#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 // ----------------------------------------------------------------------------- @@ -280,6 +297,7 @@ #define DEVICE "AI_LIGHT" #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192 + #define DUMMY_RELAY_COUNT 1 #define MY9291_DI_PIN 13 #define MY9291_DCKI_PIN 15 @@ -297,6 +315,7 @@ #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 #define LIGHT_CH1_PIN 14 // RED #define LIGHT_CH2_PIN 5 // GREEN @@ -320,6 +339,7 @@ #define LED1_PIN_INVERSE 1 #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 #define LIGHT_CH1_PIN 15 // RED #define LIGHT_CH2_PIN 13 // GREEN @@ -540,6 +560,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 cf61c80e..9b3083e3 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -118,19 +118,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() { @@ -212,6 +215,9 @@ void setup() { mqttSetup(); ntpSetup(); + #ifdef SONOFF_RFBRIDGE + rfbSetup(); + #endif #if ENABLE_I2C i2cSetup(); #endif @@ -266,9 +272,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 53d6c4d8..fca50029 100644 --- a/code/espurna/hardware.ino +++ b/code/espurna/hardware.ino @@ -284,6 +284,14 @@ void hwUpwardsCompatibility() { setSetting("ch1Logic", 0); #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 fa486bd3..a4566926 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -227,14 +227,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); // Send message event to subscribers for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index eef48c48..18811078 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(); @@ -65,6 +70,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 @@ -143,7 +152,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; @@ -170,6 +181,7 @@ bool relayStatus(unsigned char id, bool status, bool report) { } + _relays[id].scheduled = true; _relays[id].scheduledStatus = status; if (report) _relays[id].scheduledReport = true; @@ -179,7 +191,9 @@ bool relayStatus(unsigned char id, bool status, bool report) { changed = true; + #if TRACK_RELAY_STATUS } + #endif return changed; } @@ -401,16 +415,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 @@ -455,7 +467,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"); @@ -487,6 +503,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 4a724160..1160e37d 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); } @@ -580,6 +595,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() { @@ -417,10 +454,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
  • @@ -743,12 +747,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 f581e1ea..1743e8f6 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