diff --git a/src/_C005.cpp b/src/_C005.cpp index b138ce706..28a48dda5 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -18,7 +18,7 @@ /** Changelog: * 2024-11-29 tonhuisman: Add Discovery trigger setting * 2024-11-11 tonhuisman: Add AutoDiscovery options - * Home Assistant suggested discovery topic: "homeassistant/%devclass%/%sysname%/%tskname%/config" + * Home Assistant suggested discovery topic: "homeassistant/%devclass%/%sysname%/%tskname%" * 2023-08-18 tonhuisman: Clean up source for pull request * 2023-03-15 tonhuisman: Add processing of topic endpoint /set to issue a TaskValueSet,taskname,taskvalue,payload command for * topic %sysname%/#/taskname/valuename/set @@ -151,7 +151,11 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& // We have received the Discovery request topic // Generate random time-offset in 0.1 sec, range 1..10 seconds mqttDiscoveryTimeout = random(10, 100); - addLog(LOG_LEVEL_INFO, strformat(F("C005 : Request for AutoDiscovery received. %d"), mqttDiscoveryTimeout)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("C005 : Request for AutoDiscovery received. %.1f sec."), + mqttDiscoveryTimeout / 10.0f)); + } // FIXME Generate event when request received? } else diff --git a/src/_P052_SenseAir.ino b/src/_P052_SenseAir.ino index a9730ad27..a1896c97d 100644 --- a/src/_P052_SenseAir.ino +++ b/src/_P052_SenseAir.ino @@ -1,487 +1,500 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P052 - -# include "src/PluginStructs/P052_data_struct.h" - -// ####################################################################################################### -// ############################# Plugin 052: Senseair CO2 Sensors ######################################## -// ####################################################################################################### - -/** Changelog: - * 25025-01-03 tonhuisman: Small code size reduction - */ - -/* - Plugin originally written by: Daniel Tedenljung - info__AT__tedenljungconsulting.com - Rewritten by: Mikael Trieb mikael__AT__triebconsulting.se - - This plugin reads available values of Senseair Co2 Sensors. - Datasheet can be found here: - S8: http://www.senseair.com/products/oem-modules/senseair-s8/ - K30: http://www.senseair.com/products/oem-modules/k30/ - K70/tSENSE: http://www.senseair.com/products/wall-mount/tsense/ - - Circuit wiring - GPIO Setting 1 -> RX - GPIO Setting 2 -> TX - Use 1kOhm in serie on datapins! - */ - -# define PLUGIN_052 -# define PLUGIN_ID_052 52 -# define PLUGIN_NAME_052 "Gases - CO2 Senseair" - - -boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) { - boolean success = false; - - switch (function) { - case PLUGIN_DEVICE_ADD: { - auto& dev = Device[++deviceCount]; - dev.Number = PLUGIN_ID_052; - dev.Type = DEVICE_TYPE_SERIAL; - dev.VType = Sensor_VType::SENSOR_TYPE_SINGLE; - dev.FormulaOption = true; - dev.ValueCount = 1; - dev.SendDataOption = true; - dev.TimerOption = true; - dev.OutputDataType = Output_Data_type_t::Simple; - dev.ExitTaskBeforeSave = false; - dev.PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_052); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: { - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (i < P052_NR_OUTPUT_VALUES) { - const uint8_t pconfigIndex = i + P052_QUERY1_CONFIG_POS; - uint8_t choice = PCONFIG(pconfigIndex); - ExtraTaskSettings.setTaskDeviceValueName(i, P052_data_struct::Plugin_052_valuename(choice, false)); - } else { - ExtraTaskSettings.clearTaskDeviceValueName(i); - } - } - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = P052_NR_OUTPUT_VALUES; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(P052_SENSOR_TYPE_INDEX)); - event->idx = P052_SENSOR_TYPE_INDEX; - success = true; - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: { - serialHelper_getGpioNames(event); - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(P052_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); - PCONFIG(0) = 1; // "CO2" - - for (uint8_t i = 1; i < VARS_PER_TASK; ++i) { - PCONFIG(i) = 0; // "Empty" - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: - { - const __FlashStringHelper *options[P052_NR_OUTPUT_OPTIONS]; - - for (uint8_t i = 0; i < P052_NR_OUTPUT_OPTIONS; ++i) { - options[i] = P052_data_struct::Plugin_052_valuename(i, true); - } - - for (uint8_t i = 0; i < P052_NR_OUTPUT_VALUES; ++i) { - const uint8_t pconfigIndex = i + P052_QUERY1_CONFIG_POS; - sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P052_NR_OUTPUT_OPTIONS, options); - } - break; - } - - case PLUGIN_WEBFORM_LOAD: { - P052_data_struct *P052_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P052_data) && P052_data->isInitialized()) { - addFormSubHeader(F("Device Information")); - { - int value = 0; - - if (P052_data->modbus.detected_device_description.length() > 0) { - addRowLabel(F("Detected Device")); - addHtml(P052_data->modbus.detected_device_description); - } - - if (P052_data->readInputRegister(P052_IR29_FW_REV, value)) { - addRowLabel(F("FW rev.")); - addHtmlInt(value); - } - - addRowLabel(F("Sensor ID")); - addHtml(formatToHex_decimal(P052_data->getSensorID())); - - - addRowLabel(F("Checksum (pass/fail/nodata)")); - { - uint32_t reads_pass, reads_crc_failed, reads_nodata; - P052_data->modbus.getStatistics(reads_pass, reads_crc_failed, reads_nodata); - addHtml(strformat(F("%d/%d/%d"), reads_pass, reads_crc_failed, reads_nodata)); - } - - bool hasFactorySettings = false; - bool enabledABC = false; - - if (P052_data->readHoldingRegister(P052_HR19_METER_CONTROL, value)) { - hasFactorySettings = value == 255; - enabledABC = bitRead(value, 1) == 0; - addRowLabel(F("Using Factory Settings")); - addEnabled(hasFactorySettings); - - if (!hasFactorySettings) { - // addRowLabel(F("HR19")); - // addHtmlInt(value); - addRowLabel(F("nRDY")); - addEnabled(bitRead(value, 0) == 0); - addRowLabel(F("ABC")); - addEnabled(enabledABC); - addRowLabel(F("Static IIR filter")); - addEnabled(bitRead(value, 2) == 0); - addRowLabel(F("Dynamic IIR filter")); - addEnabled(bitRead(value, 3) == 0); - addRowLabel(F("Pressure Compensation")); - addEnabled(bitRead(value, 4) == 0); - } - } - - if (P052_data->readInputRegister(P052_IR7_MEASUREMENT_COUNT, value)) { - addRowLabel(F("Measurement Count")); - addHtmlInt(value); - } - - if (P052_data->readInputRegister(P052_IR8_MEASUREMENT_CYCLE_TIME, value)) { - addRowLabel(F("Measurement Cycle time")); - addHtmlInt(value * 2); - addUnit('s'); - } - - if (P052_data->readInputRegister(P052_IR9_MEASURED_UNFILTERED_CO2, value)) { - addRowLabel(F("Unfiltered CO2")); - addHtmlInt(value); - } - - if (P052_data->readHoldingRegister(P052_HR11_MEASUREMENT_MODE, value)) { - addRowLabel(F("Measurement Mode")); - addHtml(value == 0 ? F("Continuous") : F("Single Measurement")); - } - - if (enabledABC) { - if (P052_data->readHoldingRegister(P052_HR14_ABC_PERIOD, value)) { - addRowLabel(F("ABC Period")); - addHtmlInt(value); - addUnit('h'); - } - - if (P052_data->readHoldingRegister(P052_HR5_ABC_TIME, value)) { - addRowLabel(F("Time Since ABC")); - addHtmlInt(value); - addUnit('h'); - } - } - } - - { - int value = 0; - - /* - if (P052_data->readHoldingRegister(P052_HR11_MEASUREMENT_MODE, value)) { - // Disable selector for now, since single measurement not yet supported. - - const __FlashStringHelper *options[2] = { F("Continuous"), F("Single Measurement") }; - addFormSelector(F("Measurement Mode"), F("mode"), 2, options, nullptr, value); - } - */ - - if (P052_data->readHoldingRegister(P052_HR12_MEASUREMENT_PERIOD, value)) { - addFormNumericBox(F("Measurement Period"), F("period"), value, 2, 65534); - addUnit('s'); - } - - if (P052_data->readHoldingRegister(P052_HR13_NR_OF_SAMPLES, value)) { - addFormNumericBox(F("Samples per measurement"), F("samp_meas"), value, 1, 1024); - } - } - { - // P052_ABC_PERIOD - } - } - - /* - // ABC functionality disabled for now, due to a bug in the firmware. - // See https://github.com/letscontrolit/ESPEasy/issues/759 - uint8_t choiceABCperiod = PCONFIG(4); - const __FlashStringHelper * optionsABCperiod[9] = { F("disable"), F("1 h"), F("12 h"), F("1 - day"), F("2 days"), F("4 days"), F("7 days"), F("14 days"), F("30 days") }; - addFormSelector(F("ABC period"), F("ABC_period"), 9, optionsABCperiod, - nullptr, choiceABCperiod); - */ - - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: { - // Save output selector parameters. - for (uint8_t i = 0; i < P052_NR_OUTPUT_VALUES; ++i) { - const uint8_t pconfigIndex = i + P052_QUERY1_CONFIG_POS; - const uint8_t choice = PCONFIG(pconfigIndex); - sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, P052_data_struct::Plugin_052_valuename(choice, false)); - } - - P052_data_struct *P052_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P052_data) && P052_data->isInitialized()) { - bool changed = false; - uint16_t mode = getFormItemInt(F("mode"), 65535); - - if (((mode == 0) || (mode == 1))) { - uint8_t errorcode; - int readVal = P052_data->modbus.readHoldingRegister(0x0A, errorcode); - - if ((errorcode == 0) && (readVal != mode)) { - P052_data->modbus.writeMultipleRegisters(0x0A, mode); - delay(0); - changed = true; - } - } - uint16_t period = getFormItemInt(F("period"), 0); - - if (period > 1) { - uint8_t errorcode; - int readVal = P052_data->modbus.readHoldingRegister(0x0B, errorcode); - - if ((errorcode == 0) && (readVal != period)) { - P052_data->modbus.writeMultipleRegisters(0x0B, period); - delay(0); - changed = true; - } - } - uint16_t samp_meas = getFormItemInt(F("samp_meas"), 0); - - if ((samp_meas > 0) && (samp_meas <= 1024)) { - uint8_t errorcode; - int readVal = P052_data->modbus.readHoldingRegister(0x0C, errorcode); - - if ((errorcode == 0) && (readVal != samp_meas)) { - P052_data->modbus.writeMultipleRegisters(0x0C, samp_meas); - delay(0); - changed = true; - } - } - - if (changed) { - // Restart sensor. - P052_data->modbus.writeMultipleRegisters(0x11, 0xFF); - - // FIXME TD-er: Must leave the sensor to boot for a while. - delay(35); - } - } - - - /* - // ABC functionality disabled for now, due to a bug in the firmware. - // See https://github.com/letscontrolit/ESPEasy/issues/759 - PCONFIG(4) = getFormItemInt(F("ABC_period")); - */ - - success = true; - break; - } - - case PLUGIN_INIT: { - const int16_t serial_rx = CONFIG_PIN1; - const int16_t serial_tx = CONFIG_PIN2; - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - - initPluginTaskData(event->TaskIndex, new (std::nothrow) P052_data_struct()); - P052_data_struct *P052_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P052_data) { - return success; - } - - - if (P052_data->init(port, serial_rx, serial_tx)) { - /* - // ABC functionality disabled for now, due to a bug in the firmware. - // See https://github.com/letscontrolit/ESPEasy/issues/759 - const int periodInHours[9] = {0, 1, 12, (24*1), (24*2), (24*4), (24*7), - (24*14), (24*30) }; - uint8_t choiceABCperiod = PCONFIG(1); - - Plugin_052_setABCperiod(periodInHours[choiceABCperiod]); - */ - P052_data->modbus.setModbusTimeout(P052_MODBUS_TIMEOUT); - - // P052_data->modbus.writeMultipleRegisters(0x09, 1); // Start Single Measurement - // P052_data->modbus.writeMultipleRegisters(0x0B, 16); // Measurement Period - // P052_data->modbus.writeMultipleRegisters(0x0C, 8); // Number of samples - - success = true; - } else { - clearPluginTaskData(event->TaskIndex); - } - break; - } - - case PLUGIN_EXIT: { - success = true; - break; - } - - case PLUGIN_READ: { - P052_data_struct *P052_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P052_data) && P052_data->isInitialized()) { - event->sensorType = static_cast(PCONFIG(P052_SENSOR_TYPE_INDEX)); - String log = F("Senseair: "); - String logPrefix; - - for (int varnr = 0; varnr < P052_NR_OUTPUT_VALUES; ++varnr) { - uint8_t errorcode = 0; - float value = 0; - - switch (PCONFIG(varnr)) { - case 1: { - value = P052_data->modbus.readInputRegister(P052_IR4_MEASURED_FILTERED_CO2, errorcode); - logPrefix = F("co2="); - break; - } - case 2: { - int temperatureX100 = P052_data->modbus.readInputRegister(P052_IR5_TEMPERATURE, errorcode); - - if (errorcode == 2) { - // Exception code: Illegal Data Address - // SenseAir S8, not for other modules. - temperatureX100 = P052_data->modbus.read_RAM_EEPROM( - P052_CMD_READ_RAM, P052_RAM_ADDR_DET_TEMPERATURE, 2, errorcode); - } - - if (temperatureX100 >= 32768) { - // 2-complement value. - temperatureX100 -= 65536; - } - value = static_cast(temperatureX100) / 100.0f; - logPrefix = F("temp="); - break; - } - case 3: { - int rhX100 = P052_data->modbus.readInputRegister(P052_IR6_SPACE_HUMIDITY, errorcode); - value = static_cast(rhX100) / 100.0f; - logPrefix = F("hum="); - break; - } - case 4: { - int status = P052_data->modbus.readInputRegister(P052_IR29_FW_REV, errorcode); - - if (errorcode == 0) { - int relayStatus = (status >> 8) & 0x1; - UserVar.setFloat(event->TaskIndex, varnr, relayStatus); - log += F("relay status="); - log += relayStatus; - } - break; - } - case 5: { - int temperatureAdjustment = P052_data->modbus.readInputRegister(P052_IR11_MEASURED_CONCENTRATION_UNFILTERED, errorcode); - value = static_cast(temperatureAdjustment); - logPrefix = F("temp adj="); - break; - } - - case 7: { - const int errorWord = P052_data->modbus.readInputRegister(P052_IR_ERRORSTATUS, errorcode); - - if (errorcode == 0) { - value = errorWord; - - for (size_t i = 0; i < 9; i++) { - if (bitRead(errorWord, i)) { - log += F("err="); - log += i; - break; - } - } - } else { - value = -1; - log += F("err="); - log += -1; - } - break; - } - case 0: - default: { - UserVar.setFloat(event->TaskIndex, varnr, 0); - break; - } - } - - if ((errorcode == 0) && (P052_data->modbus.getLastError() == 0)) { - success = true; - UserVar.setFloat(event->TaskIndex, varnr, value); - log += logPrefix; - log += value; - } - } - addLogMove(LOG_LEVEL_INFO, log); - break; - } - break; - } - - case PLUGIN_WRITE: - { - P052_data_struct *P052_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P052_data) && P052_data->plugin_write(event, string)) { - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P052 +#include "_Plugin_Helper.h" +#ifdef USES_P052 + +# include "src/PluginStructs/P052_data_struct.h" + +// ####################################################################################################### +// ############################# Plugin 052: Senseair CO2 Sensors ######################################## +// ####################################################################################################### + +/** Changelog: + * 2025-01-03 tonhuisman: Small code size reduction + * 2024-12-05 tonhuisman: Add support for MQTT AutoDiscovery for supported values + * 2024-12 tonhuisman: Start changelog + */ + +/* + Plugin originally written by: Daniel Tedenljung + info__AT__tedenljungconsulting.com + Rewritten by: Mikael Trieb mikael__AT__triebconsulting.se + + This plugin reads available values of Senseair Co2 Sensors. + Datasheet can be found here: + S8: http://www.senseair.com/products/oem-modules/senseair-s8/ + K30: http://www.senseair.com/products/oem-modules/k30/ + K70/tSENSE: http://www.senseair.com/products/wall-mount/tsense/ + + Circuit wiring + GPIO Setting 1 -> RX + GPIO Setting 2 -> TX + Use 1kOhm in serie on datapins! + */ + +# define PLUGIN_052 +# define PLUGIN_ID_052 52 +# define PLUGIN_NAME_052 "Gases - CO2 Senseair" + + +boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) { + boolean success = false; + + switch (function) { + case PLUGIN_DEVICE_ADD: { + auto& dev = Device[++deviceCount]; + dev.Number = PLUGIN_ID_052; + dev.Type = DEVICE_TYPE_SERIAL; + dev.VType = Sensor_VType::SENSOR_TYPE_SINGLE; + dev.FormulaOption = true; + dev.ValueCount = 1; + dev.SendDataOption = true; + dev.TimerOption = true; + dev.OutputDataType = Output_Data_type_t::Simple; + dev.ExitTaskBeforeSave = false; + dev.PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_052); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: { + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (i < P052_NR_OUTPUT_VALUES) { + const uint8_t pconfigIndex = i + P052_QUERY1_CONFIG_POS; + uint8_t choice = PCONFIG(pconfigIndex); + ExtraTaskSettings.setTaskDeviceValueName(i, P052_data_struct::Plugin_052_valuename(choice, false)); + } else { + ExtraTaskSettings.clearTaskDeviceValueName(i); + } + } + break; + } + + # if FEATURE_MQTT_DISCOVER + case PLUGIN_GET_DISCOVERY_VTYPES: + { + for (uint8_t i = 0; i < P052_NR_OUTPUT_VALUES; ++i) { + event->ParN[i] = P052_data_struct::Plugin_052_valueVType(i); + } + success = true; + break; + } + # endif // if FEATURE_MQTT_DISCOVER + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P052_NR_OUTPUT_VALUES; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(P052_SENSOR_TYPE_INDEX)); + event->idx = P052_SENSOR_TYPE_INDEX; + success = true; + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: { + serialHelper_getGpioNames(event); + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(P052_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); + PCONFIG(0) = 1; // "CO2" + + for (uint8_t i = 1; i < VARS_PER_TASK; ++i) { + PCONFIG(i) = 0; // "Empty" + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + const __FlashStringHelper *options[P052_NR_OUTPUT_OPTIONS]; + + for (uint8_t i = 0; i < P052_NR_OUTPUT_OPTIONS; ++i) { + options[i] = P052_data_struct::Plugin_052_valuename(i, true); + } + + for (uint8_t i = 0; i < P052_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P052_QUERY1_CONFIG_POS; + sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P052_NR_OUTPUT_OPTIONS, options); + } + break; + } + + case PLUGIN_WEBFORM_LOAD: { + P052_data_struct *P052_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P052_data) && P052_data->isInitialized()) { + addFormSubHeader(F("Device Information")); + { + int value = 0; + + if (P052_data->modbus.detected_device_description.length() > 0) { + addRowLabel(F("Detected Device")); + addHtml(P052_data->modbus.detected_device_description); + } + + if (P052_data->readInputRegister(P052_IR29_FW_REV, value)) { + addRowLabel(F("FW rev.")); + addHtmlInt(value); + } + + addRowLabel(F("Sensor ID")); + addHtml(formatToHex_decimal(P052_data->getSensorID())); + + + addRowLabel(F("Checksum (pass/fail/nodata)")); + { + uint32_t reads_pass, reads_crc_failed, reads_nodata; + P052_data->modbus.getStatistics(reads_pass, reads_crc_failed, reads_nodata); + addHtml(strformat(F("%d/%d/%d"), reads_pass, reads_crc_failed, reads_nodata)); + } + + bool hasFactorySettings = false; + bool enabledABC = false; + + if (P052_data->readHoldingRegister(P052_HR19_METER_CONTROL, value)) { + hasFactorySettings = value == 255; + enabledABC = bitRead(value, 1) == 0; + addRowLabel(F("Using Factory Settings")); + addEnabled(hasFactorySettings); + + if (!hasFactorySettings) { + // addRowLabel(F("HR19")); + // addHtmlInt(value); + addRowLabel(F("nRDY")); + addEnabled(bitRead(value, 0) == 0); + addRowLabel(F("ABC")); + addEnabled(enabledABC); + addRowLabel(F("Static IIR filter")); + addEnabled(bitRead(value, 2) == 0); + addRowLabel(F("Dynamic IIR filter")); + addEnabled(bitRead(value, 3) == 0); + addRowLabel(F("Pressure Compensation")); + addEnabled(bitRead(value, 4) == 0); + } + } + + if (P052_data->readInputRegister(P052_IR7_MEASUREMENT_COUNT, value)) { + addRowLabel(F("Measurement Count")); + addHtmlInt(value); + } + + if (P052_data->readInputRegister(P052_IR8_MEASUREMENT_CYCLE_TIME, value)) { + addRowLabel(F("Measurement Cycle time")); + addHtmlInt(value * 2); + addUnit('s'); + } + + if (P052_data->readInputRegister(P052_IR9_MEASURED_UNFILTERED_CO2, value)) { + addRowLabel(F("Unfiltered CO2")); + addHtmlInt(value); + } + + if (P052_data->readHoldingRegister(P052_HR11_MEASUREMENT_MODE, value)) { + addRowLabel(F("Measurement Mode")); + addHtml(value == 0 ? F("Continuous") : F("Single Measurement")); + } + + if (enabledABC) { + if (P052_data->readHoldingRegister(P052_HR14_ABC_PERIOD, value)) { + addRowLabel(F("ABC Period")); + addHtmlInt(value); + addUnit('h'); + } + + if (P052_data->readHoldingRegister(P052_HR5_ABC_TIME, value)) { + addRowLabel(F("Time Since ABC")); + addHtmlInt(value); + addUnit('h'); + } + } + } + + { + int value = 0; + + /* + if (P052_data->readHoldingRegister(P052_HR11_MEASUREMENT_MODE, value)) { + // Disable selector for now, since single measurement not yet supported. + + const __FlashStringHelper *options[2] = { F("Continuous"), F("Single Measurement") }; + addFormSelector(F("Measurement Mode"), F("mode"), 2, options, nullptr, value); + } + */ + + if (P052_data->readHoldingRegister(P052_HR12_MEASUREMENT_PERIOD, value)) { + addFormNumericBox(F("Measurement Period"), F("period"), value, 2, 65534); + addUnit('s'); + } + + if (P052_data->readHoldingRegister(P052_HR13_NR_OF_SAMPLES, value)) { + addFormNumericBox(F("Samples per measurement"), F("samp_meas"), value, 1, 1024); + } + } + { + // P052_ABC_PERIOD + } + } + + /* + // ABC functionality disabled for now, due to a bug in the firmware. + // See https://github.com/letscontrolit/ESPEasy/issues/759 + uint8_t choiceABCperiod = PCONFIG(4); + const __FlashStringHelper * optionsABCperiod[9] = { F("disable"), F("1 h"), F("12 h"), F("1 + day"), F("2 days"), F("4 days"), F("7 days"), F("14 days"), F("30 days") }; + addFormSelector(F("ABC period"), F("ABC_period"), 9, optionsABCperiod, + nullptr, choiceABCperiod); + */ + + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: { + // Save output selector parameters. + for (uint8_t i = 0; i < P052_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P052_QUERY1_CONFIG_POS; + const uint8_t choice = PCONFIG(pconfigIndex); + sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, P052_data_struct::Plugin_052_valuename(choice, false)); + } + + P052_data_struct *P052_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P052_data) && P052_data->isInitialized()) { + bool changed = false; + uint16_t mode = getFormItemInt(F("mode"), 65535); + + if (((mode == 0) || (mode == 1))) { + uint8_t errorcode; + int readVal = P052_data->modbus.readHoldingRegister(0x0A, errorcode); + + if ((errorcode == 0) && (readVal != mode)) { + P052_data->modbus.writeMultipleRegisters(0x0A, mode); + delay(0); + changed = true; + } + } + uint16_t period = getFormItemInt(F("period"), 0); + + if (period > 1) { + uint8_t errorcode; + int readVal = P052_data->modbus.readHoldingRegister(0x0B, errorcode); + + if ((errorcode == 0) && (readVal != period)) { + P052_data->modbus.writeMultipleRegisters(0x0B, period); + delay(0); + changed = true; + } + } + uint16_t samp_meas = getFormItemInt(F("samp_meas"), 0); + + if ((samp_meas > 0) && (samp_meas <= 1024)) { + uint8_t errorcode; + int readVal = P052_data->modbus.readHoldingRegister(0x0C, errorcode); + + if ((errorcode == 0) && (readVal != samp_meas)) { + P052_data->modbus.writeMultipleRegisters(0x0C, samp_meas); + delay(0); + changed = true; + } + } + + if (changed) { + // Restart sensor. + P052_data->modbus.writeMultipleRegisters(0x11, 0xFF); + + // FIXME TD-er: Must leave the sensor to boot for a while. + delay(35); + } + } + + + /* + // ABC functionality disabled for now, due to a bug in the firmware. + // See https://github.com/letscontrolit/ESPEasy/issues/759 + PCONFIG(4) = getFormItemInt(F("ABC_period")); + */ + + success = true; + break; + } + + case PLUGIN_INIT: { + const int16_t serial_rx = CONFIG_PIN1; + const int16_t serial_tx = CONFIG_PIN2; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + + initPluginTaskData(event->TaskIndex, new (std::nothrow) P052_data_struct()); + P052_data_struct *P052_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P052_data) { + return success; + } + + + if (P052_data->init(port, serial_rx, serial_tx)) { + /* + // ABC functionality disabled for now, due to a bug in the firmware. + // See https://github.com/letscontrolit/ESPEasy/issues/759 + const int periodInHours[9] = {0, 1, 12, (24*1), (24*2), (24*4), (24*7), + (24*14), (24*30) }; + uint8_t choiceABCperiod = PCONFIG(1); + + Plugin_052_setABCperiod(periodInHours[choiceABCperiod]); + */ + P052_data->modbus.setModbusTimeout(P052_MODBUS_TIMEOUT); + + // P052_data->modbus.writeMultipleRegisters(0x09, 1); // Start Single Measurement + // P052_data->modbus.writeMultipleRegisters(0x0B, 16); // Measurement Period + // P052_data->modbus.writeMultipleRegisters(0x0C, 8); // Number of samples + + success = true; + } else { + clearPluginTaskData(event->TaskIndex); + } + break; + } + + case PLUGIN_EXIT: { + success = true; + break; + } + + case PLUGIN_READ: { + P052_data_struct *P052_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P052_data) && P052_data->isInitialized()) { + event->sensorType = static_cast(PCONFIG(P052_SENSOR_TYPE_INDEX)); + String log = F("Senseair: "); + String logPrefix; + + for (int varnr = 0; varnr < P052_NR_OUTPUT_VALUES; ++varnr) { + uint8_t errorcode = 0; + float value = 0; + + switch (PCONFIG(varnr)) { + case 1: { + value = P052_data->modbus.readInputRegister(P052_IR4_MEASURED_FILTERED_CO2, errorcode); + logPrefix = F("co2="); + break; + } + case 2: { + int temperatureX100 = P052_data->modbus.readInputRegister(P052_IR5_TEMPERATURE, errorcode); + + if (errorcode == 2) { + // Exception code: Illegal Data Address + // SenseAir S8, not for other modules. + temperatureX100 = P052_data->modbus.read_RAM_EEPROM( + P052_CMD_READ_RAM, P052_RAM_ADDR_DET_TEMPERATURE, 2, errorcode); + } + + if (temperatureX100 >= 32768) { + // 2-complement value. + temperatureX100 -= 65536; + } + value = static_cast(temperatureX100) / 100.0f; + logPrefix = F("temp="); + break; + } + case 3: { + int rhX100 = P052_data->modbus.readInputRegister(P052_IR6_SPACE_HUMIDITY, errorcode); + value = static_cast(rhX100) / 100.0f; + logPrefix = F("hum="); + break; + } + case 4: { + int status = P052_data->modbus.readInputRegister(P052_IR29_FW_REV, errorcode); + + if (errorcode == 0) { + int relayStatus = (status >> 8) & 0x1; + UserVar.setFloat(event->TaskIndex, varnr, relayStatus); + log += F("relay status="); + log += relayStatus; + } + break; + } + case 5: { + int temperatureAdjustment = P052_data->modbus.readInputRegister(P052_IR11_MEASURED_CONCENTRATION_UNFILTERED, errorcode); + value = static_cast(temperatureAdjustment); + logPrefix = F("temp adj="); + break; + } + + case 7: { + const int errorWord = P052_data->modbus.readInputRegister(P052_IR_ERRORSTATUS, errorcode); + + if (errorcode == 0) { + value = errorWord; + + for (size_t i = 0; i < 9; i++) { + if (bitRead(errorWord, i)) { + log += F("err="); + log += i; + break; + } + } + } else { + value = -1; + log += F("err="); + log += -1; + } + break; + } + case 0: + default: { + UserVar.setFloat(event->TaskIndex, varnr, 0); + break; + } + } + + if ((errorcode == 0) && (P052_data->modbus.getLastError() == 0)) { + success = true; + UserVar.setFloat(event->TaskIndex, varnr, value); + log += logPrefix; + log += value; + } + } + addLogMove(LOG_LEVEL_INFO, log); + break; + } + break; + } + + case PLUGIN_WRITE: + { + P052_data_struct *P052_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P052_data) && P052_data->plugin_write(event, string)) { + success = true; + } + break; + } + } + return success; +} + +#endif // USES_P052 diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 9bb7fa7c5..340d4fdd3 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -48,6 +48,7 @@ void ControllerSettingsStruct::reset() { VariousBits1.deduplicate = 0; VariousBits1.useLocalSystemTime = 0; VariousBits1.TLStype = 0; + VariousBits1.mqttAutoDiscovery = 0; safe_strncpy(ClientID, F(CONTROLLER_DEFAULT_CLIENTID), sizeof(ClientID)); } diff --git a/src/src/DataTypes/SensorVType.cpp b/src/src/DataTypes/SensorVType.cpp index 2b485e1cd..7bf641a2b 100644 --- a/src/src/DataTypes/SensorVType.cpp +++ b/src/src/DataTypes/SensorVType.cpp @@ -69,6 +69,18 @@ uint8_t getValueCountFromSensorType(Sensor_VType sensorType) case Sensor_VType::SENSOR_TYPE_UV_ONLY: case Sensor_VType::SENSOR_TYPE_UV_INDEX_ONLY: case Sensor_VType::SENSOR_TYPE_IR_ONLY: + case Sensor_VType::SENSOR_TYPE_WEIGHT_ONLY: + case Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY: + case Sensor_VType::SENSOR_TYPE_CURRENT_ONLY: + case Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY: + case Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY: + case Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY: + case Sensor_VType::SENSOR_TYPE_TVOC_ONLY: + case Sensor_VType::SENSOR_TYPE_BARO_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_RED_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_GREEN_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_BLUE_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_TEMP_ONLY: return 1; } #ifndef BUILD_NO_DEBUG @@ -112,22 +124,65 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_NONE: return F("None"); case Sensor_VType::SENSOR_TYPE_NOT_SET: break; - // FIXME To be ignored? + // FIXME To be ignored? + #if FEATURE_MQTT_DISCOVER case Sensor_VType::SENSOR_TYPE_ANALOG_ONLY: return F("Analog"); case Sensor_VType::SENSOR_TYPE_TEMP_ONLY: return F("Temp"); case Sensor_VType::SENSOR_TYPE_HUM_ONLY: return F("Hum"); - case Sensor_VType::SENSOR_TYPE_LUX_ONLY: return F("Luc"); + case Sensor_VType::SENSOR_TYPE_LUX_ONLY: return F("Lux"); case Sensor_VType::SENSOR_TYPE_DISTANCE_ONLY: return F("Distance"); case Sensor_VType::SENSOR_TYPE_DIRECTION_ONLY: return F("Direction"); case Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY: return F("Dust PM2.5"); case Sensor_VType::SENSOR_TYPE_DUSTPM1_0_ONLY: return F("Dust PM1.0"); case Sensor_VType::SENSOR_TYPE_DUSTPM10_ONLY: return F("Dust PM10"); case Sensor_VType::SENSOR_TYPE_MOISTURE_ONLY: return F("Moisture"); - case Sensor_VType::SENSOR_TYPE_CO2_ONLY: return F("CO2"); + case Sensor_VType::SENSOR_TYPE_CO2_ONLY: return F("(e)CO2"); case Sensor_VType::SENSOR_TYPE_GPS_ONLY: return F("GPS"); case Sensor_VType::SENSOR_TYPE_UV_ONLY: return F("UV"); case Sensor_VType::SENSOR_TYPE_UV_INDEX_ONLY: return F("UV Index"); case Sensor_VType::SENSOR_TYPE_IR_ONLY: return F("IR"); + case Sensor_VType::SENSOR_TYPE_WEIGHT_ONLY: return F("Weight"); + case Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY: return F("Voltage"); + case Sensor_VType::SENSOR_TYPE_CURRENT_ONLY: return F("Current"); + case Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY: return F("Power Usage"); + case Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY: return F("Power Factor"); + case Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY: return F("Apparent Power Usage"); + case Sensor_VType::SENSOR_TYPE_TVOC_ONLY: return F("TVOC"); + case Sensor_VType::SENSOR_TYPE_BARO_ONLY: return F("Baro"); + case Sensor_VType::SENSOR_TYPE_COLOR_RED_ONLY: return F("Red"); + case Sensor_VType::SENSOR_TYPE_COLOR_GREEN_ONLY: return F("Green"); + case Sensor_VType::SENSOR_TYPE_COLOR_BLUE_ONLY: return F("Blue"); + case Sensor_VType::SENSOR_TYPE_COLOR_TEMP_ONLY: return F("Color temperature"); + #else // if FEATURE_MQTT_DISCOVER + case Sensor_VType::SENSOR_TYPE_ANALOG_ONLY: + case Sensor_VType::SENSOR_TYPE_TEMP_ONLY: + case Sensor_VType::SENSOR_TYPE_HUM_ONLY: + case Sensor_VType::SENSOR_TYPE_LUX_ONLY: + case Sensor_VType::SENSOR_TYPE_DISTANCE_ONLY: + case Sensor_VType::SENSOR_TYPE_DIRECTION_ONLY: + case Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY: + case Sensor_VType::SENSOR_TYPE_DUSTPM1_0_ONLY: + case Sensor_VType::SENSOR_TYPE_DUSTPM10_ONLY: + case Sensor_VType::SENSOR_TYPE_MOISTURE_ONLY: + case Sensor_VType::SENSOR_TYPE_CO2_ONLY: + case Sensor_VType::SENSOR_TYPE_GPS_ONLY: + case Sensor_VType::SENSOR_TYPE_UV_ONLY: + case Sensor_VType::SENSOR_TYPE_UV_INDEX_ONLY: + case Sensor_VType::SENSOR_TYPE_IR_ONLY: + case Sensor_VType::SENSOR_TYPE_WEIGHT_ONLY: + case Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY: + case Sensor_VType::SENSOR_TYPE_CURRENT_ONLY: + case Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY: + case Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY: + case Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY: + case Sensor_VType::SENSOR_TYPE_TVOC_ONLY: + case Sensor_VType::SENSOR_TYPE_BARO_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_RED_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_GREEN_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_BLUE_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_TEMP_ONLY: + break; + #endif // if FEATURE_MQTT_DISCOVER } return F(""); } diff --git a/src/src/DataTypes/SensorVType.h b/src/src/DataTypes/SensorVType.h index 1931728b5..a90798503 100644 --- a/src/src/DataTypes/SensorVType.h +++ b/src/src/DataTypes/SensorVType.h @@ -49,6 +49,18 @@ enum class Sensor_VType : uint8_t { SENSOR_TYPE_UV_ONLY = 112, SENSOR_TYPE_UV_INDEX_ONLY = 113, SENSOR_TYPE_IR_ONLY = 114, + SENSOR_TYPE_WEIGHT_ONLY = 115, + SENSOR_TYPE_VOLTAGE_ONLY = 116, + SENSOR_TYPE_CURRENT_ONLY = 117, + SENSOR_TYPE_POWER_USG_ONLY = 118, + SENSOR_TYPE_POWER_FACT_ONLY = 119, + SENSOR_TYPE_APPRNT_POWER_USG_ONLY = 120, + SENSOR_TYPE_TVOC_ONLY = 121, + SENSOR_TYPE_BARO_ONLY = 122, + SENSOR_TYPE_COLOR_RED_ONLY = 123, + SENSOR_TYPE_COLOR_GREEN_ONLY = 124, + SENSOR_TYPE_COLOR_BLUE_ONLY = 125, + SENSOR_TYPE_COLOR_TEMP_ONLY = 126, SENSOR_TYPE_NOT_SET = 255 }; diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 7609455fb..e68e02420 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -332,10 +332,14 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn // Translate Device[].VType to usable VType per value for MQTT AutoDiscovery if (MQTT_DiscoveryGetDeviceVType(x, discoveryItems, valueCount)) { - const String hostName = Settings.getHostname(); - const unsigned int groupId = Settings.TaskDeviceID[ControllerIndex][x] & 0x3FFF; // Remove opt. flag bits + const String hostName = Settings.getHostname(); + const unsigned int groupId = Settings.TaskDeviceID[ControllerIndex][x] & 0x3FFF; // Remove opt. flag bits + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(ControllerIndex); + const bool usesControllerIDX = validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID; + // dev = device, ids = identifiers - const String deviceElement = groupId != 0 ? + const String deviceElement = groupId != 0 && !usesControllerIDX ? strformat(F(",\"dev\":{\"name\":\"Group %u\",\"ids\":[\"group_%u\"]}"), groupId, groupId) : strformat(F(",\"dev\":{\"name\":\"%s %s\",\"ids\":[\"%s_%s\"]}"), hostName.c_str(), taskName.c_str(), hostName.c_str(), taskName.c_str()); @@ -349,20 +353,17 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn case Sensor_VType::SENSOR_TYPE_SWITCH: for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { - const String valuename = getTaskValueName(x, v); - - if (!valuename.isEmpty()) { - const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); - const String publish = MQTT_DiscoveryBuildValueTopic(publish_topic, &TempEvent, v, F("binary_sensor")); - const String discovery = MQTT_DiscoveryBuildValueTopic(discovery_topic, &TempEvent, v, F("binary_sensor")); - discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\"," - "\"dev_cla\":\"switch\",\"cmd_t\":\"~/set\",\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), - deviceElement.c_str()); - - success &= MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, x, v, taskName); - } + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("binary_sensor"), + F("switch"), + EMPTY_STRING, // No unit of measure + &TempEvent, + deviceElement, + success, + true, false); } break; case Sensor_VType::SENSOR_TYPE_TEMP_HUM: @@ -371,6 +372,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn case Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO: case Sensor_VType::SENSOR_TYPE_TEMP_ONLY: case Sensor_VType::SENSOR_TYPE_HUM_ONLY: + case Sensor_VType::SENSOR_TYPE_BARO_ONLY: { uint8_t v = discoveryItems[s].varIndex; @@ -384,20 +386,17 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn } for (v = fromV; v < maxV; ++v) { - const String valuename = getTaskValueName(x, v); - - if (!valuename.isEmpty()) { - const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); - const String publish = MQTT_DiscoveryBuildValueTopic(publish_topic, &TempEvent, v, F("sensor")); - const String discovery = MQTT_DiscoveryBuildValueTopic(discovery_topic, &TempEvent, v, F("sensor")); - discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\"," - "\"dev_cla\":\"temperature\",\"unit_of_meas\":\"°C\",\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), - deviceElement.c_str()); - - success &= MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, x, v, taskName); - } + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("temperature"), + F("°C"), + &TempEvent, + deviceElement, + success, + false, false); } } @@ -413,44 +412,39 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn } for (v = fromV; v < maxV; ++v) { - const String valuename = getTaskValueName(x, v); - - if (!valuename.isEmpty()) { - const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); - const String publish = MQTT_DiscoveryBuildValueTopic(publish_topic, &TempEvent, v, F("sensor")); - const String discovery = MQTT_DiscoveryBuildValueTopic(discovery_topic, &TempEvent, v, F("sensor")); - discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\"," - "\"dev_cla\":\"humidity\",\"unit_of_meas\":\"%\",\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), - deviceElement.c_str()); - - success &= MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, x, v, taskName); - } + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("humidity"), + F("%"), + &TempEvent, + deviceElement, + success, + false, false); } } // Barometric pressure if ((Sensor_VType::SENSOR_TYPE_TEMP_BARO == discoveryItems[s].VType) || (Sensor_VType::SENSOR_TYPE_TEMP_HUM_BARO == discoveryItems[s].VType) || - (Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO == discoveryItems[s].VType)) { + (Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO == discoveryItems[s].VType) || + (Sensor_VType::SENSOR_TYPE_BARO_ONLY == discoveryItems[s].VType)) { if (Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO == discoveryItems[s].VType) { v++; // Skip 2nd value = 'EMPTY' } - const String valuename = getTaskValueName(x, v); - - if (!valuename.isEmpty()) { - const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); - const String publish = MQTT_DiscoveryBuildValueTopic(publish_topic, &TempEvent, v, F("sensor")); - const String discovery = MQTT_DiscoveryBuildValueTopic(discovery_topic, &TempEvent, v, F("sensor")); - discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\"," - "\"dev_cla\":\"pressure\",\"unit_of_meas\":\"hPa\",\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), - deviceElement.c_str()); - - success &= MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, x, v, taskName); - } + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("pressure"), + F("hPa"), + &TempEvent, + deviceElement, + success, + false, false); v++; } break; @@ -458,53 +452,206 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn case Sensor_VType::SENSOR_TYPE_DISTANCE_ONLY: for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { - const String valuename = getTaskValueName(x, v); - - if (!valuename.isEmpty()) { - const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); - const String publish = MQTT_DiscoveryBuildValueTopic(publish_topic, &TempEvent, v, F("sensor")); - const String discovery = MQTT_DiscoveryBuildValueTopic(discovery_topic, &TempEvent, v, F("sensor")); - discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\"," - "\"dev_cla\":\"distance\",\"unit_of_meas\":\"cm\",\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), - deviceElement.c_str()); - - success &= MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, x, v, taskName); - } + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("distance"), + F("cm"), + &TempEvent, + deviceElement, + success, + false, false); } break; + case Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY: case Sensor_VType::SENSOR_TYPE_DUSTPM1_0_ONLY: case Sensor_VType::SENSOR_TYPE_DUSTPM10_ONLY: + { + const __FlashStringHelper*dev = (Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY == discoveryItems[s].VType ? F("pm25") : + Sensor_VType::SENSOR_TYPE_DUSTPM1_0_ONLY == discoveryItems[s].VType ? F("pm1") : + F("pm10")); for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { - const String valuename = getTaskValueName(x, v); - - if (!valuename.isEmpty()) { - const String pm = (Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY == discoveryItems[2].VType ? F("pm25") : - Sensor_VType::SENSOR_TYPE_DUSTPM1_0_ONLY == discoveryItems[2].VType ? F("pm1") : - F("pm10")); - const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); - const String publish = MQTT_DiscoveryBuildValueTopic(publish_topic, &TempEvent, v, F("sensor")); - const String discovery = MQTT_DiscoveryBuildValueTopic(discovery_topic, &TempEvent, v, F("sensor")); - discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\"," - "\"dev_cla\":\"%s\",\"unit_of_meas\":\"µg/m³\",\"stat_t\":\"~\"" - "%s}"), // deviceElement last - publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), - pm.c_str(), - deviceElement.c_str()); - - success &= MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, x, v, taskName); - } + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + dev, + F("µg/m³"), + &TempEvent, + deviceElement, + success, + false, false); } break; + } + case Sensor_VType::SENSOR_TYPE_CO2_ONLY: + case Sensor_VType::SENSOR_TYPE_TVOC_ONLY: + { + const __FlashStringHelper*dev = Sensor_VType::SENSOR_TYPE_CO2_ONLY == discoveryItems[s].VType ? + F("carbon_dioxide") : F("volatile_organic_compounds"); + const __FlashStringHelper*uom = Sensor_VType::SENSOR_TYPE_CO2_ONLY == discoveryItems[s].VType ? + F("ppm") : F("ppd"); + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + dev, + uom, + &TempEvent, + deviceElement, + success, + false, false); + } + break; + } + case Sensor_VType::SENSOR_TYPE_WEIGHT_ONLY: + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("mdi:scale"), // Icon + F("kg"), + &TempEvent, + deviceElement, + success, + false, true); + } + break; + case Sensor_VType::SENSOR_TYPE_MOISTURE_ONLY: + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("mdi:cup-water"), // Icon + F("%"), + &TempEvent, + deviceElement, + success, + false, true); + } + break; + case Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY: + case Sensor_VType::SENSOR_TYPE_CURRENT_ONLY: + { + const __FlashStringHelper*dev = Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY == discoveryItems[s].VType ? + F("voltage") : F("current"); + const __FlashStringHelper*uom = Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY == discoveryItems[s].VType ? + F("V") : F("A"); + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + dev, + uom, + &TempEvent, + deviceElement, + success, + false, false); + } + break; + } + case Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY: + case Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY: + case Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY: + { + const __FlashStringHelper*dev = Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY == discoveryItems[s].VType ? + F("power_factor") : F("power"); + const __FlashStringHelper*uom = Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY == discoveryItems[s].VType ? F("Cos φ") : + Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY == discoveryItems[s].VType ? F("kWh") : + F("VA"); + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + dev, + uom, + &TempEvent, + deviceElement, + success, + false, false); + } + break; + } + case Sensor_VType::SENSOR_TYPE_LUX_ONLY: + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("illuminance"), + F("lux"), + &TempEvent, + deviceElement, + success, + false, false); + } + break; + + case Sensor_VType::SENSOR_TYPE_COLOR_RED_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_GREEN_ONLY: + case Sensor_VType::SENSOR_TYPE_COLOR_BLUE_ONLY: + { + const __FlashStringHelper*uom = Sensor_VType::SENSOR_TYPE_COLOR_RED_ONLY == discoveryItems[s].VType ? F("R") : + Sensor_VType::SENSOR_TYPE_COLOR_GREEN_ONLY == discoveryItems[s].VType ? F("G") : + F("B"); + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("mdi:palette"), + uom, + &TempEvent, + deviceElement, + success, + false, true); + } + break; + } + case Sensor_VType::SENSOR_TYPE_COLOR_TEMP_ONLY: + + for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { + success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, taskName, + ControllerIndex, + publish_topic, + discovery_topic, + F("sensor"), + F("mdi:temperature-kelvin"), + F("K"), + &TempEvent, + deviceElement, + success, + false, true); + } + break; + case Sensor_VType::SENSOR_TYPE_WIND: case Sensor_VType::SENSOR_TYPE_ANALOG_ONLY: - case Sensor_VType::SENSOR_TYPE_LUX_ONLY: case Sensor_VType::SENSOR_TYPE_DIRECTION_ONLY: - case Sensor_VType::SENSOR_TYPE_MOISTURE_ONLY: - case Sensor_VType::SENSOR_TYPE_CO2_ONLY: case Sensor_VType::SENSOR_TYPE_GPS_ONLY: case Sensor_VType::SENSOR_TYPE_UV_ONLY: case Sensor_VType::SENSOR_TYPE_UV_INDEX_ONLY: @@ -591,7 +738,12 @@ bool MQTT_DiscoveryGetDeviceVType(taskIndex_t TaskIndex, case 144: // Vindriktning / PM1006K discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY, valueCount, 0)); break; - case 47: // Soil Moisture + case 27: // INA219 + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY, 1, 0)); + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_CURRENT_ONLY, 1, 1)); + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY, 1, 2)); + break; + case 47: // Soil Moisture discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_TEMP_ONLY, 1, 0)); discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_MOISTURE_ONLY, 1, 1)); @@ -613,10 +765,18 @@ bool MQTT_DiscoveryGetDeviceVType(taskIndex_t TaskIndex, discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_DUSTPM2_5_ONLY, 1, 0)); discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_DUSTPM10_ONLY, 1, 1)); break; + case 67: // HX711 Load cell + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_WEIGHT_ONLY, valueCount, 0)); + break; case 74: // TSL2591 discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_LUX_ONLY, 3, 0)); discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_UV_ONLY, 1, 3)); break; + case 83: // SGP30 TVOC/eCO2 + case 90: // CCS811 TVOC/eCO2 + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_TVOC_ONLY, 1, 0)); + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_CO2_ONLY, 1, 1)); + break; case 84: // VEML6070 (UV and Index only) discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_UV_ONLY, 1, 0)); discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_UV_INDEX_ONLY, 1, 1)); @@ -657,6 +817,10 @@ bool MQTT_DiscoveryGetDeviceVType(taskIndex_t TaskIndex, discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_CO2_ONLY, 1, 0)); discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_TEMP_HUM, 1, 1)); break; + case 151: // Honeywell pressure + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_BARO_ONLY, 1, 0)); + discoveryItems.push_back(DiscoveryItem(Sensor_VType::SENSOR_TYPE_TEMP_ONLY, 1, 1)); + break; // Deliberately ignored/skipped for now: case 3: // Pulse: Needs special handling/not usable? @@ -669,47 +833,45 @@ bool MQTT_DiscoveryGetDeviceVType(taskIndex_t TaskIndex, VType = Sensor_VType::SENSOR_TYPE_NONE; break; - // To be reviewed/considered later: + // To be reviewed/considered later/custom/special handled: case 8: // RFID Wiegand case 17: // RFID PN532 - case 27: // INA219 case 40: // RFID ID12 case 45: // MPU6050 case 50: // TCS34725 RGB: Needs special handling case 52: // SensAir: Needs special handling case 64: // APDS9960: Needs special handling case 66: // VEML6040 RGB: Needs special handling - case 67: // HX711 Load cell case 71: // Kamstrup Heat - case 76: // HLW8012 - case 77: // CSE7766 + case 76: // HLW8012: Needs special handling + case 77: // CSE7766: Needs special handling case 78: // Eastron: Needs special handling case 82: // GPS: Needs special handling - case 83: // SGP30 TVOC/eCO2 - case 85: // AcuDC243 - case 90: // CCS811 TVOC/eCO2 - case 92: // DL-bus + case 85: // AcuDC243: Needs special handling + case 92: // DL-bus: Needs special handling case 93: // Mitsubishi Heatpump - case 102: // PZEM004 - case 103: // Atlas EZO - case 108: // DDS238 + case 102: // PZEM004: Needs special handling + case 103: // Atlas EZO: Needs special handling + case 108: // DDS238: Needs special handling case 111: // RFID RC522 case 112: // AS7265x: Needs special handling case 115: // MAX1704x case 117: // SCD30 CO2: Needs special handling - case 132: // INA3221 + case 132: // INA3221: Needs special handling case 142: // AS5600 Angle/rotation case 145: // MQxxx gases case 147: // SGP4x - case 151: // Honeywell pressure case 159: // LD2410 case 163: // RadSens - case 164: // ENS160 + case 164: // ENS160: Needs special handling case 167: // Vindstyrka: Needs special handling case 169: // AS3935 Lightning + case 176: // Victron VE.Direct: Needs user-configurable Sensor_VType + Unit_of_measure per value // Reset VType = Sensor_VType::SENSOR_TYPE_NONE; break; + + // Plugin ID numbers not listed in the above switch statement most likely have the correct Sensor_VType value set in DeviceStruct } // Use Device VType setting if not overridden or reset @@ -792,20 +954,20 @@ String MQTT_DiscoveryBuildValueTopic(const String & topic, bool MQTT_DiscoveryPublish(controllerIndex_t ControllerIndex, const String & topic, - String & discoveryMessage, + const String & discoveryMessage, taskIndex_t x, uint8_t v, const String & taskName) { bool result = false; - // TODO Send each discovery message separate - // result = MQTTpublish(ControllerIndex, INVALID_TASK_INDEX, concat(topic, F("/config")).c_str(), discoveryMessage.c_str(), false); + // Send each discovery message separate + result = MQTTpublish(ControllerIndex, INVALID_TASK_INDEX, concat(topic, F("/config")).c_str(), discoveryMessage.c_str(), false); # ifndef BUILD_NO_DEBUG // FIXME change this log to DEBUG level if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Discovery Task %d '%s': disc.topic: %s"), - x + 1, taskName.c_str(), concat(topic, F("/config")).c_str())); + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Discovery %d Task %d '%s': disc.topic: %s"), + result, x + 1, taskName.c_str(), concat(topic, F("/config")).c_str())); addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Discovery payload: %s"), discoveryMessage.c_str())); } @@ -813,6 +975,43 @@ bool MQTT_DiscoveryPublish(controllerIndex_t ControllerIndex, return result; } +bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, + uint8_t taskValue, + String taskName, + controllerIndex_t ControllerIndex, + String publishTopic, + String discoveryTopic, + const __FlashStringHelper*componentClass, + String deviceClass, + String unitOfMeasure, + struct EventStruct *event, + const String deviceElement, + bool success, + bool hasSet, + bool hasIcon) { + const String withSet = hasSet ? F(",\"cmd_t\":\"~/set\"") : EMPTY_STRING; + const String devOrIcon = hasIcon ? F("ic") : F("dev_cla"); + const String withUoM = !unitOfMeasure.isEmpty() ? + strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()) : + EMPTY_STRING; + const String valuename = getTaskValueName(taskIndex, taskValue); + + if (!valuename.isEmpty()) { + const String uniqueId = MQTT_TaskValueUniqueName(taskName, valuename); + const String publish = MQTT_DiscoveryBuildValueTopic(publishTopic, event, taskValue, componentClass); + const String discovery = MQTT_DiscoveryBuildValueTopic(discoveryTopic, event, taskValue, componentClass); + const String discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\",\"schema\":\"basic\"," + "\"%s\":\"%s\"%s%s,\"stat_t\":\"~\"" + "%s}"), // deviceElement last + publish.c_str(), taskName.c_str(), valuename.c_str(), uniqueId.c_str(), + devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), withSet.c_str(), + deviceElement.c_str()); + + return MQTT_DiscoveryPublish(ControllerIndex, discovery, discoveryMessage, taskIndex, taskValue, taskName); + } + return success; +} + # endif // if FEATURE_MQTT_DISCOVER #endif // if FEATURE_MQTT diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.h b/src/src/Helpers/_CPlugin_Helper_mqtt.h index b05058de6..de2ce5690 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.h +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.h @@ -38,12 +38,28 @@ String MQTT_DiscoveryBuildValueTopic(const String & topic, struct EventStruct *event, uint8_t taskValueIndex, const __FlashStringHelper*deviceClass); + bool MQTT_DiscoveryPublish(controllerIndex_t ControllerIndex, const String & topic, - String & discoveryMessage, + const String & discoveryMessage, taskIndex_t x, uint8_t v, const String & taskName); + +bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, + uint8_t taskValue, + String taskName, + controllerIndex_t ControllerIndex, + String publishTopic, + String discoveryTopic, + const __FlashStringHelper*componentClass, + String deviceClass, + String unitOfMeasure, + struct EventStruct *event, + const String deviceElement, + bool success, + bool hasSet, + bool hasIcon); # endif // if FEATURE_MQTT_DISCOVER #endif // if FEATURE_MQTT #endif // ifndef CPLUGIN_HELPER_MQTT_H diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 5bf1c47cd..f0cb31f9c 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -1,1620 +1,1621 @@ -#include "../WebServer/DevicesPage.h" - -#ifdef WEBSERVER_DEVICES - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" - -# include "../DataStructs/NodeStruct.h" -# if FEATURE_PLUGIN_STATS -# include "../DataStructs/PluginStats_Config.h" -# endif // if FEATURE_PLUGIN_STATS - - -# include "../Globals/CPlugins.h" -# include "../Globals/Device.h" -# include "../Globals/ExtraTaskSettings.h" -# if FEATURE_MQTT_DISCOVER -# include "../Globals/MQTT.h" -# endif // if FEATURE_MQTT_DISCOVER -# include "../Globals/Nodes.h" -# include "../Globals/Plugins.h" - -# include "../Static/WebStaticData.h" - -# include "../Helpers/_CPlugin_init.h" -# include "../Helpers/_Plugin_init.h" -# include "../Helpers/_Plugin_SensorTypeHelper.h" -# include "../Helpers/_Plugin_Helper_serial.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/I2C_Plugin_Helper.h" -# include "../Helpers/StringConverter.h" -# include "../Helpers/StringGenerator_GPIO.h" - - -# include "../../_Plugin_Helper.h" - -# include - - -void handle_devices() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_devices")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_DEVICES; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - - // char tmpString[41]; - - - // String taskindex = webArg(F("index")); - - pluginID_t taskdevicenumber; - - if (hasArg(F("del"))) { - taskdevicenumber.setInvalid(); - } - else { - taskdevicenumber = pluginID_t::toPluginID(getFormItemInt(F("TDNUM"), 0)); - } - - - // String taskdeviceid[CONTROLLER_MAX]; - // String taskdevicepin1 = webArg(F("taskdevicepin1")); // "taskdevicepin*" should not be changed because it is uses by plugins - // and expected to be saved by this code - // String taskdevicepin2 = webArg(F("taskdevicepin2")); - // String taskdevicepin3 = webArg(F("taskdevicepin3")); - // String taskdevicepin1pullup = webArg(F("TDPPU")); - // String taskdevicepin1inversed = webArg(F("TDPI")); - // String taskdevicename = webArg(F("TDN")); - // String taskdeviceport = webArg(F("TDP")); - // String taskdeviceformula[VARS_PER_TASK]; - // String taskdevicevaluename[VARS_PER_TASK]; - // String taskdevicevaluedecimals[VARS_PER_TASK]; - // String taskdevicesenddata[CONTROLLER_MAX]; - // String taskdeviceglobalsync = webArg(F("TDGS")); - // String taskdeviceenabled = webArg(F("TDE")); - - // for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) - // { - // char argc[25]; - // String arg = F("TDF"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdeviceformula[varNr] = webArg(argc); - // - // arg = F("TDVN"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicevaluename[varNr] = webArg(argc); - // - // arg = F("TDVD"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicevaluedecimals[varNr] = webArg(argc); - // } - - // for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - // { - // char argc[25]; - // String arg = F("TDID"); - // arg += controllerNr + 1; - // arg.toCharArray(argc, 25); - // taskdeviceid[controllerNr] = webArg(argc); - // - // arg = F("TDSD"); - // arg += controllerNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicesenddata[controllerNr] = webArg(argc); - // } - - uint8_t page = getFormItemInt(F("page"), 0); - - if (page == 0) { - page = 1; - } - uint8_t setpage = getFormItemInt(F("setpage"), 0); - - if (setpage > 0) - { - if (setpage <= (TASKS_MAX / TASKS_PER_PAGE)) { - page = setpage; - } - else { - page = TASKS_MAX / TASKS_PER_PAGE; - } - } - const int edit = getFormItemInt(F("edit"), 0); - - // taskIndex in the URL is 1 ... TASKS_MAX - // For use in other functions, set it to 0 ... (TASKS_MAX - 1) - taskIndex_t taskIndex = getFormItemInt(F("index"), 0); - boolean taskIndexNotSet = taskIndex == 0; - - const bool nosave = isFormItemChecked(F("nosave")); - - if (!taskIndexNotSet) { - --taskIndex; - - // LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date - } - - // FIXME TD-er: Might have to clear any caches here. - if ((edit != 0) && !taskIndexNotSet) // when form submitted - { - if (Settings.getPluginID_for_task(taskIndex) != taskdevicenumber) - { - // change of device: cleanup old device and reset default settings - setTaskDevice_to_TaskIndex(taskdevicenumber, taskIndex); - const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); - - if (validDeviceIndex(DeviceIndex)) { - const DeviceStruct& device = Device[DeviceIndex]; - - if ((device.Type == DEVICE_TYPE_I2C) && device.I2CMax100kHz) { // 100 kHz-only I2C device? - bitWrite(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED, 1); // Then: Enable Force Slow I2C speed checkbox by default - } - } - } - else if (taskdevicenumber != INVALID_PLUGIN_ID) // save settings - { - handle_devices_CopySubmittedSettings(taskIndex, taskdevicenumber); - } - - if (taskdevicenumber != INVALID_PLUGIN_ID) { - // Task index has a task device number, so it makes sense to save. - // N.B. When calling delete, the settings were already saved. - if (nosave) { - Cache.updateExtraTaskSettingsCache(); - } else { - addHtmlError(SaveTaskSettings(taskIndex)); - addHtmlError(SaveSettings()); - } - - struct EventStruct TempEvent(taskIndex); - String dummy; - - if (Settings.TaskDeviceEnabled[taskIndex]) { - if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { - PluginCall(PLUGIN_READ, &TempEvent, dummy); - } - } else { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - } - } - } - - // show all tasks as table - if (taskIndexNotSet) - { - handle_devicess_ShowAllTasksTable(page); - } - - // Show edit form if a specific entry is chosen with the edit button - else - { - handle_devices_TaskSettingsPage(taskIndex, page); - } - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_devices")); - # endif // ifndef BUILD_NO_RAM_TRACKER -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - addLogMove(LOG_LEVEL_DEBUG_DEV, concat(F("DEBUG: String size:"), static_cast(TXBuffer.sentBytes))); - } -# endif // ifndef BUILD_NO_DEBUG - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -// ******************************************************************************** -// Add a device select dropdown list -// TODO TD-er: Add JavaScript filter: -// https://www.w3schools.com/howto/howto_js_filter_dropdown.asp -// ******************************************************************************** -void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) -{ - String deviceName; - - addSelector_Head_reloadOnChange(name); - addSelector_Item(F("- None -"), 0, false); - - deviceIndex_t x; - bool done = false; - - while (!done) { - const deviceIndex_t deviceIndex = getDeviceIndex_sorted(x); - - if (!validDeviceIndex(deviceIndex)) { - done = true; - } else { - const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); - - if (validPluginID(pluginID)) { - deviceName = getPluginNameFromDeviceIndex(deviceIndex); - - - # if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) - deviceName = concat(get_formatted_Plugin_number(pluginID), F(" - ")) + deviceName; - # endif // if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) - - addSelector_Item(deviceName, - pluginID.value, - choice == pluginID); - } - } - ++x; - } - - addSelector_Foot(); -} - -// ******************************************************************************** -// Collect all submitted form data and store the task settings -// ******************************************************************************** -void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber) -{ - if (!validTaskIndex(taskIndex)) { return; } - const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); - - if (!validDeviceIndex(DeviceIndex)) { return; } - - const DeviceStruct& device = Device[DeviceIndex]; - - # if FEATURE_MQTT_DISCOVER - controllerIndex_t discoverController = INVALID_CONTROLLER_INDEX; - # endif // if FEATURE_MQTT_DISCOVER - - unsigned long taskdevicetimer = getFormItemInt(F("TDT"), 0); - - Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; - - if (device.Type == DEVICE_TYPE_I2C) { - uint8_t flags = 0; - bitWrite(flags, I2C_FLAGS_SLOW_SPEED, isFormItemChecked(F("taskdeviceflags0"))); - -# if FEATURE_I2CMULTIPLEXER - - if (isI2CMultiplexerEnabled()) { - int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); - bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, multipleMuxPortsOption == 1); - - if (multipleMuxPortsOption == 1) { - uint8_t selectedPorts = 0; - - for (int x = 0; x < I2CMultiplexerMaxChannels(); ++x) { - bitWrite(selectedPorts, x, isFormItemChecked(concat(F("taskdeviceflag1ch"), x))); - } - Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; - } else { - Settings.I2C_Multiplexer_Channel[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); - } - } - -# endif // if FEATURE_I2CMULTIPLEXER - - Settings.I2C_Flags[taskIndex] = flags; - } - - // Must load from file system to make sure all caches and checksums match. - ExtraTaskSettings.clear(); - ExtraTaskSettings.TaskIndex = taskIndex; - Cache.clearTaskCache(taskIndex); - - struct EventStruct TempEvent(taskIndex); - - // Save selected output type. - switch (device.OutputDataType) { - case Output_Data_type_t::Default: - { - String dummy; - PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); - break; - } - case Output_Data_type_t::Simple: - case Output_Data_type_t::All: - { - int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); - Sensor_VType VType = TempEvent.sensorType; - - if ((pconfigIndex >= 0) && (pconfigIndex < PLUGIN_CONFIGVAR_MAX)) { - VType = - static_cast(getFormItemInt(sensorTypeHelper_webformID(pconfigIndex), 0)); - Settings.TaskDevicePluginConfig[taskIndex][pconfigIndex] = static_cast(VType); - } - ExtraTaskSettings.clearUnusedValueNames(getValueCountFromSensorType(VType)); - break; - } - } - - { - int pins[] = { -1, -1, -1 }; - - for (int i = 0; i < 3; ++i) { - update_whenset_FormItemInt(concat(F("taskdevicepin"), i + 1), pins[i]); - } - - const bool taskEnabled = isFormItemChecked(F("TDE")); - setBasicTaskValues(taskIndex, taskdevicetimer, - taskEnabled, webArg(F("TDN")), - pins); - } - - # if FEATURE_PLUGIN_PRIORITY - - if (device.PowerManager // Check extra priority device flags when available - ) { - bool disablePrio = false; - - for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { - if (t != taskIndex) { - disablePrio = Settings.isPriorityTask(t); - } - } - bool statePriority = isFormItemChecked(F("TPRE")); - - if (device.PowerManager) { - Settings.setPowerManagerTask(taskIndex, statePriority); - } - - // Set alternative Priority flags - // Set to readonly if set as Priority task - Settings.setTaskEnableReadonly(taskIndex, statePriority); - } - # endif // if FEATURE_PLUGIN_PRIORITY - Settings.TaskDevicePort[taskIndex] = getFormItemInt(F("TDP"), 0); - update_whenset_FormItemInt(F("remoteFeed"), Settings.TaskDeviceDataFeed[taskIndex]); - Settings.CombineTaskValues_SingleEvent(taskIndex, isFormItemChecked(F("TVSE"))); - - for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - { - Settings.TaskDeviceID[controllerNr][taskIndex] = getFormItemInt(getPluginCustomArgName(F("TDID"), controllerNr)); - Settings.TaskDeviceSendData[controllerNr][taskIndex] = isFormItemChecked(getPluginCustomArgName(F("TDSD"), controllerNr)); - # if FEATURE_MQTT_DISCOVER - - if (isFormItemChecked(getPluginCustomArgName(F("TDDSC"), controllerNr))) { - discoverController = controllerNr; - } - # endif // if FEATURE_MQTT_DISCOVER - } - - if (device.PullUpOption) { - Settings.TaskDevicePin1PullUp[taskIndex] = isFormItemChecked(F("TDPPU")); - } - - if (device.InverseLogicOption) { - Settings.TaskDevicePin1Inversed[taskIndex] = isFormItemChecked(F("TDPI")); - } - - if (device.isSerial()) - { - # ifdef PLUGIN_USES_SERIAL - serialHelper_webformSave(&TempEvent); - # else // ifdef PLUGIN_USES_SERIAL - addLog(LOG_LEVEL_ERROR, F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - } - - const uint8_t valueCount = getValueCountForTask(taskIndex); - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceFormula[varNr], getPluginCustomArgName(F("TDF"), varNr)); - update_whenset_FormItemInt(getPluginCustomArgName(F("TDVD"), varNr), ExtraTaskSettings.TaskDeviceValueDecimals[varNr]); - strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[varNr], getPluginCustomArgName(F("TDVN"), varNr)); -# if FEATURE_PLUGIN_FILTER - ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); -# endif // if FEATURE_PLUGIN_FILTER -# if FEATURE_PLUGIN_STATS - PluginStats_Config_t pluginStats_Config; - pluginStats_Config.setEnabled(isFormItemChecked(getPluginCustomArgName(F("TDS"), varNr))); - pluginStats_Config.setHidden(isFormItemChecked(getPluginCustomArgName(F("TDSH"), varNr))); - const int selectedAxis = getFormItemInt(getPluginCustomArgName(F("TDSA"), varNr)); - pluginStats_Config.setAxisIndex(selectedAxis); - pluginStats_Config.setAxisPosition( - ((selectedAxis >> 2) == 0) - ? PluginStats_Config_t::AxisPosition::Left - : PluginStats_Config_t::AxisPosition::Right); - - ExtraTaskSettings.setPluginStatsConfig(varNr, pluginStats_Config); -# endif // if FEATURE_PLUGIN_STATS - } - ExtraTaskSettings.clearUnusedValueNames(valueCount); - - // ExtraTaskSettings has changed. - // The content of it is needed for sending CPLUGIN_TASK_CHANGE_NOTIFICATION and TaskInit/TaskExit events - Cache.updateExtraTaskSettingsCache(); - - // allow the plugin to save plugin-specific form settings. - { - String dummy; - - if (device.ExitTaskBeforeSave) { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - } - - PluginCall(PLUGIN_WEBFORM_SAVE, &TempEvent, dummy); - - if (device.ErrorStateValues) { - // FIXME TD-er: Must collect these from the web page. - PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, &TempEvent, dummy); - } - - // Make sure the task needs to reload using the new settings. - if (!device.ExitTaskBeforeSave) { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - } - # if FEATURE_MQTT_DISCOVER - - if (validControllerIndex(discoverController) && validControllerIndex(mqttDiscoveryController)) { - mqttDiscoverOnlyTask = taskIndex; - mqttDiscoveryTimeout = random(10, 100); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Resend AutoDiscovery for Task %d on Controller %d in %.1f sec."), - taskIndex + 1, - mqttDiscoveryController + 1, - mqttDiscoveryTimeout / 10.0f)); - } - } - # endif // if FEATURE_MQTT_DISCOVER - } - - // Store all PCONFIG values on the web page - // Must be done after PLUGIN_WEBFORM_SAVE, to allow tasks to clear the default task value names - // Output type selectors are typically stored in PCONFIG - if (device.OutputDataType != Output_Data_type_t::Default) { - for (int pconfigIndex = 0; pconfigIndex < PLUGIN_CONFIGVAR_MAX; ++pconfigIndex) { - pconfig_webformSave(&TempEvent, pconfigIndex); - } - } - - // ExtraTaskSettings may have changed during PLUGIN_WEBFORM_SAVE, so again update the cache. - Cache.updateExtraTaskSettingsCache(); - - loadDefaultTaskValueNames_ifEmpty(taskIndex); - Cache.updateExtraTaskSettingsCache(); - - // notify controllers: CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) - { - TempEvent.ControllerIndex = x; - - if (Settings.TaskDeviceSendData[TempEvent.ControllerIndex][TempEvent.TaskIndex] && - Settings.ControllerEnabled[TempEvent.ControllerIndex] && Settings.Protocol[TempEvent.ControllerIndex]) - { - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(TempEvent.ControllerIndex); - String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION, &TempEvent, dummy); - } - } - - // FIXME TD-er: Is this still needed as it is also cleared on PLUGIN_INIT and PLUGIN_EXIT? - UserVar.clear_computed(taskIndex); -} - -void html_add_setPage(uint8_t page, bool isLinkToPrev) { - addHtml(strformat( - F("devices?setpage=%u'>&%ct;"), - static_cast(page), - isLinkToPrev ? 'l' : 'g')); -} - -// ******************************************************************************** -// Show table with all selected Tasks/Devices -// ******************************************************************************** -void handle_devicess_ShowAllTasksTable(uint8_t page) -{ - serve_JS(JSfiles_e::UpdateSensorValuesDevicePage); - html_table_class_multirow(); - html_TR(); - html_table_header(F(""), 70); - - if (TASKS_MAX != TASKS_PER_PAGE) - { - html_add_button_prefix(); - - html_add_setPage((page > 1) ? page - 1 : page, true); - html_add_button_prefix(); - html_add_setPage((page < (TASKS_MAX / TASKS_PER_PAGE)) ? page + 1 : page, false); - } - - html_table_header(F("Task"), 50); - html_table_header(F("Enabled"), 100); - html_table_header(F("Device")); - html_table_header(F("Name")); - html_table_header(F("Port")); - html_table_header(F("Ctr (IDX)"), 100); - html_table_header(F("GPIO")); - html_table_header(F("Values")); - - String deviceName; - - for (taskIndex_t x = (page - 1) * TASKS_PER_PAGE; x < ((page) * TASKS_PER_PAGE) && validTaskIndex(x); x++) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); - - html_TR_TD(); - - if (pluginID_set && !supportedPluginID(Settings.getPluginID_for_task(x))) { - html_add_button_prefix(F("red"), true); - } else { - html_add_button_prefix(); - } - { - const int pageIndex = static_cast(x + 1); - addHtml(strformat( - F("devices?index=%d&page=%u'>"), - pageIndex, - static_cast(page))); - addHtml(pluginID_set ? F("Edit") : F("Add")); - addHtml(concat(F(""), pageIndex)); - html_TD(); - } - - // Show table of all configured tasks - // A task may also refer to a non supported plugin. - // This will be shown as not supported. - // Editing a task which has a non supported plugin will present the same as when assigning a new plugin to a task. - if (pluginID_set) - { - // LoadTaskSettings(x); - int8_t spi_gpios[3] { -1, -1, -1 }; - struct EventStruct TempEvent(x); - addEnabled(Settings.TaskDeviceEnabled[x] && validDeviceIndex(DeviceIndex)); - - html_TD(); - addHtml(getPluginNameFromPluginID(Settings.getPluginID_for_task(x))); - html_TD(); - addHtml(getTaskDeviceName(x)); - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - if (Settings.TaskDeviceDataFeed[x] != 0) { - # if FEATURE_ESPEASY_P2P - - // Show originating node number - const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[x]; - format_originating_node(remoteUnit); - # endif // if FEATURE_ESPEASY_P2P - } else { - String portDescr; - - if (PluginCall(PLUGIN_WEBFORM_SHOW_CONFIG, &TempEvent, portDescr)) { - addHtml(portDescr); - } else { - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.Type == DEVICE_TYPE_I2C) { - format_I2C_port_description(x); - } else if (device.isSPI()) { - format_SPI_port_description(spi_gpios); - } else if (device.isSerial()) { - # ifdef PLUGIN_USES_SERIAL - addHtml(serialHelper_getSerialTypeLabel(&TempEvent)); - # else // ifdef PLUGIN_USES_SERIAL - addHtml(F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - } else { - // Plugin has no custom port formatting, show default one. - if (device.Ports != 0) - { - addHtml(formatToHex_decimal(Settings.TaskDevicePort[x])); - } - } - } - } - } - - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - if (Device[DeviceIndex].SendDataOption) - { - boolean doBR = false; - - for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - { - if (Settings.TaskDeviceSendData[controllerNr][x]) - { - if (doBR) { - html_BR(); - } - addHtml(getControllerSymbol(controllerNr)); - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); - - if (validProtocolIndex(ProtocolIndex)) { - if (getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[controllerNr] != 0)) - { - addHtml(strformat( - F(" (%d)"), - static_cast(Settings.TaskDeviceID[controllerNr][x]))); - - if (Settings.TaskDeviceID[controllerNr][x] == 0) { - addHtml(' '); - addHtml(F(HTML_SYMBOL_WARNING)); - } - } - doBR = true; - } - } - } - } - } - - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - const DeviceStruct& device = Device[DeviceIndex]; - - if (Settings.TaskDeviceDataFeed[x] == 0) - { - String description; - bool pluginHasGPIODescription = pluginWebformShowGPIOdescription(x, F("
"), description); - - bool showpin1 = false; - bool showpin2 = false; - bool showpin3 = false; - - switch (device.Type) { - case DEVICE_TYPE_I2C: - { - format_I2C_pin_description(x); - html_BR(); - break; - } - case DEVICE_TYPE_SPI3: - showpin3 = !pluginHasGPIODescription; - - // Fall Through - case DEVICE_TYPE_SPI2: - showpin2 = !pluginHasGPIODescription; - - // Fall Through - case DEVICE_TYPE_SPI: - format_SPI_pin_description(spi_gpios, x, !pluginHasGPIODescription); - break; - case DEVICE_TYPE_ANALOG: - { - # ifdef ESP8266 - # if FEATURE_ADC_VCC - addHtml(F("ADC (VCC)")); - # else // if FEATURE_ADC_VCC - addHtml(F("ADC (TOUT)")); - # endif // if FEATURE_ADC_VCC - # endif // ifdef ESP8266 - # ifdef ESP32 - showpin1 = true; - addHtml(formatGpioName_ADC(Settings.TaskDevicePin1[x])); - html_BR(); - # endif // ifdef ESP32 - - break; - } - case DEVICE_TYPE_SERIAL_PLUS1: - showpin3 = true; - - // fallthrough - case DEVICE_TYPE_SERIAL: - { - # ifdef PLUGIN_USES_SERIAL - const String serialDescription = serialHelper_getGpioDescription(static_cast(Settings.TaskDevicePort[x]), - Settings.TaskDevicePin1[x], - Settings.TaskDevicePin2[x], - F("
")); - addHtml(serialDescription); - # else // ifdef PLUGIN_USES_SERIAL - addHtml(F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - - if ( -# ifdef PLUGIN_USES_SERIAL - serialDescription.length() || -# endif // ifdef PLUGIN_USES_SERIAL - showpin3) { - html_BR(); - } - break; - } - case DEVICE_TYPE_CUSTOM3: - showpin3 = true; - - // fallthrough - case DEVICE_TYPE_CUSTOM2: - showpin2 = true; - - // fallthrough - case DEVICE_TYPE_CUSTOM1: - case DEVICE_TYPE_CUSTOM0: - { - showpin1 = true; - - if (pluginHasGPIODescription || (device.Type == DEVICE_TYPE_CUSTOM0)) { - addHtml(description); - showpin1 = false; - showpin2 = false; - showpin3 = false; - } - break; - } - - default: - showpin1 = true; - showpin2 = true; - showpin3 = true; - break; - } - - if (showpin1) - { - addGpioHtml(Settings.getTaskDevicePin(x, 1)); - } - - if (showpin2) - { - html_BR(); - addGpioHtml(Settings.getTaskDevicePin(x, 2)); - } - - if (showpin3) - { - html_BR(); - addGpioHtml(Settings.getTaskDevicePin(x, 3)); - } - - // Allow for tasks to show their own specific GPIO pins. - if (!device.isCustom() && - pluginHasGPIODescription) { - if (showpin1 || showpin2 || showpin3) { - html_BR(); - } - addHtml(description); - } - } - } - - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - String customValuesString; - const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); - - if (!customValues) - { - const uint8_t valueCount = getValueCountForTask(x); - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) - { - pluginWebformShowValue( - x, - varNr, - Cache.getTaskDeviceValueName(x, varNr), - formatUserVarNoCheck(&TempEvent, varNr)); - } - } - } - } - } - else { - html_TD(6); - } - } // next - html_end_table(); - html_end_form(); -} - -# if FEATURE_ESPEASY_P2P -void format_originating_node(uint8_t remoteUnit) { - addHtml(F("Unit ")); - addHtmlInt(remoteUnit); - - if (remoteUnit != 255) { - const NodeStruct *node = Nodes.getNode(remoteUnit); - - if (node != nullptr) { - addHtml(F(" - ")); - addHtml(node->getNodeName()); - } else { - addHtml(F(" - Not Seen recently")); - } - } -} - -# endif // if FEATURE_ESPEASY_P2P - -void format_I2C_port_description(taskIndex_t x) -{ - addHtml(F("I2C")); - # if FEATURE_I2C_GET_ADDRESS - const uint8_t i2cAddr = getTaskI2CAddress(x); - - if (i2cAddr > 0) { - addHtml(' '); - addHtml(formatToHex(i2cAddr, 2)); - } - # endif // if FEATURE_I2C_GET_ADDRESS - # if FEATURE_I2CMULTIPLEXER - - if (isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(x)) { - String mux; - - if (bitRead(Settings.I2C_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel - mux = F("
Multiplexer channel(s)"); - uint8_t b = 0; // For adding lineBreaks - - for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { - if (bitRead(Settings.I2C_Multiplexer_Channel[x], c)) { - mux += b == 0 ? F("
") : F(", "); - b++; - mux += String(c); - } - } - } else { // Single channel - mux = concat(F("
Multiplexer channel "), static_cast(Settings.I2C_Multiplexer_Channel[x])); - } - addHtml(mux); - } - # endif // if FEATURE_I2CMULTIPLEXER -} - -void format_SPI_port_description(int8_t spi_gpios[3]) -{ - if (!Settings.getSPI_pins(spi_gpios)) { - addHtml(F("SPI (Not enabled)")); - return; - } - # ifdef ESP32 - addHtml(getSPI_optionToShortString(static_cast(Settings.InitSPI))); - # endif // ifdef ESP32 - # ifdef ESP8266 - addHtml(F("SPI")); - # endif // ifdef ESP8266 -} - -void format_I2C_pin_description(taskIndex_t x) -{ - if (checkI2CConfigValid_toHtml(x)) { - Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.Pin_i2c_sda, false)); - html_BR(); - Label_Gpio_toHtml(F("SCL"), formatGpioLabel(Settings.Pin_i2c_scl, false)); - } -} - -void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin) -{ - if (Settings.InitSPI > static_cast(SPI_Options_e::None)) { - const __FlashStringHelper*labels[] = { F("CLK"), F("MISO"), F("MOSI") }; - - for (int i = 0; i < 3; ++i) { - if (i != 0) { - html_BR(); - } - - Label_Gpio_toHtml(labels[i], formatGpioLabel(spi_gpios[i], false)); - } - - if (showCSpin) { - html_BR(); - Label_Gpio_toHtml(F("CS"), formatGpioLabel(Settings.TaskDevicePin1[x], false)); - } - } -} - -// ******************************************************************************** -// Show the task settings page -// ******************************************************************************** -void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) -{ - if (!validTaskIndex(taskIndex)) { return; } - - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - // LoadTaskSettings(taskIndex); - - html_add_form(); - html_table_class_normal(); - addFormHeader(F("Task Settings")); - - - addHtml(F("Device:")); - - // no (supported) device selected, this effectively checks for validDeviceIndex - if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) - { - // takes lots of memory/time so call this only when needed. - addDeviceSelect(F("TDNUM"), Settings.getPluginID_for_task(taskIndex)); // ="taskdevicenumber" - addFormSeparator(4); - } - - // device selected - else - { - const DeviceStruct& device = Device[DeviceIndex]; - - // remember selected device number - addHtml(F("'); - - // show selected device name and delete button - addHtml(getPluginNameFromDeviceIndex(DeviceIndex)); - - addHelpButton(concat(F("Plugin"), Settings.getPluginID_for_task(taskIndex).value)); - addRTDPluginButton(Settings.getPluginID_for_task(taskIndex)); - - addFormTextBox(F("Name"), F("TDN"), getTaskDeviceName(taskIndex), NAME_FORMULA_LENGTH_MAX); // ="taskdevicename" - - addFormCheckBox(F("Enabled"), F("TDE"), - Settings.TaskDeviceEnabled[taskIndex], - - // Settings.TaskDeviceEnabled[taskIndex].enabled, - Settings.isTaskEnableReadonly(taskIndex)); // ="taskdeviceenabled" - - # if FEATURE_PLUGIN_PRIORITY - - if (device.PowerManager) { // Check extra priority device flags when available - bool disablePrio = !Settings.TaskDeviceEnabled[taskIndex]; - - for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { - if (t != taskIndex) { // Ignore current device - if (device.PowerManager && Settings.isPowerManagerTask(t)) { - disablePrio = true; // Allow only a single PowerManager plugin - } - - // Add other Priority options checks - } - } - addFormSubHeader(F("Priority task")); - addFormCheckBox(F("Priority task"), F("TPRE"), Settings.isPriorityTask(taskIndex), disablePrio); // ="taskpriorityenabled" - - if (!disablePrio) { - addFormNote(F("After enabling a Priority task, a reboot is required to activate. See documentation.")); - } - } - # endif // if FEATURE_PLUGIN_PRIORITY - - const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; - # if FEATURE_ESPEASY_P2P - - if (device.SendDataOption) - { - // Show remote feed information. - addFormSubHeader(F("Data Source")); - addFormNumericBox(F("Remote Unit"), F("remoteFeed"), remoteUnit, 0, 255); - - if (remoteUnit != 255) { - const NodeStruct*node = Nodes.getNode(remoteUnit); - - if (node != nullptr) { - addUnit(node->getNodeName()); - } else { - addUnit(F("Unknown Unit Name")); - } - } - addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. - } - # endif // if FEATURE_ESPEASY_P2P - - bool addPinConfig = false; - - // section: Sensor / Actuator - if (!device.Custom && (Settings.TaskDeviceDataFeed[taskIndex] == 0) && - ((device.Ports != 0) || - (device.PullUpOption) || - (device.InverseLogicOption) || - (device.connectedToGPIOpins()))) - { - addFormSubHeader((device.SendDataOption) ? F("Sensor") : F("Actuator")); - - if (device.Ports != 0) { - addFormNumericBox(F("Port"), F("TDP"), Settings.TaskDevicePort[taskIndex]); // ="taskdeviceport" - } - - addPinConfig = true; - } - - if (addPinConfig || (device.Type == DEVICE_TYPE_I2C)) { - if (device.isSerial()) { - # ifdef PLUGIN_USES_SERIAL - devicePage_show_serial_config(taskIndex); - # else // ifdef PLUGIN_USES_SERIAL - addHtml(F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - - devicePage_show_pin_config(taskIndex, DeviceIndex); - addPinConfig = false; - - html_add_script(F("document.getElementById('serPort').onchange();"), false); - } else if (device.Type == DEVICE_TYPE_I2C) { - devicePage_show_pin_config(taskIndex, DeviceIndex); - addPinConfig = false; - - if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { - devicePage_show_I2C_config(taskIndex, DeviceIndex); - } - } - - if (addPinConfig) { - devicePage_show_pin_config(taskIndex, DeviceIndex); - } - } - - String webformLoadString; - struct EventStruct TempEvent(taskIndex); - - - // add plugins content - if ((DEVICE_TYPE_DUMMY != device.Type) && (remoteUnit == 0)) { - addFormSubHeader(F("Device Settings")); - } - - if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors - PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); - # ifndef BUILD_NO_DEBUG - - if (webformLoadString.length() > 0) { - String errorMessage; - PluginCall(PLUGIN_GET_DEVICENAME, &TempEvent, errorMessage); - errorMessage += F(": Bug in PLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead"); - addHtmlError(errorMessage); - } - # endif // ifndef BUILD_NO_DEBUG - } - PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices - - devicePage_show_output_data_type(taskIndex, DeviceIndex); - - # if FEATURE_PLUGIN_STATS - - // Task statistics and historic data in a chart - devicePage_show_task_statistics(taskIndex, DeviceIndex); - # endif // if FEATURE_PLUGIN_STATS - - // section: Data Acquisition - devicePage_show_controller_config(taskIndex, DeviceIndex); - - addFormSeparator(2); - - devicePage_show_interval_config(taskIndex, DeviceIndex); - - devicePage_show_task_values(taskIndex, DeviceIndex); - } - - html_TR_TD(); - addHtml(F("")); - html_add_button_prefix(); - addHtml(F("devices?setpage=")); - addHtmlInt(page); - addHtml(F("'>Close")); - # if FEATURE_PLUGIN_PRIORITY - - if (!Settings.isPriorityTask(taskIndex)) - # endif // if FEATURE_PLUGIN_PRIORITY - { - addSubmitButton(); - } - addHtml(F("")); - addHtml(F("")); - - // if user selected a device, add the delete button, except for Priority tasks - if (validPluginID_fullcheck(Settings.getPluginID_for_task(taskIndex)) - # if FEATURE_PLUGIN_PRIORITY - && !Settings.isPriorityTask(taskIndex) - # endif // if FEATURE_PLUGIN_PRIORITY - ) { - addSubmitButton(F("Delete"), F("del")); - } - - html_end_table(); - # if FEATURE_PLUGIN_PRIORITY - - if (Settings.isPriorityTask(taskIndex)) { - addFormNote(F("A Priority task can't be updated or deleted. See documentation.")); - } - # endif // if FEATURE_PLUGIN_PRIORITY - html_end_form(); - serve_JS(JSfiles_e::SplitPasteInput); -} - -void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.PullUpOption) - { - addFormCheckBox(F("Internal PullUp"), F("TDPPU"), Settings.TaskDevicePin1PullUp[taskIndex]); // ="taskdevicepin1pullup" - addFormNote(F("Best to (also) configure pull-up on Hardware tab under \"GPIO boot states\"")); - # if defined(ESP8266) - - if ((Settings.TaskDevicePin1[taskIndex] == 16) || (Settings.TaskDevicePin2[taskIndex] == 16) || - (Settings.TaskDevicePin3[taskIndex] == 16)) { - addFormNote(F("PullDown for GPIO-16 (D0)")); - } - # endif // if defined(ESP8266) - } - - if (device.InverseLogicOption) - { - addFormCheckBox(F("Inversed Logic"), F("TDPI"), Settings.TaskDevicePin1Inversed[taskIndex]); // ="taskdevicepin1inversed" - addFormNote(F("Will go into effect on next input change.")); - } - - if (device.isSPI() - && (Settings.InitSPI == static_cast(SPI_Options_e::None))) { - addFormNote(F("SPI Interface is not configured yet (Hardware page).")); - } - - if (device.connectedToGPIOpins()) { - // get descriptive GPIO-names from plugin - struct EventStruct TempEvent(taskIndex); - - TempEvent.String1 = F("1st GPIO"); - TempEvent.String2 = F("2nd GPIO"); - TempEvent.String3 = F("3rd GPIO"); - String dummy; - PluginCall(PLUGIN_GET_DEVICEGPIONAMES, &TempEvent, dummy); - - if (device.usesTaskDevicePin(1)) { - PinSelectPurpose purpose = device.getPinSelectPurpose(1); // PinSelectPurpose::Generic; - - if (device.isSerial()) - { - // Pin1 = GPIO <--- TX - purpose = PinSelectPurpose::Serial_input; - } else if (device.isSPI()) - { - // All selectable SPI pins are output only - purpose = PinSelectPurpose::Generic_output; - } - - addFormPinSelect(purpose, TempEvent.String1, F("taskdevicepin1"), Settings.TaskDevicePin1[taskIndex]); - } - - if (device.usesTaskDevicePin(2)) { - PinSelectPurpose purpose = device.getPinSelectPurpose(2); // PinSelectPurpose::Generic; - - if (device.isSerial()) - { - // Serial Pin2 = GPIO ---> RX - purpose = PinSelectPurpose::Serial_output; - } - - if (device.isSPI()) - { - // SPI only needs output pins - purpose = PinSelectPurpose::Generic_output; - } - addFormPinSelect(purpose, TempEvent.String2, F("taskdevicepin2"), Settings.TaskDevicePin2[taskIndex]); - } - - if (device.usesTaskDevicePin(3)) { - PinSelectPurpose purpose = device.getPinSelectPurpose(3); // PinSelectPurpose::Generic; - - if (device.isSPI()) - { - // SPI only needs output pins - purpose = PinSelectPurpose::Generic_output; - } - addFormPinSelect(purpose, TempEvent.String3, F("taskdevicepin3"), Settings.TaskDevicePin3[taskIndex]); - } - } -} - -# ifdef PLUGIN_USES_SERIAL -void devicePage_show_serial_config(taskIndex_t taskIndex) -{ - struct EventStruct TempEvent(taskIndex); - - String webformLoadString; - - PluginCall(PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, &TempEvent, webformLoadString); - - serialHelper_webformLoad(&TempEvent); - - PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); -} - -# endif // ifdef PLUGIN_USES_SERIAL - -void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - - addFormSubHeader(F("I2C options")); - - if (!Settings.isI2CEnabled()) { - addFormNote(F("I2C Interface is not configured yet (Hardware page).")); - } - - String dummy; - - PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); - addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); - - if (Device[DeviceIndex].I2CMax100kHz) { - addFormNote(F("This device is specified for max. 100 kHz operation!")); - } - - # if FEATURE_I2CMULTIPLEXER - - // Show selector for an I2C multiplexer port if a multiplexer is configured - if (isI2CMultiplexerEnabled()) { - bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); - { - const __FlashStringHelper *i2c_mux_channels[] = { - F("Single channel"), - F("Multiple channels") }; - constexpr int i2c_mux_channelOptions[] = { 0, 1 }; - int i2c_mux_channelCount = 1; - - if (Settings.I2C_Multiplexer_Type == I2C_MULTIPLEXER_PCA9540) { - multipleMuxPorts = false; // force off - } else { - i2c_mux_channelCount++; - } - addFormSelector(F("Multiplexer channels"), - F("taskdeviceflags1"), - i2c_mux_channelCount, - i2c_mux_channels, - i2c_mux_channelOptions, - multipleMuxPorts ? 1 : 0, - true); - } - - if (multipleMuxPorts) { - addRowLabel(F("Select connections"), EMPTY_STRING); - html_table(EMPTY_STRING, false); // Sub-table - html_table_header(F("Channel"), 100); - html_table_header(F("Enable"), 80); - html_table_header(F("Channel"), 100); - html_table_header(F("Enable"), 80); - - for (int x = 0; x < I2CMultiplexerMaxChannels(); x++) { - if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels - html_TD(); - addHtml(concat(F("Channel "), x)); - html_TD(); - addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(Settings.I2C_Multiplexer_Channel[taskIndex], x), false); - } - html_end_table(); - } else { - int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Channel[taskIndex]; - const uint32_t mux_max = I2CMultiplexerMaxChannels(); - String i2c_mux_portoptions[mux_max + 1]; - int i2c_mux_portchoices[mux_max + 1]; - i2c_mux_portoptions[0] = F("(Not connected via multiplexer)"); - i2c_mux_portchoices[0] = -1; - - for (uint32_t x = 0; x < mux_max; x++) { - const uint32_t mux_opt = x + 1; - i2c_mux_portoptions[mux_opt] = concat(F("Channel "), x); - i2c_mux_portchoices[mux_opt] = x; - } - - if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range - addFormSelector(F("Connected to"), - F("taskdevicei2cmuxport"), - mux_max + 1, - i2c_mux_portoptions, - i2c_mux_portchoices, - taskDeviceI2CMuxPort); - } - } - # endif // if FEATURE_I2CMULTIPLEXER -} - -void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); - - switch (Device[DeviceIndex].OutputDataType) { - case Output_Data_type_t::Default: - return; - case Output_Data_type_t::Simple: - - if (pconfigIndex >= 0) { - sensorTypeHelper_webformLoad_simple(&TempEvent, pconfigIndex); - return; - } - break; - case Output_Data_type_t::All: - { - if (pconfigIndex >= 0) { - sensorTypeHelper_webformLoad_allTypes(&TempEvent, pconfigIndex); - return; - } - break; - } - } - addFormSubHeader(F("Output Configuration")); - String dummy; - PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, &TempEvent, dummy); -} - -# if FEATURE_PLUGIN_STATS -void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (Device[DeviceIndex].PluginStats) - { - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); - - if (taskData != nullptr) { - if (taskData->hasPluginStats()) { - addFormSubHeader(F("Statistics")); - } - # if FEATURE_CHART_JS - - if (taskData->nrSamplesPresent() > 0) { - addRowLabel(F("Historic data")); - taskData->plot_ChartJS(); - } - # endif // if FEATURE_CHART_JS - - struct EventStruct TempEvent(taskIndex); - String dummy; - bool somethingAdded = false; - - if (!PluginCall(PLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { - somethingAdded = taskData->webformLoad_show_stats(&TempEvent); - } else { somethingAdded = true; } - - if (somethingAdded) { - if (taskData->hasPeaks()) { - addFormNote(strformat( - F("Peak values recorded since last \"%s.resetpeaks\"."), - getTaskDeviceName(taskIndex).c_str())); - } - } - } - } -} - -# endif // if FEATURE_PLUGIN_STATS - - -void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) { return; } - - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.SendDataOption) - { - addFormSubHeader(F("Data Acquisition")); - - if (device.ErrorStateValues) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options - } - - addRowLabel(F("Single event with all values")); - addCheckBox(F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex)); - addFormNote(strformat( - F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), - getTaskDeviceName(taskIndex).c_str())); - - bool separatorAdded = false; - - for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - { - if (Settings.Protocol[controllerNr] != 0) - { - if (!separatorAdded) { - addFormSeparator(2); - } - separatorAdded = true; - html_TR_TD(); - addHtml(F("Send to Controller ")); - addHtml(getControllerSymbol(controllerNr)); - addHtmlDiv(F("note"), wrap_braces(getCPluginNameFromCPluginID(Settings.Protocol[controllerNr]) + F(", ") + // Most compact code... - (Settings.ControllerEnabled[controllerNr] ? F("enabled") : F("disabled")))); - html_TD(); - - addHtml(F("")); // remove left padding 2x to align vertically with other inputs - html_TD(F("width:50px;padding-left:0")); - addCheckBox( - getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" - Settings.TaskDeviceSendData[controllerNr][taskIndex]); - - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); - # if FEATURE_MQTT_DISCOVER - const bool showMqttGroup = (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).mqttAutoDiscover && - (mqttDiscoveryController == controllerNr) && - Settings.TaskDeviceSendData[controllerNr][taskIndex]); - # endif // if FEATURE_MQTT_DISCOVER - - if ((validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).usesID && - (Settings.Protocol[controllerNr] != 0)) - # if FEATURE_MQTT_DISCOVER - || showMqttGroup - # endif // if FEATURE_MQTT_DISCOVER - ) { - html_TD(); - addHtml( - # if FEATURE_MQTT_DISCOVER - showMqttGroup ? F("Group:") : - # endif // if FEATURE_MQTT_DISCOVER - F("IDX:")); - html_TD(); - addNumericBox( - getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" - Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); - } - # if FEATURE_MQTT_DISCOVER - - if (showMqttGroup) { - html_TD(); - addHtml(F("Resend MQTT Discovery:")); - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDDSC"), controllerNr), // ="taskdevicediscover" - false); - } - # endif // if FEATURE_MQTT_DISCOVER - html_end_table(); - } - } - } -} - -void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) { return; } - - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.TimerOption) - { - // FIXME: shoudn't the max be ULONG_MAX because Settings.TaskDeviceTimer is an unsigned long? addFormNumericBox only supports ints - // for min and max specification - addFormNumericBox(F("Interval"), F("TDT"), Settings.TaskDeviceTimer[taskIndex], 0, 65535); // ="taskdevicetimer" - addUnit(F("sec")); - - if (device.TimerOptional) { - addHtml(F(" (Optional for this Device)")); - } - } -} - -void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) { return; } - - // section: Values - const uint8_t valueCount = getValueCountForTask(taskIndex); - - const DeviceStruct& device = Device[DeviceIndex]; - - if (!device.Custom && (valueCount > 0)) - { - int colCount = 2; - addFormSubHeader(F("Values")); - html_end_table(); - html_table_class_normal(); - - // table header - addHtml(F("
#")); - html_table_header(F("Name"), 500); - - if (device.FormulaOption) - { - html_table_header(F("Formula"), F("RTDPlugin/_Plugin.html#formula"), 500); - ++colCount; - } - - if (device.configurableDecimals()) - { - html_table_header(F("Decimals"), 30); - ++colCount; - } - -# if FEATURE_PLUGIN_STATS - - if (device.PluginStats) - { - html_table_header(F("Stats"), 30); - ++colCount; - html_table_header(F("Hide"), 30); - ++colCount; - html_table_header(F("Axis"), 30); - ++colCount; - } -# endif // if FEATURE_PLUGIN_STATS - - // placeholder header - html_table_header(F("")); - ++colCount; - - // table body - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - html_TR_TD(); - addHtmlInt(varNr + 1); - html_TD(); - { - const String id = getPluginCustomArgName(F("TDVN"), varNr); // ="taskdevicevaluename" - addTextBox(id, Cache.getTaskDeviceValueName(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); - } - - if (device.FormulaOption) - { - html_TD(); - const String id = getPluginCustomArgName(F("TDF"), varNr); // ="taskdeviceformula" - addTextBox(id, Cache.getTaskDeviceFormula(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); - } - - if (device.configurableDecimals()) - { - html_TD(); - const String id = getPluginCustomArgName(F("TDVD"), varNr); // ="taskdevicevaluedecimals" - addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); - } - -# if FEATURE_PLUGIN_STATS - - if (device.PluginStats) - { - PluginStats_Config_t cachedConfig = Cache.getPluginStatsConfig(taskIndex, varNr); - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDS"), varNr), // ="taskdevicestats" - cachedConfig.isEnabled()); - - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDSH"), varNr), // ="taskdevicestats Hidden" - cachedConfig.showHidden()); - - html_TD(); - - const __FlashStringHelper *chartAxis[] = { - F("L1"), - F("L2"), - F("L3"), - F("L4"), - F("R1"), - F("R2"), - F("R3"), - F("R4") - }; - - int selected = cachedConfig.getAxisIndex(); - - if (!cachedConfig.isLeft()) { - selected += 4; - } - - addSelector( - getPluginCustomArgName(F("TDSA"), varNr), - NR_ELEMENTS(chartAxis), - chartAxis, - nullptr, - nullptr, - selected); - } -# endif // if FEATURE_PLUGIN_STATS - } - addFormSeparator(colCount); - } -} - -#endif // ifdef WEBSERVER_DEVICES +#include "../WebServer/DevicesPage.h" + +#ifdef WEBSERVER_DEVICES + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" +# include "../WebServer/Markup_Forms.h" + +# include "../DataStructs/NodeStruct.h" +# if FEATURE_PLUGIN_STATS +# include "../DataStructs/PluginStats_Config.h" +# endif // if FEATURE_PLUGIN_STATS + + +# include "../Globals/CPlugins.h" +# include "../Globals/Device.h" +# include "../Globals/ExtraTaskSettings.h" +# if FEATURE_MQTT_DISCOVER +# include "../Globals/MQTT.h" +# endif // if FEATURE_MQTT_DISCOVER +# include "../Globals/Nodes.h" +# include "../Globals/Plugins.h" + +# include "../Static/WebStaticData.h" + +# include "../Helpers/_CPlugin_init.h" +# include "../Helpers/_Plugin_init.h" +# include "../Helpers/_Plugin_SensorTypeHelper.h" +# include "../Helpers/_Plugin_Helper_serial.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/I2C_Plugin_Helper.h" +# include "../Helpers/StringConverter.h" +# include "../Helpers/StringGenerator_GPIO.h" + + +# include "../../_Plugin_Helper.h" + +# include + + +void handle_devices() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_devices")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_DEVICES; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + + // char tmpString[41]; + + + // String taskindex = webArg(F("index")); + + pluginID_t taskdevicenumber; + + if (hasArg(F("del"))) { + taskdevicenumber.setInvalid(); + } + else { + taskdevicenumber = pluginID_t::toPluginID(getFormItemInt(F("TDNUM"), 0)); + } + + + // String taskdeviceid[CONTROLLER_MAX]; + // String taskdevicepin1 = webArg(F("taskdevicepin1")); // "taskdevicepin*" should not be changed because it is uses by plugins + // and expected to be saved by this code + // String taskdevicepin2 = webArg(F("taskdevicepin2")); + // String taskdevicepin3 = webArg(F("taskdevicepin3")); + // String taskdevicepin1pullup = webArg(F("TDPPU")); + // String taskdevicepin1inversed = webArg(F("TDPI")); + // String taskdevicename = webArg(F("TDN")); + // String taskdeviceport = webArg(F("TDP")); + // String taskdeviceformula[VARS_PER_TASK]; + // String taskdevicevaluename[VARS_PER_TASK]; + // String taskdevicevaluedecimals[VARS_PER_TASK]; + // String taskdevicesenddata[CONTROLLER_MAX]; + // String taskdeviceglobalsync = webArg(F("TDGS")); + // String taskdeviceenabled = webArg(F("TDE")); + + // for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) + // { + // char argc[25]; + // String arg = F("TDF"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdeviceformula[varNr] = webArg(argc); + // + // arg = F("TDVN"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicevaluename[varNr] = webArg(argc); + // + // arg = F("TDVD"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicevaluedecimals[varNr] = webArg(argc); + // } + + // for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + // { + // char argc[25]; + // String arg = F("TDID"); + // arg += controllerNr + 1; + // arg.toCharArray(argc, 25); + // taskdeviceid[controllerNr] = webArg(argc); + // + // arg = F("TDSD"); + // arg += controllerNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicesenddata[controllerNr] = webArg(argc); + // } + + uint8_t page = getFormItemInt(F("page"), 0); + + if (page == 0) { + page = 1; + } + uint8_t setpage = getFormItemInt(F("setpage"), 0); + + if (setpage > 0) + { + if (setpage <= (TASKS_MAX / TASKS_PER_PAGE)) { + page = setpage; + } + else { + page = TASKS_MAX / TASKS_PER_PAGE; + } + } + const int edit = getFormItemInt(F("edit"), 0); + + // taskIndex in the URL is 1 ... TASKS_MAX + // For use in other functions, set it to 0 ... (TASKS_MAX - 1) + taskIndex_t taskIndex = getFormItemInt(F("index"), 0); + boolean taskIndexNotSet = taskIndex == 0; + + const bool nosave = isFormItemChecked(F("nosave")); + + if (!taskIndexNotSet) { + --taskIndex; + + // LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date + } + + // FIXME TD-er: Might have to clear any caches here. + if ((edit != 0) && !taskIndexNotSet) // when form submitted + { + if (Settings.getPluginID_for_task(taskIndex) != taskdevicenumber) + { + // change of device: cleanup old device and reset default settings + setTaskDevice_to_TaskIndex(taskdevicenumber, taskIndex); + const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); + + if (validDeviceIndex(DeviceIndex)) { + const DeviceStruct& device = Device[DeviceIndex]; + + if ((device.Type == DEVICE_TYPE_I2C) && device.I2CMax100kHz) { // 100 kHz-only I2C device? + bitWrite(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED, 1); // Then: Enable Force Slow I2C speed checkbox by default + } + } + } + else if (taskdevicenumber != INVALID_PLUGIN_ID) // save settings + { + handle_devices_CopySubmittedSettings(taskIndex, taskdevicenumber); + } + + if (taskdevicenumber != INVALID_PLUGIN_ID) { + // Task index has a task device number, so it makes sense to save. + // N.B. When calling delete, the settings were already saved. + if (nosave) { + Cache.updateExtraTaskSettingsCache(); + } else { + addHtmlError(SaveTaskSettings(taskIndex)); + addHtmlError(SaveSettings()); + } + + struct EventStruct TempEvent(taskIndex); + String dummy; + + if (Settings.TaskDeviceEnabled[taskIndex]) { + if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { + PluginCall(PLUGIN_READ, &TempEvent, dummy); + } + } else { + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + } + } + + // show all tasks as table + if (taskIndexNotSet) + { + handle_devicess_ShowAllTasksTable(page); + } + + // Show edit form if a specific entry is chosen with the edit button + else + { + handle_devices_TaskSettingsPage(taskIndex, page); + } + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_devices")); + # endif // ifndef BUILD_NO_RAM_TRACKER +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + addLogMove(LOG_LEVEL_DEBUG_DEV, concat(F("DEBUG: String size:"), static_cast(TXBuffer.sentBytes))); + } +# endif // ifndef BUILD_NO_DEBUG + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +// ******************************************************************************** +// Add a device select dropdown list +// TODO TD-er: Add JavaScript filter: +// https://www.w3schools.com/howto/howto_js_filter_dropdown.asp +// ******************************************************************************** +void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) +{ + String deviceName; + + addSelector_Head_reloadOnChange(name); + addSelector_Item(F("- None -"), 0, false); + + deviceIndex_t x; + bool done = false; + + while (!done) { + const deviceIndex_t deviceIndex = getDeviceIndex_sorted(x); + + if (!validDeviceIndex(deviceIndex)) { + done = true; + } else { + const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); + + if (validPluginID(pluginID)) { + deviceName = getPluginNameFromDeviceIndex(deviceIndex); + + + # if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) + deviceName = concat(get_formatted_Plugin_number(pluginID), F(" - ")) + deviceName; + # endif // if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) + + addSelector_Item(deviceName, + pluginID.value, + choice == pluginID); + } + } + ++x; + } + + addSelector_Foot(); +} + +// ******************************************************************************** +// Collect all submitted form data and store the task settings +// ******************************************************************************** +void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber) +{ + if (!validTaskIndex(taskIndex)) { return; } + const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); + + if (!validDeviceIndex(DeviceIndex)) { return; } + + const DeviceStruct& device = Device[DeviceIndex]; + + # if FEATURE_MQTT_DISCOVER + controllerIndex_t discoverController = INVALID_CONTROLLER_INDEX; + # endif // if FEATURE_MQTT_DISCOVER + + unsigned long taskdevicetimer = getFormItemInt(F("TDT"), 0); + + Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; + + if (device.Type == DEVICE_TYPE_I2C) { + uint8_t flags = 0; + bitWrite(flags, I2C_FLAGS_SLOW_SPEED, isFormItemChecked(F("taskdeviceflags0"))); + +# if FEATURE_I2CMULTIPLEXER + + if (isI2CMultiplexerEnabled()) { + int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); + bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, multipleMuxPortsOption == 1); + + if (multipleMuxPortsOption == 1) { + uint8_t selectedPorts = 0; + + for (int x = 0; x < I2CMultiplexerMaxChannels(); ++x) { + bitWrite(selectedPorts, x, isFormItemChecked(concat(F("taskdeviceflag1ch"), x))); + } + Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; + } else { + Settings.I2C_Multiplexer_Channel[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); + } + } + +# endif // if FEATURE_I2CMULTIPLEXER + + Settings.I2C_Flags[taskIndex] = flags; + } + + // Must load from file system to make sure all caches and checksums match. + ExtraTaskSettings.clear(); + ExtraTaskSettings.TaskIndex = taskIndex; + Cache.clearTaskCache(taskIndex); + + struct EventStruct TempEvent(taskIndex); + + // Save selected output type. + switch (device.OutputDataType) { + case Output_Data_type_t::Default: + { + String dummy; + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); + break; + } + case Output_Data_type_t::Simple: + case Output_Data_type_t::All: + { + int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); + Sensor_VType VType = TempEvent.sensorType; + + if ((pconfigIndex >= 0) && (pconfigIndex < PLUGIN_CONFIGVAR_MAX)) { + VType = + static_cast(getFormItemInt(sensorTypeHelper_webformID(pconfigIndex), 0)); + Settings.TaskDevicePluginConfig[taskIndex][pconfigIndex] = static_cast(VType); + } + ExtraTaskSettings.clearUnusedValueNames(getValueCountFromSensorType(VType)); + break; + } + } + + { + int pins[] = { -1, -1, -1 }; + + for (int i = 0; i < 3; ++i) { + update_whenset_FormItemInt(concat(F("taskdevicepin"), i + 1), pins[i]); + } + + const bool taskEnabled = isFormItemChecked(F("TDE")); + setBasicTaskValues(taskIndex, taskdevicetimer, + taskEnabled, webArg(F("TDN")), + pins); + } + + # if FEATURE_PLUGIN_PRIORITY + + if (device.PowerManager // Check extra priority device flags when available + ) { + bool disablePrio = false; + + for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { + if (t != taskIndex) { + disablePrio = Settings.isPriorityTask(t); + } + } + bool statePriority = isFormItemChecked(F("TPRE")); + + if (device.PowerManager) { + Settings.setPowerManagerTask(taskIndex, statePriority); + } + + // Set alternative Priority flags + // Set to readonly if set as Priority task + Settings.setTaskEnableReadonly(taskIndex, statePriority); + } + # endif // if FEATURE_PLUGIN_PRIORITY + Settings.TaskDevicePort[taskIndex] = getFormItemInt(F("TDP"), 0); + update_whenset_FormItemInt(F("remoteFeed"), Settings.TaskDeviceDataFeed[taskIndex]); + Settings.CombineTaskValues_SingleEvent(taskIndex, isFormItemChecked(F("TVSE"))); + + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + { + Settings.TaskDeviceID[controllerNr][taskIndex] = getFormItemInt(getPluginCustomArgName(F("TDID"), controllerNr)); + Settings.TaskDeviceSendData[controllerNr][taskIndex] = isFormItemChecked(getPluginCustomArgName(F("TDSD"), controllerNr)); + # if FEATURE_MQTT_DISCOVER + + if (isFormItemChecked(getPluginCustomArgName(F("TDDSC"), controllerNr))) { + discoverController = controllerNr; + } + # endif // if FEATURE_MQTT_DISCOVER + } + + if (device.PullUpOption) { + Settings.TaskDevicePin1PullUp[taskIndex] = isFormItemChecked(F("TDPPU")); + } + + if (device.InverseLogicOption) { + Settings.TaskDevicePin1Inversed[taskIndex] = isFormItemChecked(F("TDPI")); + } + + if (device.isSerial()) + { + # ifdef PLUGIN_USES_SERIAL + serialHelper_webformSave(&TempEvent); + # else // ifdef PLUGIN_USES_SERIAL + addLog(LOG_LEVEL_ERROR, F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + } + + const uint8_t valueCount = getValueCountForTask(taskIndex); + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceFormula[varNr], getPluginCustomArgName(F("TDF"), varNr)); + update_whenset_FormItemInt(getPluginCustomArgName(F("TDVD"), varNr), ExtraTaskSettings.TaskDeviceValueDecimals[varNr]); + strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[varNr], getPluginCustomArgName(F("TDVN"), varNr)); +# if FEATURE_PLUGIN_FILTER + ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); +# endif // if FEATURE_PLUGIN_FILTER +# if FEATURE_PLUGIN_STATS + PluginStats_Config_t pluginStats_Config; + pluginStats_Config.setEnabled(isFormItemChecked(getPluginCustomArgName(F("TDS"), varNr))); + pluginStats_Config.setHidden(isFormItemChecked(getPluginCustomArgName(F("TDSH"), varNr))); + const int selectedAxis = getFormItemInt(getPluginCustomArgName(F("TDSA"), varNr)); + pluginStats_Config.setAxisIndex(selectedAxis); + pluginStats_Config.setAxisPosition( + ((selectedAxis >> 2) == 0) + ? PluginStats_Config_t::AxisPosition::Left + : PluginStats_Config_t::AxisPosition::Right); + + ExtraTaskSettings.setPluginStatsConfig(varNr, pluginStats_Config); +# endif // if FEATURE_PLUGIN_STATS + } + ExtraTaskSettings.clearUnusedValueNames(valueCount); + + // ExtraTaskSettings has changed. + // The content of it is needed for sending CPLUGIN_TASK_CHANGE_NOTIFICATION and TaskInit/TaskExit events + Cache.updateExtraTaskSettingsCache(); + + // allow the plugin to save plugin-specific form settings. + { + String dummy; + + if (device.ExitTaskBeforeSave) { + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + + PluginCall(PLUGIN_WEBFORM_SAVE, &TempEvent, dummy); + + if (device.ErrorStateValues) { + // FIXME TD-er: Must collect these from the web page. + PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, &TempEvent, dummy); + } + + // Make sure the task needs to reload using the new settings. + if (!device.ExitTaskBeforeSave) { + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + # if FEATURE_MQTT_DISCOVER + + if (validControllerIndex(discoverController) && validControllerIndex(mqttDiscoveryController)) { + mqttDiscoverOnlyTask = taskIndex; + mqttDiscoveryTimeout = random(10, 100); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Resend AutoDiscovery for Task %d on Controller %d in %.1f sec."), + taskIndex + 1, + mqttDiscoveryController + 1, + mqttDiscoveryTimeout / 10.0f)); + } + } + # endif // if FEATURE_MQTT_DISCOVER + } + + // Store all PCONFIG values on the web page + // Must be done after PLUGIN_WEBFORM_SAVE, to allow tasks to clear the default task value names + // Output type selectors are typically stored in PCONFIG + if (device.OutputDataType != Output_Data_type_t::Default) { + for (int pconfigIndex = 0; pconfigIndex < PLUGIN_CONFIGVAR_MAX; ++pconfigIndex) { + pconfig_webformSave(&TempEvent, pconfigIndex); + } + } + + // ExtraTaskSettings may have changed during PLUGIN_WEBFORM_SAVE, so again update the cache. + Cache.updateExtraTaskSettingsCache(); + + loadDefaultTaskValueNames_ifEmpty(taskIndex); + Cache.updateExtraTaskSettingsCache(); + + // notify controllers: CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) + { + TempEvent.ControllerIndex = x; + + if (Settings.TaskDeviceSendData[TempEvent.ControllerIndex][TempEvent.TaskIndex] && + Settings.ControllerEnabled[TempEvent.ControllerIndex] && Settings.Protocol[TempEvent.ControllerIndex]) + { + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(TempEvent.ControllerIndex); + String dummy; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION, &TempEvent, dummy); + } + } + + // FIXME TD-er: Is this still needed as it is also cleared on PLUGIN_INIT and PLUGIN_EXIT? + UserVar.clear_computed(taskIndex); +} + +void html_add_setPage(uint8_t page, bool isLinkToPrev) { + addHtml(strformat( + F("devices?setpage=%u'>&%ct;"), + static_cast(page), + isLinkToPrev ? 'l' : 'g')); +} + +// ******************************************************************************** +// Show table with all selected Tasks/Devices +// ******************************************************************************** +void handle_devicess_ShowAllTasksTable(uint8_t page) +{ + serve_JS(JSfiles_e::UpdateSensorValuesDevicePage); + html_table_class_multirow(); + html_TR(); + html_table_header(F(""), 70); + + if (TASKS_MAX != TASKS_PER_PAGE) + { + html_add_button_prefix(); + + html_add_setPage((page > 1) ? page - 1 : page, true); + html_add_button_prefix(); + html_add_setPage((page < (TASKS_MAX / TASKS_PER_PAGE)) ? page + 1 : page, false); + } + + html_table_header(F("Task"), 50); + html_table_header(F("Enabled"), 100); + html_table_header(F("Device")); + html_table_header(F("Name")); + html_table_header(F("Port")); + html_table_header(F("Ctr (IDX)"), 100); + html_table_header(F("GPIO")); + html_table_header(F("Values")); + + String deviceName; + + for (taskIndex_t x = (page - 1) * TASKS_PER_PAGE; x < ((page) * TASKS_PER_PAGE) && validTaskIndex(x); x++) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); + + html_TR_TD(); + + if (pluginID_set && !supportedPluginID(Settings.getPluginID_for_task(x))) { + html_add_button_prefix(F("red"), true); + } else { + html_add_button_prefix(); + } + { + const int pageIndex = static_cast(x + 1); + addHtml(strformat( + F("devices?index=%d&page=%u'>"), + pageIndex, + static_cast(page))); + addHtml(pluginID_set ? F("Edit") : F("Add")); + addHtml(concat(F(""), pageIndex)); + html_TD(); + } + + // Show table of all configured tasks + // A task may also refer to a non supported plugin. + // This will be shown as not supported. + // Editing a task which has a non supported plugin will present the same as when assigning a new plugin to a task. + if (pluginID_set) + { + // LoadTaskSettings(x); + int8_t spi_gpios[3] { -1, -1, -1 }; + struct EventStruct TempEvent(x); + addEnabled(Settings.TaskDeviceEnabled[x] && validDeviceIndex(DeviceIndex)); + + html_TD(); + addHtml(getPluginNameFromPluginID(Settings.getPluginID_for_task(x))); + html_TD(); + addHtml(getTaskDeviceName(x)); + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + if (Settings.TaskDeviceDataFeed[x] != 0) { + # if FEATURE_ESPEASY_P2P + + // Show originating node number + const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[x]; + format_originating_node(remoteUnit); + # endif // if FEATURE_ESPEASY_P2P + } else { + String portDescr; + + if (PluginCall(PLUGIN_WEBFORM_SHOW_CONFIG, &TempEvent, portDescr)) { + addHtml(portDescr); + } else { + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.Type == DEVICE_TYPE_I2C) { + format_I2C_port_description(x); + } else if (device.isSPI()) { + format_SPI_port_description(spi_gpios); + } else if (device.isSerial()) { + # ifdef PLUGIN_USES_SERIAL + addHtml(serialHelper_getSerialTypeLabel(&TempEvent)); + # else // ifdef PLUGIN_USES_SERIAL + addHtml(F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + } else { + // Plugin has no custom port formatting, show default one. + if (device.Ports != 0) + { + addHtml(formatToHex_decimal(Settings.TaskDevicePort[x])); + } + } + } + } + } + + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + if (Device[DeviceIndex].SendDataOption) + { + boolean doBR = false; + + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + { + if (Settings.TaskDeviceSendData[controllerNr][x]) + { + if (doBR) { + html_BR(); + } + addHtml(getControllerSymbol(controllerNr)); + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); + + if (validProtocolIndex(ProtocolIndex)) { + if (getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[controllerNr] != 0)) + { + addHtml(strformat( + F(" (%d)"), + static_cast(Settings.TaskDeviceID[controllerNr][x]))); + + if (Settings.TaskDeviceID[controllerNr][x] == 0) { + addHtml(' '); + addHtml(F(HTML_SYMBOL_WARNING)); + } + } + doBR = true; + } + } + } + } + } + + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + const DeviceStruct& device = Device[DeviceIndex]; + + if (Settings.TaskDeviceDataFeed[x] == 0) + { + String description; + bool pluginHasGPIODescription = pluginWebformShowGPIOdescription(x, F("
"), description); + + bool showpin1 = false; + bool showpin2 = false; + bool showpin3 = false; + + switch (device.Type) { + case DEVICE_TYPE_I2C: + { + format_I2C_pin_description(x); + html_BR(); + break; + } + case DEVICE_TYPE_SPI3: + showpin3 = !pluginHasGPIODescription; + + // Fall Through + case DEVICE_TYPE_SPI2: + showpin2 = !pluginHasGPIODescription; + + // Fall Through + case DEVICE_TYPE_SPI: + format_SPI_pin_description(spi_gpios, x, !pluginHasGPIODescription); + break; + case DEVICE_TYPE_ANALOG: + { + # ifdef ESP8266 + # if FEATURE_ADC_VCC + addHtml(F("ADC (VCC)")); + # else // if FEATURE_ADC_VCC + addHtml(F("ADC (TOUT)")); + # endif // if FEATURE_ADC_VCC + # endif // ifdef ESP8266 + # ifdef ESP32 + showpin1 = true; + addHtml(formatGpioName_ADC(Settings.TaskDevicePin1[x])); + html_BR(); + # endif // ifdef ESP32 + + break; + } + case DEVICE_TYPE_SERIAL_PLUS1: + showpin3 = true; + + // fallthrough + case DEVICE_TYPE_SERIAL: + { + # ifdef PLUGIN_USES_SERIAL + const String serialDescription = serialHelper_getGpioDescription(static_cast(Settings.TaskDevicePort[x]), + Settings.TaskDevicePin1[x], + Settings.TaskDevicePin2[x], + F("
")); + addHtml(serialDescription); + # else // ifdef PLUGIN_USES_SERIAL + addHtml(F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + + if ( +# ifdef PLUGIN_USES_SERIAL + serialDescription.length() || +# endif // ifdef PLUGIN_USES_SERIAL + showpin3) { + html_BR(); + } + break; + } + case DEVICE_TYPE_CUSTOM3: + showpin3 = true; + + // fallthrough + case DEVICE_TYPE_CUSTOM2: + showpin2 = true; + + // fallthrough + case DEVICE_TYPE_CUSTOM1: + case DEVICE_TYPE_CUSTOM0: + { + showpin1 = true; + + if (pluginHasGPIODescription || (device.Type == DEVICE_TYPE_CUSTOM0)) { + addHtml(description); + showpin1 = false; + showpin2 = false; + showpin3 = false; + } + break; + } + + default: + showpin1 = true; + showpin2 = true; + showpin3 = true; + break; + } + + if (showpin1) + { + addGpioHtml(Settings.getTaskDevicePin(x, 1)); + } + + if (showpin2) + { + html_BR(); + addGpioHtml(Settings.getTaskDevicePin(x, 2)); + } + + if (showpin3) + { + html_BR(); + addGpioHtml(Settings.getTaskDevicePin(x, 3)); + } + + // Allow for tasks to show their own specific GPIO pins. + if (!device.isCustom() && + pluginHasGPIODescription) { + if (showpin1 || showpin2 || showpin3) { + html_BR(); + } + addHtml(description); + } + } + } + + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + String customValuesString; + const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); + + if (!customValues) + { + const uint8_t valueCount = getValueCountForTask(x); + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) + { + pluginWebformShowValue( + x, + varNr, + Cache.getTaskDeviceValueName(x, varNr), + formatUserVarNoCheck(&TempEvent, varNr)); + } + } + } + } + } + else { + html_TD(6); + } + } // next + html_end_table(); + html_end_form(); +} + +# if FEATURE_ESPEASY_P2P +void format_originating_node(uint8_t remoteUnit) { + addHtml(F("Unit ")); + addHtmlInt(remoteUnit); + + if (remoteUnit != 255) { + const NodeStruct *node = Nodes.getNode(remoteUnit); + + if (node != nullptr) { + addHtml(F(" - ")); + addHtml(node->getNodeName()); + } else { + addHtml(F(" - Not Seen recently")); + } + } +} + +# endif // if FEATURE_ESPEASY_P2P + +void format_I2C_port_description(taskIndex_t x) +{ + addHtml(F("I2C")); + # if FEATURE_I2C_GET_ADDRESS + const uint8_t i2cAddr = getTaskI2CAddress(x); + + if (i2cAddr > 0) { + addHtml(' '); + addHtml(formatToHex(i2cAddr, 2)); + } + # endif // if FEATURE_I2C_GET_ADDRESS + # if FEATURE_I2CMULTIPLEXER + + if (isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(x)) { + String mux; + + if (bitRead(Settings.I2C_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel + mux = F("
Multiplexer channel(s)"); + uint8_t b = 0; // For adding lineBreaks + + for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { + if (bitRead(Settings.I2C_Multiplexer_Channel[x], c)) { + mux += b == 0 ? F("
") : F(", "); + b++; + mux += String(c); + } + } + } else { // Single channel + mux = concat(F("
Multiplexer channel "), static_cast(Settings.I2C_Multiplexer_Channel[x])); + } + addHtml(mux); + } + # endif // if FEATURE_I2CMULTIPLEXER +} + +void format_SPI_port_description(int8_t spi_gpios[3]) +{ + if (!Settings.getSPI_pins(spi_gpios)) { + addHtml(F("SPI (Not enabled)")); + return; + } + # ifdef ESP32 + addHtml(getSPI_optionToShortString(static_cast(Settings.InitSPI))); + # endif // ifdef ESP32 + # ifdef ESP8266 + addHtml(F("SPI")); + # endif // ifdef ESP8266 +} + +void format_I2C_pin_description(taskIndex_t x) +{ + if (checkI2CConfigValid_toHtml(x)) { + Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.Pin_i2c_sda, false)); + html_BR(); + Label_Gpio_toHtml(F("SCL"), formatGpioLabel(Settings.Pin_i2c_scl, false)); + } +} + +void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin) +{ + if (Settings.InitSPI > static_cast(SPI_Options_e::None)) { + const __FlashStringHelper*labels[] = { F("CLK"), F("MISO"), F("MOSI") }; + + for (int i = 0; i < 3; ++i) { + if (i != 0) { + html_BR(); + } + + Label_Gpio_toHtml(labels[i], formatGpioLabel(spi_gpios[i], false)); + } + + if (showCSpin) { + html_BR(); + Label_Gpio_toHtml(F("CS"), formatGpioLabel(Settings.TaskDevicePin1[x], false)); + } + } +} + +// ******************************************************************************** +// Show the task settings page +// ******************************************************************************** +void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) +{ + if (!validTaskIndex(taskIndex)) { return; } + + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + // LoadTaskSettings(taskIndex); + + html_add_form(); + html_table_class_normal(); + addFormHeader(F("Task Settings")); + + + addHtml(F("
Device:")); + + // no (supported) device selected, this effectively checks for validDeviceIndex + if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) + { + // takes lots of memory/time so call this only when needed. + addDeviceSelect(F("TDNUM"), Settings.getPluginID_for_task(taskIndex)); // ="taskdevicenumber" + addFormSeparator(4); + } + + // device selected + else + { + const DeviceStruct& device = Device[DeviceIndex]; + + // remember selected device number + addHtml(F("'); + + // show selected device name and delete button + addHtml(getPluginNameFromDeviceIndex(DeviceIndex)); + + addHelpButton(concat(F("Plugin"), Settings.getPluginID_for_task(taskIndex).value)); + addRTDPluginButton(Settings.getPluginID_for_task(taskIndex)); + + addFormTextBox(F("Name"), F("TDN"), getTaskDeviceName(taskIndex), NAME_FORMULA_LENGTH_MAX); // ="taskdevicename" + + addFormCheckBox(F("Enabled"), F("TDE"), + Settings.TaskDeviceEnabled[taskIndex], + + // Settings.TaskDeviceEnabled[taskIndex].enabled, + Settings.isTaskEnableReadonly(taskIndex)); // ="taskdeviceenabled" + + # if FEATURE_PLUGIN_PRIORITY + + if (device.PowerManager) { // Check extra priority device flags when available + bool disablePrio = !Settings.TaskDeviceEnabled[taskIndex]; + + for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { + if (t != taskIndex) { // Ignore current device + if (device.PowerManager && Settings.isPowerManagerTask(t)) { + disablePrio = true; // Allow only a single PowerManager plugin + } + + // Add other Priority options checks + } + } + addFormSubHeader(F("Priority task")); + addFormCheckBox(F("Priority task"), F("TPRE"), Settings.isPriorityTask(taskIndex), disablePrio); // ="taskpriorityenabled" + + if (!disablePrio) { + addFormNote(F("After enabling a Priority task, a reboot is required to activate. See documentation.")); + } + } + # endif // if FEATURE_PLUGIN_PRIORITY + + const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; + # if FEATURE_ESPEASY_P2P + + if (device.SendDataOption) + { + // Show remote feed information. + addFormSubHeader(F("Data Source")); + addFormNumericBox(F("Remote Unit"), F("remoteFeed"), remoteUnit, 0, 255); + + if (remoteUnit != 255) { + const NodeStruct*node = Nodes.getNode(remoteUnit); + + if (node != nullptr) { + addUnit(node->getNodeName()); + } else { + addUnit(F("Unknown Unit Name")); + } + } + addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. + } + # endif // if FEATURE_ESPEASY_P2P + + bool addPinConfig = false; + + // section: Sensor / Actuator + if (!device.Custom && (Settings.TaskDeviceDataFeed[taskIndex] == 0) && + ((device.Ports != 0) || + (device.PullUpOption) || + (device.InverseLogicOption) || + (device.connectedToGPIOpins()))) + { + addFormSubHeader((device.SendDataOption) ? F("Sensor") : F("Actuator")); + + if (device.Ports != 0) { + addFormNumericBox(F("Port"), F("TDP"), Settings.TaskDevicePort[taskIndex]); // ="taskdeviceport" + } + + addPinConfig = true; + } + + if (addPinConfig || (device.Type == DEVICE_TYPE_I2C)) { + if (device.isSerial()) { + # ifdef PLUGIN_USES_SERIAL + devicePage_show_serial_config(taskIndex); + # else // ifdef PLUGIN_USES_SERIAL + addHtml(F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + + devicePage_show_pin_config(taskIndex, DeviceIndex); + addPinConfig = false; + + html_add_script(F("document.getElementById('serPort').onchange();"), false); + } else if (device.Type == DEVICE_TYPE_I2C) { + devicePage_show_pin_config(taskIndex, DeviceIndex); + addPinConfig = false; + + if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { + devicePage_show_I2C_config(taskIndex, DeviceIndex); + } + } + + if (addPinConfig) { + devicePage_show_pin_config(taskIndex, DeviceIndex); + } + } + + String webformLoadString; + struct EventStruct TempEvent(taskIndex); + + + // add plugins content + if ((DEVICE_TYPE_DUMMY != device.Type) && (remoteUnit == 0)) { + addFormSubHeader(F("Device Settings")); + } + + if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors + PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); + # ifndef BUILD_NO_DEBUG + + if (webformLoadString.length() > 0) { + String errorMessage; + PluginCall(PLUGIN_GET_DEVICENAME, &TempEvent, errorMessage); + errorMessage += F(": Bug in PLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead"); + addHtmlError(errorMessage); + } + # endif // ifndef BUILD_NO_DEBUG + } + PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices + + devicePage_show_output_data_type(taskIndex, DeviceIndex); + + # if FEATURE_PLUGIN_STATS + + // Task statistics and historic data in a chart + devicePage_show_task_statistics(taskIndex, DeviceIndex); + # endif // if FEATURE_PLUGIN_STATS + + // section: Data Acquisition + devicePage_show_controller_config(taskIndex, DeviceIndex); + + addFormSeparator(2); + + devicePage_show_interval_config(taskIndex, DeviceIndex); + + devicePage_show_task_values(taskIndex, DeviceIndex); + } + + html_TR_TD(); + addHtml(F("")); + html_add_button_prefix(); + addHtml(F("devices?setpage=")); + addHtmlInt(page); + addHtml(F("'>Close")); + # if FEATURE_PLUGIN_PRIORITY + + if (!Settings.isPriorityTask(taskIndex)) + # endif // if FEATURE_PLUGIN_PRIORITY + { + addSubmitButton(); + } + addHtml(F("")); + addHtml(F("")); + + // if user selected a device, add the delete button, except for Priority tasks + if (validPluginID_fullcheck(Settings.getPluginID_for_task(taskIndex)) + # if FEATURE_PLUGIN_PRIORITY + && !Settings.isPriorityTask(taskIndex) + # endif // if FEATURE_PLUGIN_PRIORITY + ) { + addSubmitButton(F("Delete"), F("del")); + } + + html_end_table(); + # if FEATURE_PLUGIN_PRIORITY + + if (Settings.isPriorityTask(taskIndex)) { + addFormNote(F("A Priority task can't be updated or deleted. See documentation.")); + } + # endif // if FEATURE_PLUGIN_PRIORITY + html_end_form(); + serve_JS(JSfiles_e::SplitPasteInput); +} + +void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.PullUpOption) + { + addFormCheckBox(F("Internal PullUp"), F("TDPPU"), Settings.TaskDevicePin1PullUp[taskIndex]); // ="taskdevicepin1pullup" + addFormNote(F("Best to (also) configure pull-up on Hardware tab under \"GPIO boot states\"")); + # if defined(ESP8266) + + if ((Settings.TaskDevicePin1[taskIndex] == 16) || (Settings.TaskDevicePin2[taskIndex] == 16) || + (Settings.TaskDevicePin3[taskIndex] == 16)) { + addFormNote(F("PullDown for GPIO-16 (D0)")); + } + # endif // if defined(ESP8266) + } + + if (device.InverseLogicOption) + { + addFormCheckBox(F("Inversed Logic"), F("TDPI"), Settings.TaskDevicePin1Inversed[taskIndex]); // ="taskdevicepin1inversed" + addFormNote(F("Will go into effect on next input change.")); + } + + if (device.isSPI() + && (Settings.InitSPI == static_cast(SPI_Options_e::None))) { + addFormNote(F("SPI Interface is not configured yet (Hardware page).")); + } + + if (device.connectedToGPIOpins()) { + // get descriptive GPIO-names from plugin + struct EventStruct TempEvent(taskIndex); + + TempEvent.String1 = F("1st GPIO"); + TempEvent.String2 = F("2nd GPIO"); + TempEvent.String3 = F("3rd GPIO"); + String dummy; + PluginCall(PLUGIN_GET_DEVICEGPIONAMES, &TempEvent, dummy); + + if (device.usesTaskDevicePin(1)) { + PinSelectPurpose purpose = device.getPinSelectPurpose(1); // PinSelectPurpose::Generic; + + if (device.isSerial()) + { + // Pin1 = GPIO <--- TX + purpose = PinSelectPurpose::Serial_input; + } else if (device.isSPI()) + { + // All selectable SPI pins are output only + purpose = PinSelectPurpose::Generic_output; + } + + addFormPinSelect(purpose, TempEvent.String1, F("taskdevicepin1"), Settings.TaskDevicePin1[taskIndex]); + } + + if (device.usesTaskDevicePin(2)) { + PinSelectPurpose purpose = device.getPinSelectPurpose(2); // PinSelectPurpose::Generic; + + if (device.isSerial()) + { + // Serial Pin2 = GPIO ---> RX + purpose = PinSelectPurpose::Serial_output; + } + + if (device.isSPI()) + { + // SPI only needs output pins + purpose = PinSelectPurpose::Generic_output; + } + addFormPinSelect(purpose, TempEvent.String2, F("taskdevicepin2"), Settings.TaskDevicePin2[taskIndex]); + } + + if (device.usesTaskDevicePin(3)) { + PinSelectPurpose purpose = device.getPinSelectPurpose(3); // PinSelectPurpose::Generic; + + if (device.isSPI()) + { + // SPI only needs output pins + purpose = PinSelectPurpose::Generic_output; + } + addFormPinSelect(purpose, TempEvent.String3, F("taskdevicepin3"), Settings.TaskDevicePin3[taskIndex]); + } + } +} + +# ifdef PLUGIN_USES_SERIAL +void devicePage_show_serial_config(taskIndex_t taskIndex) +{ + struct EventStruct TempEvent(taskIndex); + + String webformLoadString; + + PluginCall(PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, &TempEvent, webformLoadString); + + serialHelper_webformLoad(&TempEvent); + + PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); +} + +# endif // ifdef PLUGIN_USES_SERIAL + +void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + + addFormSubHeader(F("I2C options")); + + if (!Settings.isI2CEnabled()) { + addFormNote(F("I2C Interface is not configured yet (Hardware page).")); + } + + String dummy; + + PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); + addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); + + if (Device[DeviceIndex].I2CMax100kHz) { + addFormNote(F("This device is specified for max. 100 kHz operation!")); + } + + # if FEATURE_I2CMULTIPLEXER + + // Show selector for an I2C multiplexer port if a multiplexer is configured + if (isI2CMultiplexerEnabled()) { + bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); + { + const __FlashStringHelper *i2c_mux_channels[] = { + F("Single channel"), + F("Multiple channels") }; + constexpr int i2c_mux_channelOptions[] = { 0, 1 }; + int i2c_mux_channelCount = 1; + + if (Settings.I2C_Multiplexer_Type == I2C_MULTIPLEXER_PCA9540) { + multipleMuxPorts = false; // force off + } else { + i2c_mux_channelCount++; + } + addFormSelector(F("Multiplexer channels"), + F("taskdeviceflags1"), + i2c_mux_channelCount, + i2c_mux_channels, + i2c_mux_channelOptions, + multipleMuxPorts ? 1 : 0, + true); + } + + if (multipleMuxPorts) { + addRowLabel(F("Select connections"), EMPTY_STRING); + html_table(EMPTY_STRING, false); // Sub-table + html_table_header(F("Channel"), 100); + html_table_header(F("Enable"), 80); + html_table_header(F("Channel"), 100); + html_table_header(F("Enable"), 80); + + for (int x = 0; x < I2CMultiplexerMaxChannels(); x++) { + if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels + html_TD(); + addHtml(concat(F("Channel "), x)); + html_TD(); + addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(Settings.I2C_Multiplexer_Channel[taskIndex], x), false); + } + html_end_table(); + } else { + int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Channel[taskIndex]; + const uint32_t mux_max = I2CMultiplexerMaxChannels(); + String i2c_mux_portoptions[mux_max + 1]; + int i2c_mux_portchoices[mux_max + 1]; + i2c_mux_portoptions[0] = F("(Not connected via multiplexer)"); + i2c_mux_portchoices[0] = -1; + + for (uint32_t x = 0; x < mux_max; x++) { + const uint32_t mux_opt = x + 1; + i2c_mux_portoptions[mux_opt] = concat(F("Channel "), x); + i2c_mux_portchoices[mux_opt] = x; + } + + if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range + addFormSelector(F("Connected to"), + F("taskdevicei2cmuxport"), + mux_max + 1, + i2c_mux_portoptions, + i2c_mux_portchoices, + taskDeviceI2CMuxPort); + } + } + # endif // if FEATURE_I2CMULTIPLEXER +} + +void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); + + switch (Device[DeviceIndex].OutputDataType) { + case Output_Data_type_t::Default: + return; + case Output_Data_type_t::Simple: + + if (pconfigIndex >= 0) { + sensorTypeHelper_webformLoad_simple(&TempEvent, pconfigIndex); + return; + } + break; + case Output_Data_type_t::All: + { + if (pconfigIndex >= 0) { + sensorTypeHelper_webformLoad_allTypes(&TempEvent, pconfigIndex); + return; + } + break; + } + } + addFormSubHeader(F("Output Configuration")); + String dummy; + PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, &TempEvent, dummy); +} + +# if FEATURE_PLUGIN_STATS +void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (Device[DeviceIndex].PluginStats) + { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); + + if (taskData != nullptr) { + if (taskData->hasPluginStats()) { + addFormSubHeader(F("Statistics")); + } + # if FEATURE_CHART_JS + + if (taskData->nrSamplesPresent() > 0) { + addRowLabel(F("Historic data")); + taskData->plot_ChartJS(); + } + # endif // if FEATURE_CHART_JS + + struct EventStruct TempEvent(taskIndex); + String dummy; + bool somethingAdded = false; + + if (!PluginCall(PLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { + somethingAdded = taskData->webformLoad_show_stats(&TempEvent); + } else { somethingAdded = true; } + + if (somethingAdded) { + if (taskData->hasPeaks()) { + addFormNote(strformat( + F("Peak values recorded since last \"%s.resetpeaks\"."), + getTaskDeviceName(taskIndex).c_str())); + } + } + } + } +} + +# endif // if FEATURE_PLUGIN_STATS + + +void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) { return; } + + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.SendDataOption) + { + addFormSubHeader(F("Data Acquisition")); + + if (device.ErrorStateValues) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options + } + + addRowLabel(F("Single event with all values")); + addCheckBox(F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex)); + addFormNote(strformat( + F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), + getTaskDeviceName(taskIndex).c_str())); + + bool separatorAdded = false; + + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + { + if (Settings.Protocol[controllerNr] != 0) + { + if (!separatorAdded) { + addFormSeparator(2); + } + separatorAdded = true; + html_TR_TD(); + addHtml(F("Send to Controller ")); + addHtml(getControllerSymbol(controllerNr)); + addHtmlDiv(F("note"), wrap_braces(getCPluginNameFromCPluginID(Settings.Protocol[controllerNr]) + F(", ") + // Most compact code... + (Settings.ControllerEnabled[controllerNr] ? F("enabled") : F("disabled")))); + html_TD(); + + addHtml(F("")); // remove left padding 2x to align vertically with other inputs + html_TD(F("width:50px;padding-left:0")); + addCheckBox( + getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" + Settings.TaskDeviceSendData[controllerNr][taskIndex]); + + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); + const bool showControllerIDX = validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID && + (Settings.Protocol[controllerNr] != 0); + # if FEATURE_MQTT_DISCOVER + const bool showMqttGroup = (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).mqttAutoDiscover && + Settings.TaskDeviceSendData[controllerNr][taskIndex]); + # endif // if FEATURE_MQTT_DISCOVER + + if (showControllerIDX + # if FEATURE_MQTT_DISCOVER + || showMqttGroup + # endif // if FEATURE_MQTT_DISCOVER + ) { + html_TD(); + addHtml( + # if FEATURE_MQTT_DISCOVER + showMqttGroup && !showControllerIDX ? F("Group:") : + # endif // if FEATURE_MQTT_DISCOVER + F("IDX:")); + html_TD(); + addNumericBox( + getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" + Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); + } + # if FEATURE_MQTT_DISCOVER + + if (showMqttGroup && + (mqttDiscoveryController == controllerNr)) { + html_TD(); + addHtml(F("Resend MQTT Discovery:")); + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDDSC"), controllerNr), // ="taskdevicediscover" + false); + } + # endif // if FEATURE_MQTT_DISCOVER + html_end_table(); + } + } + } +} + +void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) { return; } + + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.TimerOption) + { + // FIXME: shoudn't the max be ULONG_MAX because Settings.TaskDeviceTimer is an unsigned long? addFormNumericBox only supports ints + // for min and max specification + addFormNumericBox(F("Interval"), F("TDT"), Settings.TaskDeviceTimer[taskIndex], 0, 65535); // ="taskdevicetimer" + addUnit(F("sec")); + + if (device.TimerOptional) { + addHtml(F(" (Optional for this Device)")); + } + } +} + +void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) { return; } + + // section: Values + const uint8_t valueCount = getValueCountForTask(taskIndex); + + const DeviceStruct& device = Device[DeviceIndex]; + + if (!device.Custom && (valueCount > 0)) + { + int colCount = 2; + addFormSubHeader(F("Values")); + html_end_table(); + html_table_class_normal(); + + // table header + addHtml(F("
#")); + html_table_header(F("Name"), 500); + + if (device.FormulaOption) + { + html_table_header(F("Formula"), F("RTDPlugin/_Plugin.html#formula"), 500); + ++colCount; + } + + if (device.configurableDecimals()) + { + html_table_header(F("Decimals"), 30); + ++colCount; + } + +# if FEATURE_PLUGIN_STATS + + if (device.PluginStats) + { + html_table_header(F("Stats"), 30); + ++colCount; + html_table_header(F("Hide"), 30); + ++colCount; + html_table_header(F("Axis"), 30); + ++colCount; + } +# endif // if FEATURE_PLUGIN_STATS + + // placeholder header + html_table_header(F("")); + ++colCount; + + // table body + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + html_TR_TD(); + addHtmlInt(varNr + 1); + html_TD(); + { + const String id = getPluginCustomArgName(F("TDVN"), varNr); // ="taskdevicevaluename" + addTextBox(id, Cache.getTaskDeviceValueName(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); + } + + if (device.FormulaOption) + { + html_TD(); + const String id = getPluginCustomArgName(F("TDF"), varNr); // ="taskdeviceformula" + addTextBox(id, Cache.getTaskDeviceFormula(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); + } + + if (device.configurableDecimals()) + { + html_TD(); + const String id = getPluginCustomArgName(F("TDVD"), varNr); // ="taskdevicevaluedecimals" + addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); + } + +# if FEATURE_PLUGIN_STATS + + if (device.PluginStats) + { + PluginStats_Config_t cachedConfig = Cache.getPluginStatsConfig(taskIndex, varNr); + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDS"), varNr), // ="taskdevicestats" + cachedConfig.isEnabled()); + + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDSH"), varNr), // ="taskdevicestats Hidden" + cachedConfig.showHidden()); + + html_TD(); + + const __FlashStringHelper *chartAxis[] = { + F("L1"), + F("L2"), + F("L3"), + F("L4"), + F("R1"), + F("R2"), + F("R3"), + F("R4") + }; + + int selected = cachedConfig.getAxisIndex(); + + if (!cachedConfig.isLeft()) { + selected += 4; + } + + addSelector( + getPluginCustomArgName(F("TDSA"), varNr), + NR_ELEMENTS(chartAxis), + chartAxis, + nullptr, + nullptr, + selected); + } +# endif // if FEATURE_PLUGIN_STATS + } + addFormSeparator(colCount); + } +} + +#endif // ifdef WEBSERVER_DEVICES