From 37800d4da9f3a90d2eb294859cf3f7b46717edb1 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 20 Oct 2023 23:16:59 +0200 Subject: [PATCH 1/7] [P103] Add support for EZO-HUM sensor --- src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 394 +++++++++++++-------- src/src/PluginStructs/P103_data_struct.cpp | 27 +- src/src/PluginStructs/P103_data_struct.h | 34 +- 3 files changed, 297 insertions(+), 158 deletions(-) diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index d8f5b0fcde..a9455d1d92 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -2,17 +2,21 @@ #ifdef USES_P103 -// ########################################################################### -// ################## Plugin 103 : Atlas Scientific EZO pH ORP EC DO sensors # -// ########################################################################### - -// datasheet at https://atlas-scientific.com/files/pH_EZO_Datasheet.pdf -// datasheet at https://atlas-scientific.com/files/ORP_EZO_Datasheet.pdf -// datasheet at https://atlas-scientific.com/files/EC_EZO_Datasheet.pdf -// datasheet at https://atlas-scientific.com/files/DO_EZO_Datasheet.pdf +// ######################################################################################## +// ################## Plugin 103 : Atlas Scientific EZO pH ORP EC DO HUM RTD FLOW sensors # +// ######################################################################################## + +// datasheet at https://atlas-scientific.com/files/pH_EZO_Datasheet.pdf (0x63, pH level) +// datasheet at https://atlas-scientific.com/files/ORP_EZO_Datasheet.pdf (0x62, Oxidation Reduction Potential) +// datasheet at https://atlas-scientific.com/files/EC_EZO_Datasheet.pdf (0x64, electric conductivity) +// datasheet at https://atlas-scientific.com/files/DO_EZO_Datasheet.pdf (0x61, dissolved oxigen) +// datasheet at https://files.atlas-scientific.com/EZO-HUM-C-Datasheet.pdf (0x6F, humidity) +// datasheet at https://files.atlas-scientific.com/EZO_RTD_Datasheet.pdf (0x66, thermosensors) +// datasheet at https://files.atlas-scientific.com/flow_EZO_Datasheet.pdf (0x68, flow meter) // only i2c mode is supported /** Changelog: + * 2023-10-17 tonhuisman: Add support for EZO HUM, RTD and FLOW sensor modules (I2C only!) (RTD, FLOW disabled, default to UART mode) * 2023-01-08 tonhuisman: Replace ambiguous #define UNKNOWN, move support functions to plugin_struct source * 2023-01-07 tonhuisman: Refactored strings (a.o. shorter names for WEBFORM_LOAD and WEBFORM_SAVE events), separate javascript function * instead of repeated code, extract red/orange/green messages into functions @@ -22,9 +26,17 @@ # define PLUGIN_103 # define PLUGIN_ID_103 103 -# define PLUGIN_NAME_103 "Environment - Atlas EZO pH ORP EC DO" +# define PLUGIN_NAME_103 "Environment - Atlas EZO pH ORP EC DO HUM" +# if P103_USE_RTD +" RTD" +# endif // if P103_USE_RTD +# if P103_USE_FLOW +" FLOW" +# endif // if P103_USE_FLOW # define PLUGIN_VALUENAME1_103 "SensorData" # define PLUGIN_VALUENAME2_103 "Voltage" +# define PLUGIN_VALUENAME3_103 "Temperature" // Only used for HUM +# define PLUGIN_VALUENAME4_103 "Dewpoint" // Only used for HUM # include "src/PluginStructs/P103_data_struct.h" @@ -32,7 +44,18 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; - AtlasEZO_Sensors_e board_type = AtlasEZO_Sensors_e::UNKNOWN; + AtlasEZO_Sensors_e board_type = AtlasEZO_Sensors_e::UNKNOWN; + const uint8_t i2cAddressValues[] = { 0x63, 0x62, 0x64, 0x61, 0x6F + # if P103_USE_RTD + , 0x66 + # endif // if P103_USE_RTD + # if P103_USE_FLOW + , 0x68 + # endif // if P103_USE_FLOW + }; + constexpr int i2c_nr_elements = NR_ELEMENTS(i2cAddressValues); + + char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; switch (function) { @@ -62,21 +85,45 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) { strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_103)); strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_103)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_103)); // Only used for HUM + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_103)); // Only used for HUM + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P103_NR_OUTPUT_VALUES = 2; + + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P103_NR_OUTPUT_VALUES; // Depends on sensor + + success = true; + break; } case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const uint8_t i2cAddressValues[] = { 0x61, 0x62, 0x63, 0x64 }; // , 0x65, 0x66, 0x67}; // Disabled unsupported devices as discussed - // here: https://github.com/letscontrolit/ESPEasy/pull/3733 (review - // comment by TD-er) + // Disabled unsupported devices as discussed + // here: https://github.com/letscontrolit/ESPEasy/pull/3733 (review comment by TD-er) if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c"), P103_ATLASEZO_I2C_NB_OPTIONS, i2cAddressValues, P103_I2C_ADDRESS); - addFormNote(F("pH: 0x63, ORP: 0x62, EC: 0x64, DO: 0x61. The plugin is able to detect the type of device automatically.")); + addFormSelectorI2C(F("i2c"), i2c_nr_elements, i2cAddressValues, P103_I2C_ADDRESS); + addFormNote(F("pH: 0x63, ORP: 0x62, EC: 0x64, DO: 0x61, HUM: 0x6F" + # if P103_USE_RTD + ", RTD: 0x66" + # endif // if P103_USE_RTD + # if P103_USE_FLOW + ", FLOW: 0x68" + # endif // if P103_USE_FLOW + ". The plugin can detect the type of device.")); } else { - success = intArrayContains(P103_ATLASEZO_I2C_NB_OPTIONS, i2cAddressValues, event->Par1); + success = intArrayContains(i2c_nr_elements, i2cAddressValues, event->Par1); } break; } @@ -96,35 +143,69 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) P103_addDisabler(); // JS function disabler(clear,single,l,h,dry,nul,atm) - char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + addFormCheckBox(F("Setup without sensor"), F("uncon"), P103_UNCONNECTED_SETUP == 1); - if (P103_send_I2C_command(P103_I2C_ADDRESS, F("i"), boarddata)) - { - String boardInfo(boarddata); + if (P103_send_I2C_command(P103_I2C_ADDRESS, F("i"), boarddata) || P103_UNCONNECTED_SETUP) { + const String boardInfo(boarddata); addRowLabel(F("Board type")); - String board = boardInfo.substring(boardInfo.indexOf(',') + 1, boardInfo.lastIndexOf(',')); - String version = boardInfo.substring(boardInfo.lastIndexOf(',') + 1); - addHtml(board); + String board = parseStringKeepCase(boardInfo, 2); + const String version = parseStringKeepCase(boardInfo, 3); - String boardTypes = F("pH ORP EC D.O."); - AtlasEZO_Sensors_e boardIDs[] = { + const String boardTypes = F("pH ORP EC D.O.HUM RTD FLO"); // Unsupported boards are still ignored + const AtlasEZO_Sensors_e boardIDs[] = { AtlasEZO_Sensors_e::PH, AtlasEZO_Sensors_e::ORP, AtlasEZO_Sensors_e::EC, AtlasEZO_Sensors_e::DO, + AtlasEZO_Sensors_e::HUM, + # if P103_USE_RTD + AtlasEZO_Sensors_e::RTD, + # endif // if P103_USE_RTD + # if P103_USE_FLOW + AtlasEZO_Sensors_e::FLOW, + # endif // if P103_USE_FLOW }; int bType = boardTypes.indexOf(board); + if ((board.isEmpty() || (bType == -1)) && P103_UNCONNECTED_SETUP) { + // Not recognized, lets assume I2C address is correct, so we can setup the options + for (uint8_t i = 0; i < i2c_nr_elements; ++i) { + if (i2cAddressValues[i] == P103_I2C_ADDRESS) { + bType = i * 4; // Divided im the next check + break; + } + } + } + if (bType > -1) { board_type = boardIDs[bType / 4]; + board = toString(board_type); } - P103_BOARD_TYPE = static_cast(board_type); + addHtml(board); - if (board_type == AtlasEZO_Sensors_e::UNKNOWN) - { - P103_html_red(F(" WARNING : Board type should be 'pH', 'ORP', 'EC' or 'DO', check your i2c address? ")); + P103_BOARD_TYPE = static_cast(board_type); + const int output_values[] = { 2, 2, 2, 2, 2, 4 + # if P103_USE_RTD + , 2 + # endif // if P103_USE_RTD + # if P103_USE_FLOW + , 3 + # endif // if P103_USE_FLOW + }; + P103_NR_OUTPUT_VALUES = output_values[P103_BOARD_TYPE]; + + + if (board_type == AtlasEZO_Sensors_e::UNKNOWN) { + P103_html_red(F(" WARNING : Board type should be 'pH', 'ORP', 'EC', 'DO', 'HUM'" + # if P103_USE_RTD + ", 'RTD'" + # endif // if P103_USE_RTD + # if P103_USE_FLOW + ", 'FLOW'" + # endif // if P103_USE_FLOW + ", check your i2c address?")); } addRowLabel(F("Board version")); addHtml(version); @@ -132,14 +213,18 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) addHtml(F("'); - } - else - { + } else { P103_html_red(F("Unable to send command to device")); - if (board_type == AtlasEZO_Sensors_e::UNKNOWN) - { - P103_html_red(F(" WARNING : Board type should be 'pH', 'ORP', 'EC' or 'DO', check your i2c address? ")); + if (board_type == AtlasEZO_Sensors_e::UNKNOWN) { + P103_html_red(F(" WARNING : Board type should be 'pH', 'ORP', 'EC', 'DO', 'HUM'" + # if P103_USE_RTD + ", 'RTD'" + # endif // if P103_USE_RTD + # if P103_USE_FLOW + ", 'FLOW'" + # endif // if P103_USE_FLOW + ", check your i2c address?")); } success = false; break; @@ -147,87 +232,81 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup - if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Status"), boarddata)) - { + if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Status"), boarddata) || P103_UNCONNECTED_SETUP) { String boardStatus(boarddata); addRowLabel(F("Board restart code")); - # ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, boardStatus); - # endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG - char *statuschar = strchr(boarddata, ','); + // FIXME tonhuisman: To improve + const String stat = parseStringKeepCase(boardStatus, 2); - if (statuschar > 0) - { - switch (boarddata[statuschar - boarddata + 1]) - { + if (!stat.isEmpty()) { + switch (stat[0]) { case 'P': - { addHtml(F("powered off")); break; - } case 'S': - { addHtml(F("software reset")); break; - } case 'B': - { addHtml(F("brown out")); break; - } case 'W': - { addHtml(F("watch dog")); break; - } case 'U': default: - { addHtml(F("unknown")); break; - } } } addRowLabel(F("Board voltage")); - addHtml(boardStatus.substring(boardStatus.lastIndexOf(',') + 1)); + addHtml(parseString(boardStatus, 3)); addUnit('V'); addRowLabel(F("Sensor Data")); addHtmlFloat(UserVar[event->BaseVarIndex]); - switch (board_type) - { + switch (board_type) { case AtlasEZO_Sensors_e::PH: - { addUnit(F("pH")); break; - } case AtlasEZO_Sensors_e::ORP: - { addUnit(F("mV")); break; - } case AtlasEZO_Sensors_e::EC: - { addUnit(F("µS")); break; - } case AtlasEZO_Sensors_e::DO: - { addUnit(F("mg/L")); break; + case AtlasEZO_Sensors_e::HUM: // TODO Show Temp & Dew point also + { + addUnit(F("%RH")); + break; + } + # if P103_USE_RTD + case AtlasEZO_Sensors_e::RTD: + { + addUnit(F("°C")); // TODO Read current scale (C/F/K) from device, show flow + break; } + # endif // if P103_USE_RTD + # if P103_USE_FLOW + case AtlasEZO_Sensors_e::FLOW: + addUnit(F("mL/min")); + break; + # endif // if P103_USE_FLOW case AtlasEZO_Sensors_e::UNKNOWN: break; } - } - else - { - P103_html_red(F("Unable to send status command to device")); + } else { + P103_html_red(F("Unable to send Status command to device")); success = false; break; } @@ -236,35 +315,30 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) addFormCheckBox(F("Status LED"), F("status_led"), P103_STATUS_LED); // Ability to see and change EC Probe Type (e.g., 0.1, 1.0, 10) - if (board_type == AtlasEZO_Sensors_e::EC) - { + if (board_type == AtlasEZO_Sensors_e::EC) { memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup - if (P103_send_I2C_command(P103_I2C_ADDRESS, F("K,?"), boarddata)) - { + if (P103_send_I2C_command(P103_I2C_ADDRESS, F("K,?"), boarddata)) { String ecProbeType(boarddata); - addFormTextBox(F("EC Probe Type"), F("ec_probe_type"), ecProbeType.substring(ecProbeType.lastIndexOf(',') + 1), 32); + addFormTextBox(F("EC Probe Type"), F("ec_probe_type"), parseString(ecProbeType, 2), 32); addFormCheckBox(F("Set Probe Type"), F("en_set_probe_type"), false); } } // calibrate - switch (board_type) - { + switch (board_type) { case AtlasEZO_Sensors_e::PH: { addFormSubHeader(F("pH Calibration")); - addFormNote(F( - "Calibration for pH-Probe could be 1 (single), 2 (single, low) or 3 point (single, low, high). The sequence is important.")); + addFormNote(F("Calibration for pH-Probe could be 1 (single), 2 (single, low) or 3 point (single, low, high)." + " The sequence is important.")); const int nb_calibration_points = P103_addCreate3PointCalibration(board_type, event, P103_I2C_ADDRESS, F("pH"), 0.0, 14.0, 2, 0.01); - if (nb_calibration_points > 1) - { + if (nb_calibration_points > 1) { memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup - if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Slope,?"), boarddata)) - { + if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Slope,?"), boarddata)) { addFormNote(concat(F("Answer to 'Slope' command : "), String(boarddata))); } } @@ -272,38 +346,44 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) } case AtlasEZO_Sensors_e::ORP: - { addFormSubHeader(F("ORP Calibration")); P103_addCreateSinglePointCalibration(board_type, event, P103_I2C_ADDRESS, F("mV"), 0.0, 1500.0, 0, 1.0); break; - } case AtlasEZO_Sensors_e::EC: - { addFormSubHeader(F("EC Calibration")); P103_addCreateDryCalibration(); P103_addCreate3PointCalibration(board_type, event, P103_I2C_ADDRESS, F("µS"), 0.0, 500000.0, 0, 1.0); break; - } case AtlasEZO_Sensors_e::DO: - { addFormSubHeader(F("DO Calibration")); P103_addDOCalibration(P103_I2C_ADDRESS); break; - } + + case AtlasEZO_Sensors_e::HUM: // No calibration + # if P103_USE_RTD + case AtlasEZO_Sensors_e::RTD: // TODO Decide what calibration data to retrieve/store + # endif // if P103_USE_RTD + # if P103_USE_FLOW + case AtlasEZO_Sensors_e::FLOW: // TODO Size/type of flow meter, default: 1/2", Flow rate, Conversion factor, Output values: total, + // flow rate + # endif // if P103_USE_FLOW case AtlasEZO_Sensors_e::UNKNOWN: break; } - // Clear calibration - P103_addClearCalibration(); + if ((AtlasEZO_Sensors_e::PH == board_type) || + (AtlasEZO_Sensors_e::ORP == board_type) || + (AtlasEZO_Sensors_e::EC == board_type)) { + // Clear calibration option, only when using calibration + P103_addClearCalibration(); + } // Temperature compensation - if ((board_type == AtlasEZO_Sensors_e::PH) || - (board_type == AtlasEZO_Sensors_e::EC) || - (board_type == AtlasEZO_Sensors_e::DO)) - { + if ((AtlasEZO_Sensors_e::PH == board_type) || + (AtlasEZO_Sensors_e::ORP == board_type) || + (AtlasEZO_Sensors_e::EC == board_type)) { ESPEASY_RULES_FLOAT_TYPE value{}; addFormSubHeader(F("Temperature compensation")); @@ -311,14 +391,20 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemperatureTemplate), sizeof(deviceTemperatureTemplate)); ZERO_TERMINATE(deviceTemperatureTemplate); addFormTextBox(F("Temperature "), F("_template"), deviceTemperatureTemplate, sizeof(deviceTemperatureTemplate)); - addFormNote(F("You can use a formula and idealy refer to a temp sensor (directly, via ESPEasyP2P or MQTT import)," - " e.g. '[Pool#Temperature]'. If you don't have a sensor, you could type a fixed value like '25' or '25.5'.")); + addFormNote(F("You can use a formula and idealy refer to a temp sensor" + # ifndef LIMIT_BUILD_SIZE + " (directly, via ESPEasyP2P or MQTT import)," + " e.g. '[Pool#Temperature]'. If you don't have a sensor, you could" + # else // ifndef LIMIT_BUILD_SIZE + " or" + # endif // ifndef LIMIT_BUILD_SIZE + " type a fixed value like '25' or '25.5'." + )); String deviceTemperatureTemplateString(deviceTemperatureTemplate); - String pooltempString(parseTemplate(deviceTemperatureTemplateString, 40)); + const String pooltempString(parseTemplate(deviceTemperatureTemplateString)); - if (Calculate(pooltempString, value) != CalculateReturnCode::OK) - { + if (Calculate(pooltempString, value) != CalculateReturnCode::OK) { addFormNote(F("It seems I can't parse your formula. Fixed value will be used!")); value = P103_FIXED_TEMP_VALUE; } @@ -334,24 +420,22 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) { board_type = static_cast(P103_BOARD_TYPE); - P103_I2C_ADDRESS = getFormItemInt(F("i2c")); + P103_I2C_ADDRESS = getFormItemInt(F("i2c")); + P103_UNCONNECTED_SETUP = isFormItemChecked(F("uncon")) ? 1 : 0; P103_SENSOR_VERSION = getFormItemFloat(F("sensorVersion")); - char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + // char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; - if (isFormItemChecked(F("status_led"))) - { + P103_STATUS_LED = isFormItemChecked(F("status_led")); + + if (P103_STATUS_LED) { P103_send_I2C_command(P103_I2C_ADDRESS, F("L,1"), boarddata); - } - else - { + } else { P103_send_I2C_command(P103_I2C_ADDRESS, F("L,0"), boarddata); } - P103_STATUS_LED = isFormItemChecked(F("status_led")); - if ((board_type == AtlasEZO_Sensors_e::EC) && isFormItemChecked(F("en_set_probe_type"))) - { + if ((board_type == AtlasEZO_Sensors_e::EC) && isFormItemChecked(F("en_set_probe_type"))) { # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("isFormItemChecked")); # endif // ifndef BUILD_NO_DEBUG @@ -368,58 +452,46 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) P103_CALIBRATION_LOW = getFormItemFloat(F("ref_cal_L")); P103_CALIBRATION_HIGH = getFormItemFloat(F("ref_cal_H")); - if (isFormItemChecked(F("en_cal_clear"))) - { + if (isFormItemChecked(F("en_cal_clear"))) { cmd += F("clear"); triggerCalibrate = true; - } - else if (isFormItemChecked(F("en_cal_dry"))) - { + } else if (isFormItemChecked(F("en_cal_dry"))) { cmd += F("dry"); triggerCalibrate = true; - } - else if (isFormItemChecked(F("en_cal_single"))) - { - if (board_type == AtlasEZO_Sensors_e::PH) - { + } else if (isFormItemChecked(F("en_cal_single"))) { + if (board_type == AtlasEZO_Sensors_e::PH) { cmd += F("mid,"); } cmd += P103_CALIBRATION_SINGLE; triggerCalibrate = true; - } - else if (isFormItemChecked(F("en_cal_L"))) - { + } else if (isFormItemChecked(F("en_cal_L"))) { cmd += F("low,"); cmd += P103_CALIBRATION_LOW; triggerCalibrate = true; - } - else if (isFormItemChecked(F("en_cal_H"))) - { + } else if (isFormItemChecked(F("en_cal_H"))) { cmd += F("high,"); cmd += P103_CALIBRATION_HIGH; triggerCalibrate = true; - } - else if (isFormItemChecked(F("en_cal_atm"))) - { + } else if (isFormItemChecked(F("en_cal_atm"))) { triggerCalibrate = true; - } - else if (isFormItemChecked(F("en_cal_0"))) - { + } else if (isFormItemChecked(F("en_cal_0"))) { cmd += '0'; triggerCalibrate = true; } - if (triggerCalibrate) - { + if (triggerCalibrate && + ((AtlasEZO_Sensors_e::PH == board_type) || + (AtlasEZO_Sensors_e::EC == board_type) || + (AtlasEZO_Sensors_e::DO == board_type)) + ) { memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup P103_send_I2C_command(P103_I2C_ADDRESS, cmd, boarddata); } - if ((board_type == AtlasEZO_Sensors_e::PH) || - (board_type == AtlasEZO_Sensors_e::EC) || - (board_type == AtlasEZO_Sensors_e::DO)) - { + if ((AtlasEZO_Sensors_e::PH == board_type) || + (AtlasEZO_Sensors_e::EC == board_type) || + (AtlasEZO_Sensors_e::DO == board_type)) { char deviceTemperatureTemplate[40] = { 0 }; String tmpString = webArg(F("_template")); safe_strncpy(deviceTemperatureTemplate, tmpString.c_str(), sizeof(deviceTemperatureTemplate) - 1); @@ -445,9 +517,9 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) String readCommand; - if ((board_type == AtlasEZO_Sensors_e::PH) || - (board_type == AtlasEZO_Sensors_e::EC) || - (board_type == AtlasEZO_Sensors_e::DO)) + if ((AtlasEZO_Sensors_e::PH == board_type) || + (AtlasEZO_Sensors_e::EC == board_type) || + (AtlasEZO_Sensors_e::DO == board_type)) { // first set the temperature of reading char deviceTemperatureTemplate[40] = { 0 }; @@ -455,41 +527,59 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) ZERO_TERMINATE(deviceTemperatureTemplate); String deviceTemperatureTemplateString(deviceTemperatureTemplate); - String temperatureString(parseTemplate(deviceTemperatureTemplateString, 40)); + String temperatureString(parseTemplate(deviceTemperatureTemplateString)); readCommand = F("RT,"); ESPEASY_RULES_FLOAT_TYPE temperatureReading{}; - if (Calculate(temperatureString, temperatureReading) != CalculateReturnCode::OK) - { + if (Calculate(temperatureString, temperatureReading) != CalculateReturnCode::OK) { temperatureReading = P103_FIXED_TEMP_VALUE; } readCommand += temperatureReading; } - else if (board_type == AtlasEZO_Sensors_e::ORP) - { + else if ((AtlasEZO_Sensors_e::ORP == board_type) || + (AtlasEZO_Sensors_e::HUM == board_type) + # if P103_USE_RTD + || (AtlasEZO_Sensors_e::RTD == board_type) + # endif // if P103_USE_RTD + # if P103_USE_FLOW + || (AtlasEZO_Sensors_e::FLOW == board_type) + # endif // if P103_USE_FLOW + ) { readCommand = F("R,"); } // ok, now we can read the sensor data - char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; - UserVar[event->BaseVarIndex] = -1; + // char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + UserVar[event->BaseVarIndex] = -1; + UserVar[event->BaseVarIndex + 1] = -1; + UserVar[event->BaseVarIndex + 2] = -1; - if (P103_send_I2C_command(P103_I2C_ADDRESS, readCommand, boarddata)) - { + if (P103_send_I2C_command(P103_I2C_ADDRESS, readCommand, boarddata)) { String sensorString(boarddata); - string2float(sensorString, UserVar[event->BaseVarIndex]); + string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex]); + + if (board_type == AtlasEZO_Sensors_e::HUM) { + string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 1]); + string2float(parseString(sensorString, 4), UserVar[event->BaseVarIndex + 2]); + } + + # if P103_USE_FLOW + + if (board_type == AtlasEZO_Sensors_e::FLOW) { + string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 1]); + } + # endif // if P103_USE_FLOW } // we read the voltagedata memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup UserVar[event->BaseVarIndex + 1] = -1; - if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Status"), boarddata)) - { + if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Status"), boarddata)) { String voltage(boarddata); - string2float(voltage.substring(voltage.lastIndexOf(',') + 1), UserVar[event->BaseVarIndex + 1]); + string2float(parseString(voltage, 2), UserVar[event->BaseVarIndex + 1]); } success = true; diff --git a/src/src/PluginStructs/P103_data_struct.cpp b/src/src/PluginStructs/P103_data_struct.cpp index 80cc6ba5c6..8d340fad7d 100644 --- a/src/src/PluginStructs/P103_data_struct.cpp +++ b/src/src/PluginStructs/P103_data_struct.cpp @@ -6,6 +6,24 @@ // The other containing an allocatted char array for answer // Returns true on success, false otherwise +const __FlashStringHelper* toString(AtlasEZO_Sensors_e sensor) { + switch (sensor) { + case AtlasEZO_Sensors_e::PH: return F("pH"); + case AtlasEZO_Sensors_e::ORP: return F("Oxidation Reduction Potential"); + case AtlasEZO_Sensors_e::EC: return F("Electric conductivity"); + case AtlasEZO_Sensors_e::DO: return F("Dissolved Oxigen"); + case AtlasEZO_Sensors_e::HUM: return F("Humidity"); + # if P103_USE_RTD + case AtlasEZO_Sensors_e::RTD: return F("Thermosensor"); + # endif // if P103_USE_RTD + # if P103_USE_FLOW + case AtlasEZO_Sensors_e::FLOW: return F("Flow meter"); + # endif // if P103_USE_FLOW + case AtlasEZO_Sensors_e::UNKNOWN: break; + } + return F("Unknown"); +} + bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensordata) { sensordata[0] = '\0'; @@ -28,7 +46,14 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda if (error != 0) { - addLog(LOG_LEVEL_ERROR, F("Wire.endTransmission() returns error: Check Atlas shield, pH, ORP, EC and DO are supported.")); + addLog(LOG_LEVEL_ERROR, F("Wire.endTransmission() returns error: Check Atlas shield, pH, ORP, EC, DO, HUM" + # if P103_USE_RTD + ", RTD" + # endif // if P103_USE_RTD + # if P103_USE_FLOW + ", FLOW" + # endif // if P103_USE_FLOW + " are supported.")); return false; } diff --git a/src/src/PluginStructs/P103_data_struct.h b/src/src/PluginStructs/P103_data_struct.h index f615e38964..a48a57d8d1 100644 --- a/src/src/PluginStructs/P103_data_struct.h +++ b/src/src/PluginStructs/P103_data_struct.h @@ -8,17 +8,40 @@ # include "../Globals/RulesCalculate.h" +# define P103_USE_RTD 0 // Defaults to UART, so disabled for now +# define P103_USE_FLOW 0 // Defaults to UART, so disabled for now + +# ifdef PLUGIN_SET_MAX // Enable RTD and FLOW for MAX builds +# if !P103_USE_RTD +# undef P103_USE_RTD +# define P103_USE_RTD 1 +# endif // if !P103_USE_RTD +# if !P103_USE_FLOW +# undef P103_USE_FLOW +# define P103_USE_FLOW 1 +# endif // if !P103_USE_FLOW +# endif // ifdef PLUGIN_SET_MAX + enum class AtlasEZO_Sensors_e : uint8_t { UNKNOWN = 0u, PH = 1u, ORP = 2u, EC = 3u, DO = 4u, + HUM = 5u, + # if P103_USE_RTD + RTD = 6u, // Defaults to UART, so disabled for now + # endif // if P103_USE_RTD + # if P103_USE_FLOW + FLOW = 7u, // Defaults to UART, so disabled for now + # endif // if P103_USE_FLOW }; # define P103_BOARD_TYPE PCONFIG(0) # define P103_I2C_ADDRESS PCONFIG(1) # define P103_STATUS_LED PCONFIG(2) +# define P103_NR_OUTPUT_VALUES PCONFIG(3) +# define P103_UNCONNECTED_SETUP PCONFIG(4) # define P103_SENSOR_VERSION PCONFIG_FLOAT(0) # define P103_CALIBRATION_SINGLE PCONFIG_FLOAT(1) # define P103_CALIBRATION_LOW PCONFIG_FLOAT(2) @@ -26,13 +49,14 @@ enum class AtlasEZO_Sensors_e : uint8_t { # define ATLAS_EZO_RETURN_ARRAY_SIZE 33 // Max expected result 32 bytes + \0 -# define P103_ATLASEZO_I2C_NB_OPTIONS 4 // was: 6 see comment below at 'const int i2cAddressValues' - # define P103_FIXED_TEMP_VALUE 20 // Temperature correction for pH and EC sensor if no temperature is given from calculation -bool P103_send_I2C_command(uint8_t I2Caddress, - const String& cmd, - char *sensordata); // Forward declarations +const __FlashStringHelper* toString(AtlasEZO_Sensors_e sensor); + + +bool P103_send_I2C_command(uint8_t I2Caddress, + const String& cmd, + char *sensordata); // Forward declarations void P103_addDisabler(); void P103_html_color_message(const __FlashStringHelper *color, From e10e8ddf449a8dc793cf4aae9311c982cfcd709e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 22 Oct 2023 14:41:36 +0200 Subject: [PATCH 2/7] [P103] Fix some issues, add logging for analysis --- src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index a9455d1d92..5d377e06cf 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -16,6 +16,7 @@ // only i2c mode is supported /** Changelog: + * 2023-10-22 tonhuisman: Fix some irregularities, add logging for status read (UI) and value(s) read (INFO log) * 2023-10-17 tonhuisman: Add support for EZO HUM, RTD and FLOW sensor modules (I2C only!) (RTD, FLOW disabled, default to UART mode) * 2023-01-08 tonhuisman: Replace ambiguous #define UNKNOWN, move support functions to plugin_struct source * 2023-01-07 tonhuisman: Refactored strings (a.o. shorter names for WEBFORM_LOAD and WEBFORM_SAVE events), separate javascript function @@ -235,6 +236,9 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) if (P103_send_I2C_command(P103_I2C_ADDRESS, F("Status"), boarddata) || P103_UNCONNECTED_SETUP) { String boardStatus(boarddata); + addRowLabel(F("Board status")); + addHtml(boardStatus); + addRowLabel(F("Board restart code")); # ifndef BUILD_NO_DEBUG @@ -553,22 +557,24 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) // ok, now we can read the sensor data // char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; UserVar[event->BaseVarIndex] = -1; - UserVar[event->BaseVarIndex + 1] = -1; UserVar[event->BaseVarIndex + 2] = -1; + UserVar[event->BaseVarIndex + 3] = -1; if (P103_send_I2C_command(P103_I2C_ADDRESS, readCommand, boarddata)) { String sensorString(boarddata); + addLog(LOG_LEVEL_INFO, concat(F("P103: READ result: "), sensorString)); + string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex]); if (board_type == AtlasEZO_Sensors_e::HUM) { - string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 1]); - string2float(parseString(sensorString, 4), UserVar[event->BaseVarIndex + 2]); + string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 2]); + string2float(parseString(sensorString, 4), UserVar[event->BaseVarIndex + 3]); } # if P103_USE_FLOW if (board_type == AtlasEZO_Sensors_e::FLOW) { - string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 1]); + string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 2]); } # endif // if P103_USE_FLOW } From 9c3e769cf1141eed5f2fda5da7f7efeeaa379056 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 22 Oct 2023 21:55:56 +0200 Subject: [PATCH 3/7] [P103] Fix more issues, add/apply settings for EZO-HUM --- src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 73 ++++++++++++++++++---- src/src/PluginStructs/P103_data_struct.cpp | 30 +++++++++ src/src/PluginStructs/P103_data_struct.h | 24 ++++--- 3 files changed, 104 insertions(+), 23 deletions(-) diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index 5d377e06cf..5684a0c3d8 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -16,6 +16,8 @@ // only i2c mode is supported /** Changelog: + * 2023-10-22 tonhuisman: Fix more irregularities, read configured EZO-HUM output options, and add options to enable/disable + * Temperature and Dew point values * 2023-10-22 tonhuisman: Fix some irregularities, add logging for status read (UI) and value(s) read (INFO log) * 2023-10-17 tonhuisman: Add support for EZO HUM, RTD and FLOW sensor modules (I2C only!) (RTD, FLOW disabled, default to UART mode) * 2023-01-08 tonhuisman: Replace ambiguous #define UNKNOWN, move support functions to plugin_struct source @@ -25,6 +27,8 @@ * Reuse char arrays instead of instantiating a new one */ +# include "src/PluginStructs/P103_data_struct.h" + # define PLUGIN_103 # define PLUGIN_ID_103 103 # define PLUGIN_NAME_103 "Environment - Atlas EZO pH ORP EC DO HUM" @@ -36,11 +40,9 @@ # endif // if P103_USE_FLOW # define PLUGIN_VALUENAME1_103 "SensorData" # define PLUGIN_VALUENAME2_103 "Voltage" -# define PLUGIN_VALUENAME3_103 "Temperature" // Only used for HUM +# define PLUGIN_VALUENAME3_103 "Temperature" // TODO Only used for HUM TODO: Fix for EZO-FLOW extra reading # define PLUGIN_VALUENAME4_103 "Dewpoint" // Only used for HUM -# include "src/PluginStructs/P103_data_struct.h" - boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; @@ -58,6 +60,10 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + bool _HUMhasHum = true; // EZO-HUM options (& defaults) + bool _HUMhasTemp = false; + bool _HUMhasDew = false; + switch (function) { case PLUGIN_DEVICE_ADD: @@ -122,7 +128,7 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) # if P103_USE_FLOW ", FLOW: 0x68" # endif // if P103_USE_FLOW - ". The plugin can detect the type of device.")); + ". The plugin can recognize the type of device.")); } else { success = intArrayContains(i2c_nr_elements, i2cAddressValues, event->Par1); } @@ -173,7 +179,7 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) // Not recognized, lets assume I2C address is correct, so we can setup the options for (uint8_t i = 0; i < i2c_nr_elements; ++i) { if (i2cAddressValues[i] == P103_I2C_ADDRESS) { - bType = i * 4; // Divided im the next check + bType = i * 4; // Divided in the next check break; } } @@ -289,9 +295,27 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) case AtlasEZO_Sensors_e::DO: addUnit(F("mg/L")); break; - case AtlasEZO_Sensors_e::HUM: // TODO Show Temp & Dew point also + case AtlasEZO_Sensors_e::HUM: // TODO Show Temp & Dew point also { addUnit(F("%RH")); + memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup + + if (P103_getHUMOutputOptions(event, + _HUMhasHum, + _HUMhasTemp, + _HUMhasDew)) { + if (_HUMhasTemp) { + addRowLabel(F("Temperature")); + addHtmlFloat(UserVar[event->BaseVarIndex + 2]); + addUnit(F("°C")); + } + + if (_HUMhasDew) { + addRowLabel(F("Dew point")); + addHtmlFloat(UserVar[event->BaseVarIndex + 3]); + addUnit(F("°C")); + } + } break; } # if P103_USE_RTD @@ -325,7 +349,7 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) if (P103_send_I2C_command(P103_I2C_ADDRESS, F("K,?"), boarddata)) { String ecProbeType(boarddata); - addFormTextBox(F("EC Probe Type"), F("ec_probe_type"), parseString(ecProbeType, 2), 32); + addFormTextBox(F("EC Probe Type"), F("ec_probe_type"), parseStringKeepCase(ecProbeType, 2), 32); addFormCheckBox(F("Set Probe Type"), F("en_set_probe_type"), false); } } @@ -416,6 +440,12 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) addFormNote(concat(F("Actual value: "), toString(value, 2))); } + if (AtlasEZO_Sensors_e::HUM == board_type) { + addFormSubHeader(F("EZO-HUM Options")); + addFormCheckBox(F("Enable Temperature reading"), F("hum_temp"), _HUMhasTemp); + addFormCheckBox(F("Enable Dew point reading"), F("hum_dew"), _HUMhasTemp); + } + success = true; break; } @@ -429,8 +459,6 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) P103_SENSOR_VERSION = getFormItemFloat(F("sensorVersion")); - // char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; - P103_STATUS_LED = isFormItemChecked(F("status_led")); if (P103_STATUS_LED) { @@ -505,6 +533,25 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) sizeof(deviceTemperatureTemplate))); } + if ((AtlasEZO_Sensors_e::HUM == board_type) && P103_getHUMOutputOptions(event, + _HUMhasHum, + _HUMhasTemp, + _HUMhasDew)) { + if (!_HUMhasHum) { // If humidity not enabled, then enable it + P103_send_I2C_command(P103_I2C_ADDRESS, F("O,Hum,1"), boarddata); + } + bool _humOpt = isFormItemChecked(F("hum_temp")); + + if (_humOpt != _HUMhasTemp) { + P103_send_I2C_command(P103_I2C_ADDRESS, concat(F("O,T,"), _humOpt ? 1 : 0), boarddata); + } + _humOpt = isFormItemChecked(F("hum_dew")); + + if (_humOpt != _HUMhasDew) { + P103_send_I2C_command(P103_I2C_ADDRESS, concat(F("O,Dew,"), _humOpt ? 1 : 0), boarddata); + } + } + success = true; break; } @@ -564,17 +611,17 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) String sensorString(boarddata); addLog(LOG_LEVEL_INFO, concat(F("P103: READ result: "), sensorString)); - string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex]); + string2float(parseString(sensorString, 1), UserVar[event->BaseVarIndex]); if (board_type == AtlasEZO_Sensors_e::HUM) { - string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 2]); - string2float(parseString(sensorString, 4), UserVar[event->BaseVarIndex + 3]); + string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex + 2]); + string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 3]); } # if P103_USE_FLOW if (board_type == AtlasEZO_Sensors_e::FLOW) { - string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 2]); + string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex + 2]); } # endif // if P103_USE_FLOW } diff --git a/src/src/PluginStructs/P103_data_struct.cpp b/src/src/PluginStructs/P103_data_struct.cpp index 8d340fad7d..edfae8f96c 100644 --- a/src/src/PluginStructs/P103_data_struct.cpp +++ b/src/src/PluginStructs/P103_data_struct.cpp @@ -336,4 +336,34 @@ int P103_addCreate3PointCalibration(AtlasEZO_Sensors_e board_type, return nb_calibration_points; } +bool P103_getHUMOutputOptions(struct EventStruct *event, + bool & _HUMhasHum, + bool & _HUMhasTemp, + bool & _HUMhasDew) { + bool result = false; + + char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + + if ((result = P103_send_I2C_command(P103_I2C_ADDRESS, F("O,?"), boarddata))) { + String outputs(boarddata); + int o = 2; + String outPar = parseString(outputs, o); + + while (!outPar.isEmpty()) { + if (equals(outPar, F("hum"))) { + _HUMhasHum = true; + } else + if (equals(outPar, F("t"))) { + _HUMhasTemp = true; + } else + if (equals(outPar, F("dew"))) { + _HUMhasDew = true; + } + o++; + outPar = parseString(outputs, o); + } + } + return result; +} + #endif // ifdef USES_P103 diff --git a/src/src/PluginStructs/P103_data_struct.h b/src/src/PluginStructs/P103_data_struct.h index a48a57d8d1..7975af3d9a 100644 --- a/src/src/PluginStructs/P103_data_struct.h +++ b/src/src/PluginStructs/P103_data_struct.h @@ -11,16 +11,16 @@ # define P103_USE_RTD 0 // Defaults to UART, so disabled for now # define P103_USE_FLOW 0 // Defaults to UART, so disabled for now -# ifdef PLUGIN_SET_MAX // Enable RTD and FLOW for MAX builds -# if !P103_USE_RTD -# undef P103_USE_RTD -# define P103_USE_RTD 1 -# endif // if !P103_USE_RTD -# if !P103_USE_FLOW -# undef P103_USE_FLOW -# define P103_USE_FLOW 1 -# endif // if !P103_USE_FLOW -# endif // ifdef PLUGIN_SET_MAX +// # ifdef PLUGIN_SET_MAX // Enable RTD and FLOW for MAX builds +// # if !P103_USE_RTD +// # undef P103_USE_RTD +// # define P103_USE_RTD 1 +// # endif // if !P103_USE_RTD +// # if !P103_USE_FLOW +// # undef P103_USE_FLOW +// # define P103_USE_FLOW 1 +// # endif // if !P103_USE_FLOW +// # endif // ifdef PLUGIN_SET_MAX enum class AtlasEZO_Sensors_e : uint8_t { UNKNOWN = 0u, @@ -84,6 +84,10 @@ int P103_addCreate3PointCalibration(AtlasEZO_Sensors_e board_type, float max, uint8_t nrDecimals, float stepsize); +bool P103_getHUMOutputOptions(struct EventStruct *event, + bool & _HUMhasHum, + bool & _HUMhasTemp, + bool & _HUMhasDew); #endif // ifdef USED_P103 #endif // ifndef PLUGINSTRUCTS_P103_DATA_STRUCT_H From 48f8b8daba93c28e493ff248cde001d5c1a07230 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 23 Oct 2023 21:03:33 +0200 Subject: [PATCH 4/7] [P103] Handle EZO-HUM firmware issue of unexpected 'Dew' in output --- src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index 5684a0c3d8..84051e33a9 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -16,6 +16,9 @@ // only i2c mode is supported /** Changelog: + * 2023-10-23 tonhuisman: Handle EZO-HUM firmware issue of including 'Dew,' in the result values + * // TODO Rewrite plugin using PluginDataStruct so it will allow proper async handling of commands requiring 300 msec delay before reading + * responses * 2023-10-22 tonhuisman: Fix more irregularities, read configured EZO-HUM output options, and add options to enable/disable * Temperature and Dew point values * 2023-10-22 tonhuisman: Fix some irregularities, add logging for status read (UI) and value(s) read (INFO log) @@ -613,9 +616,14 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) string2float(parseString(sensorString, 1), UserVar[event->BaseVarIndex]); - if (board_type == AtlasEZO_Sensors_e::HUM) { + if (board_type == AtlasEZO_Sensors_e::HUM) { // TODO Fix reading Dew point without Temperature enabled string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex + 2]); - string2float(parseString(sensorString, 3), UserVar[event->BaseVarIndex + 3]); + String dewVal = parseString(sensorString, 3); + + if (equals(dewVal, F("dew"))) { // Handle EZO-HUM firmware bug including 'Dew,' in the result string + dewVal = parseString(sensorString, 4); + } + string2float(dewVal, UserVar[event->BaseVarIndex + 3]); } # if P103_USE_FLOW From 156266449c2577e0472e0113d8fc68b90228fe5e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 1 Feb 2024 23:32:15 +0100 Subject: [PATCH 5/7] [P103] Fix to apply new `UserVar` usage --- src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index afee4eaa03..dae76e36ca 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -610,36 +610,42 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) } // ok, now we can read the sensor data - char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + // char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup UserVar.setFloat(event->TaskIndex, 0, -1); if (P103_send_I2C_command(P103_I2C_ADDRESS, readCommand, boarddata)) { String sensorString(boarddata); addLog(LOG_LEVEL_INFO, concat(F("P103: READ result: "), sensorString)); - float tmpFloat{}; - string2float(parseString(sensorString, 1), tmpFloat); - UserVar.setFloat(event->TaskIndex, 0, tmpFloat); + float sensor_f{}; + + if (string2float(parseString(sensorString, 1), sensor_f)) { + UserVar.setFloat(event->TaskIndex, 0, sensor_f); + } if (board_type == AtlasEZO_Sensors_e::HUM) { // TODO Fix reading Dew point without Temperature enabled - string2float(parseString(sensorString, 2), tmpFloat); - UserVar.setFloat(event->TaskIndex, 2, tmpFloat); + if (string2float(parseString(sensorString, 2), sensor_f)) { + UserVar.setFloat(event->TaskIndex, 2, sensor_f); + } String dewVal = parseString(sensorString, 3); if (equals(dewVal, F("dew"))) { // Handle EZO-HUM firmware bug including 'Dew,' in the result string dewVal = parseString(sensorString, 4); } - string2float(dewVal, tmpFloat); - UserVar.setFloat(event->TaskIndex, 3, tmpFloat); + + if (string2float(dewVal, sensor_f)) { + UserVar.setFloat(event->TaskIndex, 3, sensor_f); + } } # if P103_USE_FLOW - if (board_type == AtlasEZO_Sensors_e::FLOW) { - string2float(parseString(sensorString, 2), UserVar[event->BaseVarIndex + 2]); + if ((board_type == AtlasEZO_Sensors_e::FLOW) && + string2float(parseString(sensorString, 2), sensor_f)) { + UserVar.setFloat(event->TaskIndex, 2, sensor_f); } # endif // if P103_USE_FLOW - float sensor_f{}; string2float(sensorString, sensor_f); UserVar.setFloat(event->TaskIndex, 0, sensor_f); } From 085255743d9e134a85a3d53e2bc35892aebe77a3 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 19 Oct 2024 17:29:07 +0200 Subject: [PATCH 6/7] [P103] Javascript fixes, code improvements --- src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 84 +++++++------------ src/src/PluginStructs/P103_data_struct.cpp | 94 +++++++++++++--------- src/src/PluginStructs/P103_data_struct.h | 11 +-- 3 files changed, 88 insertions(+), 101 deletions(-) diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index dae76e36ca..ec2553f7bd 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -16,6 +16,7 @@ // only i2c mode is supported /** Changelog: + * 2024-10-19 tonhuisman: Fix javascript errors, some code improvements * 2023-10-23 tonhuisman: Handle EZO-HUM firmware issue of including 'Dew,' in the result values * // TODO Rewrite plugin using PluginDataStruct so it will allow proper async handling of commands requiring 300 msec delay before reading * responses @@ -61,7 +62,7 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) }; constexpr int i2c_nr_elements = NR_ELEMENTS(i2cAddressValues); - char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE]{}; bool _HUMhasHum = true; // EZO-HUM options (& defaults) bool _HUMhasTemp = false; @@ -71,17 +72,14 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) { case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_103; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; + Device[++deviceCount].Number = PLUGIN_ID_103; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; break; } @@ -248,34 +246,16 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) addRowLabel(F("Board status")); addHtml(boardStatus); - addRowLabel(F("Board restart code")); - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, boardStatus); + addLog(LOG_LEVEL_DEBUG, concat(F("Board status: "), boardStatus)); # endif // ifndef BUILD_NO_DEBUG - // FIXME tonhuisman: To improve + addRowLabel(F("Board restart code")); + const String stat = parseStringKeepCase(boardStatus, 2); if (!stat.isEmpty()) { - switch (stat[0]) { - case 'P': - addHtml(F("powered off")); - break; - case 'S': - addHtml(F("software reset")); - break; - case 'B': - addHtml(F("brown out")); - break; - case 'W': - addHtml(F("watch dog")); - break; - case 'U': - default: - addHtml(F("unknown")); - break; - } + addHtml(P103_statusToString(stat[0])); } addRowLabel(F("Board voltage")); @@ -283,7 +263,7 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) addUnit('V'); addRowLabel(F("Sensor Data")); - addHtmlFloat(UserVar[event->BaseVarIndex]); + addHtmlFloat(UserVar.getFloat(event->TaskIndex, 0)); switch (board_type) { case AtlasEZO_Sensors_e::PH: @@ -298,8 +278,7 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) case AtlasEZO_Sensors_e::DO: addUnit(F("mg/L")); break; - case AtlasEZO_Sensors_e::HUM: // TODO Show Temp & Dew point also - { + case AtlasEZO_Sensors_e::HUM: addUnit(F("%RH")); memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup @@ -309,24 +288,21 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) _HUMhasDew)) { if (_HUMhasTemp) { addRowLabel(F("Temperature")); - addHtmlFloat(UserVar[event->BaseVarIndex + 2]); + addHtmlFloat(UserVar.getFloat(event->TaskIndex, 2)); addUnit(F("°C")); } if (_HUMhasDew) { addRowLabel(F("Dew point")); - addHtmlFloat(UserVar[event->BaseVarIndex + 3]); + addHtmlFloat(UserVar.getFloat(event->TaskIndex, 3)); addUnit(F("°C")); } } break; - } # if P103_USE_RTD case AtlasEZO_Sensors_e::RTD: - { addUnit(F("°C")); // TODO Read current scale (C/F/K) from device, show flow break; - } # endif // if P103_USE_RTD # if P103_USE_FLOW case AtlasEZO_Sensors_e::FLOW: @@ -418,11 +394,11 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) ESPEASY_RULES_FLOAT_TYPE value{}; addFormSubHeader(F("Temperature compensation")); - char deviceTemperatureTemplate[40] = { 0 }; + char deviceTemperatureTemplate[40]{}; LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemperatureTemplate), sizeof(deviceTemperatureTemplate)); ZERO_TERMINATE(deviceTemperatureTemplate); addFormTextBox(F("Temperature "), F("_template"), deviceTemperatureTemplate, sizeof(deviceTemperatureTemplate)); - addFormNote(F("You can use a formula and idealy refer to a temp sensor" + addFormNote(F("You can use a formula and ideally refer to a temp sensor" # ifndef LIMIT_BUILD_SIZE " (directly, via ESPEasyP2P or MQTT import)," " e.g. '[Pool#Temperature]'. If you don't have a sensor, you could" @@ -463,20 +439,15 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) P103_SENSOR_VERSION = getFormItemFloat(F("sensorVersion")); - P103_STATUS_LED = isFormItemChecked(F("status_led")); + P103_STATUS_LED = isFormItemChecked(F("status_led")) ? 1 : 0; - if (P103_STATUS_LED) { - P103_send_I2C_command(P103_I2C_ADDRESS, F("L,1"), boarddata); - } else { - P103_send_I2C_command(P103_I2C_ADDRESS, F("L,0"), boarddata); - } + P103_send_I2C_command(P103_I2C_ADDRESS, concat(F("L,"), P103_STATUS_LED), boarddata); if ((board_type == AtlasEZO_Sensors_e::EC) && isFormItemChecked(F("en_set_probe_type"))) { # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, F("isFormItemChecked")); # endif // ifndef BUILD_NO_DEBUG - String probeType(F("K,")); - probeType += webArg(F("ec_probe_type")); + const String probeType = concat(F("K,"), webArg(F("ec_probe_type"))); memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup P103_send_I2C_command(P103_I2C_ADDRESS, probeType, boarddata); } @@ -532,8 +503,8 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) if ((AtlasEZO_Sensors_e::PH == board_type) || (AtlasEZO_Sensors_e::EC == board_type) || (AtlasEZO_Sensors_e::DO == board_type)) { - char deviceTemperatureTemplate[40] = { 0 }; - String tmpString = webArg(F("_template")); + char deviceTemperatureTemplate[40]{}; + String tmpString = webArg(F("_template")); safe_strncpy(deviceTemperatureTemplate, tmpString.c_str(), sizeof(deviceTemperatureTemplate) - 1); ZERO_TERMINATE(deviceTemperatureTemplate); // be sure that our string ends with a \0 @@ -581,12 +552,12 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) (AtlasEZO_Sensors_e::DO == board_type)) { // first set the temperature of reading - char deviceTemperatureTemplate[40] = { 0 }; + char deviceTemperatureTemplate[40]{}; LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemperatureTemplate), sizeof(deviceTemperatureTemplate)); ZERO_TERMINATE(deviceTemperatureTemplate); String deviceTemperatureTemplateString(deviceTemperatureTemplate); - String temperatureString(parseTemplate(deviceTemperatureTemplateString)); + const String temperatureString(parseTemplate(deviceTemperatureTemplateString)); readCommand = F("RT,"); ESPEASY_RULES_FLOAT_TYPE temperatureReading{}; @@ -610,7 +581,6 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) } // ok, now we can read the sensor data - // char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; memset(boarddata, 0, ATLAS_EZO_RETURN_ARRAY_SIZE); // Cleanup UserVar.setFloat(event->TaskIndex, 0, -1); diff --git a/src/src/PluginStructs/P103_data_struct.cpp b/src/src/PluginStructs/P103_data_struct.cpp index 2d81395d2d..19d95a5a88 100644 --- a/src/src/PluginStructs/P103_data_struct.cpp +++ b/src/src/PluginStructs/P103_data_struct.cpp @@ -2,28 +2,41 @@ #ifdef USES_P103 -// Call this function with two char arrays, one containing the command -// The other containing an allocatted char array for answer -// Returns true on success, false otherwise - const __FlashStringHelper* toString(AtlasEZO_Sensors_e sensor) { switch (sensor) { - case AtlasEZO_Sensors_e::PH: return F("pH"); - case AtlasEZO_Sensors_e::ORP: return F("Oxidation Reduction Potential"); - case AtlasEZO_Sensors_e::EC: return F("Electric conductivity"); - case AtlasEZO_Sensors_e::DO: return F("Dissolved Oxigen"); - case AtlasEZO_Sensors_e::HUM: return F("Humidity"); + case AtlasEZO_Sensors_e::PH: return F("pH (Potential of Hydrogen)"); + case AtlasEZO_Sensors_e::ORP: return F("ORP (Oxidation Reduction Potential)"); + case AtlasEZO_Sensors_e::EC: return F("EC (Electric conductivity)"); + case AtlasEZO_Sensors_e::DO: return F("DO (Dissolved Oxigen)"); + case AtlasEZO_Sensors_e::HUM: return F("HUM (Humidity)"); # if P103_USE_RTD - case AtlasEZO_Sensors_e::RTD: return F("Thermosensor"); + case AtlasEZO_Sensors_e::RTD: return F("RTD (Thermosensor)"); # endif // if P103_USE_RTD # if P103_USE_FLOW - case AtlasEZO_Sensors_e::FLOW: return F("Flow meter"); + case AtlasEZO_Sensors_e::FLOW: return F("FLOW (Flow meter)"); # endif // if P103_USE_FLOW case AtlasEZO_Sensors_e::UNKNOWN: break; } return F("Unknown"); } +const __FlashStringHelper* P103_statusToString(char status) { + switch (status) { + case 'P': + return F("powered off"); + case 'S': + return F("software reset"); + case 'B': + return F("brown out"); + case 'W': + return F("watch dog"); + case 'U': + default: + break; + } + return F("unknown"); +} + bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensordata) { sensordata[0] = '\0'; @@ -41,6 +54,7 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda } # endif // ifndef BUILD_NO_DEBUG Wire.beginTransmission(I2Caddress); + for (size_t i = 0; i < cmd.length(); ++i) { Wire.write(static_cast(cmd[i])); } @@ -78,7 +92,7 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda in_char = Wire.read(); if (in_char == 0) - { // if we receive a null caracter, we're done + { // if we receive a null character, we're done while (Wire.available()) { // purge the data line if needed Wire.read(); @@ -88,7 +102,7 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda } else { - if (sensor_bytes_received > ATLAS_EZO_RETURN_ARRAY_SIZE) + if (sensor_bytes_received >= ATLAS_EZO_RETURN_ARRAY_SIZE) { addLog(LOG_LEVEL_ERROR, F("< result array to short!")); return false; @@ -102,7 +116,6 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda switch (i2c_response_code) { case 1: - { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { @@ -110,7 +123,6 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda } # endif // ifndef BUILD_NO_DEBUG break; - } case 2: # ifndef BUILD_NO_DEBUG @@ -137,8 +149,8 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda int P103_getCalibrationPoints(uint8_t i2cAddress) { - int nb_calibration_points = -1; - char sensordata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + int nb_calibration_points = -1; + char sensordata[ATLAS_EZO_RETURN_ARRAY_SIZE]{}; if (P103_send_I2C_command(i2cAddress, F("Cal,?"), sensordata)) { @@ -177,25 +189,29 @@ void P103_html_green(const String& message) { void P103_addDisabler() { // disabler(): argument(s) on 'true' will set that checkbox to false! non-boolean ignores argument // addHtml(F("\n")); // Minified: addHtml(F("\n")); } @@ -203,7 +219,7 @@ void P103_addClearCalibration() { addRowLabel(F("Clear calibration")); addFormCheckBox(F("Clear"), F("en_cal_clear"), false); - addHtml(F("\n")); + addHtml(F("\n")); addFormNote(F("Attention! This will reset all calibrated data. New calibration will be needed!!!")); } @@ -221,11 +237,11 @@ int P103_addDOCalibration(uint8_t I2Cchoice) addRowLabel(F("Calibrate to atmospheric oxygen levels")); addFormCheckBox(F("Enable"), F("en_cal_atm"), false); - addHtml(F("\n")); + addHtml(F("\n")); addRowLabel(F("Calibrate device to 0 dissolved oxygen")); addFormCheckBox(F("Enable"), F("en_cal_0"), false); - addHtml(F("\n")); + addHtml(F("\n")); if (nb_calibration_points > 0) { @@ -243,7 +259,7 @@ void P103_addCreateDryCalibration() { addRowLabel(F("Dry calibration")); addFormCheckBox(F("Enable"), F("en_cal_dry"), false); - addHtml(F("\n")); + addHtml(F("\n")); addFormNote(F("Dry calibration must always be done first!")); addFormNote(F("Calibration for pH-Probe could be 1 (single) or 2 point (low, high).")); } @@ -288,7 +304,7 @@ int P103_addCreateSinglePointCalibration(AtlasEZO_Sensors_e board_type, } } addFormCheckBox(F("Enable"), F("en_cal_single"), false); - addHtml(F("\n")); + addHtml(F("\n")); return nb_calibration_points; } @@ -317,7 +333,7 @@ int P103_addCreate3PointCalibration(AtlasEZO_Sensors_e board_type, P103_html_orange(F("Not yet calibrated")); } addFormCheckBox(F("Enable"), F("en_cal_L"), false); - addHtml(F("\n")); + addHtml(F("\n")); addRowLabel(F("High calibration")); addFormFloatNumberBox(F("Ref high point"), F("ref_cal_H"), P103_CALIBRATION_HIGH, min, max, nrDecimals, stepsize); @@ -333,7 +349,7 @@ int P103_addCreate3PointCalibration(AtlasEZO_Sensors_e board_type, P103_html_orange(F("Not yet calibrated")); } addFormCheckBox(F("Enable"), F("en_cal_H"), false); - addHtml(F("\n")); + addHtml(F("\n")); return nb_calibration_points; } @@ -344,7 +360,7 @@ bool P103_getHUMOutputOptions(struct EventStruct *event, bool & _HUMhasDew) { bool result = false; - char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE] = { 0 }; + char boarddata[ATLAS_EZO_RETURN_ARRAY_SIZE]{}; if ((result = P103_send_I2C_command(P103_I2C_ADDRESS, F("O,?"), boarddata))) { String outputs(boarddata); diff --git a/src/src/PluginStructs/P103_data_struct.h b/src/src/PluginStructs/P103_data_struct.h index 7975af3d9a..5dd82a4d64 100644 --- a/src/src/PluginStructs/P103_data_struct.h +++ b/src/src/PluginStructs/P103_data_struct.h @@ -51,12 +51,13 @@ enum class AtlasEZO_Sensors_e : uint8_t { # define P103_FIXED_TEMP_VALUE 20 // Temperature correction for pH and EC sensor if no temperature is given from calculation +// Forward declarations const __FlashStringHelper* toString(AtlasEZO_Sensors_e sensor); - +const __FlashStringHelper* P103_statusToString(char status); bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, - char *sensordata); // Forward declarations + char *sensordata); void P103_addDisabler(); void P103_html_color_message(const __FlashStringHelper *color, @@ -85,9 +86,9 @@ int P103_addCreate3PointCalibration(AtlasEZO_Sensors_e board_type, uint8_t nrDecimals, float stepsize); bool P103_getHUMOutputOptions(struct EventStruct *event, - bool & _HUMhasHum, - bool & _HUMhasTemp, - bool & _HUMhasDew); + bool & _HUMhasHum, + bool & _HUMhasTemp, + bool & _HUMhasDew); #endif // ifdef USED_P103 #endif // ifndef PLUGINSTRUCTS_P103_DATA_STRUCT_H From ce60f4342434c044cd2f876201f0039d92cd7c6f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 19 Oct 2024 20:43:25 +0200 Subject: [PATCH 7/7] [Build] Fix: Use package with Solo1 'autodetect' (Solo1 specific packaged removed) --- platformio_esp32_solo1.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio_esp32_solo1.ini b/platformio_esp32_solo1.ini index 9854619dc8..4f90b20fad 100644 --- a/platformio_esp32_solo1.ini +++ b/platformio_esp32_solo1.ini @@ -3,7 +3,7 @@ [esp32_solo1_common_LittleFS] extends = esp32_base_idf5 platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF53 -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/3020/framework-arduinoespressif32-solo1-release_v5.3-98aecc7e.zip +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/3085/framework-arduinoespressif32-all-release_v5.3-4c885a26.zip build_flags = ${esp32_base_idf5.build_flags} -DFEATURE_ARDUINO_OTA=1 -DUSE_LITTLEFS