diff --git a/docs/source/Plugin/P028.rst b/docs/source/Plugin/P028.rst index c835fcea99..80c4c979d0 100644 --- a/docs/source/Plugin/P028.rst +++ b/docs/source/Plugin/P028.rst @@ -99,11 +99,38 @@ Single event with all values, Send to Controller and Interval settings are stand * **Interval** By default, Interval will be set to 60 sec. The minimum value allowed is 1 sec. + +Data Source +^^^^^^^^^^^ + +.. note:: The **Data Source** section is only available when the task is configured to receive data from a remote node! + +When using :ref:`c013_page` and having multiple ESP nodes using the same ``ESPEasy p2p UDP port`` (see the Tools/Advanced page, default: 8266, IANA registered), you can receive values from a remote node with the same plugin active. How to configure this is documented in the P2P Controller page. + +In a regular configuration, having the sensor connected locally, the plugin auto-detects what type of sensor is used, either BME280 or BMP280, and auto-adjusts the values supported. When receiving data from the P2P network, this 'setting' is verified and must match on the receiving end of the P2P connection. This information is not included in the P2P protocol data, so a configuration option is available, only shown if the task is configured to receive remote data. + +.. image:: P028_DataSource.png + +* **Remote Unit**: Shows the unit number and name the data is received from. + +* **Output values mode**: Allows selection of the sensor that is installed on the sending unit (node). + +.. image:: P028_OutputValuesMode.png + +Available options: + +* *BME280*: Sending node has a BME280 sensor, will provide the Temperature, Humidity and Pressure values. + +* *BMP280*: Sending node has a BMP280 sensor, will provide the Temperature and Pressure values. + + Values ^^^^^^ The measured values are available in ``Temperature``, ``Humidity`` and ``Pressure``. A formula can be set to recalculate. The number of decimals is by default set to 2, and can be set to 0 for ``Humidity`` and ``Pressure``, as no decimals are provided from the measurement. +.. note:: When a BMP280 sensor, that provides only ``Temperature`` and ``Pressure`` data, is connected, the ``Humidity`` value will still be visible, but show 0. + .. Commands available .. ^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/Plugin/P028_DataSource.png b/docs/source/Plugin/P028_DataSource.png new file mode 100644 index 0000000000..4c7ac0bfe0 Binary files /dev/null and b/docs/source/Plugin/P028_DataSource.png differ diff --git a/docs/source/Plugin/P028_OutputValuesMode.png b/docs/source/Plugin/P028_OutputValuesMode.png new file mode 100644 index 0000000000..e82bdbe926 Binary files /dev/null and b/docs/source/Plugin/P028_OutputValuesMode.png differ diff --git a/src/_C013.cpp b/src/_C013.cpp index 1f89810ea0..19b1475f01 100644 --- a/src/_C013.cpp +++ b/src/_C013.cpp @@ -48,6 +48,7 @@ bool CPlugin_013(CPlugin::Function function, struct EventStruct *event, String& Protocol[protocolCount].usesTemplate = false; Protocol[protocolCount].usesAccount = false; Protocol[protocolCount].usesPassword = false; + Protocol[protocolCount].usesHost = false; Protocol[protocolCount].defaultPort = 8266; Protocol[protocolCount].usesID = false; Protocol[protocolCount].Custom = true; @@ -79,6 +80,12 @@ bool CPlugin_013(CPlugin::Function function, struct EventStruct *event, String& break; } + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + string = F("-"); + break; + } + /* case CPlugin::Function::CPLUGIN_FLUSH: { @@ -154,7 +161,8 @@ void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t d // 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.getTaskValues_Data(event->TaskIndex); + const TaskValues_Data_t *taskValues = UserVar.getTaskValues_Data(event->TaskIndex); + if (taskValues != nullptr) { for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) { @@ -313,7 +321,8 @@ void C013_Receive(struct EventStruct *event) { const Sensor_VType sensorType = TempEvent.getSensorType(); if (dataReply.matchesSensorType(sensorType)) { - TaskValues_Data_t * taskValues = UserVar.getTaskValues_Data(dataReply.destTaskIndex); + TaskValues_Data_t *taskValues = UserVar.getTaskValues_Data(dataReply.destTaskIndex); + if (taskValues != nullptr) { for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) { diff --git a/src/_C016.cpp b/src/_C016.cpp index 8be71a35e7..848979c0dc 100644 --- a/src/_C016.cpp +++ b/src/_C016.cpp @@ -150,6 +150,12 @@ bool CPlugin_016(CPlugin::Function function, struct EventStruct *event, String& break; } + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + string = F("-"); + break; + } + default: break; } diff --git a/src/_P028_BME280.ino b/src/_P028_BME280.ino index 5613e3be17..08189ea94c 100644 --- a/src/_P028_BME280.ino +++ b/src/_P028_BME280.ino @@ -5,6 +5,14 @@ // #################### Plugin 028 BME280 I2C Temp/Hum/Barometric Pressure Sensor ####################### // ####################################################################################################### +/** Changelog: + * 2023-07-27 tonhuisman: Revert most below changes and implement PLUGIN_GET_DEVICEVTYPE so the P2P controller validates against the correct + * setting. Setting is only available if a remote data-feed is active, and offers BME280 and BMP280 options only. + * 2023-07-26 tonhuisman: Ignore all humidity data (and log messages) if BMP280 Sensor model is selected + * 2023-07-25 tonhuisman: Add setting to enable forcing the plugin into either BME280 or BMP280 mode, default is Auto-detect + * Add changelog + */ + # include "src/PluginStructs/P028_data_struct.h" // #include @@ -94,6 +102,21 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_GET_DEVICEVTYPE: + { + const P028_data_struct::BMx_DetectMode detectMode = static_cast(P028_DETECTION_MODE); + + // We want to configure this only when a remote data-feed is used + if ((Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) && (P028_data_struct::BMx_DetectMode::BMP280 == detectMode)) { + // Patch the sensor type to output only the measured values, and/or match with a P2P remote sensor + event->sensorType = Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO; + event->idx = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO); + } + + success = true; + break; + } + case PLUGIN_INIT: { const float tempOffset = P028_TEMPERATURE_OFFSET / 10.0f; @@ -138,7 +161,7 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) if (nullptr != P028_data) { if (P028_data->sensorID != P028_data_struct::Unknown_DEVICE) { String detectedString = F("Detected: "); - detectedString += P028_data->getDeviceName(); + detectedString += P028_data_struct::getDeviceName(P028_data->sensorID); addUnit(detectedString); } } @@ -151,7 +174,8 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) String offsetNote = F("Offset in units of 0.1 degree Celsius"); if (nullptr != P028_data) { - if (P028_data->hasHumidity()) { + if ((P028_data_struct::BMx_DetectMode::BMP280 != static_cast(P028_DETECTION_MODE)) && + P028_data->hasHumidity()) { offsetNote += F(" (also correct humidity)"); } } @@ -161,6 +185,24 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_LOAD_ALWAYS: + { + if (Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) { // We want to configure this *only* when a remote data-feed is used + const __FlashStringHelper *detectOptionList[] = { + P028_data_struct::getDeviceName(P028_data_struct::BMx_ChipId::BME280_DEVICE), + P028_data_struct::getDeviceName(P028_data_struct::BMx_ChipId::BMP280_DEVICE), + }; + const int detectOptions[] = { + static_cast(P028_data_struct::BMx_DetectMode::BME280), + static_cast(P028_data_struct::BMx_DetectMode::BMP280), + }; + addFormSelector(F("Output values mode"), F("det"), 2, detectOptionList, detectOptions, P028_DETECTION_MODE); + + success = true; + } + break; + } + case PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT: { # ifndef BUILD_NO_DEBUG @@ -221,7 +263,11 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) P028_ALTITUDE = getFormItemInt(F("elev")); P028_TEMPERATURE_OFFSET = getFormItemInt(F("tempoffset")); P028_ERROR_STATE_OUTPUT = getFormItemInt(F("err")); - success = true; + + if (Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) { // We want to configure this only when a remote data-feed is used + P028_DETECTION_MODE = getFormItemInt(F("det")); + } + success = true; break; } case PLUGIN_ONCE_A_SECOND: @@ -264,6 +310,7 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) if (!P028_data->hasHumidity()) { // Patch the sensor type to output only the measured values. event->sensorType = Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO; + event->idx = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO); } UserVar[event->BaseVarIndex] = ExtraTaskSettings.checkAllowedRange(0, P028_data->last_temp_val); UserVar[event->BaseVarIndex + 1] = P028_data->last_hum_val; @@ -281,24 +328,24 @@ boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) String log; if (log.reserve(40)) { // Prevent re-allocation - log = P028_data->getDeviceName(); + log = P028_data_struct::getDeviceName(P028_data->sensorID); log += F(": Address: "); log += formatToHex(P028_I2C_ADDRESS, 2); addLogMove(LOG_LEVEL_INFO, log); // addLogMove does also clear the string. - log = P028_data->getDeviceName(); + log = P028_data_struct::getDeviceName(P028_data->sensorID); log += F(": Temperature: "); log += formatUserVarNoCheck(event->TaskIndex, 0); addLogMove(LOG_LEVEL_INFO, log); if (P028_data->hasHumidity()) { - log = P028_data->getDeviceName(); + log = P028_data_struct::getDeviceName(P028_data->sensorID); log += F(": Humidity: "); log += formatUserVarNoCheck(event->TaskIndex, 1); addLogMove(LOG_LEVEL_INFO, log); } - log = P028_data->getDeviceName(); + log = P028_data_struct::getDeviceName(P028_data->sensorID); log += F(": Barometric Pressure: "); log += formatUserVarNoCheck(event->TaskIndex, 2); addLogMove(LOG_LEVEL_INFO, log); diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index c59d598691..906af32af2 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -60,6 +60,7 @@ #define PLUGIN_PROCESS_CONTROLLER_DATA 48 // Can be called from the controller to signal the plugin to generate (or handle) sending the data. #define PLUGIN_PRIORITY_INIT_ALL 49 // Pre-initialize all plugins that are set to PowerManager priority (not implemented in plugins) #define PLUGIN_PRIORITY_INIT 50 // Pre-initialize a singe plugins that is set to PowerManager priority +#define PLUGIN_WEBFORM_LOAD_ALWAYS 51 // Loaded *after* PLUGIN_WEBFORM_LOAD, also shown for remote data-feed devices diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index de431d59fb..844761def1 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -658,6 +658,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) case PLUGIN_INIT: case PLUGIN_EXIT: case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_LOAD_ALWAYS: case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: case PLUGIN_READ: case PLUGIN_GET_PACKED_RAW_DATA: @@ -682,7 +683,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) // Only exception is when ErrorStateValues is needed. // Therefore only need to call LoadTaskSettings for those tasks with ErrorStateValues LoadTaskSettings(event->TaskIndex); - } else if (Function == PLUGIN_INIT || Function == PLUGIN_WEBFORM_LOAD) { + } else if (Function == PLUGIN_INIT || Function == PLUGIN_WEBFORM_LOAD || Function == PLUGIN_WEBFORM_LOAD_ALWAYS) { // LoadTaskSettings may call PLUGIN_GET_DEVICEVALUENAMES. LoadTaskSettings(event->TaskIndex); } @@ -842,6 +843,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) if (Function == PLUGIN_GET_DEVICEVALUENAMES || Function == PLUGIN_WEBFORM_SAVE || Function == PLUGIN_WEBFORM_LOAD || + Function == PLUGIN_WEBFORM_LOAD_ALWAYS || Function == PLUGIN_SET_DEFAULTS || Function == PLUGIN_INIT_VALUE_RANGES || Function == PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS diff --git a/src/src/PluginStructs/P028_data_struct.cpp b/src/src/PluginStructs/P028_data_struct.cpp index cd36b1db41..be6f0689c0 100644 --- a/src/src/PluginStructs/P028_data_struct.cpp +++ b/src/src/PluginStructs/P028_data_struct.cpp @@ -21,10 +21,10 @@ uint8_t P028_data_struct::get_control_settings() const { return sensorID == Unknown_DEVICE ? 0u : 0x93; // Oversampling: 8x P, 8x T, normal mode } -const __FlashStringHelper * P028_data_struct::getDeviceName() const { +const __FlashStringHelper * P028_data_struct::getDeviceName(BMx_ChipId sensorID) { switch (sensorID) { case BMP280_DEVICE_SAMPLE1: - case BMP280_DEVICE_SAMPLE2: return F("BMP280 sample"); + case BMP280_DEVICE_SAMPLE2: return F("sample BMP280"); case BMP280_DEVICE: return F("BMP280"); case BME280_DEVICE: return F("BME280"); default: return F("Unknown"); @@ -102,7 +102,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { log.reserve(120); // Prevent re-allocation - log = getDeviceName(); + log = getDeviceName(sensorID); log += ':'; } bool logAdded = false; @@ -210,7 +210,7 @@ bool P028_data_struct::check() { if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("BMx280: Detected "); - log += getDeviceName(); + log += getDeviceName(sensorID); addLogMove(LOG_LEVEL_INFO, log); } } diff --git a/src/src/PluginStructs/P028_data_struct.h b/src/src/PluginStructs/P028_data_struct.h index 99643a1e27..c23762b0d4 100644 --- a/src/src/PluginStructs/P028_data_struct.h +++ b/src/src/PluginStructs/P028_data_struct.h @@ -61,6 +61,7 @@ # define P028_ALTITUDE PCONFIG(1) # define P028_TEMPERATURE_OFFSET PCONFIG(2) # define P028_ERROR_STATE_OUTPUT PCONFIG(3) +# define P028_DETECTION_MODE PCONFIG(4) struct P028_data_struct : public PluginTaskData_base { struct bme280_calib_data @@ -107,6 +108,12 @@ struct P028_data_struct : public PluginTaskData_base { BME280_DEVICE = 0x60 }; + enum BMx_DetectMode : uint8_t { + Auto = 0u, + BME280 = BMx_ChipId::BME280_DEVICE, + BMP280 = BMx_ChipId::BMP280_DEVICE, + }; + enum BMx_state { BMx_Uninitialized = 0, BMx_Initialized, @@ -119,7 +126,7 @@ struct P028_data_struct : public PluginTaskData_base { P028_data_struct(uint8_t addr, float tempOffset); - P028_data_struct() = delete; + P028_data_struct() = delete; virtual ~P028_data_struct() = default; private: @@ -130,11 +137,11 @@ struct P028_data_struct : public PluginTaskData_base { public: - const __FlashStringHelper* getDeviceName() const; + static const __FlashStringHelper* getDeviceName(BMx_ChipId sensorID); - bool hasHumidity() const; + bool hasHumidity() const; - bool initialized() const; + bool initialized() const; private: diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index f79d879775..bb511a95cd 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -56,7 +56,8 @@ void handle_controllers() { } else { // Need to make sure every byte between the members is also zero // Otherwise the checksum will fail and settings will be saved too often. - ControllerSettings->reset(); + ControllerSettings.reset(); + if (Settings.Protocol[controllerindex] != protocol) { // Protocol has changed. @@ -145,7 +146,7 @@ void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSet struct EventStruct TempEvent; // Hand over the controller settings in the Data pointer, so the controller can set some defaults. - TempEvent.Data = (uint8_t*)(&ControllerSettings); + TempEvent.Data = (uint8_t *)(&ControllerSettings); if (Protocol[ProtocolIndex].usesTemplate) { String dummy; @@ -244,8 +245,8 @@ void handle_controllers_ShowAllControllersTable() html_TD(); addHtml(getCPluginNameFromCPluginID(Settings.Protocol[x])); html_TD(); + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); { - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); String hostDescription; CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, 0, hostDescription); @@ -257,7 +258,9 @@ void handle_controllers_ShowAllControllersTable() } html_TD(); - addHtmlInt(ControllerSettings->Port); + if ((INVALID_PROTOCOL_INDEX == ProtocolIndex) || (Protocol[ProtocolIndex].usesPort)) { + addHtmlInt(13 == Settings.Protocol[x] ? Settings.UDPPort : ControllerSettings->Port); // P2P/C013 exception + } } else { html_TD(3); @@ -328,6 +331,7 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_IP); } } + if (Protocol[ProtocolIndex].usesPort) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); } @@ -363,6 +367,7 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (Protocol[ProtocolIndex].usesSampleSets) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR); } + if (Protocol[ProtocolIndex].allowLocalSystemTime) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_LOCAL_SYSTEM_TIME); } @@ -385,7 +390,8 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PASS); } - #if FEATURE_MQTT + # if FEATURE_MQTT + if (Protocol[ProtocolIndex].usesMQTT) { addTableSeparator(F("MQTT"), 2, 3); @@ -396,7 +402,7 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addFormNote(F("Updated on load of this page")); addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_RETAINFLAG); } - #endif // if FEATURE_MQTT + # endif // if FEATURE_MQTT if (Protocol[ProtocolIndex].usesTemplate || Protocol[ProtocolIndex].usesMQTT) @@ -404,7 +410,8 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SUBSCRIBE); addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PUBLISH); } - #if FEATURE_MQTT + # if FEATURE_MQTT + if (Protocol[ProtocolIndex].usesMQTT) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_TOPIC); @@ -414,7 +421,7 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_WILL_RETAIN); addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLEAN_SESSION); } - #endif // if FEATURE_MQTT + # endif // if FEATURE_MQTT } } @@ -447,4 +454,4 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex html_end_form(); } -#endif // ifdef WEBSERVER_CONTROLLERS \ No newline at end of file +#endif // ifdef WEBSERVER_CONTROLLERS diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index f22d2975e0..e67468c41f 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -961,10 +961,10 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addFormSubHeader(F("Device Settings")); } + String webformLoadString; + struct EventStruct TempEvent(taskIndex); // add plugins content if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors - String webformLoadString; - struct EventStruct TempEvent(taskIndex); PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); if (webformLoadString.length() > 0) { @@ -973,12 +973,14 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) errorMessage += F(": Bug in PLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead"); addHtmlError(errorMessage); } + + PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices } else { #if FEATURE_ESPEASY_P2P // Show remote feed information. addFormSubHeader(F("Data Source")); - uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; + const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; addFormNumericBox(F("Remote Unit"), F("RemoteUnit"), remoteUnit, 0, 255); if (remoteUnit != 255) { @@ -992,6 +994,8 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) } addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. #endif + + PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices } devicePage_show_output_data_type(taskIndex, DeviceIndex);