#include "src/Helpers/_CPlugin_Helper.h" #ifdef USES_C015 # include "src/Globals/CPlugins.h" # include "src/Commands/Common.h" # include "src/ESPEasyCore/ESPEasy_backgroundtasks.h" // ####################################################################################################### // ########################### Controller Plugin 015: Blynk ############################################# // ####################################################################################################### // This plugin provides blynk native protocol. This makes possible receive callbacks from user // like button press, slider move etc. // This require much more ESP resources, than use of blynk http API. // So, use C012 Blynk HTTP plugin when you don't need blynk calbacks. // // Only one blynk controller instance is supported. // // https://www.youtube.com/watch?v=5_V_DibOypE // Uncomment this to use ssl connection. This requires more device resources than unencrypted one. // Also it requires valid server thumbprint string to be entered in plugin settings. // #define CPLUGIN_015_SSL # define CPLUGIN_015 # define CPLUGIN_ID_015 15 # define _BLYNK_USE_DEFAULT_FREE_RAM # define BLYNK_TIMEOUT_MS 2000UL # define BLYNK_HEARTBEAT 30 # define CPLUGIN_015_RECONNECT_INTERVAL 60000 # ifdef CPLUGIN_015_SSL #ifdef ESP8266 # include #endif #ifdef ESP32 # include #endif # define CPLUGIN_NAME_015 "Blynk SSL" // Current official blynk server thumbprint # define CPLUGIN_015_DEFAULT_THUMBPRINT "FD C0 7D 8D 47 97 F7 E3 07 05 D3 4E E3 BB 8E 3D C0 EA BE 1C" # define C015_LOG_PREFIX "BL (ssl): " # else // ifdef CPLUGIN_015_SSL #ifdef ESP8266 # include #endif #ifdef ESP32 # include #endif # define CPLUGIN_NAME_015 "Blynk" # define C015_LOG_PREFIX "BL: " # endif // ifdef CPLUGIN_015_SSL // Forward declarations: boolean Blynk_send_c015(const String& value, int vPin, unsigned int clientTimeout); boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings); static unsigned long _C015_LastConnectAttempt[CONTROLLER_MAX] = { 0, 0, 0 }; void CPlugin_015_handleInterrupt() { // This cplugin uses modified blynk library. // It includes support of calling this during time-wait operations // like blynk connection process to keep espeasy stability. backgroundtasks(); } void Blynk_Run_c015() { // user callbacks processing. Called from run10TimesPerSecond. if (Blynk.connected()) { Blynk.run(); } } bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& string) { bool success = false; switch (function) { case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: { ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_015; proto.usesMQTT = false; proto.usesAccount = false; proto.usesPassword = true; proto.usesExtCreds = true; proto.defaultPort = 80; proto.usesID = false; break; } case CPlugin::Function::CPLUGIN_GET_DEVICENAME: { string = F(CPLUGIN_NAME_015); break; } case CPlugin::Function::CPLUGIN_INIT: { success = init_c015_delay_queue(event->ControllerIndex); // when connected to another server and user has changed settings if (success && Blynk.connected()) { addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "disconnect from server")); Blynk.disconnect(); } break; } case CPlugin::Function::CPLUGIN_EXIT: { exit_c015_delay_queue(); break; } # ifdef CPLUGIN_015_SSL case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: { char thumbprint[60] = {0}; LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); if (strlen(thumbprint) != 59) { strcpy(thumbprint, CPLUGIN_015_DEFAULT_THUMBPRINT); } addFormTextBox(F("Server thumbprint string"), F("c015_thumbprint"), thumbprint, 60); success = true; break; } # endif // ifdef CPLUGIN_015_SSL case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: { success = true; if (isFormItemChecked(F("controllerenabled"))) { for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); if (validProtocolIndex(ProtocolIndex)) { const cpluginID_t number = getCPluginID_from_ProtocolIndex(ProtocolIndex); if ((i != event->ControllerIndex) && (number == 15) && Settings.ControllerEnabled[i]) { success = false; // FIXME: this will only show a warning message and not uncheck "enabled" in webform. // Webserver object is not checking result of "success" var :( addHtmlError(F("Only one enabled instance of blynk controller is supported")); break; } } } // force to connect without delay when webform saved _C015_LastConnectAttempt[event->ControllerIndex] = 0; # ifdef CPLUGIN_015_SSL char thumbprint[60] = {0}; String error = F("Specify server thumbprint with exactly 59 symbols string like " CPLUGIN_015_DEFAULT_THUMBPRINT); if (!safe_strncpy(thumbprint, webArg("c015_thumbprint"), 60) || (strlen(thumbprint) != 59)) { addHtmlError(error); } SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); # endif // ifdef CPLUGIN_015_SSL } break; } case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { if (C015_DelayHandler == nullptr) { break; } if (C015_DelayHandler->queueFull(event->ControllerIndex)) { break; } if (!Settings.ControllerEnabled[event->ControllerIndex]) { break; } // Collect the values at the same run, to make sure all are from the same sample uint8_t valueCount = getValueCountForTask(event->TaskIndex); std::unique_ptr element(new C015_queue_element(event, valueCount)); success = C015_DelayHandler->addToQueue(std::move(element)); if (success) { // Element was added. // Now we try to append to the existing element // and thus preventing the need to create a long string only to copy it to a queue element. C015_queue_element& element = static_cast(*(C015_DelayHandler->sendQueue.back())); for (uint8_t x = 0; x < valueCount; x++) { bool isvalid; String formattedValue = formatUserVar(event, x, isvalid); if (!isvalid) { // send empty string to Blynk in case of error formattedValue = String(); } const String valueName = getTaskValueName(event->TaskIndex, x); String valueFullName = getTaskDeviceName(event->TaskIndex); valueFullName += F("."); valueFullName += valueName; String vPinNumberStr = valueName.substring(1, 4); int vPinNumber = vPinNumberStr.toInt(); if ((vPinNumber < 0) || (vPinNumber > 255)) { vPinNumber = -1; } if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F(C015_LOG_PREFIX); log += Blynk.connected() ? F("(online): ") : F("(offline): "); if ((vPinNumber > 0) && (vPinNumber < 256)) { log += F("send "); log += valueFullName; log += F(" = "); log += formattedValue; log += F(" to blynk pin v"); log += vPinNumber; } else { log += F("error got vPin number for "); log += valueFullName; log += F(", got not valid value: "); log += vPinNumberStr; } addLogMove(LOG_LEVEL_INFO, log); } element.vPin[x] = vPinNumber; element.txt[x] = formattedValue; } } Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE, C015_DelayHandler->getNextScheduleTime()); break; } default: break; } return success; } // ******************************************************************************** // Process Queued Blynk request, with data set to NULL // ******************************************************************************** // controller_plugin_number = 015 because of C015 // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* bool do_process_c015_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C015_queue_element& element = static_cast(element_base); // *INDENT-ON* if (!Settings.ControllerEnabled[element._controller_idx]) { // controller has been disabled. Answer true to flush queue. return true; } if (!NetworkConnected()) { return false; } if (!Blynk_keep_connection_c015(element._controller_idx, ControllerSettings)) { return false; } while (element.vPin[element.valuesSent] == -1) { // A non valid value, which we are not going to send. // answer ok and skip real sending if (element.checkDone(true)) { return true; } } bool sendSuccess = Blynk_send_c015( element.txt[element.valuesSent], element.vPin[element.valuesSent], ControllerSettings.ClientTimeout); return element.checkDone(sendSuccess); } boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings) { if (!NetworkConnected()) { return false; } if (!Blynk.connected()) { String auth = getControllerPass(controllerIndex, ControllerSettings); boolean connectDefault = false; if (timePassedSince(_C015_LastConnectAttempt[controllerIndex]) < CPLUGIN_015_RECONNECT_INTERVAL) { // "skip connect to blynk server too often. Wait a little..."; return false; } _C015_LastConnectAttempt[controllerIndex] = millis(); # ifdef CPLUGIN_015_SSL char thumbprint[60] = {0}; LoadCustomControllerSettings(controllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); if (strlen(thumbprint) != 59) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "Saved thumprint value is not correct:")); addLog(LOG_LEVEL_INFO, thumbprint); } strcpy(thumbprint, CPLUGIN_015_DEFAULT_THUMBPRINT); if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "using default one:")); addLog(LOG_LEVEL_INFO, thumbprint); } } # endif // ifdef CPLUGIN_015_SSL String log = F(C015_LOG_PREFIX); if (ControllerSettings.UseDNS) { String hostName = ControllerSettings.getHost(); if (!hostName.isEmpty()) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { log += F("Connecting to custom blynk server "); log += ControllerSettings.getHostPortString(); } Blynk.config(auth.c_str(), CPlugin_015_handleInterrupt, hostName.c_str(), ControllerSettings.Port # ifdef CPLUGIN_015_SSL , thumbprint # endif // ifdef CPLUGIN_015_SSL ); } else { if (loglevelActiveFor(LOG_LEVEL_INFO)) { log += F("Custom blynk server name not specified. "); } connectDefault = true; } } else { IPAddress ip = ControllerSettings.getIP(); if ((ip[0] + ip[1] + ip[2] + ip[3]) > 0) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { log += F("Connecting to custom blynk server "); log += ControllerSettings.getHostPortString(); } Blynk.config(auth.c_str(), CPlugin_015_handleInterrupt, ip, ControllerSettings.Port # ifdef CPLUGIN_015_SSL , thumbprint # endif // ifdef CPLUGIN_015_SSL ); } else { if (loglevelActiveFor(LOG_LEVEL_INFO)) { log += F("Custom blynk server ip not specified. "); } connectDefault = true; } } addLogMove(LOG_LEVEL_INFO, log); if (connectDefault) { addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "Connecting to default server")); Blynk.config(auth.c_str(), CPlugin_015_handleInterrupt, BLYNK_DEFAULT_DOMAIN # ifdef CPLUGIN_015_SSL , BLYNK_DEFAULT_PORT_SSL , thumbprint # else // ifdef CPLUGIN_015_SSL , BLYNK_DEFAULT_PORT # endif // ifdef CPLUGIN_015_SSL ); } # ifdef CPLUGIN_015_SSL if (!Blynk.connect()) { if (!_blynkWifiClient.verify(thumbprint, BLYNK_DEFAULT_DOMAIN)) { addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "thumbprint check FAILED! Check thumbprint in device settings and server thumbprint")); addLog(LOG_LEVEL_INFO, thumbprint); } } # else // ifdef CPLUGIN_015_SSL Blynk.connect(); # endif // ifdef CPLUGIN_015_SSL } return Blynk.connected(); } String Command_Blynk_Set_c015(struct EventStruct *event, const char *Line) { // todo add multicontroller support and chek it is connected and enabled if (!Blynk.connected()) { return F("Not connected to blynk server"); } int vPin = event->Par1; if ((vPin < 0) || (vPin > 255)) { String err = F("Not correct blynk vPin number "); err += vPin; return err; } String data = parseString(Line, 3); if (data.isEmpty()) { String err = F("Skip sending empty data to blynk vPin "); err += vPin; return err; } if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F(C015_LOG_PREFIX "(online): send blynk pin v"); log += vPin; log += F(" = "); log += data; addLogMove(LOG_LEVEL_INFO, log); } Blynk.virtualWrite(vPin, data); return return_command_success(); } boolean Blynk_send_c015(const String& value, int vPin, unsigned int clientTimeout) { Blynk.virtualWrite(vPin, value); unsigned long timer = millis() + clientTimeout; while (!timeOutReached(timer)) { backgroundtasks(); } return true; } // This is called for all virtual pins, that don't have BLYNK_WRITE handler BLYNK_WRITE_DEFAULT() { uint8_t vPin = request.pin; float pinValue = param.asFloat(); if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F(C015_LOG_PREFIX "server set v"); log += vPin; log += F(" to "); log += pinValue; addLogMove(LOG_LEVEL_INFO, log); } if (Settings.UseRules) { String eventCommand = F("blynkv"); eventCommand += vPin; eventCommand += '='; eventCommand += pinValue; eventQueue.addMove(std::move(eventCommand)); } } BLYNK_CONNECTED() { // Your code here when hardware connects to Blynk Cloud or private server. // It’s common to call sync functions inside of this function. // Requests all stored on the server latest values for all widgets. if (Settings.UseRules) { eventQueue.add(F("blynk_connected")); } // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "connected handler")); } // This is called when Smartphone App is opened BLYNK_APP_CONNECTED() { if (Settings.UseRules) { eventQueue.add(F("blynk_app_connected")); } // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "app connected handler")); } // This is called when Smartphone App is closed BLYNK_APP_DISCONNECTED() { if (Settings.UseRules) { eventQueue.add(F("blynk_app_disconnected")); } // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "app disconnected handler")); } #endif // ifdef USES_C015