#include "src/Helpers/_CPlugin_Helper.h" #ifdef USES_C013 # if FEATURE_ESPEASY_P2P == 0 # error "Controller C013 ESPEasy P2P requires the FEATURE_ESPEASY_P2P enabled" # endif // if FEATURE_ESPEASY_P2P == 0 # include "src/Globals/Nodes.h" # include "src/DataStructs/C013_p2p_SensorDataStruct.h" # include "src/DataStructs/C013_p2p_SensorInfoStruct.h" # include "src/ESPEasyCore/ESPEasyRules.h" # include "src/Helpers/Misc.h" # include "src/Helpers/Network.h" // ####################################################################################################### // ########################### Controller Plugin 013: ESPEasy P2P network ################################ // ####################################################################################################### # define CPLUGIN_013 # define CPLUGIN_ID_013 13 # define CPLUGIN_NAME_013 "ESPEasy P2P Networking" // Forward declarations void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t destTaskIndex); void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t destTaskIndex); void C013_sendUDP(uint8_t unit, const uint8_t *data, size_t size); void C013_Receive(struct EventStruct *event); bool CPlugin_013(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_013; proto.usesMQTT = false; proto.usesTemplate = false; proto.usesAccount = false; proto.usesPassword = false; proto.usesHost = false; proto.defaultPort = 8266; proto.usesID = false; proto.Custom = true; break; } case CPlugin::Function::CPLUGIN_GET_DEVICENAME: { string = F(CPLUGIN_NAME_013); break; } case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: { C013_SendUDPTaskInfo(0, event->TaskIndex, event->TaskIndex); break; } case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { C013_SendUDPTaskData(event, 0, event->TaskIndex); success = true; break; } case CPlugin::Function::CPLUGIN_UDP_IN: { C013_Receive(event); break; } case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: { string = F("-"); break; } /* case CPlugin::Function::CPLUGIN_FLUSH: { process_c013_delay_queue(event->ControllerIndex); delay(0); break; } */ default: break; } return success; } // ******************************************************************************** // Generic UDP message // ******************************************************************************** void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t destTaskIndex) { if (!NetworkConnected(10)) { return; } if (!validTaskIndex(sourceTaskIndex) || !validTaskIndex(destTaskIndex)) { return; } pluginID_t pluginID = Settings.getPluginID_for_task(sourceTaskIndex); if (!validPluginID_fullcheck(pluginID)) { return; } struct C013_SensorInfoStruct infoReply; infoReply.sourceUnit = Settings.Unit; infoReply.sourceTaskIndex = sourceTaskIndex; infoReply.destTaskIndex = destTaskIndex; infoReply.deviceNumber = pluginID; infoReply.destUnit = destUnit; if (destUnit == 0) { // Send to broadcast address infoReply.destUnit = 255; } size_t sizeToSend{}; if (infoReply.prepareForSend(sizeToSend)) { C013_sendUDP(infoReply.destUnit, reinterpret_cast(&infoReply), sizeToSend); } } void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t destTaskIndex) { if (!NetworkConnected(10)) { return; } struct C013_SensorDataStruct dataReply; dataReply.sourceUnit = Settings.Unit; dataReply.sourceTaskIndex = event->TaskIndex; dataReply.destTaskIndex = destTaskIndex; dataReply.deviceNumber = Settings.getPluginID_for_task(event->TaskIndex); // FIXME TD-er: We should check for sensorType and pluginID on both sides. // For example sending different sensor type data from one dummy to another is probably not going to work well dataReply.sensorType = event->getSensorType(); const TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(event->TaskIndex); if (taskValues != nullptr) { for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) { dataReply.values.copyValue(*taskValues, x, dataReply.sensorType); } } dataReply.destUnit = destUnit; if (destUnit == 0) { // Send to broadcast address dataReply.destUnit = 255; } dataReply.prepareForSend(); C013_sendUDP(dataReply.destUnit, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); } /*********************************************************************************************\ Send UDP message (unit 255=broadcast) \*********************************************************************************************/ void C013_sendUDP(uint8_t unit, const uint8_t *data, size_t size) { START_TIMER if (!NetworkConnected(10)) { return; } const IPAddress remoteNodeIP = getIPAddressForUnit(unit); # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( F("C013 : Send UDP message to %d (%s)"), unit, remoteNodeIP.toString().c_str())); } # endif // ifndef BUILD_NO_DEBUG statusLED(true); WiFiUDP C013_portUDP; if (!beginWiFiUDP_randomPort(C013_portUDP)) { STOP_TIMER(C013_SEND_UDP_FAIL); return; } FeedSW_watchdog(); if (C013_portUDP.beginPacket(remoteNodeIP, Settings.UDPPort) == 0) { STOP_TIMER(C013_SEND_UDP_FAIL); return; } C013_portUDP.write(data, size); C013_portUDP.endPacket(); C013_portUDP.stop(); FeedSW_watchdog(); delay(0); STOP_TIMER(C013_SEND_UDP); } void C013_Receive(struct EventStruct *event) { if (event->Par2 < 6) { return; } # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { if ((event->Data != nullptr) && (event->Data[1] > 1) && (event->Data[1] < 6)) { String log = (F("C013 : msg ")); for (uint8_t x = 1; x < 6; x++) { log += ' '; log += static_cast(event->Data[x]); } addLogMove(LOG_LEVEL_DEBUG_MORE, log); } } # endif // ifndef BUILD_NO_DEBUG START_TIMER switch (event->Data[1]) { case 2: // sensor info pull request { // SendUDPTaskInfo(packetBuffer[2], packetBuffer[5], packetBuffer[4]); break; } case 3: // sensor info { bool mustSave = false; taskIndex_t taskIndex = INVALID_TASK_INDEX; { // Allocate this is a separate scope since C013_SensorInfoStruct is a HUGE object // Should not be left allocated on the stack when calling PLUGIN_INIT and save, etc. struct C013_SensorInfoStruct infoReply; if (infoReply.setData(event->Data, event->Par2)) { // to prevent flash wear out (bugs in communication?) we can only write to an empty task // so it will write only once and has to be cleared manually through webgui // Also check the receiving end does support the plugin ID. const pluginID_t currentPluginID = Settings.getPluginID_for_task(infoReply.destTaskIndex); bool mustUpdateCurrentTask = false; if (currentPluginID == infoReply.deviceNumber) { // Check to see if task already is set to receive from this host if ((Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] == infoReply.sourceUnit) && Settings.TaskDeviceEnabled[infoReply.destTaskIndex]) { mustUpdateCurrentTask = true; } } if ((mustUpdateCurrentTask || !validPluginID_fullcheck(currentPluginID)) && supportedPluginID(infoReply.deviceNumber)) { taskClear(infoReply.destTaskIndex, false); Settings.TaskDeviceNumber[infoReply.destTaskIndex] = infoReply.deviceNumber.value; Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] = infoReply.sourceUnit; // remote feed store unit nr sending the data if (mustUpdateCurrentTask) { Settings.TaskDeviceEnabled[infoReply.destTaskIndex] = true; } constexpr pluginID_t DUMMY_PLUGIN_ID{ 33 }; if ((infoReply.deviceNumber == DUMMY_PLUGIN_ID) && (infoReply.sensorType != Sensor_VType::SENSOR_TYPE_NONE)) { // Received a dummy device and the sensor type is actually set Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][0] = static_cast(infoReply.sensorType); } for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { Settings.TaskDeviceSendData[x][infoReply.destTaskIndex] = false; } safe_strncpy(ExtraTaskSettings.TaskDeviceName, infoReply.taskName, sizeof(infoReply.taskName)); for (uint8_t x = 0; x < VARS_PER_TASK; x++) { safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[x], infoReply.ValueNames[x], sizeof(infoReply.ValueNames[x])); } if (infoReply.sourceNodeBuild >= 20871) { ExtraTaskSettings.version = infoReply.ExtraTaskSettings_version; for (uint8_t x = 0; x < VARS_PER_TASK; x++) { // safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[x], infoReply.TaskDeviceFormula[x], sizeof(infoReply.TaskDeviceFormula[x])); ExtraTaskSettings.TaskDeviceValueDecimals[x] = infoReply.TaskDeviceValueDecimals[x]; ExtraTaskSettings.TaskDeviceMinValue[x] = infoReply.TaskDeviceMinValue[x]; ExtraTaskSettings.TaskDeviceMaxValue[x] = infoReply.TaskDeviceMaxValue[x]; ExtraTaskSettings.TaskDeviceErrorValue[x] = infoReply.TaskDeviceErrorValue[x]; ExtraTaskSettings.VariousBits[x] = infoReply.VariousBits[x]; } for (uint8_t x = 0; x < PLUGIN_CONFIGVAR_MAX; ++x) { Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][x] = infoReply.TaskDevicePluginConfig[x]; } } ExtraTaskSettings.TaskIndex = infoReply.destTaskIndex; taskIndex = infoReply.destTaskIndex; mustSave = true; } } } if (mustSave) { SaveTaskSettings(taskIndex); SaveSettings(); if (Settings.TaskDeviceEnabled[taskIndex]) { struct EventStruct TempEvent(taskIndex); TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; String dummy; PluginCall(PLUGIN_INIT, &TempEvent, dummy); } } break; } case 4: // sensor data pull request { // SendUDPTaskData(packetBuffer[2], packetBuffer[5], packetBuffer[4]); break; } case 5: // sensor data { struct C013_SensorDataStruct dataReply; // FIXME TD-er: We should check for sensorType and pluginID on both sides. // For example sending different sensor type data from one dummy to another is probably not going to work well if (dataReply.setData(event->Data, event->Par2)) { // only if this task has a remote feed, update values const uint8_t remoteFeed = Settings.TaskDeviceDataFeed[dataReply.destTaskIndex]; if ((remoteFeed != 0) && (remoteFeed == dataReply.sourceUnit)) { // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) // See: // https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 // Thus should not be checked // // If the node is not present in the nodes list (e.g. it had not announced itself in the last 10 minutes or announcement was // missed) // Then we cannot be sure about its build. const bool mustMatch = dataReply.sourceNodeBuild >= 20460; if (mustMatch && !dataReply.matchesPluginID(Settings.getPluginID_for_task(dataReply.destTaskIndex))) { // Mismatch in plugin ID from sending node if (loglevelActiveFor(LOG_LEVEL_ERROR)) { String log = concat(F("P2P data : PluginID mismatch for task "), dataReply.destTaskIndex + 1); log += concat(F(" from unit "), dataReply.sourceUnit); log += concat(F(" remote: "), dataReply.deviceNumber.value); log += concat(F(" local: "), Settings.getPluginID_for_task(dataReply.destTaskIndex).value); addLogMove(LOG_LEVEL_ERROR, log); } } else { struct EventStruct TempEvent(dataReply.destTaskIndex); TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; const Sensor_VType sensorType = TempEvent.getSensorType(); if (!mustMatch || dataReply.matchesSensorType(sensorType)) { TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(dataReply.destTaskIndex); if (taskValues != nullptr) { for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) { taskValues->copyValue(dataReply.values, x, sensorType); } } STOP_TIMER(C013_RECEIVE_SENSOR_DATA); if (node_time.systemTimePresent() && (dataReply.timestamp_sec != 0)) { // Only use timestamp of remote unit when we got a system time ourselves // If not, then the order of samples can get messed up. // timestamp_fraq is 16 bit, so need to scale it to 32 bit TempEvent.timestamp_frac = static_cast(dataReply.timestamp_frac) << 16; SensorSendTask(&TempEvent, dataReply.timestamp_sec); } else { SensorSendTask(&TempEvent); } } else { // Mismatch in sensor types if (loglevelActiveFor(LOG_LEVEL_ERROR)) { String log = concat(F("P2P data : SensorType mismatch for task "), dataReply.destTaskIndex + 1); log += concat(F(" from unit "), dataReply.sourceUnit); addLogMove(LOG_LEVEL_ERROR, log); } } } } } break; } } } #endif // ifdef USES_C013