From 63b02da68fd537f92ffce8717a97d8256f5d06ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Wed, 9 Aug 2017 13:25:10 +0200 Subject: [PATCH] MQTT to RF bridge --- code/espurna/config/arduino.h | 1 + code/espurna/config/debug.h | 2 +- code/espurna/config/general.h | 8 +- code/espurna/config/hardware.h | 18 +++ code/espurna/espurna.ino | 15 +- code/espurna/hardware.ino | 9 ++ code/espurna/mqtt.ino | 5 +- code/espurna/relay.ino | 34 +++-- code/espurna/rfbridge.ino | 242 +++++++++++++++++++++++++++++++++ code/platformio.ini | 27 +++- 10 files changed, 336 insertions(+), 25 deletions(-) create mode 100644 code/espurna/rfbridge.ino diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 5bb1d126..1385ffa3 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..e350dbe8 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) | defined(SONOFF_RFBRIDGE) #undef DEBUG_PORT #endif diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index aa344a20..f420e089 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -20,8 +20,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 8111 +#define DEBUG_UDP_IP IPAddress(192, 168, 1, 100) +#define DEBUG_UDP_PORT 8113 //-------------------------------------------------------------------------------- // EEPROM @@ -111,6 +111,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 @@ -225,6 +226,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" // Periodic reports #define MQTT_REPORT_STATUS 1 diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 0e391e81..4990092d 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,20 @@ #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 4 + // ----------------------------------------------------------------------------- // Electrodragon boards // ----------------------------------------------------------------------------- @@ -269,6 +284,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 +298,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 +324,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 diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index e044085c..e2b44235 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 @@ -264,9 +270,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..c0c495c9 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", 25); + 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 7ede93ff..b0112e96 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -208,14 +208,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 b80d54ad..d03a3c2b 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -18,6 +18,7 @@ typedef struct { unsigned char led; unsigned int floodWindowStart; unsigned char floodWindowChanges; + bool scheduled; unsigned int scheduledStatusTime; bool scheduledStatus; bool scheduledReport; @@ -38,6 +39,10 @@ void relayProviderStatus(unsigned char id, bool status) { if (id >= _relays.size()) return; + #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE + rfbState(id, status); + #endif + #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL _dual_status ^= (1 << id); Serial.flush(); @@ -62,6 +67,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 @@ -156,7 +165,7 @@ bool relayStatus(unsigned char id, bool status, bool report) { bool changed = false; - if (relayStatus(id) != status) { + //if (relayStatus(id) != status) { unsigned int floodWindowEnd = _relays[id].floodWindowStart + 1000 * RELAY_FLOOD_WINDOW; unsigned int currentTime = millis(); @@ -171,6 +180,7 @@ bool relayStatus(unsigned char id, bool status, bool report) { _relays[id].scheduledStatusTime = floodWindowEnd; } + _relays[id].scheduled = true; _relays[id].scheduledStatus = status; _relays[id].scheduledReport = (report ? true : _relays[id].scheduledReport); @@ -180,7 +190,7 @@ bool relayStatus(unsigned char id, bool status, bool report) { changed = true; - } + //} return changed; } @@ -456,16 +466,14 @@ void relaySetupMQTT() { void relaySetup() { - #if defined(SONOFF_DUAL) - - // Two dummy relays for the dual - _relays.push_back((relay_t) {0, 0}); - _relays.push_back((relay_t) {0, 0}); + // Dummy relays for AI Light, Magic Home LED Controller, H801, + // Sonoff Dual and Sonoff RF Bridge + #ifdef DUMMY_RELAY_COUNT - #elif defined(AI_LIGHT) | defined(LED_CONTROLLER) | defined(H801_LED_CONTROLLER) - - // One dummy relay for the AI Thinker Light & Magic Home and H801 led controllers - _relays.push_back((relay_t) {0, 0}); + for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) { + _relays.push_back((relay_t) {0, 0}); + _relays[i].scheduled = false; + } #else @@ -513,7 +521,8 @@ void relayLoop(void) { unsigned int currentTime = millis(); bool status = _relays[id].scheduledStatus; - if (relayStatus(id) != status && currentTime >= _relays[id].scheduledStatusTime) { + //if (relayStatus(id) != status && currentTime >= _relays[id].scheduledStatusTime) { + if (_relays[id].scheduled && currentTime >= _relays[id].scheduledStatusTime) { DEBUG_MSG_P(PSTR("[RELAY] %d => %s\n"), id, status ? "ON" : "OFF"); @@ -541,6 +550,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..f4d5c1f4 --- /dev/null +++ b/code/espurna/rfbridge.ino @@ -0,0 +1,242 @@ +/* + +ITEAD RF BRIDGE MODULE + +Copyright (C) 2017 by Xose PĂ©rez + +*/ + +#ifdef SONOFF_RFBRIDGE + +#define RF_MESSAGE_SIZE 9 +#define RF_BUFFER_SIZE 12 +#define RF_SEND_TIMES 3 +#define RF_SEND_DELAY 50 +#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 + +unsigned char _uartbuf[RF_BUFFER_SIZE] = {0}; +unsigned char _uartpos = 0; +unsigned char _learnId = 0; +bool _learnState = true; + +// ----------------------------------------------------------------------------- +// PRIVATES +// ----------------------------------------------------------------------------- + +void _rfbAck() { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n")); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_ACK); + Serial.write(RF_CODE_STOP); + Serial.flush(); +} + +void _rfbLearn() { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n")); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_LEARN); + Serial.write(RF_CODE_STOP); + Serial.flush(); +} + +void _rfbSend(byte * message) { + 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")); + } + + 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) { + // TODO: notify websocket + _rfbStore(_learnId, _learnState, buffer); + DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success. Storing %d-%s => '%s'\n"), _learnId, _learnState ? "ON" : "OFF", buffer); + } + + if (action == RF_CODE_RFIN) { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer); + } + +} + +void _rfbReceive() { + + static bool receiving = false; + + while (Serial.available()) { + + yield(); + byte c = Serial.read(); + //DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c); + + if (receiving) { + if (c == RF_CODE_STOP) { + _rfbDecode(); + receiving = false; + } else { + _uartbuf[_uartpos++] = c; + } + } else if (c == RF_CODE_START) { + _uartpos = 0; + receiving = true; + } + + } + + +} + +/* +From an hexa char array ("A220EE...") to a byte array (half the size) + */ +bool _rfbToArray(const char * in, byte * out) { + if (strlen(in) != RF_MESSAGE_SIZE * 2) return false; + char tmp[3] = {0}; + for (unsigned char p = 0; p= relayCount()) { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId); + return; + } + _learnState = (char)payload[0] != '0'; + _rfbLearn(); + + } + + if (t.equals(MQTT_TOPIC_RFOUT)) { + byte message[RF_MESSAGE_SIZE]; + if (_rfbToArray(payload, message)) { + _rfbSend(message, RF_SEND_TIMES); + } + } + + } + +} + +void _rfbStore(unsigned char id, bool status, char * 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); +} + +// ----------------------------------------------------------------------------- +// PUBLIC +// ----------------------------------------------------------------------------- + +void rfbState(unsigned char id, bool status) { + String value = _rfbRetrieve(id, status); + DEBUG_MSG_P(PSTR("[RFBRIDGE] Retrieving value for %d-%s => %s\n"), id, status ? "ON" : "OFF", value.c_str()); + if (value.length() > 0) { + byte message[RF_MESSAGE_SIZE]; + _rfbToArray(value.c_str(), message); + _rfbSend(message, RF_SEND_TIMES); + } +} + +void rfbLearn(unsigned char id, bool status) { + _learnId = id; + _learnState = status; + _rfbLearn(); +} + +void rfbForget(unsigned char id, bool status) { + char key[8] = {0}; + sprintf(key, "rfb%d%s", id, status ? "on" : "off"); + delSetting(key); +} + +// ----------------------------------------------------------------------------- +// SETUP & LOOP +// ----------------------------------------------------------------------------- + +void rfbSetup() { + mqttRegister(_rfbMqttCallback); +} + +void rfbLoop() { + _rfbReceive(); +} + +#endif diff --git a/code/platformio.ini b/code/platformio.ini index 0b1c08fd..fb805131 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -182,8 +182,7 @@ upload_flags = --auth=fibonacci --port 8266 [env:sonoff-4ch-debug] platform = espressif8266 framework = arduino -board = esp01_1m -board_flash_mode = dout +board = esp8285 lib_deps = ${common.lib_deps} lib_ignore = ${common.lib_ignore} build_flags = ${common.build_flags_1m} -DSONOFF_4CH @@ -191,8 +190,7 @@ build_flags = ${common.build_flags_1m} -DSONOFF_4CH [env:sonoff-4ch-debug-ota] platform = espressif8266 framework = arduino -board = esp01_1m -board_flash_mode = dout +board = esp8285 lib_deps = ${common.lib_deps} lib_ignore = ${common.lib_ignore} build_flags = ${common.build_flags_1m} -DSONOFF_4CH @@ -263,6 +261,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