diff --git a/README.md b/README.md index 394351d5aa..9d3c538d4d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari * Integration via MQTT Discover or copy-pasting configuration code * [**InfluxDB**](https://www.influxdata.com/) integration via HTTP API * [**Thingspeak**](https://thingspeak.com/) integration via HTTP API (HTTPS available for custom builds) +* [**Blynk**](https://www.blynk.cc/) integration via native Blynk API. * **Sonoff RF Bridge** support * Multiple virtual switches (tested with up to 16) * MQTT-to-RF two-way bridge (no need to learn codes) diff --git a/code/espurna/blynk.ino b/code/espurna/blynk.ino new file mode 100755 index 0000000000..e67ff2208e --- /dev/null +++ b/code/espurna/blynk.ino @@ -0,0 +1,401 @@ +/* + +BLYNK MODULE + +Copyright (C) 2018 by Thomas Häger +Adapted by Maxim Prokhorov + +*/ + +#if BLYNK_SUPPORT + +// +// Blynk wrapper +// + +// ref: blynk-library/src/BlynkSimpleEsp8266.h +// adapted esp8266 class, without 2.4.x requirement +#include +#include +#include +#include + +#if BLYNK_SECURE_CLIENT + +#include +#include + +#if BLYNK_USE_CUSTOM_CERT +#include "config/user_blynk_certificate.h" +#else +#include "config/blynk_certificate.h" +#endif + +#if defined(ARDUINO_ESP8266_RELEASE_2_4_2) + #define BEARSSL_X509LIST BearSSLX509List +#else + #define BEARSSL_X509LIST BearSSL::X509List +#endif + +template +class BlynkArduinoClientSecure + : public BlynkArduinoClientGen +{ +public: + BlynkArduinoClientSecure(Client& client) + : BlynkArduinoClientGen(client) + {} + + void setX509Time(time_t ts) { + this->client->setX509Time(ts); + } + + bool connect() { + setX509Time(ntpLocal2UTC(now())); + BEARSSL_X509LIST cert(_blynk_cert); + this->client->setTrustAnchors(&cert); + + if (BlynkArduinoClientGen::connect()) { + return this->client->connected(); + } + return false; + } +}; + +#endif + + +template +class BlynkWifi + : public BlynkProtocol +{ + typedef BlynkProtocol Base; +public: + + BlynkWifi(Transport& transp) + : Base(transp), + backoff(0), + backoff_step(3), + backoff_limit(15), + timeout(BLYNK_TIMEOUT_MS), + last_attempt(0) + {} + + char* getToken() { return _token; } + char* getHost() { return _host; } + uint16_t getPort() { return _port; } + + void config(const char* token, const char* host = BLYNK_HOST, uint16_t port = BLYNK_PORT) { + Base::begin(_token); + this->conn.begin(_host, _port); + } + + void resetConfig() { + free(_token); + free(_host); + _port = 0; + } + + void storeConfig(const String& token, const String& host, const uint16_t port) { + resetConfig(); + + _token = strdup(token.c_str()); + _host = strdup(host.c_str()); + _port = port; + + config(_token, _host, _port); + } + + void setTimeout(uint32_t time) { + timeout = time; + } + + bool reachedBackoffLimit() { + return (backoff >= backoff_limit); + } + + void resetBackoff() { + backoff = 0; + last_attempt = 0; + } + + bool connectWithBackoff() { + if (!last_attempt) last_attempt = millis(); + + uint32_t current_timeout = (0 == backoff) + ? this->timeout + : (this->timeout * backoff); + + if ((millis() - last_attempt) < timeout) { + return false; + } + + last_attempt = millis(); + if (!Base::connect(current_timeout)) { + if (!reachedBackoffLimit()) backoff += backoff_step; + last_attempt = millis(); + return false; + } + + resetBackoff(); + + return true; + } + +private: + char* _token; + char* _host; + uint16_t _port; + + uint8_t backoff; + uint8_t backoff_step; + uint8_t backoff_limit; + + uint32_t timeout; + uint32_t last_attempt; + +}; + + +// TODO construct only when enabled? +#if BLYNK_SECURE_CLIENT +BearSSL::WiFiClientSecure _blynkWifiClient; +BlynkArduinoClientSecure _blynkTransport(_blynkWifiClient); +BlynkWifi> Blynk(_blynkTransport); +#else +WiFiClient _blynkWifiClient; +BlynkArduinoClient _blynkTransport(_blynkWifiClient); +BlynkWifi Blynk(_blynkTransport); +#endif + + +// +// Module state & methods +// + + +bool _blynk_enabled = false; + +// vpin <-> relays, sensors mapping +template +bool _blynkIndexFromVPin(RangeFunc range_func, const char* key, uint8_t vpin, uint8_t* res) { + for (uint8_t id=0; id < range_func(); id++) { + String mapping = getSetting(key, id, ""); + if (!mapping.length()) continue; + + if (strtoul(mapping.c_str(), nullptr, 10) == vpin) { + *res = id; + return true; + } + } + + return false; +} + +bool _blynkVPinFromIndex(const char* key, uint8_t index, uint8_t* vpin) { + String mapping = getSetting(key, index, ""); + if (mapping.length()) { + *vpin = strtoul(mapping.c_str(), nullptr, 10); + return true; + } + + return false; +} + + +bool _blynkVPinRelay(uint8_t vpin, uint8_t* relayID) { + return _blynkIndexFromVPin(relayCount, "blnkRelay", vpin, relayID); +} + +bool _blynkRelayVPin(uint8_t relayID, uint8_t* vpin) { + return _blynkVPinFromIndex("blnkRelay", relayID, vpin); +} + +bool _blynkVPinMagnitude(uint8_t vpin, uint8_t* magnitudeIndex) { + return _blynkIndexFromVPin(magnitudeCount, "blnkMagnitude", vpin, magnitudeIndex); +} + +bool _blynkMagnitudeVPin(uint8_t magnitudeIndex, uint8_t* vpin) { + return _blynkVPinFromIndex("blnkMagnitude", magnitudeIndex, vpin); +} + +#if WEB_SUPPORT + +bool _blnkWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "blnk", 4) == 0); +} + +void _blnkWebSocketOnSend(JsonObject& root) { + root["blnkVisible"] = 1; + root["blnkEnabled"] = getSetting("blnkEnabled", BLYNK_ENABLED).toInt() == 1; + root["blnkToken"] = getSetting("blnkToken", BLYNK_AUTH_TOKEN); + root["blnkHost"] = getSetting("blnkHost", BLYNK_HOST); + root["blnkPort"] = getSetting("blnkPort", BLYNK_PORT).toInt(); + + JsonArray& relays = root.createNestedArray("blnkRelays"); + for (uint8_t id=0; id + + // --------------------------------------------------------------------- + // Blynk + // --------------------------------------------------------------------- + + // Blynk - Relays + if ("blnkRelays" === key) { + createRelayList(value, "blnkRelays", "blnkRelayTemplate"); + return; + } + + // Blynk - Magnitudes + + if ("blnkMagnitudes" === key) { + createMagnitudeList(value, "blnkMagnitudes", "blnkMagnitudeTemplate"); + return; + } // --------------------------------------------------------------------- diff --git a/code/html/index.html b/code/html/index.html index c94743b4ea..e70a6e44b8 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -107,6 +107,10 @@

Before using this device you have to change the default password for the use INFLUXDB +
  • + BLYNK +
  • +
  • LIGHTS @@ -1001,6 +1005,59 @@

    +
    +
    + +
    +

    BLYNK

    +

    + Configure the connection to your Blynk server. +

    +
    + +
    + +
    + + General + +
    + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + + VPin mapping + +
    +
    Connect VPins to the hardware. For relays - sync (both ways) the binary state. For sensors - send (one way) current value. VPins must be in 0..255 range and must be unique!
    +
    + +
    + + +
    + + +
    +
    + +
    +
    @@ -1578,6 +1635,23 @@

    +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + +
    diff --git a/code/platformio.ini b/code/platformio.ini index 9d81cf1371..0a5eb28a53 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -75,6 +75,7 @@ lib_deps = ArduinoJson https://github.com/marvinroger/async-mqtt-client#v0.8.1 Brzo I2C + Blynk@~0.5.4 https://github.com/xoseperez/debounceevent.git#2.0.4 https://github.com/xoseperez/eeprom_rotate#0.9.1 Embedis