diff --git a/src/_P094_CULReader.ino b/src/_P094_CULReader.ino index d07d8c3599..b2cae7b197 100644 --- a/src/_P094_CULReader.ino +++ b/src/_P094_CULReader.ino @@ -9,34 +9,26 @@ // Allows to control the mode of the CUL receiver // +# include "src/ESPEasyCore/ESPEasyNetwork.h" -#include "src/Helpers/ESPEasy_Storage.h" -#include "src/Helpers/StringConverter.h" -#include "src/PluginStructs/P094_data_struct.h" +# include "src/Helpers/ESPEasy_Storage.h" +# include "src/Helpers/StringConverter.h" +# include "src/PluginStructs/P094_data_struct.h" -#include +# include -#define PLUGIN_094 -#define PLUGIN_ID_094 94 -#define PLUGIN_NAME_094 "Communication - CUL Reader" +# define PLUGIN_094 +# define PLUGIN_ID_094 94 +# define PLUGIN_NAME_094 "Communication - CUL Reader" +# define PLUGIN_VALUENAME1_094 "v" -#define P094_BAUDRATE PCONFIG_LONG(0) -#define P094_BAUDRATE_LABEL PCONFIG_LABEL(0) - -#define P094_DEBUG_SENTENCE_LENGTH PCONFIG_LONG(1) -#define P094_DEBUG_SENTENCE_LABEL PCONFIG_LABEL(1) - -#define P094_APPEND_RECEIVE_SYSTIME PCONFIG(0) - -#define P094_QUERY_VALUE 0 // Temp placement holder until we know what selectors are needed. -#define P094_NR_OUTPUT_OPTIONS 1 - -#define P094_NR_OUTPUT_VALUES 1 -#define P094_QUERY1_CONFIG_POS 3 - -#define P094_DEFAULT_BAUDRATE 38400 +bool Plugin_094_match_all(taskIndex_t taskIndex, + const String& received, + const String& source, + bool fromCUL); +void Plugin_094_setFlags(struct EventStruct *event); // Plugin settings: // Validate: @@ -64,6 +56,7 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) Device[++deviceCount].Number = PLUGIN_ID_094; Device[deviceCount].Type = DEVICE_TYPE_SERIAL; Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; + Device[deviceCount].OutputDataType = Output_Data_type_t::Default; Device[deviceCount].Ports = 0; Device[deviceCount].PullUpOption = false; Device[deviceCount].InverseLogicOption = false; @@ -73,8 +66,9 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) Device[deviceCount].TimerOption = true; Device[deviceCount].GlobalSyncOption = false; Device[deviceCount].DuplicateDetection = true; + // FIXME TD-er: Not sure if access to any existing task data is needed when saving - Device[deviceCount].ExitTaskBeforeSave = false; + Device[deviceCount].ExitTaskBeforeSave = true; break; } @@ -83,16 +77,9 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_GET_DEVICEVALUENAMES: { - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (i < P094_NR_OUTPUT_VALUES) { - const uint8_t pconfigIndex = i + P094_QUERY1_CONFIG_POS; - uint8_t choice = PCONFIG(pconfigIndex); - ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_094_valuename(choice, false)); - } else { - ExtraTaskSettings.clearTaskDeviceValueName(i); - } - } + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_094)); break; } @@ -121,7 +108,7 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_SET_DEFAULTS: { - P094_BAUDRATE = P094_DEFAULT_BAUDRATE; + P094_BAUDRATE = P094_DEFAULT_BAUDRATE; P094_DEBUG_SENTENCE_LENGTH = 0; success = true; @@ -142,47 +129,74 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_LOAD: { + addFormCheckBox(F("Append system time"), F("systime"), P094_GET_APPEND_RECEIVE_SYSTIME); + +# if P094_DEBUG_OPTIONS + addFormSubHeader(F("Debug Options")); + addFormNumericBox(F("(debug) Generated length"), P094_DEBUG_SENTENCE_LABEL, P094_DEBUG_SENTENCE_LENGTH, 0, 1024); + addFormCheckBox(F("(debug) Generate CUL data"), F("debug_data"), P094_GET_GENERATE_DEBUG_CUL_DATA); +# endif // if P094_DEBUG_OPTIONS + addFormSubHeader(F("Filtering")); + addFormCheckBox(F("Mute Messages"), F("mute"), P094_GET_MUTE_MESSAGES); P094_html_show_matchForms(event); + addFormCheckBox(F("Enable Interval Filter"), F("interval_filter"), P094_GET_INTERVAL_FILTER); addFormSubHeader(F("Statistics")); - P094_html_show_stats(event); + addFormCheckBox(F("Collect W-MBus Stats"), F("collect_stats"), P094_GET_COLLECT_STATS); + addFormNote(F("Collect reception statistics of W-MBus devices received by the CUL reader")); - addFormNumericBox(F("(debug) Generated length"), P094_DEBUG_SENTENCE_LABEL, P094_DEBUG_SENTENCE_LENGTH, 0, 1024); - addFormCheckBox(F("Append system time"), F("systime"), P094_APPEND_RECEIVE_SYSTIME); + P094_html_show_stats(event); success = true; break; } case PLUGIN_WEBFORM_SAVE: { - P094_BAUDRATE = getFormItemInt(P094_BAUDRATE_LABEL); + P094_BAUDRATE = getFormItemInt(P094_BAUDRATE_LABEL); P094_DEBUG_SENTENCE_LENGTH = getFormItemInt(P094_DEBUG_SENTENCE_LABEL); + P094_DISABLE_WINDOW_TIME_MS = getFormItemInt(F("disableTime")); + P094_NR_FILTERS = getFormItemInt(F("nrfilters")); + + + P094_SET_APPEND_RECEIVE_SYSTIME(isFormItemChecked(F("systime"))); +# if P094_DEBUG_OPTIONS + P094_SET_GENERATE_DEBUG_CUL_DATA(isFormItemChecked(F("debug_data"))); +# endif // if P094_DEBUG_OPTIONS + P094_SET_INTERVAL_FILTER(isFormItemChecked(F("interval_filter"))); + P094_SET_MUTE_MESSAGES(isFormItemChecked(F("mute"))); + P094_SET_COLLECT_STATS(isFormItemChecked(F("collect_stats"))); + + P094_data_struct *P094_data = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != P094_data) { - for (uint8_t varNr = 0; varNr < P94_Nlines; varNr++) - { - P094_data->setLine(varNr, webArg(getPluginCustomArgName(varNr))); - } - addHtmlError(SaveCustomTaskSettings(event->TaskIndex, P094_data->_lines, P94_Nlines, 0)); - success = true; + const bool localAllocated = nullptr == P094_data; + + if (localAllocated) { + P094_data = new (std::nothrow) P094_data_struct(); } - P094_APPEND_RECEIVE_SYSTIME = isFormItemChecked(F("systime")); + if ((nullptr != P094_data)) { + P094_data->WebformSaveFilters(event, P094_NR_FILTERS); + success = true; + + if (localAllocated) { + delete P094_data; + } + } break; } case PLUGIN_INIT: { - const int16_t serial_rx = CONFIG_PIN1; - const int16_t serial_tx = CONFIG_PIN2; + 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) P094_data_struct()); P094_data_struct *P094_data = @@ -192,9 +206,20 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) return success; } - if (P094_data->init(port, serial_rx, serial_tx, P094_BAUDRATE)) { - LoadCustomTaskSettings(event->TaskIndex, P094_data->_lines, P94_Nlines, 0); - P094_data->post_init(); + if (P094_data->init( + port, + serial_rx, + serial_tx, + P094_BAUDRATE)) { + P094_data->setFlags( + P094_DISABLE_WINDOW_TIME_MS, + P094_GET_INTERVAL_FILTER, + P094_GET_MUTE_MESSAGES, + P094_GET_COLLECT_STATS); + P094_data->loadFilters(event, P094_NR_FILTERS); +# if P094_DEBUG_OPTIONS + P094_data->setGenerate_DebugCulData(P094_GET_GENERATE_DEBUG_CUL_DATA); +# endif // if P094_DEBUG_OPTIONS success = true; serialHelper_log_GpioDescription(port, serial_rx, serial_tx); @@ -204,6 +229,27 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_ONCE_A_SECOND: + { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + P094_data->interval_filter_purgeExpired(); + + if (P094_data->dump_next_stats(event->String2)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Reader: "), event->String2)); + } + + // Do not create events for dumping stats. + const bool sendEvents = false; + sendData(event, sendEvents); + } + } + break; + } + case PLUGIN_FIFTY_PER_SECOND: { if (Settings.TaskDeviceEnabled[event->TaskIndex]) { P094_data_struct *P094_data = @@ -213,15 +259,20 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) // Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); delay(0); // Processing a full sentence may take a while, run some // background tasks. - P094_data->getSentence(event->String2, P094_APPEND_RECEIVE_SYSTIME); + P094_data->getSentence(event->String2, P094_GET_APPEND_RECEIVE_SYSTIME); if (event->String2.length() > 0) { - if (Plugin_094_match_all(event->TaskIndex, event->String2)) { + const bool fromCUL = true; + const String source = NetworkGetHostname(); + + if (Plugin_094_match_all(event->TaskIndex, event->String2, source, fromCUL)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; + if (log.reserve(128)) { log = F("CUL Reader: Sending: "); const size_t messageLength = event->String2.length(); + if (messageLength < 100) { log += event->String2; } else { @@ -233,10 +284,12 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) addLogMove(LOG_LEVEL_INFO, log); } } + // Filter length options: - // - 22 char, for hash-value then we filter the exact meter including serial and meter type, (that will also prevent very quit sending meters, which normaly is a fault) + // - 22 char, for hash-value then we filter the exact meter including serial and meter type, (that will also prevent very quit + // sending meters, which normaly is a fault) // - 38 char, The exact message, because we have 2 uint8_t from the value payload - //sendData_checkDuplicates(event, event->String2.substring(0, 22)); + // sendData_checkDuplicates(event, event->String2.substring(0, 22)); sendData(event); } } @@ -252,53 +305,169 @@ boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) static_cast(getPluginTaskData(event->TaskIndex)); if ((nullptr != P094_data)) { + # if P094_DEBUG_OPTIONS const uint32_t debug_count = P094_data->getDebugCounter(); event->String2.reserve(P094_DEBUG_SENTENCE_LENGTH); event->String2 += String(debug_count); event->String2 += '_'; const char c = '0' + debug_count % 10; + for (long i = event->String2.length(); i < P094_DEBUG_SENTENCE_LENGTH; ++i) { event->String2 += c; } + # endif // if P094_DEBUG_OPTIONS + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("CUL Reader: Sending: "); log += event->String2.substring(0, 20); log += F("..."); addLogMove(LOG_LEVEL_INFO, log); } -// sendData_checkDuplicates(event, event->String2.substring(0, 22)); + + // sendData_checkDuplicates(event, event->String2.substring(0, 22)); sendData(event); } } break; } + case PLUGIN_GET_CONFIG_VALUE: + { + const String command = parseString(string, 1); + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + if (equals(command, F("getfiltermd5"))) { + // to output a MD5 of the currently active filters. + string = P094_data->getFiltersMD5(); + success = true; + } else if (equals(command, F("getfilterenabled"))) { + string = P094_GET_INTERVAL_FILTER; + success = true; + } else if (equals(command, F("getmuteenabled"))) { + string = P094_GET_MUTE_MESSAGES; + success = true; + } + } + break; + } + + case PLUGIN_WRITE: { - String cmd = parseString(string, 1); + const String cmd = parseString(string, 1); + const String subcmd = parseString(string, 2); if (cmd.startsWith(F("culreader"))) { - if (equals(cmd, F("culreader_write"))) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); - if ((nullptr != P094_data)) { + if (equals(subcmd, F("enablefilter"))) { + // culreader,enablefilter + P094_SET_INTERVAL_FILTER(1); + success = true; + } else if (equals(subcmd, F("disablefilter"))) { + // culreader,disablefilter + P094_SET_INTERVAL_FILTER(0); + success = true; + } else if (equals(subcmd, F("mute"))) { + // culreader,mute + P094_SET_MUTE_MESSAGES(1); + success = true; + } else if (equals(subcmd, F("unmute"))) { + // culreader,unmute + P094_SET_MUTE_MESSAGES(0); + success = true; + } else if ((nullptr != P094_data)) { + if (equals(cmd, F("culreader_write")) || + equals(subcmd, F("write"))) { + // culreader,write, String param1 = parseStringKeepCase(string, 2); parseSystemVariables(param1, false); P094_data->sendString(param1); addLogMove(LOG_LEVEL_INFO, param1); success = true; + } else if (equals(subcmd, F("dumpstats"))) { + // culreader,dumpstats + P094_data->prepare_dump_stats(); + success = true; + } else if (equals(subcmd, F("clearfilters"))) { + // culreader,clearfilters + P094_data->clearFilters(); + success = true; + } else if (equals(subcmd, F("savefilters"))) { + // culreader,savefilters + P094_data->saveFilters(event); + SaveSettings(); + success = true; + } else if (equals(subcmd, F("addfilter"))) { + // culreader,addfilter, + // Examples for a filter definition + // EBZ.02.12345678;all + // *.02.*;15m + // TCH.44.*;once + // *.*.*;5m + success = true; + P094_data->addFilter(event, parseString(string, 3)); + } else if (equals(subcmd, F("setfilters"))) { + // culreader,setfilters,|...| + // Examples for a filter definition + // culreader,setfilters,EBZ.02.12345678;all|*.02.*;15m|TCH.44.*;once|*.*.*;5m + success = true; + P094_data->clearFilters(); + const String argument = parseString(string, 3); + + if (!argument.isEmpty()) { + int argNr = 1; + + while (argNr > 0) { + const String filter = parseString(argument, argNr, '|'); + + if (!filter.isEmpty()) + { + P094_data->addFilter(event, filter); + ++argNr; + } else { + argNr = 0; + } + } + } + P094_data->saveFilters(event); + SaveSettings(); } } + + if (success) { + Plugin_094_setFlags(event); + } } break; } +#ifdef USES_ESPEASY_NOW + case PLUGIN_FILTEROUT_CONTROLLER_DATA: + { + // event->String1 => topic; + // event->String2 => payload; + if (Settings.TaskDeviceEnabled[event->TaskIndex]) { + const bool fromCUL = false; + + if (!Plugin_094_match_all(event->TaskIndex, event->String2, event->String1, fromCUL)) { + // Inverse as we check for filtering 'out' the messages. + success = true; + } + } + + break; + } +#endif } return success; } -bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received) +bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received, const String& source, bool fromCUL) { P094_data_struct *P094_data = static_cast(getPluginTaskData(taskIndex)); @@ -307,133 +476,79 @@ bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received) return false; } - if (P094_data->disableFilterWindowActive()) { addLog(LOG_LEVEL_INFO, F("CUL Reader: Disable Filter Window active")); return true; } - bool res = P094_data->parsePacket(received); + mBusPacket_t packet; + bool res = P094_data->parsePacket(received, packet); - if (P094_data->invertMatch()) { - addLog(LOG_LEVEL_INFO, F("CUL Reader: invert filter")); - return !res; - } - return res; + # ifdef ESP8266 + + if (res && fromCUL) { + # endif // ifdef ESP8266 + + // Only collect stats from the actual CUL receiver, not when processing forwarded packets. + // On ESP8266: only collect stats on the filtered nodes or else we will likely run out of memory + P094_data->collect_stats_add(packet, source); + # ifdef ESP8266 } -String Plugin_094_valuename(uint8_t value_nr, bool displayString) { - switch (value_nr) { - case P094_QUERY_VALUE: return displayString ? F("Value") : F("v"); - } - return EMPTY_STRING; + # endif // ifdef ESP8266 + + return res; } -void P094_html_show_matchForms(struct EventStruct *event) { +void Plugin_094_setFlags(struct EventStruct *event) +{ P094_data_struct *P094_data = static_cast(getPluginTaskData(event->TaskIndex)); - if ((nullptr != P094_data)) { - addFormNumericBox(F("Filter Off Window after send"), - getPluginCustomArgName(P094_FILTER_OFF_WINDOW_POS), - P094_data->getFilterOffWindowTime(), - 0, - 60000); - addUnit(F("msec")); - addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); + if (nullptr != P094_data) { + P094_data->setFlags( + P094_DISABLE_WINDOW_TIME_MS, + P094_GET_INTERVAL_FILTER, + P094_GET_MUTE_MESSAGES, + P094_GET_COLLECT_STATS); + } +} - { - const __FlashStringHelper * options[P094_Match_Type_NR_ELEMENTS]; - int optionValues[P094_Match_Type_NR_ELEMENTS]; +void P094_html_show_matchForms(struct EventStruct *event) { + addFormNumericBox(F("Filter Off Window after send"), + F("disableTime"), + P094_DISABLE_WINDOW_TIME_MS, + 0, + 60000); + addUnit(F("msec")); + addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); + + addFormNumericBox( + F("Nr Filters"), + F("nrfilters"), + P094_NR_FILTERS, + 0, + P094_MAX_NR_FILTERS); - for (int i = 0; i < P094_Match_Type_NR_ELEMENTS; ++i) { - P094_Match_Type matchType = static_cast(i); - options[i] = P094_data_struct::MatchType_toString(matchType); - optionValues[i] = matchType; - } - P094_Match_Type choice = P094_data->getMatchType(); - addFormSelector(F("Filter Mode"), - getPluginCustomArgName(P094_MATCH_TYPE_POS), - P094_Match_Type_NR_ELEMENTS, - options, - optionValues, - choice, - false); - } + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); - uint8_t filterSet = 0; - uint32_t optional = 0; - P094_Filter_Value_Type capture = P094_Filter_Value_Type::P094_packet_length; - P094_Filter_Comp comparator = P094_Filter_Comp::P094_Equal_OR; - String filter; + const bool localAllocated = nullptr == P094_data; - for (uint8_t filterLine = 0; filterLine < P094_NR_FILTERS; ++filterLine) - { - // Filter parameter number on a filter line. - bool newLine = (filterLine % P094_AND_FILTER_BLOCK) == 0; - - for (uint8_t filterLinePar = 0; filterLinePar < P094_ITEMS_PER_FILTER; ++filterLinePar) - { - String id = getPluginCustomArgName(P094_data_struct::P094_Get_filter_base_index(filterLine) + filterLinePar); - - switch (filterLinePar) { - case 0: - { - filter = P094_data->getFilter(filterLine, capture, optional, comparator); - - if (newLine) { - // Label + first parameter - ++filterSet; - addRowLabel_tr_id(concat(F("Filter "), static_cast(filterSet)), id); - } else { - html_B(F("AND")); - html_BR(); - } + if (localAllocated) { + P094_data = new (std::nothrow) P094_data_struct(); - // Combo box with filter types - { - const __FlashStringHelper * options[P094_FILTER_VALUE_Type_NR_ELEMENTS]; - int optionValues[P094_FILTER_VALUE_Type_NR_ELEMENTS]; + if (nullptr != P094_data) { + P094_data->loadFilters(event, P094_NR_FILTERS); + } + } - for (int i = 0; i < P094_FILTER_VALUE_Type_NR_ELEMENTS; ++i) { - P094_Filter_Value_Type filterValueType = static_cast(i); - options[i] = P094_data_struct::P094_FilterValueType_toString(filterValueType); - optionValues[i] = filterValueType; - } - addSelector(id, P094_FILTER_VALUE_Type_NR_ELEMENTS, options, optionValues, nullptr, capture, false, true, F("")); - } + if ((nullptr != P094_data)) { + P094_data->WebformLoadFilters(P094_NR_FILTERS); - break; - } - case 1: - { - // Optional numerical value - addNumericBox(id, optional, 0, 1024); - break; - } - case 2: - { - // Comparator - const __FlashStringHelper * options[P094_FILTER_COMP_NR_ELEMENTS]; - int optionValues[P094_FILTER_COMP_NR_ELEMENTS]; - - for (int i = 0; i < P094_FILTER_COMP_NR_ELEMENTS; ++i) { - P094_Filter_Comp enumValue = static_cast(i); - options[i] = P094_data_struct::P094_FilterComp_toString(enumValue); - optionValues[i] = enumValue; - } - addSelector(id, P094_FILTER_COMP_NR_ELEMENTS, options, optionValues, nullptr, comparator, false, true, F("")); - break; - } - case 3: - { - // Compare with - addTextBox(id, filter, 8, false, false, EMPTY_STRING, F("")); - break; - } - } - } + if (localAllocated) { + delete P094_data; } } } @@ -445,6 +560,11 @@ void P094_html_show_stats(struct EventStruct *event) { if ((nullptr == P094_data) || !P094_data->isInitialized()) { return; } + + P094_data->html_show_interval_filter_stats(); + + P094_data->html_show_mBus_stats(); + { addRowLabel(F("Current Sentence")); addHtml(P094_data->peekSentence()); @@ -462,4 +582,4 @@ void P094_html_show_stats(struct EventStruct *event) { } } -#endif // USES_P094 \ No newline at end of file +#endif // USES_P094 diff --git a/src/src/DataStructs/mBusPacket.cpp b/src/src/DataStructs/mBusPacket.cpp new file mode 100644 index 0000000000..4e0a5537a6 --- /dev/null +++ b/src/src/DataStructs/mBusPacket.cpp @@ -0,0 +1,428 @@ +#include "../DataStructs/mBusPacket.h" + +#include "../Helpers/CRC_functions.h" +#include "../Helpers/StringConverter.h" + +#define FRAME_FORMAT_A_FIRST_BLOCK_LENGTH 10 +#define FRAME_FORMAT_A_OTHER_BLOCK_LENGTH 16 + + +mBusPacket_header_t::mBusPacket_header_t() +{ + _manufacturer = mBus_packet_wildcard_manufacturer; + _meterType = mBus_packet_wildcard_metertype; + _serialNr = mBus_packet_wildcard_serial; + _length = 0u; +} + +String mBusPacket_header_t::decodeManufacturerID(int id) +{ + String res; + int shift = 15; + + for (int i = 0; i < 3; ++i) { + shift -= 5; + res += static_cast(((id >> shift) & 0x1f) + 64); + } + return res; +} + +int mBusPacket_header_t::encodeManufacturerID(const String& id_str) +{ + int res = 0; + int nrChars = id_str.length(); + + if (nrChars > 3) { nrChars = 3; } + + int i = 0; + + while (i < nrChars) { + res <<= 5; + const int c = static_cast(toUpperCase(id_str[i])) - 64; + + if (c >= 0) { + res += c & 0x1f; + } + ++i; + } + return res; +} + +String mBusPacket_header_t::getManufacturerId() const +{ + return decodeManufacturerID(_manufacturer); +} + +String mBusPacket_header_t::toString() const +{ + String res = decodeManufacturerID(_manufacturer); + + res += '.'; + res += formatToHex_no_prefix(_meterType, 2); + res += '.'; + res += formatToHex_no_prefix(_serialNr, 8); + return res; +} + +uint64_t mBusPacket_header_t::encode_toUInt64() const +{ + if (!isValid()) { return 0ull; } + mBusPacket_header_t tmp(*this); + + tmp._length = 0; + return tmp._encodedValue; +} + +void mBusPacket_header_t::decode_fromUint64(uint64_t encodedValue) +{ + _encodedValue = encodedValue; + _length = 1; // To pass isValid() check +} + +bool mBusPacket_header_t::isValid() const +{ + return + _manufacturer != mBus_packet_wildcard_manufacturer && + _meterType != mBus_packet_wildcard_metertype && + _serialNr != mBus_packet_wildcard_serial && + _length > 0; +} + +void mBusPacket_header_t::clear() +{ + _manufacturer = mBus_packet_wildcard_manufacturer; + _meterType = mBus_packet_wildcard_metertype; + _serialNr = mBus_packet_wildcard_serial; + _length = 0; +} + +bool mBusPacket_header_t::matchSerial(uint32_t serialNr) const +{ + return isValid() && (_serialNr == serialNr); +} + +const mBusPacket_header_t * mBusPacket_t::getDeviceHeader() const +{ + // FIXME TD-er: Which deviceID is the device and which the wrapper? + if (_deviceId1.isValid()) { return &_deviceId1; } + + if (_deviceId2.isValid()) { return &_deviceId2; } + + return nullptr; +} + +uint32_t mBusPacket_t::getDeviceSerial() const +{ + const mBusPacket_header_t *header = getDeviceHeader(); + + if (header == nullptr) { return 0u; } + return header->_serialNr; +} + +uint32_t mBusPacket_t::deviceID_to_map_key() const +{ + return deviceID_to_map_key(_deviceId1._encodedValue, _deviceId2._encodedValue); +} + +uint32_t mBusPacket_t::deviceID_to_map_key_no_length() const { + return deviceID_to_map_key(_deviceId1.encode_toUInt64(), _deviceId2.encode_toUInt64()); +} + +uint32_t mBusPacket_t::deviceID_to_map_key(uint64_t id1, uint64_t id2) +{ + uint32_t res = 0; + + if (id1 != 0ull) { + res ^= calc_CRC32((const uint8_t *)(&id1), sizeof(uint64_t)); + } + + if (id2 != 0ull) { + // There is a forwarding device. + // To prevent issues when the forwarding device is the same as the forwarded device, alter the already existing checksum. + res ^= calc_CRC32((const uint8_t *)(&res), sizeof(res)); + res ^= calc_CRC32((const uint8_t *)(&id2), sizeof(uint64_t)); + } + + return res; +} + +bool mBusPacket_t::parse(const String& payload) +{ + if (payload[0] != 'b') { return false; } + + _checksum = 0; + mBusPacket_data payloadWithoutChecksums; + + if (payload[1] == 'Y') { + // Start with "bY" + payloadWithoutChecksums = removeChecksumsFrameB(payload, _checksum); + } else { + payloadWithoutChecksums = removeChecksumsFrameA(payload, _checksum); + } + + if (payloadWithoutChecksums.size() < 10) { return false; } + + int pos_semicolon = payload.indexOf(';'); + + if (pos_semicolon == -1) { pos_semicolon = payload.length(); } + + _lqi_rssi = hexToUL(payload, pos_semicolon - 4, 4); + return parseHeaders(payloadWithoutChecksums); +} + +int16_t mBusPacket_t::decode_LQI_RSSI(uint16_t lqi_rssi, uint8_t& LQI) +{ + LQI = (lqi_rssi >> 8) & 0x7f; // Bit 7 = CRC OK Bit + + int rssi = lqi_rssi & 0xFF; + + if (rssi >= 128) { + rssi -= 256; // 2-complement + } + return (rssi / 2) - 74; +} + +bool mBusPacket_t::matchSerial(uint32_t serialNr) const +{ + return _deviceId1.matchSerial(serialNr) || _deviceId2.matchSerial(serialNr); +} + +bool mBusPacket_t::parseHeaders(const mBusPacket_data& payloadWithoutChecksums) +{ + const int payloadSize = payloadWithoutChecksums.size(); + + _deviceId1.clear(); + _deviceId2.clear(); + + if (payloadSize < 10) { return false; } + int offset = 0; + + // 1st block is a static DataLinkLayer of 10 bytes + { + _deviceId1._manufacturer = makeWord(payloadWithoutChecksums[offset + 3], payloadWithoutChecksums[offset + 2]); + + // Type (offset + 9; convert to hex) + _deviceId1._meterType = payloadWithoutChecksums[offset + 9]; + + // Serial (offset + 4; 4 Bytes; least significant first; converted to hex) + _deviceId1._serialNr = 0; + + for (int i = 0; i < 4; ++i) { + const uint32_t val = payloadWithoutChecksums[offset + 4 + i]; + _deviceId1._serialNr += val << (i * 8); + } + offset += 10; + _deviceId1._length = payloadWithoutChecksums[0]; + } + + // next blocks can be anything. we skip known blocks of no interest, parse known blocks if interest and stop on onknown blocks + while (offset < payloadSize) { + switch (static_cast(payloadWithoutChecksums[offset])) { + case 0x8C: // ELL short + offset += 3; // fixed length + _deviceId1._length = payloadSize - offset; + break; + case 0x90: // AFL + offset++; + offset += (payloadWithoutChecksums[offset] & 0xff); // dynamic length with length in 1st byte + offset++; // length byte + _deviceId1._length = payloadSize - offset; + break; + case 0x72: // TPL_RESPONSE_MBUS_LONG_HEADER + _deviceId2 = _deviceId1; + + // note that serial/manufacturer are swapped !! + + _deviceId1._manufacturer = makeWord(payloadWithoutChecksums[offset + 6], payloadWithoutChecksums[offset + 5]); + + // Type (offset + 9; convert to hex) + _deviceId1._meterType = payloadWithoutChecksums[offset + 8]; + + // Serial (offset + 4; 4 Bytes; least significant first; converted to hex) + _deviceId1._serialNr = 0; + + + for (int i = 0; i < 4; ++i) { + const uint32_t val = payloadWithoutChecksums[offset + 1 + i]; + _deviceId1._serialNr += val << (i * 8); + } + + // We're done + offset = payloadSize; + break; + default: + // We're done + // addLog(LOG_LEVEL_ERROR, concat(F("CUL : offset "), offset) + F(" Data: ") + formatToHex(payloadWithoutChecksums[offset])); + offset = payloadSize; + break; + } + } + + if (_deviceId1.isValid() && _deviceId2.isValid() && _deviceId1.toString().startsWith(F("ITW.30."))) { + // ITW does not follow the spec and puts the redio converter behind the actual meter. Need to swap both + std::swap(_deviceId1, _deviceId2); + } + + return _deviceId1.isValid(); +} + +String mBusPacket_t::toString() const +{ + static size_t expectedSize = 96; + String res; + + if (res.reserve(expectedSize)) { + if (_deviceId1.isValid()) { + res += F(" deviceId1: "); + res += _deviceId1.toString(); + res += '('; + res += static_cast(_deviceId1._length); + res += ')'; + } + + if (_deviceId2.isValid()) { + res += F(" deviceId2: "); + res += _deviceId2.toString(); + res += '('; + res += static_cast(_deviceId2._length); + res += ')'; + } + res += F(" chksum: "); + res += formatToHex(_checksum, 8); + + uint8_t LQI = 0; + const int16_t rssi = decode_LQI_RSSI(_lqi_rssi, LQI); + res += F(" LQI: "); + res += LQI; + res += F(" RSSI: "); + res += rssi; + } + + if (res.length() > expectedSize) { expectedSize = res.length(); } + + return res; +} + +uint8_t mBusPacket_t::hexToByte(const String& str, size_t index) +{ + // Need to have at least 2 HEX nibbles + if ((index + 1) >= str.length()) { return 0; } + return hexToUL(str, index, 2); +} + +/** + * Format: + * [10 bytes message] + [2 bytes CRC] + * [16 bytes message] + [2 bytes CRC] + * [16 bytes message] + [2 bytes CRC] + * ... + * (last block can be < 16 bytes) + */ +mBusPacket_data mBusPacket_t::removeChecksumsFrameA(const String& payload, uint32_t& checksum) +{ + mBusPacket_data result; + const int payloadLength = payload.length(); + + if (payloadLength < 4) { return result; } + + int sourceIndex = 1; // Starts with "b" + int targetIndex = 0; + + // 1st byte contains length of data (excuding 1st byte and excluding CRC) + const int expectedMessageSize = hexToByte(payload, sourceIndex) + 1; + + if (payloadLength < (2 * expectedMessageSize)) { + // Not an exact check, but close enough to fail early on packets which are seriously too short. + return result; + } + + result.reserve(expectedMessageSize); + + while (targetIndex < expectedMessageSize) { + // end index is start index + block size + 2 byte checksums + int blockSize = (sourceIndex == 1) ? FRAME_FORMAT_A_FIRST_BLOCK_LENGTH : FRAME_FORMAT_A_OTHER_BLOCK_LENGTH; + + if ((targetIndex + blockSize) > expectedMessageSize) { // last block + blockSize = expectedMessageSize - targetIndex; + } + + // FIXME: handle truncated source messages + for (int i = 0; i < blockSize; ++i) { + result.push_back(hexToByte(payload, sourceIndex)); + sourceIndex += 2; // 2 hex chars + } + + // [2 bytes CRC] + checksum <<= 8; + checksum ^= hexToUL(payload, sourceIndex, 4); + sourceIndex += 4; // Skip 2 bytes CRC => 4 hex chars + targetIndex += blockSize; + } + return result; +} + +/** + * Format: + * [126 bytes message] + [2 bytes CRC] + * [125 bytes message] + [2 bytes CRC] + * (if message length <=126 bytes, only the 1st block exists) + * (last block can be < 125 bytes) + */ +mBusPacket_data mBusPacket_t::removeChecksumsFrameB(const String& payload, uint32_t& checksum) +{ + mBusPacket_data result; + const int payloadLength = payload.length(); + + if (payloadLength < 4) { return result; } + + int sourceIndex = 2; // Starts with "bY" + + // 1st byte contains length of data (excuding 1st byte BUT INCLUDING CRC) + int expectedMessageSize = hexToByte(payload, sourceIndex) + 1; + + if (payloadLength < (2 * expectedMessageSize)) { + return result; + } + + expectedMessageSize -= 2; // CRC of 1st block + + if (expectedMessageSize > 128) { + expectedMessageSize -= 2; // CRC of 2nd block + } + + result.reserve(expectedMessageSize); + + // FIXME: handle truncated source messages + + const int block1Size = expectedMessageSize < 126 ? expectedMessageSize : 126; + + for (int i = 0; i < block1Size; ++i) { + result.push_back(hexToByte(payload, sourceIndex)); + sourceIndex += 2; // 2 hex chars + } + + // [2 bytes CRC] + checksum <<= 8; + checksum ^= hexToUL(payload, sourceIndex, 4); + sourceIndex += 4; // Skip 2 bytes CRC => 4 hex chars + + if (expectedMessageSize > 126) { + int block2Size = expectedMessageSize - 127; + + if (block2Size > 124) { block2Size = 124; } + + for (int i = 0; i < block2Size; ++i) { + result.push_back(hexToByte(payload, sourceIndex)); + sourceIndex += 2; // 2 hex chars + } + + // [2 bytes CRC] + checksum <<= 8; + checksum ^= hexToUL(payload, sourceIndex, 4); + } + + // remove the checksums and the 1st byte from the actual message length, so that the meaning of this byte is the same as in Frame A + result[0] = static_cast((expectedMessageSize - 1) & 0xff); + + return result; +} diff --git a/src/src/DataStructs/mBusPacket.h b/src/src/DataStructs/mBusPacket.h new file mode 100644 index 0000000000..3a6b9ac75b --- /dev/null +++ b/src/src/DataStructs/mBusPacket.h @@ -0,0 +1,120 @@ +#ifndef DATASTRUCTS_MBUSPACKET_H +#define DATASTRUCTS_MBUSPACKET_H + +#include "../../ESPEasy_common.h" + +#include + + +// 0 is sometimes used ("@@@") +// 0xFFFF does not seem to be used ("___") +#define mBus_packet_wildcard_manufacturer 0xFFFF + +// 0 is a valid meter type and 0xFF seems to be reserved +#define mBus_packet_wildcard_metertype 0xFE + +// 0 is a valid serial and 0xFFFFFFFF seems to be reserved +#define mBus_packet_wildcard_serial 0xFFFFFFFE + + +typedef std::vector mBusPacket_data; + +struct mBusPacket_header_t { + mBusPacket_header_t(); + + static String decodeManufacturerID(int id); + static int encodeManufacturerID(const String& id_str); + + String getManufacturerId() const; + + String toString() const; + + uint64_t encode_toUInt64() const; + + void decode_fromUint64(uint64_t encodedValue); + + bool isValid() const; + + bool matchSerial(uint32_t serialNr) const; + + void clear(); + + // Use for stats as key: + union { + uint64_t _encodedValue{}; + struct { + uint64_t _serialNr : 32; + uint64_t _manufacturer : 16; + uint64_t _meterType : 8; + + // Use for filtering + uint64_t _length : 8; + }; + }; +}; + +struct mBusPacket_t { +public: + + bool parse(const String& payload); + + // Get the header of the actual device, not the forwarding device (if present) + const mBusPacket_header_t* getDeviceHeader() const; + + static int16_t decode_LQI_RSSI(uint16_t lqi_rssi, + uint8_t& LQI); + + bool matchSerial(uint32_t serialNr) const; + + uint32_t getDeviceSerial() const; + + String toString() const; + + // 32 bit value used to generate a map key for filtering + // Essentially the XOR of the first 32-bit with the second 32-bit of + // serial, manufacturer, metertype and length. + uint32_t deviceID_to_map_key() const; + + uint32_t deviceID_to_map_key_no_length() const; + +private: + + static uint32_t deviceID_to_map_key(uint64_t id1, uint64_t id2); + + static uint8_t hexToByte(const String& str, + size_t index); + + static mBusPacket_data removeChecksumsFrameA(const String& payload, + uint32_t & checksum); + static mBusPacket_data removeChecksumsFrameB(const String& payload, + uint32_t & checksum); + + bool parseHeaders(const mBusPacket_data& payloadWithoutChecksums); + +public: + + mBusPacket_header_t _deviceId1; + mBusPacket_header_t _deviceId2; + uint16_t _lqi_rssi{}; + + + /* + // Statistics: + // Key: + deviceID1: + - manufacturer + - metertype + - serialnr + + // Value: + - message count + - rssi + - lqi??? + */ + + + // Checksum based on the XOR of all removed checksums from the message + uint32_t _checksum = 0; +}; + +#endif // ifndef DATASTRUCTS_MBUSPACKET_H diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index f94648b8f3..0952da261b 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -62,6 +62,9 @@ enum PluginFunctions_e { PLUGIN_PRIORITY_INIT_ALL , // Pre-initialize all plugins that are set to PowerManager priority (not implemented in plugins) PLUGIN_PRIORITY_INIT , // Pre-initialize a singe plugins that is set to PowerManager priority PLUGIN_WEBFORM_LOAD_ALWAYS , // Loaded *after* PLUGIN_WEBFORM_LOAD, also shown for remote data-feed devices +#ifdef USES_ESPEASY_NOW + PLUGIN_FILTEROUT_CONTROLLER_DATA , // Can be called from the controller to query a task whether the data should be processed further. +#endif PLUGIN_WEBFORM_PRE_SERIAL_PARAMS , // Before serial parameters, convert additional parameters like baudrate or specific serial config PLUGIN_MAX_FUNCTION // Leave as last one. diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 2a5e5214ee..063b0cd8b2 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -36,7 +36,7 @@ constexpr pluginID_t PLUGIN_ID_MQTT_IMPORT(37); // ******************************************************************************** // Interface for Sending to Controllers // ******************************************************************************** -void sendData(struct EventStruct *event) +void sendData(struct EventStruct *event, bool sendEvents) { START_TIMER; #ifndef BUILD_NO_RAM_TRACKER @@ -44,7 +44,7 @@ void sendData(struct EventStruct *event) #endif // ifndef BUILD_NO_RAM_TRACKER // LoadTaskSettings(event->TaskIndex); - if (Settings.UseRules) { + if (Settings.UseRules && sendEvents) { createRuleEvents(event); } diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index 7c2f405ca8..4dd08e996e 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -9,7 +9,7 @@ // ******************************************************************************** // Interface for Sending to Controllers // ******************************************************************************** -void sendData(struct EventStruct *event); +void sendData(struct EventStruct *event, bool sendEvents = true); bool validUserVar(struct EventStruct *event); diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index 7b93b9e1ab..a9d9ffba7a 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -854,6 +854,9 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT: case PLUGIN_INIT_VALUE_RANGES: + #ifdef USES_ESPEASY_NOW + case PLUGIN_FILTEROUT_CONTROLLER_DATA: + #endif // PLUGIN_MQTT_xxx functions are directly called from the scheduler. //case PLUGIN_MQTT_CONNECTION_STATE: diff --git a/src/src/Helpers/CUL_interval_filter.cpp b/src/src/Helpers/CUL_interval_filter.cpp new file mode 100644 index 0000000000..6cc2d860e8 --- /dev/null +++ b/src/src/Helpers/CUL_interval_filter.cpp @@ -0,0 +1,101 @@ +#include "../Helpers/CUL_interval_filter.h" + + +#ifdef USES_P094 + +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/TimeZone.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/StringConverter.h" + + +CUL_time_filter_struct::CUL_time_filter_struct(uint32_t checksum, unsigned long UnixTimeExpiration) + : _checksum(checksum), _UnixTimeExpiration(UnixTimeExpiration) {} + +String CUL_interval_filter_getExpiration_log_str(const P094_filter& filter) +{ + const unsigned long expiration = filter.computeUnixTimeExpiration(); + + if ((expiration != 0) && (expiration != 0xFFFFFFFF)) { + struct tm exp_tm; + breakTime(time_zone.toLocal(expiration), exp_tm); + + return concat(F(" Expiration: "), formatDateTimeString(exp_tm)); + } + return EMPTY_STRING; +} + +bool CUL_interval_filter::filter(const mBusPacket_t& packet, const P094_filter& filter) +{ + if (!enabled) { + return true; + } + + if (filter.getFilterWindow() == P094_Filter_Window::None) { + // Will always be rejected, so no need to keep track of the message + return false; + } + + if (filter.getFilterWindow() == P094_Filter_Window::All) { + // Will always be allowed, so no need to keep track of the message + return true; + } + + const uint32_t key = packet.deviceID_to_map_key(); + auto it = _mBusFilterMap.find(key); + + if (it != _mBusFilterMap.end()) { + // Already present + if (node_time.getUnixTime() < it->second._UnixTimeExpiration) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("CUL : Interval filtered: "), packet.toString()); + log += CUL_interval_filter_getExpiration_log_str(filter); + addLogMove(LOG_LEVEL_INFO, log); + } + return false; + } + + if (packet._checksum == it->second._checksum) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL : Interval Same Checksum: "), packet.toString())); + } + return false; + } + + // Has expired, so remove from filter map + _mBusFilterMap.erase(it); + } + + const unsigned long expiration = filter.computeUnixTimeExpiration(); + + CUL_time_filter_struct item(packet._checksum, expiration); + + _mBusFilterMap[key] = item; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("CUL : Add to IntervalFilter: "), packet.toString()); + log += CUL_interval_filter_getExpiration_log_str(filter); + + addLogMove(LOG_LEVEL_INFO, log); + } + + return true; +} + +void CUL_interval_filter::purgeExpired() +{ + auto it = _mBusFilterMap.begin(); + + const unsigned long currentTime = node_time.getUnixTime(); + + for (; it != _mBusFilterMap.end();) { + if (currentTime > it->second._UnixTimeExpiration) { + it = _mBusFilterMap.erase(it); + } else { + ++it; + } + } +} + +#endif // ifdef USES_P094 diff --git a/src/src/Helpers/CUL_interval_filter.h b/src/src/Helpers/CUL_interval_filter.h new file mode 100644 index 0000000000..b68f3cb958 --- /dev/null +++ b/src/src/Helpers/CUL_interval_filter.h @@ -0,0 +1,42 @@ +#ifndef DATASTRUCTS_P094_CUL_TIME_FILTER_STRUCT_H +#define DATASTRUCTS_P094_CUL_TIME_FILTER_STRUCT_H + +#include "../../ESPEasy_common.h" +#ifdef USES_P094 + +# include "../DataStructs/mBusPacket.h" +# include "../PluginStructs/P094_Filter.h" + +# include + + +struct CUL_time_filter_struct { + CUL_time_filter_struct() = default; + CUL_time_filter_struct(uint32_t checksum, + unsigned long UnixTimeExpiration); + + uint32_t _checksum{}; + unsigned long _UnixTimeExpiration{}; +}; + +typedef uint32_t mBusSerial; + +typedef std::map mBusFilterMap; + + +struct CUL_interval_filter { + // Return true when packet wasn't already present. + bool filter(const mBusPacket_t& packet, + const P094_filter & filter); + + // Remove packets that have expired. + void purgeExpired(); + + + mBusFilterMap _mBusFilterMap; + + bool enabled = false; +}; + +#endif // ifdef USES_P094 +#endif // ifndef DATASTRUCTS_P094_CUL_TIME_FILTER_STRUCT_H diff --git a/src/src/Helpers/CUL_stats.cpp b/src/src/Helpers/CUL_stats.cpp new file mode 100644 index 0000000000..b857bda1dc --- /dev/null +++ b/src/src/Helpers/CUL_stats.cpp @@ -0,0 +1,135 @@ +#include "../Helpers/CUL_stats.h" + +#ifdef USES_P094 + +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/CRC_functions.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/StringConverter.h" + +# include "../WebServer/Markup.h" +# include "../WebServer/HTML_wrappers.h" + +String CUL_Stats::toString(const CUL_Stats_struct& element) const +{ + uint8_t LQI = 0; + const int16_t rssi = mBusPacket_t::decode_LQI_RSSI(element._lqi_rssi, LQI); + + // e.g.: THC.02.12345678;1674030412;1674031412;123;101,-36 + static size_t estimated_length = 52; + + String res; + + res.reserve(estimated_length); + { + auto it = _mBusStatsSourceMap.find(element._sourceHash); + if (element._sourceHash != 0u && it != _mBusStatsSourceMap.end()) { + res += it->second; + } else { + res += '-'; + } + } + res += ';'; + + if (element._id1 != 0u) { + mBusPacket_header_t deviceID; + deviceID.decode_fromUint64(element._id1); + res += deviceID.toString(); + } else { + res += '-'; + } + res += ';'; + + if (element._id2 != 0u) { + mBusPacket_header_t deviceID; + deviceID.decode_fromUint64(element._id2); + res += deviceID.toString(); + } else { + res += '-'; + } + res += ';'; + + res += element._UnixTimeFirstSeen; + res += ';'; + res += element._UnixTimeLastSeen; + res += ';'; + res += element._count; + res += ';'; + res += LQI; + res += ';'; + res += rssi; + + if (res.length() > estimated_length) { + estimated_length = res.length(); + } + return res; +} + +bool CUL_Stats::add(const mBusPacket_t& packet) +{ + const CUL_stats_hash sourceHash{}; + return add(packet, packet.deviceID_to_map_key_no_length(), sourceHash); +} + + +bool CUL_Stats::add(const mBusPacket_t& packet, const String& source) +{ + CUL_stats_hash key = packet.deviceID_to_map_key_no_length(); + CUL_stats_hash sourceHash{}; + + if (!source.isEmpty()) { + sourceHash = calc_CRC32((const uint8_t *)(source.c_str()), source.length()); + _mBusStatsSourceMap[sourceHash] = source; + key ^= sourceHash; + } + + return add(packet, key, sourceHash); +} + +bool CUL_Stats::add(const mBusPacket_t& packet, CUL_stats_hash key, CUL_stats_hash sourceHash) +{ + if (key == 0) { return false; } + + auto it = _mBusStatsMap.find(key); + + if (it == _mBusStatsMap.end()) { + CUL_Stats_struct tmp; + tmp._count = 1; + tmp._id1 = packet._deviceId1.encode_toUInt64(); + tmp._id2 = packet._deviceId2.encode_toUInt64(); + tmp._lqi_rssi = packet._lqi_rssi; + tmp._UnixTimeFirstSeen = node_time.now(); + tmp._UnixTimeLastSeen = tmp._UnixTimeFirstSeen; + tmp._sourceHash = sourceHash; + _mBusStatsMap[key] = tmp; + return true; + } + it->second._count++; + it->second._lqi_rssi = packet._lqi_rssi; + it->second._UnixTimeLastSeen = node_time.now(); + return false; +} + +String CUL_Stats::getFront() +{ + auto it = _mBusStatsMap.begin(); + + if (it == _mBusStatsMap.end()) { return EMPTY_STRING; } + const String res = toString(it->second); + + _mBusStatsMap.erase(it); + return res; +} + +void CUL_Stats::toHtml() const +{ + addRowLabel(F("CUL stats")); + + for (auto it = _mBusStatsMap.begin(); it != _mBusStatsMap.end(); ++it) { + addHtml(toString(it->second)); + addHtml(F("
")); + } +} + +#endif // ifdef USES_P094 diff --git a/src/src/Helpers/CUL_stats.h b/src/src/Helpers/CUL_stats.h new file mode 100644 index 0000000000..4c131f96e4 --- /dev/null +++ b/src/src/Helpers/CUL_stats.h @@ -0,0 +1,59 @@ +#ifndef DATASTRUCTS_P094_CUL_STATS_H +#define DATASTRUCTS_P094_CUL_STATS_H + +#include "../../ESPEasy_common.h" +#ifdef USES_P094 + +# include "../DataStructs/mBusPacket.h" + +# include + + +typedef uint64_t mBus_EncodedDeviceID; + +typedef uint32_t CUL_stats_hash; + + +struct CUL_Stats_struct { + mBus_EncodedDeviceID _id1{}; + mBus_EncodedDeviceID _id2{}; + uint32_t _UnixTimeFirstSeen{}; + uint32_t _UnixTimeLastSeen{}; + uint16_t _lqi_rssi{}; + uint16_t _count{}; + CUL_stats_hash _sourceHash{}; +}; + + +typedef std::map mBusStatsMap; +typedef std::map mBusStatsSourceMap; + + +struct CUL_Stats { + // Create a string like this: + // mBus device ID;UNIX time first;UNIX time last;count;LQI;RSSI + // THC.02.12345678;1674030412;1674031412;123;101,-36 + String toString(const CUL_Stats_struct& element) const; + + + // Return true when packet wasn't already present. + bool add(const mBusPacket_t& packet); + bool add(const mBusPacket_t& packet, const String& source); + +private: + + bool add(const mBusPacket_t& packet, CUL_stats_hash key, CUL_stats_hash sourceHash); + +public: + + // Create string from front element and remove from map + String getFront(); + + void toHtml() const; + + mBusStatsMap _mBusStatsMap; + mBusStatsSourceMap _mBusStatsSourceMap; +}; + +#endif // ifdef USES_P094 +#endif // ifndef DATASTRUCTS_P094_CUL_STATS_H diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index 7739ffa6bf..bad0bba60a 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -17,6 +17,11 @@ #include "../Globals/Settings.h" #include "../Globals/TimeZone.h" +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_handler.h" +#endif + + #include "../Helpers/Convert.h" #include "../Helpers/Hardware.h" #include "../Helpers/Hardware_I2C.h" diff --git a/src/src/Helpers/ESPEasy_time_calc.cpp b/src/src/Helpers/ESPEasy_time_calc.cpp index 2a156d56e8..9b144d8b00 100644 --- a/src/src/Helpers/ESPEasy_time_calc.cpp +++ b/src/src/Helpers/ESPEasy_time_calc.cpp @@ -28,6 +28,10 @@ uint8_t getMonthDays(int year, uint8_t month) { return monthDays[month]; } +uint8_t getMonthDays(const struct tm& tm) { + return getMonthDays(tm.tm_year + 1900, tm.tm_mon); +} + /********************************************************************************************\ Unix Time computations \*********************************************************************************************/ @@ -408,4 +412,4 @@ bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet) } return (clockEvent == clockSet); -} +} \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_time_calc.h b/src/src/Helpers/ESPEasy_time_calc.h index 03cad24336..6aaef59916 100644 --- a/src/src/Helpers/ESPEasy_time_calc.h +++ b/src/src/Helpers/ESPEasy_time_calc.h @@ -65,6 +65,7 @@ bool isLeapYear(int year); // Get number of days in a month. // Month starts at 0 for January. uint8_t getMonthDays(int year, uint8_t month); +uint8_t getMonthDays(const struct tm& tm); uint32_t makeTime(const struct tm& tm); diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp index c42b7eddac..d51c4dd4b6 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp @@ -10,7 +10,7 @@ -void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, uint8_t pconfigIndex) +void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, int pconfigIndex) { const uint8_t optionValues[] { static_cast(Sensor_VType::SENSOR_TYPE_SINGLE), @@ -48,7 +48,7 @@ void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, uint8_t pc sensorTypeHelper_webformLoad(event, pconfigIndex, optionCount, optionValues); } -void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, uint8_t pconfigIndex) +void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, int pconfigIndex) { const uint8_t optionValues[] { static_cast(Sensor_VType::SENSOR_TYPE_SINGLE), @@ -61,29 +61,31 @@ void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, uint8_t pcon sensorTypeHelper_webformLoad(event, pconfigIndex, optionCount, optionValues); } -void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigIndex, int optionCount, const uint8_t options[]) +void sensorTypeHelper_webformLoad(struct EventStruct *event, int pconfigIndex, int optionCount, const uint8_t options[]) { addFormSubHeader(F("Output Configuration")); - if (pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { return; } Sensor_VType choice = static_cast(PCONFIG(pconfigIndex)); const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); if (!validDeviceIndex(DeviceIndex)) { + // FIXME TD-er: Should we even continue here? choice = Sensor_VType::SENSOR_TYPE_NONE; PCONFIG(pconfigIndex) = static_cast(choice); } else if (getValueCountFromSensorType(choice) != getValueCountForTask(event->TaskIndex)) { // Invalid value - checkDeviceVTypeForTask(event); - choice = event->sensorType; - PCONFIG(pconfigIndex) = static_cast(choice); + if (checkDeviceVTypeForTask(event) >= 0) { + choice = event->sensorType; + PCONFIG(pconfigIndex) = static_cast(choice); + } } const __FlashStringHelper *outputTypeLabel = F("Output Data Type"); - if (Device[DeviceIndex].OutputDataType == Output_Data_type_t::Simple) { + if (validDeviceIndex(DeviceIndex) && Device[DeviceIndex].OutputDataType == Output_Data_type_t::Simple) { if (!isSimpleOutputDataType(event->sensorType)) { choice = Device[DeviceIndex].VType; @@ -92,7 +94,7 @@ void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigInde outputTypeLabel = F("Number Output Values"); } addRowLabel(outputTypeLabel); - addSelector_Head(PCONFIG_LABEL(pconfigIndex)); + addSelector_Head(sensorTypeHelper_webformID(pconfigIndex)); for (uint8_t x = 0; x < optionCount; x++) { @@ -114,7 +116,7 @@ void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigInde PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, event, dummy); } -void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uint8_t valueIndex, const String& defaultValueName) +void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, int pconfigIndex, uint8_t valueIndex, const String& defaultValueName) { const bool isDefault = defaultValueName.equals(ExtraTaskSettings.TaskDeviceValueNames[valueIndex]); if (isDefault) { @@ -124,18 +126,26 @@ void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, uint8_t pcon pconfig_webformSave(event, pconfigIndex); } -void pconfig_webformSave(struct EventStruct *event, uint8_t pconfigIndex) +void pconfig_webformSave(struct EventStruct *event, int pconfigIndex) { - PCONFIG(pconfigIndex) = getFormItemInt(PCONFIG_LABEL(pconfigIndex), PCONFIG(pconfigIndex)); + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + return; + } + + PCONFIG(pconfigIndex) = getFormItemInt(sensorTypeHelper_webformID(pconfigIndex), PCONFIG(pconfigIndex)); } void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const __FlashStringHelper *options[], const int indices[]) { + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + return; + } + addFormSelector( concat(F("Value "), valuenr + 1), - PCONFIG_LABEL(pconfigIndex), + sensorTypeHelper_webformID(pconfigIndex), optionCount, options, indices, @@ -143,14 +153,26 @@ void sensorTypeHelper_loadOutputSelector( } void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const String options[], const int indices[]) { + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + return; + } + addFormSelector( concat(F("Value "), valuenr + 1), - PCONFIG_LABEL(pconfigIndex), + sensorTypeHelper_webformID(pconfigIndex), optionCount, options, indices, PCONFIG(pconfigIndex)); } + +String sensorTypeHelper_webformID(int pconfigIndex) +{ + if (pconfigIndex >= 0 && pconfigIndex < PLUGIN_CONFIGVAR_MAX) { + return concat(F("pconfigIndex_"), pconfigIndex); + } + return F("error"); +} \ No newline at end of file diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.h b/src/src/Helpers/_Plugin_SensorTypeHelper.h index 165322c262..262d42fe32 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.h +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.h @@ -5,23 +5,24 @@ #include "../DataStructs/DeviceStruct.h" -void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, uint8_t pconfigIndex); +void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, int pconfigIndex); -void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, uint8_t pconfigIndex); +void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, int pconfigIndex); -void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigIndex, int optionCount, const uint8_t options[]); +void sensorTypeHelper_webformLoad(struct EventStruct *event, int pconfigIndex, int optionCount, const uint8_t options[]); -void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uint8_t valueIndex, const String& defaultValueName); +void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, int pconfigIndex, uint8_t valueIndex, const String& defaultValueName); -void pconfig_webformSave(struct EventStruct *event, uint8_t pconfigIndex); +void pconfig_webformSave(struct EventStruct *event, int pconfigIndex); void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const __FlashStringHelper * options[], const int indices[] = nullptr); void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const String options[], const int indices[] = nullptr); +String sensorTypeHelper_webformID(int pconfigIndex); #endif // HELPER_CPLUGIN_SENSORTYPEHELPER_H \ No newline at end of file diff --git a/src/src/PluginStructs/P078_data_struct.cpp b/src/src/PluginStructs/P078_data_struct.cpp index f4a50c5ebc..11478262e1 100644 --- a/src/src/PluginStructs/P078_data_struct.cpp +++ b/src/src/PluginStructs/P078_data_struct.cpp @@ -211,7 +211,7 @@ void SDM_loadOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uin { const SDM_MODEL model = static_cast(P078_MODEL); const String label = concat(F("Value "), valuenr + 1); - const String id = PCONFIG_LABEL(pconfigIndex); + const String id = sensorTypeHelper_webformID(pconfigIndex); addRowLabel_tr_id(label, id); do_addSelector_Head(id, F("wide"), EMPTY_STRING, false); diff --git a/src/src/PluginStructs/P094_Filter.cpp b/src/src/PluginStructs/P094_Filter.cpp new file mode 100644 index 0000000000..1111cc1cea --- /dev/null +++ b/src/src/PluginStructs/P094_Filter.cpp @@ -0,0 +1,463 @@ +#include "../PluginStructs/P094_Filter.h" + +#ifdef USES_P094 + + +# include "../DataStructs/mBusPacket.h" + +# include "../Globals/ESPEasy_time.h" +# include "../Globals/TimeZone.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringConverter.h" + + +// *INDENT-OFF* +# define P094_FILTER_WEBARG_LABEL(x) getPluginCustomArgName((x * 10) + 10) +# define P094_FILTER_WEBARG_MANUFACTURER(x) getPluginCustomArgName((x * 10) + 11) +# define P094_FILTER_WEBARG_METERTYPE(x) getPluginCustomArgName((x * 10) + 12) +# define P094_FILTER_WEBARG_SERIAL(x) getPluginCustomArgName((x * 10) + 13) +# define P094_FILTER_WEBARG_FILTER_WINDOW(x) getPluginCustomArgName((x * 10) + 14) +// *INDENT-ON* + +const char P094_Filter_Window_names[] PROGMEM = "none|all|1m|5m|15m|1h|day|month|once"; + +P094_Filter_Window get_FilterWindow(const String& str) +{ + char tmp[10]{}; + const int command_i = GetCommandCode(tmp, sizeof(tmp), str.c_str(), P094_Filter_Window_names); + + if (command_i == -1) { + // No match found + return P094_Filter_Window::None; + } + return static_cast(command_i); +} + +String Filter_WindowToString(P094_Filter_Window filterWindow) +{ + char tmp[10]{}; + String res(GetTextIndexed(tmp, sizeof(tmp), static_cast(filterWindow), P094_Filter_Window_names)); + + return res; +} + +P094_filter::P094_filter() { + _filter._manufacturer = mBus_packet_wildcard_manufacturer; + _filter._meterType = mBus_packet_wildcard_metertype; + _filter._serialNr = mBus_packet_wildcard_serial; + _filter._filterWindow = static_cast(P094_Filter_Window::None); +} + +void P094_filter::fromString(String str) +{ + // Set everything to wildcards + _filter._manufacturer = mBus_packet_wildcard_manufacturer; + _filter._meterType = mBus_packet_wildcard_metertype; + _filter._serialNr = mBus_packet_wildcard_serial; + _filter._filterWindow = static_cast(P094_Filter_Window::None); + + const int semicolonPos = str.indexOf(';'); + + if (semicolonPos != -1) { + _filter._filterWindow = static_cast(get_FilterWindow(str.substring(semicolonPos + 1))); + str = str.substring(0, semicolonPos); + } + + for (size_t i = 0; i < 3; ++i) { + String tmp; + + if (GetArgv(str.c_str(), tmp, (i + 1), '.')) { + if (!(tmp.isEmpty() || tmp.startsWith(F("*")))) { + if (i != 0) { + // Make sure the numerical values are parsed as HEX + if (!tmp.startsWith(F("0x")) && !tmp.startsWith(F("0X"))) { + tmp = concat(F("0x"), tmp); + } + } + + switch (i) { + case 0: // Manufacturer + _filter._manufacturer = mBusPacket_header_t::encodeManufacturerID(tmp); + break; + case 1: // Meter type + { + int32_t metertype = mBus_packet_wildcard_metertype; + + if (validIntFromString(tmp, metertype)) { + _filter._meterType = metertype; + } + break; + } + case 2: // Serial + { + int32_t serial = mBus_packet_wildcard_serial; + + if (validIntFromString(tmp, serial)) { + _filter._serialNr = serial; + } + break; + } + } + } + } + } +} + +String P094_filter::toString() const +{ + String res; + + res += getManufacturer(); + res += '.'; + + res += getMeterType(); + res += '.'; + + res += getSerial(); + res += ';'; + + res += Filter_WindowToString(getFilterWindow()); + + return res; +} + +const uint8_t * P094_filter::toBinary(size_t& size) const +{ + size = getBinarySize(); + return (uint8_t *)this; +} + +size_t P094_filter::fromBinary(const uint8_t *data) +{ + memcpy(this, data, getBinarySize()); + return getBinarySize(); +} + +bool P094_filter::isValid() const +{ + if ((_filter._manufacturer == 0) && + (_filter._meterType == 0) && + (_filter._serialNr == 0) && + (getFilterWindow() == P094_Filter_Window::None)) { + return false; + } + return + !isWildcardManufacturer() || + !isWildcardMeterType() || + !isWildcardSerial() || + getFilterWindow() != P094_Filter_Window::None; +} + +bool P094_filter::operator<(const P094_filter& rhs) const +{ + if (isValid() != rhs.isValid()) { + return isValid(); + } +/* + // Disable sorting, only sort by having valid filters at top. + if (isWildcardManufacturer() != rhs.isWildcardManufacturer()) { + return rhs.isWildcardManufacturer(); + } + + if (isWildcardMeterType() != rhs.isWildcardMeterType()) { + return rhs.isWildcardMeterType(); + } + + if (isWildcardSerial() != rhs.isWildcardSerial()) { + return rhs.isWildcardSerial(); + } + + if (!isWildcardManufacturer() && (_filter._manufacturer != rhs._filter._manufacturer)) { + return _filter._manufacturer < rhs._filter._manufacturer; + } + + if (!isWildcardMeterType() && (_filter._meterType != rhs._filter._meterType)) { + return _filter._meterType < rhs._filter._meterType; + } + + if (!isWildcardSerial() && (_filter._serialNr != rhs._filter._serialNr)) { + return _filter._serialNr < rhs._filter._serialNr; + } +*/ + return false; +} + +bool P094_filter::operator==(const P094_filter& rhs) const +{ + return equals(*this, rhs); +} + +bool P094_filter::operator!=(const P094_filter& rhs) const +{ + return !equals(*this, rhs); +} + +bool P094_filter::equals(const P094_filter& lhs, const P094_filter& rhs) +{ + if (!lhs.isValid() && !rhs.isValid()) { return true; } + + return lhs.toString() == rhs.toString(); +} + +size_t P094_filter::getBinarySize() +{ + // Only store the filter + constexpr size_t P094_filter_size = sizeof(_filter); + + return P094_filter_size; +} + +bool P094_filter::matches(const mBusPacket_header_t& other) const +{ + if (!isWildcardManufacturer()) { + if (_filter._manufacturer != other._manufacturer) { return false; } + } + + if (!isWildcardMeterType()) { + if (_filter._meterType != other._meterType) { return false; } + } + + if (!isWildcardSerial()) { + if (_filter._serialNr != other._serialNr) { return false; } + } + + return true; +} + +unsigned long P094_filter::computeUnixTimeExpiration() const +{ + // Match the interval window. + const P094_Filter_Window filterWindow = getFilterWindow(); + + if ((filterWindow == P094_Filter_Window::None) || + (filterWindow == P094_Filter_Window::Once)) { + // Return date infinitely far in the future + return 0xFFFFFFFF; + } + + if (filterWindow == P094_Filter_Window::All) { + // Return timestamp in the past + return 0; + } + + // Using UnixTime + const unsigned long currentTime = node_time.getUnixTime(); + unsigned long window_max = currentTime; + + if ((filterWindow == P094_Filter_Window::One_hour) || + (filterWindow == P094_Filter_Window::Day) || + (filterWindow == P094_Filter_Window::Month)) + { + // Create time struct in local time. + struct tm tm_max; + breakTime(time_zone.toLocal(currentTime), tm_max); + tm_max.tm_sec = 59; + tm_max.tm_min = 59; + + if (filterWindow == P094_Filter_Window::Day) { + // Using local time, thus incl. timezone and DST. + if (tm_max.tm_hour < 23) { + // Either: + // - between 00:00 and 12:00 => Max: 11:59:59 + // - between 12:00 and 23:00 => Max: 22:59:59 + + tm_max.tm_hour = (tm_max.tm_hour < 12) ? 11 : 22; + } else { + // between 23:00 and 00:00 => Max: 23:59:59 + tm_max.tm_hour = 23; + } + } else if (filterWindow == P094_Filter_Window::Month) { + // First set minute to midnight of today => Max: 23:59:59 + tm_max.tm_hour = 23; + + if (tm_max.tm_mday < 15) { + // - between 1st of month 00:00:00 and 15th of month 00:00:00 + tm_max.tm_mday = 14; + } else { + // Check if this is the last day of the month. + // Add 24h to the time and see if it is still the same month. + const uint8_t maxMonthDay = getMonthDays(tm_max); + + if (tm_max.tm_mday < maxMonthDay) { + // - between 15th of month 00:00:00 and last of month 00:00:00 + // So we must subtract one day. + tm_max.tm_mday = maxMonthDay - 1; + } else { + // - between last of month 00:00:00 and 1st of next month 00:00:00 + // Thus do not change the date as it is already at the last day of the month + } + } + } + + // Convert from local time. + window_max = time_zone.fromLocal(makeTime(tm_max)); + } else { + switch (filterWindow) { + case P094_Filter_Window::One_minute: + window_max = currentTime - (currentTime % (1 * 60)) + (1 * 60 - 1); + break; + case P094_Filter_Window::Five_minutes: + window_max = currentTime - (currentTime % (5 * 60)) + (5 * 60 - 1); + break; + case P094_Filter_Window::Fifteen_minutes: + window_max = currentTime - (currentTime % (15 * 60)) + (15 * 60 - 1); + break; + + default: + break; + } + } + return window_max; +} + +void P094_filter::WebformLoad(uint8_t filterIndex) const +{ + addRowLabel_tr_id( + concat(F("Filter "), static_cast(filterIndex + 1)), + P094_FILTER_WEBARG_LABEL(filterIndex)); + + // Manufacturer + addTextBox( + P094_FILTER_WEBARG_MANUFACTURER(filterIndex), + getManufacturer(), + 3, false, false, EMPTY_STRING, F("widenumber") +# if FEATURE_TOOLTIPS + , F("Manufacturer") +# endif // if FEATURE_TOOLTIPS + ); + + // Meter Type + addTextBox( + P094_FILTER_WEBARG_METERTYPE(filterIndex), + getMeterType(), + 4, false, false, EMPTY_STRING, F("widenumber") +# if FEATURE_TOOLTIPS + , F("Meter Type (HEX)") +# endif // if FEATURE_TOOLTIPS + ); + + // Serial nr + addTextBox( + P094_FILTER_WEBARG_SERIAL(filterIndex), + getSerial(), + 10, false, false, EMPTY_STRING, F("widenumber") +# if FEATURE_TOOLTIPS + , F("Serial (HEX)") +# endif // if FEATURE_TOOLTIPS + ); + + { + // Filter Window + const int optionValues[] = { + static_cast(P094_Filter_Window::All), + static_cast(P094_Filter_Window::One_minute), + static_cast(P094_Filter_Window::Five_minutes), + static_cast(P094_Filter_Window::Fifteen_minutes), + static_cast(P094_Filter_Window::One_hour), + static_cast(P094_Filter_Window::Day), + static_cast(P094_Filter_Window::Month), + static_cast(P094_Filter_Window::Once), + static_cast(P094_Filter_Window::None) + }; + + constexpr size_t nrOptions = sizeof(optionValues) / sizeof(optionValues[0]); + + String options[nrOptions]; + + for (size_t i = 0; i < nrOptions; ++i) { + const P094_Filter_Window filterWindow = static_cast(optionValues[i]); + options[i] = Filter_WindowToString(filterWindow); + } + addSelector(P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex), + nrOptions, + options, + optionValues, + nullptr, + _filter._filterWindow, + false, + true, + F("widenumber") +# if FEATURE_TOOLTIPS + , F("Filter Window") +# endif // if FEATURE_TOOLTIPS + ); + } +} + +String P094_WebformSave_GetWebArg(const String& id) { + String webarg_str = webArg(id); + + if (webarg_str.isEmpty()) { + webarg_str = '*'; + } + return webarg_str; +} + +bool P094_filter::WebformSave(uint8_t filterIndex) +{ + String filterString; + + // Manufacturer + filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_MANUFACTURER(filterIndex)); + filterString += '.'; + + // Meter Type + filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_METERTYPE(filterIndex)); + filterString += '.'; + + // Serial nr + filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_SERIAL(filterIndex)); + + fromString(filterString); + + // Filter Window + _filter._filterWindow = getFormItemInt( + P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex), + 0); + + return isValid(); +} + +String P094_filter::getManufacturer() const +{ + String manufacturer; + + if (isWildcardManufacturer()) { + manufacturer = '*'; + } else { + manufacturer = mBusPacket_header_t::decodeManufacturerID(_filter._manufacturer); + } + return manufacturer; +} + +String P094_filter::getMeterType() const +{ + String metertype; + + if (isWildcardMeterType()) { + metertype = '*'; + } else { + metertype = formatToHex_no_prefix(_filter._meterType, 2); + } + return metertype; +} + +String P094_filter::getSerial() const +{ + String serial; + + if (isWildcardSerial()) { + serial = '*'; + } else { + serial = formatToHex_no_prefix(_filter._serialNr, 8); + } + return serial; +} + +P094_Filter_Window P094_filter::getFilterWindow() const +{ + return static_cast(_filter._filterWindow); +} + + +#endif // ifdef USES_P094 \ No newline at end of file diff --git a/src/src/PluginStructs/P094_Filter.h b/src/src/PluginStructs/P094_Filter.h new file mode 100644 index 0000000000..952b32b0cd --- /dev/null +++ b/src/src/PluginStructs/P094_Filter.h @@ -0,0 +1,95 @@ +#ifndef PLUGINSTRUCTS_P094_FILTER_H +#define PLUGINSTRUCTS_P094_FILTER_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P094 + +# include "../DataStructs/mBusPacket.h" + +// Is stored, so do not change the int values. +enum class P094_Filter_Window : uint8_t { + None = 0, // no messages pass the filter + All = 1, // Realtime, every message passes the filter + One_minute = 2, // a message passes the filter every 1 minutes, aligned to time (00:00:00, 00:01:00, ...) + Five_minutes = 3, // a message passes the filter every 5 minutes, aligned to time (00:00:00, 00:05:00, ...) + Fifteen_minutes = 4, // a message passes the filter every 15 minutes, aligned to time + One_hour = 5, // a message passes the filter every hour, aligned to time + Day = 6, // a message passes the filter once every day + // - between 00:00 and 12:00, + // - between 12:00 and 23:00 and + // - between 23:00 and 00:00 + Month = 7, // a message passes the filter + // - between 1st of month 00:00:00 and 15th of month 00:00:00 + // - between 15th of month 00:00:00 and last of month 00:00:00 + // - between last of month 00:00:00 and 1st of next month 00:00:00 + Once = 8 // only one message passes the filter until next reboot +}; + + +// Examples for a filter definition list +// EBZ.02.12345678;all +// *.02.*;15m +// TCH.44.*;Once +// *.*.*;5m + +struct P094_filter { + P094_filter(); + + void fromString(String str); + String toString() const; + + const uint8_t* toBinary(size_t& size) const; + size_t fromBinary(const uint8_t *data); + + // Is valid when it doesn't match: *.*.*;none + bool isValid() const; + + bool operator<(const P094_filter& rhs) const; + bool operator==(const P094_filter& rhs) const; + bool operator!=(const P094_filter& rhs) const; + + static bool equals(const P094_filter& lhs, const P094_filter& rhs); + + static size_t getBinarySize(); + + + // Check to see if the manufacturer, metertype and serial matches. + bool matches(const mBusPacket_header_t& other) const; + + // Compute expiration UnixTime + unsigned long computeUnixTimeExpiration() const; + + void WebformLoad(uint8_t filterIndex) const; + bool WebformSave(uint8_t filterIndex); + + bool isWildcardManufacturer() const { + return _filter._manufacturer == mBus_packet_wildcard_manufacturer; + } + + bool isWildcardMeterType() const { + return _filter._meterType == mBus_packet_wildcard_metertype; + } + + bool isWildcardSerial() const { + return _filter._serialNr == mBus_packet_wildcard_serial; + } + + String getManufacturer() const; + String getMeterType() const; + String getSerial() const; + P094_Filter_Window getFilterWindow() const; + + // Keep this order of members as this is how it will be stored. + struct { + uint64_t _serialNr : 32; + uint64_t _manufacturer : 16; + uint64_t _meterType : 8; + + // Use for filtering + uint64_t _filterWindow : 8; + } _filter; +}; + +#endif // ifdef USES_P094 + +#endif // ifndef PLUGINSTRUCTS_P094_FILTER_H \ No newline at end of file diff --git a/src/src/PluginStructs/P094_data_struct.cpp b/src/src/PluginStructs/P094_data_struct.cpp index 30a8449a71..bca4243549 100644 --- a/src/src/PluginStructs/P094_data_struct.cpp +++ b/src/src/PluginStructs/P094_data_struct.cpp @@ -2,22 +2,24 @@ #ifdef USES_P094 -// Needed also here for PlatformIO's library finder as the .h file +// Needed also here for PlatformIO's library finder as the .h file // is in a directory which is excluded in the src_filter -#include +# include -#include +# include -#include "../Globals/ESPEasy_time.h" -#include "../Helpers/StringConverter.h" +#include +# include "../DataStructs/mBusPacket.h" +# include "../Globals/MQTT.h" + +// # include "../Globals/ESPEasy_time.h" +// # include "../Globals/TimeZone.h" +// # include "../Helpers/ESPEasy_Storage.h" +// # include "../Helpers/StringConverter.h" -P094_data_struct::P094_data_struct() : easySerial(nullptr) { - for (int i = 0; i < P094_NR_FILTERS; ++i) { - valueType_index[i] = P094_Filter_Value_Type::P094_not_used; - filter_comp[i] = P094_Filter_Comp::P094_Equal_OR; - } -} + +P094_data_struct::P094_data_struct() : easySerial(nullptr) {} P094_data_struct::~P094_data_struct() { if (easySerial != nullptr) { @@ -33,46 +35,222 @@ void P094_data_struct::reset() { } } -bool P094_data_struct::init(ESPEasySerialPort port, - const int16_t serial_rx, - const int16_t serial_tx, - unsigned long baudrate) { +bool P094_data_struct::init(ESPEasySerialPort port, + const int16_t serial_rx, + const int16_t serial_tx, + unsigned long baudrate) { if ((serial_rx < 0) && (serial_tx < 0)) { return false; } reset(); easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx); - if (isInitialized()) { - easySerial->begin(baudrate); - return true; + if (easySerial == nullptr) { + return false; } - return false; + easySerial->begin(baudrate); + return true; +} + +void P094_data_struct::setFlags(unsigned long filterOffWindowTime_ms, + bool intervalFilterEnabled, + bool mute, + bool collectStats) +{ + filterOffWindowTime = filterOffWindowTime_ms; + interval_filter.enabled = intervalFilterEnabled; + collect_stats = collectStats; + mute_messages = mute; } -void P094_data_struct::post_init() { - for (uint8_t i = 0; i < P094_FILTER_VALUE_Type_NR_ELEMENTS; ++i) { - valueType_used[i] = false; + +void P094_data_struct::loadFilters(struct EventStruct *event, uint8_t nrFilters) +{ + int offset_in_block = 0; + + + const size_t chunkSize = P094_filter::getBinarySize(); + const size_t maxNrFilters = 1024u / chunkSize; + + if (nrFilters > maxNrFilters) { nrFilters = maxNrFilters; } + + _filters.clear(); + + size_t nrChunks = 8; + + if (nrFilters < nrChunks) { + nrChunks = nrFilters; } - for (uint8_t i = 0; i < P094_NR_FILTERS; ++i) { - size_t lines_baseindex = P094_Get_filter_base_index(i); - int index = _lines[lines_baseindex].toInt(); - int tmp_filter_comp = _lines[lines_baseindex + 2].toInt(); - const bool filter_string_notempty = _lines[lines_baseindex + 3].length() > 0; - const bool valid_index = index >= 0 && index < P094_FILTER_VALUE_Type_NR_ELEMENTS; - const bool valid_filter_comp = tmp_filter_comp >= 0 && tmp_filter_comp < P094_FILTER_COMP_NR_ELEMENTS; + const size_t bufferSize = nrChunks * chunkSize; - valueType_index[i] = P094_not_used; + while (nrFilters > 0) { + uint8_t buffer[bufferSize]; + ZERO_FILL(buffer); + + LoadCustomTaskSettings(event->TaskIndex, buffer, bufferSize, offset_in_block); + offset_in_block += bufferSize; + + uint8_t *readPos = buffer; + + for (size_t i = 0; i < nrChunks && nrFilters > 0; ++i) { + P094_filter filter; + filter.fromBinary(readPos); + + if (filter.isValid()) { + _filters.push_back(filter); + } - if (valid_index && valid_filter_comp && filter_string_notempty) { - valueType_used[index] = true; - valueType_index[i] = static_cast(index); - filter_comp[i] = static_cast(tmp_filter_comp); + --nrFilters; + readPos += chunkSize; } } } +String P094_data_struct::saveFilters(struct EventStruct *event) const +{ + int offset_in_block = 0; + + String res; + const size_t nrFilters = _filters.size(); + size_t currentFilter = 0; + const size_t chunkSize = P094_filter::getBinarySize(); + const size_t nrChunks = 8; + #ifdef ESP32 + const size_t bufferSize = 1024; + #else + const size_t bufferSize = 256; + #endif + + std::vector buffer; + buffer.resize(bufferSize); + + + while ((offset_in_block + bufferSize) <= 1024 && res.isEmpty()) { + for (auto it = buffer.begin(); it != buffer.end(); ++it) { + *it = 0; + } + + uint8_t *writePos = &buffer[0]; + size_t writeSize = 0; + + while (writeSize < bufferSize && currentFilter < nrFilters) { + if (_filters[currentFilter].isValid()) { + size_t size{}; + const uint8_t *binaryData = _filters[currentFilter].toBinary(size); + memcpy(writePos, binaryData, size); + writePos += size; + writeSize += size; + } + ++currentFilter; + } + res = SaveCustomTaskSettings(event->TaskIndex, &buffer[0], bufferSize, offset_in_block); + offset_in_block += bufferSize; + } + return res; +} + +void P094_data_struct::clearFilters() +{ + _filters.clear(); +} + +bool P094_data_struct::addFilter(struct EventStruct *event, const String& filter) +{ + P094_filter f; + + f.fromString(filter); + + if (!f.isValid()) { + return false; + } + + if (isDuplicate(f)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("CUL Reader : Duplicate filter found: "), f.toString())); + } + + return false; + } + + _filters.push_back(f); + + std::sort(_filters.begin(), _filters.end()); + + if (P094_NR_FILTERS < _filters.size()) { + P094_NR_FILTERS = _filters.size(); + } + return true; +} + +String P094_data_struct::getFiltersMD5() const +{ + if (mute_messages) { + return F("blockall"); + } + + MD5Builder md5; + uint8_t checksum[16]{}; + md5.begin(); + + uint8_t nrFiltersAdded = 0; + const char separator[] = {'|', 0}; + for (auto it = _filters.begin(); it != _filters.end(); ++it) { + if (it->isValid()) { + if (nrFiltersAdded != 0) { + md5.add(separator); + } + md5.add(it->toString().c_str()); + ++nrFiltersAdded; + } + } + + if (nrFiltersAdded == 0) { + // No filters, thus all messages will just pass + return F("pass"); + } + + md5.calculate(); + md5.getBytes(checksum); + + return formatToHex_array(checksum, sizeof(checksum)); +} + +void P094_data_struct::WebformLoadFilters(uint8_t nrFilters) const +{ + if (nrFilters > 0) { + addFormNote(F("Filter Fields: Manufacturer, Meter Type, Serial, Filter Window")); + } + + for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine) + { + if (filterLine < _filters.size()) { + _filters[filterLine].WebformLoad(filterLine); + } else { + P094_filter dummy; + dummy.WebformLoad(filterLine); + } + } +} + +void P094_data_struct::WebformSaveFilters(struct EventStruct *event, uint8_t nrFilters) +{ + _filters.clear(); + + for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine) + { + P094_filter dummy; + + if (dummy.WebformSave(filterLine)) { + // Filter with filled in values, worth storing + if (!isDuplicate(dummy)) { + _filters.push_back(dummy); + } + } + } + addHtmlError(saveFilters(event)); +} + bool P094_data_struct::isInitialized() const { return easySerial != nullptr; } @@ -84,14 +262,301 @@ void P094_data_struct::sendString(const String& data) { easySerial->write(data.c_str()); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Proxy: Sending: "); - log += data; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("Proxy: Sending: "), data)); } } } } +# if P094_DEBUG_OPTIONS + +const __FlashStringHelper* getDebugSentences(int& count) { + // *INDENT-OFF* + switch (count) { + case 1: return F("b3C449344369291352337D55472593107009344230A920000200C0538ECE32625004C0527262500426CBF2CCC0805BDF032262500C2086CDF21326CFFFF046D26BB1103DA22B4E093E2"); break; //QDS.0A.00073159"); break; //QDS.37.35919236 + case 2: return F("b9644A732260729700A0AB8487A4E10002002747D00046D030AC1270CB02E0600000000446D3B17BF2C4C0600000083410084016D3B17DE268C010600000000CC190B0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22030402000F841001245C84E7"); break; //LUG.0A.70290726 + case 3: return F("bCE44A8153132000801022ADC7F6900C005098D2F2D70E24F3E43458739FD572B2DB7CB22EA563C57F3017308E093A4CBC662DF70F000A2E2B18215FC7098DBC7DC8A2ABAD8202F700C5A7D8B0FC89094823FC6B54565730369E73039146898536381B5DE8B8F3A5377A807EB30383ACC0176176C6C18265932082844F0B5A3F69B0A66FD0E35FAED9A53B825E073FC1E67193A727C97BC1025229C87421FA0381443A5F2F2897AE44D383FE125614F08BABEC6B46DF0FFCB910DAD1B3CD53B44AA83726492D845F840A2D20B738E9FB212D5C74FF91FD2796A22D669CBF0B0FEC1BAFA171A65FFB165B2E9"); break; //EMH.02.08003231 + case 4: return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162 + case 5: return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180 + case 6: return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346 + case 7: return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000 + case 8: return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966 + case 9: return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036 + case 10 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 11 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039 + case 12 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490 + case 13 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 14 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510 + case 15 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337 + case 16 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 17 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224 + case 18 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199 + case 19 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075 + case 20 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA + case 21 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424 + case 22 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003 + case 23 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003 + case 24 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003 + case 25 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182 + case 26 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002 + case 27 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764 + case 28 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096 + case 29 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869 + case 30 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 31 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608 + case 32 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984 + case 33 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + case 34 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525 + case 35 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660 + case 36 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + case 37 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 + case 38 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672 + case 39 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371 + case 40 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371 + case 41 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917 + case 42 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917 + case 43 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177 + case 44 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177 + case 45 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484 + case 46 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384 + case 47 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384 + case 48 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 49 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 50 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909 + case 51 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234 + case 52 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419 + case 53 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082 + case 54 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492 + case 55 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105 + case 56 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 + case 57 : return F("b8E44A81524229207010276807F680080056E68ED762185BEDAB68F79E2A8BF6562ED1C21105B74E82AA9D1B5E79311AB713DC2F86C785D18F6E470067B86284E8A9D1AF9F36330FE18968F826F8D1B21E99F1C08D45FB9CDBF04B37136E2FC1CED74FF3186482FA3058F6F1BD712BBE20098CCB0C72D51F6013CBB974678706CF2F4D6D0D07A793FE1EC8EAF701AD2BEE5922E69F72C356AC1B009A0E0646B0413DD45802B"); break; //EMH.02.07922224 + case 58 : return F("b374465B2118222001604DDD67A26140000046D1107152A01FD0C06326E796CFFFF0DFF5F0C00083D300001061308131C0BFFFC02FD1700140C7896145559D9FF8032"); break; //LSE.04.00228211 + case 59 : return F("b4BC465B251A16000F193DC2CA083110049244E01394465324604401311FC17067A4F0000000C13714800004C1310BC7A000000426C7F2C02BB560000326CFFFF420A046D2009912682046C9F258C04136643DF650000FFFF80DE"); break; //LSE.00.00000000 + case 60 : return F("b4BC465B251A18000F1950519A08311009E2423003944653252044013781E17067A500000000C13063401004C1310623C000000426C7F2C02BB560000326CFFFF420A046D330E912682046C9F258C04138720452E01000B2780DD"); break; //LSE.00.00000000 + case 61 : return F("b43C465B251A12001F0DDDD95A08310008A2460013144653221587296186735087A4F0000000B6E9102004B6E000041BB00426CFFFF326CFFFF046D2108912682BC3E046C9F258B046E9102000B2A80DD"); break; //LSE.00.00000000 + case 62 : return F("b25C465B251A0000AF1033454A2653205834710290E862400005F3573BB1B9A276422A2271D001D0004C9253580E4"); break; //LSE.00.00000000 + case 63 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003 + case 64 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003 + case 65 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003 + case 66 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182 + case 67 : return F("bYB04424341931031950067A800000002F2F0413F1570000046D092EC52504FD17004000000E780000000000004413184B0000426CBF2C840113F057000082016CDE24D3013B4E0400C4016D3128692C8104FD280182046CDE24840413F0570000C40413FD560000840513D1530000C40513CC4F0000840613184B0000C40678D313EA47000084071323430000C40713153F0000840813FD3B0000C4081393380000840913A3340000C4091311310000271081D7"); break; //MAD.06.19033119 + case 68 : return F("b4E44A5113748906370076F917A570040053A66831E6B6C8F06FB2C7CC1F60A673B63ACBF2F6A8D3347D34DE4BBDD4677E5DEC850D5CBBDF5B0AA7095B415EFCD93A124AC3B884FAF226845C439DBF3A2EB486CBE7D8D845383E3E480ED"); break; //DME.07.63904837 + case 69 : return F("b5344A511292535727507858F8C000E900F002C25823E090045AF7EFEDB43FBE691577A82003107105692A21A8C0C7531EEEAC3AA631D6BD790CA1B271C7E3C8860A189DA37AE30E89AB63AD316663EB4AD472596156E592602F8A016E51A5E0D8753"); break; //DME.07.72352529 + case 70 : return F("b5344A5117157367276075A048C00B0900F002C25218501000C373A89C912675F09407A2100310710DEF25F58D772CBD51AD6D2B487ED868B6B086976F51AE52E65D877F0310E9DCA942F4E7F94196DD2821329E662FD4E4C9F20A1A8829C54328EEC"); break; //DME.07.72365771 + case 71 : return F("b2E44A511880110827B0780AF7A4573200592F4DF44D5F258DF39AB376DD4A5BC338BBB2493257A98DC52C5E6299029DC79C01ED377C7F48028"); break; //DME.07.82100188 + case 72 : return F("b3644E61E95413900010E89957212517615E61E3C074230206593240B570397DF6230FBC015B276D63CD694FA79B17D1436E0E9FA1003C0763761BD8FBD67B73A8D"); break; //GWF.07.15765112"); break; //GWF.0E.00394195 + case 73 : return F("b9644FA12237704190007308E7AA8000020046D1E2B862B0413D1EA01F9CA0002FD17000001FD481D426C7F2C4413455C9E8A000084011370E50100C40113A0C200F70100840213EA9E0100C4021349870100735F8403130A700100C40313595301008404C386139F2B0100C4041345FA0000840513BABE2BCD0000C40513DEAA00008406139E8A00A1F700C40613D966000084071390400000C4F6040713E11C00008408136200000087F382DE"); break; //DWZ.07.19047723 + case 74 : return F("bA944FA1283211320020766737A6B009025ADD27B2058B2ADAAE5B5D2C8B11BD295CE460BD1ECA50B533A58FF19F7F9DEC291893DA502B763E7C80CEF440B899C92DE87D5548838FC2C9FF3873927441B0B17222FB14545D2E5A0CF81216074B3DF0911CB99BD20A558F2D873A9A649092F3C7B01BA2BEBCC26A58C0FD99ABC2E5E0E5AFC241A27B8C0863C571C3D586B7E2A06044B792CFE021C21BB1B71BCEE4D5D84E75C3FC0624DF07C8C8832C82140086903FD0C08000002FD0B011116A380EF"); break; //DWZ.07.20132183 + case 75 : return F("b3944934419504913170628617A660000200C13480900004C130000007BBF00426CFFFFCC081315020000C2086C9FC6D42A02BB560000326CFFFF046D1316952B82B084D3"); break; //QDS.06.13495019 + case 76 : return F("b2E446850915944496262C568A0009F276D03B017BC0000060A0A0809A71C0908090706070A0A09090A0B0B080708CB640A060A07071EA586E2"); break; //TCH.62.49445991 + case 77 : return F("b2E446850573060566562ADA6A0007E293200C0191400001400000000CC37000000000000000001010101010101023FB20101010001328686E1"); break; //TCH.62.56603057 + case 78 : return F("b2E4468505162250070628CD3A0009F277D00600A0B00000001010101002A010302010101020102010101010101018F6C0001010102AF7786F8"); break; //TCH.62.00256251 + case 79 : return F("b2F446850302976627462A01BA2064D280000600A240004000307070747EA0706060000000000000000000000000077A2000000000000FFFF80F6"); break; //TCH.62.62762930 + case 80 : return F("b2F446850370321809562C217A2069F270C00A00E0700000001000001B0E800000100010100000101000100000100B1AD000101010001328680F9"); break; //TCH.62.80210337 + case 81 : return F("b2D44653277993613170784017ABA0000000C13196100004C131100003AE500426C7F2C02BB560000326CFFFF046D2ACD2E068527CCD580DF"); break; //LSE.07.13369977 + case 82 : return F("bY24442D2C394864681D168D20AE91BF1622CC255857FE7B524DD67C4944CD428FBE5E0DFAB98021"); break; //KAM.16.68644839 + case 83 : return F("b2D446532490340131706417B7AB20000000C13814000004C13100000A12600426C7F2C02BB560000326CFFFF046D2ACD3B168327AC23800F"); break; //LSE.06.13400349 + case 84 : return F("b3644E61E08106100020E7F627236090021AE4C01071B0020A56EE84D172DF9648D9C4409131B88D3FD85BFD022BCB54971CACB714D617E5563719B95E9B2059A59"); break; //SEN.07.21000936"); break; //GWF.0E.00611008 + case 85 : return F("b3E44FA1287530019011691D07AC6002025D541F7DE1D910813127884F7DF4D80BF600A4323BD730B4639E4E0EA8B86129BDE9DD71D0F800C000109002487721866530201310101B10187CE"); break; //DWZ.16.19005387 + case 86 : return F("b9644FA1261281221000689447AB9000020046D302BAF2704136300009A0E0002FD17000001FD481C426C000044130E9B0000000084011300000000C4011300002A2D000084021300000000C402130000000098F984031300000000C40313000000008404B0D51300000000C404130000000084051300A00E000000C4051300000000840613000000B5CF00C406130000000084071300000000C4E74A071300000000840813000000009995812A"); break; //DWZ.06.21122861 + case 87 : return F("b3E44FA12336300190106617D7A81002025F1114AE0F19A04778065DD02E84809E9F7163157F05FB2506D26A3835904CED370FBCA9E0F800C00010900E10E03000A70207F3101013FFE80E3"); break; //DWZ.06.19006333 + case 88 : return F("b4C44B40968440303170787F77A3F0000000C1305000000046D1A2EAAA19F250F8F00010000000000000000000000D5BD00000000000000000000000000000000FFFF00000000000000000000000000000000FFFF000000FFFF80EE"); break; //BMT.07.03034468 + case 89 : return F("bY29442D2C394864681D168D20B3B0BF162236090AB83DFCC84216131495B5A2DF59242760EDECA043AF20801F"); break; //KAM.16.68644839 + case 90 : return F("b2844C51473278071030605C97237253816C5140006612000202F2F0464E66D243498260413070B010001FD1700146B80DD"); break; //EFE.06.16382537"); break; //EFE.06.71802773 + case 91 : return F("b39449344715387121606AE447AD40000200C13969799994C132798998CC999426C7F2CCC081396979999C2086C9F18DC2502BB560000326CFFFF046D2E0D8D261542803D"); break; //QDS.06.12875371 + case 92 : return F("b39449344724733131706336E7A270000200C13330600004C13100000218B00426C7F2CCC081382010000C2086C9FF2142A02BB560000326CFFFF046D1116952BF4D081DC"); break; //QDS.06.13334772 + case 93 : return F("b3944934451926513180637967A7D0000200C13110000004C13000000B0B100426CFFFFCC081311000000C2086C9F52D52A02BB560000326CFFFF046D3A0B862B28D08027"); break; //QDS.06.13659251 + case 94 : return F("bY5444A85C3281262703077AA90040254A1AED9189683FF741015BCF9C9FD17914758544A14121969793DAA718C7C091F9E26BF16197828BD514A4E66C5460849605A64ACFBD3D3167332F6AF040711E426CBF237DD6802A"); break; //WEH.07.27268132 + case 95 : return F("b394493444993671216075B197AD00000200C13850441004C13286138A3F900426CBF2CCC081345574000C2086CDF284D2302BB560000326CFFFF046D330BD62497B485F8"); break; //QDS.07.12679349 + case 96 : return F("b39449344782049131707F5A37A480000200C13121902004C13000000D42000426CFFFFCC081353750100C2086C9F077F2A02BB560000326CFFFF046D0716952B9ADF80EA"); break; //QDS.07.13492078 + case 97 : return F("b3944934462526513180768437A880000200C13060400004C13000000950300426CFFFFCC081398000000C2086C9F0D7D2A02BB560000326CFFFF046D320B862BCC358012"); break; //QDS.07.13655262 + case 98 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 99 : return F("b6644496A721102551437F5C77224831715496A000726005005827161FD1AB240AE86C2A6D7F691E4C8531E4530AE4FC8A294BE87862FDDDCE843D7679005C55E082A744BC18EA87FF12298AE4258EE9D89B1F9511318B8D7152464FB11007F5CEB827893513C954A6E9735185FE268124771D567A080E8"); break; //ZRI.07.15178324"); break; //ZRI.37.55021172 + case 100 : return F("bY50449726041331160007728499181897A60030E10030A59E74DF73596296EE08CF3F1BA88B47A3A264C30177EC921B750B03B99F992A0EEB064560EFAD6C5EC7CCA1009D72C63B0E79000000000000697E801E"); break; //ITW.07.16311304"); break; //ITW.30.18189984 + case 101 : return F("b2F44090773754205100794947ADD100000046D070DBB2404130F000029E0000259F0D834FD17010000000424132B5729820001FD7462313A802A"); break; //AXI.07.05427573 + case 102 : return F("bAE44EE4D449858203C07C8B87A0100A0257AB76440C2E16196857D0FAC3205AFB9D036CD7885C1F60C61095F2300D08DF56026E74FFB2F876BD89D27F65B3EA3729F53B26A48F2A5684EDD67A16177F8DD127C2CD8FE1C42CD5035E7EE110515C7369DB07AA59D5954E077C30AA29423D12429C77F6A7DEF679B3A90D7FD075AB7ED262466ECB4FDB66C609FC5DCBEA89FBCEB7BAA4EB8C312CF1A242B39A696A25E81CF0B5994B25CBDC06749D29F5B0F9E9F98476FFF9ED428840C0082459878C8FCEBA90EC380F7"); break; //SON.07.20589844 + case 103 : return F("b4344010648020780011656A78C001D900F002C2561675D61AD80F90FDEC404B8CAB47A80032007100A387268EF9F9CC8106E5D7FEE6E9D41D7E8780E2B18C2F71743F96AC442C6191D9B8E8E2AEE85F1"); break; //APA.16.80070248 + case 104 : return F("b2E44685017467703627254FFA0009F299A23A02C3A000008060605050B45060504020407060412140C0F0F100E111BFE070B0A0908C0EC81DC"); break; //TCH.72.03774617 + case 105 : return F("b2E446850812896626572C47AA0009F290D1620272200060001018DE4606A430F1101000701010302010403020403DC700002050302E7F581EE"); break; //TCH.72.62962881 + case 106 : return F("b2E44685007457014707281B0A0009F270400600A0100000000000001A1A60000000000010000000000000000010086A10000000000FFFF8007"); break; //TCH.72.14704507 + case 107 : return F("b2F4468509851603374725693A2069F27CE00600A600000000409090A8C140B090C0C0F0E070F0C0D0F0E0E0B0F0A640F09090D0E1009932C80FB"); break; //TCH.72.33605198 + case 108 : return F("b2F446850396450729572A1CAA2069F27FC09800D3E0200000238383EF87E353F3A3B3A2C2E1E2B3830111E2A181FA0AB2432273805208FCC86D2"); break; //TCH.72.72506439 + case 109 : return F("b76447916987435614037E4C2729646866079161102FC0060058CA7D7567090FFC84CBDE4FE2996BF8080E6A2C41D6DF46D8921894C814CC0BB36C550312F03A2734512ED7A69349DE3C349CFC079B62A3AD93BAA84A087C739785EFC9A415F6AF0662B05401FD0C4C9D4A676DA7FC1F6C592823EA6B6DFCAA3DDB2E4D5B6E87C5EA1B92E903D62"); break; //ESY.02.60864696"); break; //ESY.37.61357498 + case 110 : return F("bCE44A8154457060801022C397FDE00C005F4FEE3A225126DE3E8344CEACB8502C615B4F7EE9D9CA99F2E3AB5AEA9B3417ABD2B8DF835C0A8C31510DC184ACC8E261E24B717F51C01887D9B39D57965A4004CB68B65206E173F7374489CCBBAB63D4F4B33B488DE06DD33C93EB719BDD805E331238755D22C7E94ECD3636C1CCD965CF4E6DC37123EA8D95771BBCD12431AD12B646EBD21FCB77BD2D100C9CCCE7546268B3AF080DEC8F6B61BCC209013295A74A2730D9CB11E52056D0C0879EA29A2CE24210EAF58587855A790FF343CF82914E0164ED08C70FD54DB800BD9B1C0F2431883324E43A1D293"); break; //EMH.02.08065744 + case 111 : return F("bCE44A8153132000801022ADC7F9000C00546CF1AA7C3268079B9F3A0D8D2FA9AE941DDCDA1D0D8CFA6358FB9FDAD0AF38BB344D954ADD742E77428CA48DD918F361E23D5805BA6BB4CD2470016F66B92CF5D2AF69C7751F1FD2D887A10F26865E6AD95F58754980416935DA7A6C669823CDA9A3CF4D0EBE233D7F706DA6ECFD8209C8F140A7666B9AB2D9ED8C0D5AE04C3600B0899207B54AA98FABF9D14F773F41A53378E9A5678516B95EDEDED334821972EC196927F794E72725C78300FE65155E4E49B43A329EE44AD6FD90070548499CCFFD9FB71ACB4183B4B14DEF7D8B99BF02B754E651850960D80F6"); break; //EMH.02.08003231 + case 112 : return F("b7B445A1415200000023724958C2049900F002C259D7515009EC6163C4A7CB4E8935772324371005A140102490050646D0710FDE80DD548AA00BBCFEB3152BF11CDD91D4D31F1C73144BBB17696622104EB91D19A32FF7CA939126767495134EE598AF96AC9F0081A83B4D7EAF7A497981C15604C85E4E3B9FBA393BDAD8E0DED15EED4BA887D9970D17797849FE68045"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 113 : return F("b5B445A14152000000237E4CE8C2048900F002C259C751500003AD02FC41ACF50E92F72324371005A140102480030E8FC8710B2BC8D30DC227AD803ED24F3B6FCC6E5131442E65A7F5CC4589D51AE2FB2690E9457810BDAAAC81988A3E5E81E6CDA24BE256218D907511C8044"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 114 : return F("b1E44C418557901000102E4437ADB00108561D52810CD237CA87FB7C5A2F62A65C25C457BF78028"); break; //FFD.02.00017955 + case 115 : return F("bY4344471325798761013672193170604713010210000000000004788F419E030DFD110E39313133303730363030475A4431060200000000000006823C0000000000004E88"); break; //DZG.02.60703119"); break; //DZG.36.61877925 + case 116 : return F("b7B447916419806613037A83A8C204C900F002C25B4AA0000CC2128E94C8F3D946F737210552961791611024C00500ACF0710FCFB475BDA1FD49D52FF6FB45E833D5D97609373AB3EB562E01CE23D1389FFEC7EE41D4E7B20D35D8B80581C289834F4E3C542C09B0C37331332A321B79DAF7F60CDB028452AE3A26291EFD25C87DDC9EACF462C7B441510340F755C800B"); break; //ESY.02.61295510"); break; //ESY.37.61069841 + case 117 : return F("b6B44791641980661303756A58C204B900F002C25B3AA0000ABEB03EC47E933C072B97210552961791611024B00400536871036D216BF2B74E11F1952539F406B7D4DB7721B1154E21C36F0F55BD4B5B5FF9977572BCD27D93A909347A0D84AE9E804C361E9E6D98F9BE296F76E3A5100357CDD3FA09AABE44103EB508044"); break; //ESY.02.61295510"); break; //ESY.37.61069841 + case 118 : return F("b73447916856565601002F8DC8C0037900F012C25809A62002B5C02299AC829AD82267A370050471010A9AE87B893A51E226DD3203AD775D84E2B2E04D96F85DA0CD258EBC10912241ACAEB1406433A637F6C70811B7FC99565C388A0365A9B57F94E0380317F5CA1052D6FE55C7BE3118A4D1CD05AABC646F5D288B129D7934E68CF030A"); break; //ESY.02.60656585 + case 119 : return F("b7B447916419806613037A83A8C20EC900F002C259EB10000F76F8B1B2D9E402B9498721055296179161102EC00508B8E0710072DE1601CB75C3C9C30E9E037024B1D516151D3724CCF1654B2AC7707B5B41F2BBE3F2C19E9467A0740AB6A4F7F03F5E764FCCECC689EEA66BB4A9A6F25FDDCE8A60E9D998A43936B8A3B2EB57A8F667B476D1ED1CD2381D0ABF6118033"); break; //ESY.02.61295510"); break; //ESY.37.61069841 + case 120 : return F("b6344FA30883161560002C10A8C2042900F002C2549030000BC2C8A72651D570AEB967AA7004007103CB056B91B8007B8882351411FD19256641A220725C724891287445B85FDFDA2DB638B542BB7FC7209833DE16EDCBB90FA686C10076EED63BD40AB0EE3EBCDFDF40032F5C0DCD66322BE804A"); break; //LGZ.02.56613188 + case 121 : return F("b6B445A14043300000237C1FB8C200E900F002C250E000000F96A649A04D7DC457E8972807759005A1401020E0040A6D20710A07B663F2ED91A0DEA4515C0C1D4A296569A7C4DC27AE724F38CF0E518949AFAA9C3AA8703638F5305E2A333941756C4F5BF4703A411B7FE92F58D7F2724E31E10DCDCBF2C7EE18CD4738043"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304 + case 122 : return F("b5B445A14043300000237FF3F8C2001900F002C2501000000574262230ACE2FB2FB5072807759005A140102010030303C8710DD05806D5E2EFB0ACD9DA1964A191D7BADA8E1A508EE7CFA86336B14B1C756AA1E20FA5CE029D2CE99CC84BFA0CAFD723A5D0BC20E6FECC585FF"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304 + case 123 : return F("b4E447916110000600702D8607A99004005860F4BADF0716289AEE56290239E4549E908B33CF4C280DEDD3382DF293865824ADFA25EBDDDF28DB046A59DA2A4C2DB62CF177F2E77EF3E62D6A67DEA6BBD01BEA1DFB9ED3DC8743E2E"); break; //ESY.02.60000011 + case 124 : return F("bD344A815394643100002D3DD8C2003900F002C25741E0000A17997475A31870EDCF27A0300B007101E6BCA68B34F4603824A04FC0B87A398438300B31366E3B94F45C0EEBFF210719F87A8382509BC329B59A4031ECAC4CA734374B06825329A3665EAA2C626DA096C8E50002E95443D540F92C90EE305931E8E066BB8A76AA2DDE94042B90B94E9D3174612DBF8755145BBA760A85B7C84DDF075950FFDE6EEB4EF76A664F4A474CE050E12021434806BB9C4CDDA5CA8F13DDE8010C5DB0EAD57FD5E6DF042A1C0AE83DA23EB2B8C3444A11D8A6F6274072E516F42DA449DC460D14C1D9E12F95CAE43112976493AD493F0"); break; //EMH.02.10434639 + case 125 : return F("b8E44A815262292070102C8F57FED00800592BC9808524F745DE09733D6B862F1FEF9AC816CCD59B730C0BC8D173E7B1B187735C505768A284780C3B7E53CCF252EC84F44DF2A8949DB12139FD80D000930321F57E73C8F22B44AB57C03F93C35B02B853E45A0960F0FCD326F4760CA76945EADF6F294356AC8308DE284EEBCECB57EDF9BB63BCC5D424A764348B2D40E86C812AEA58CA14B652DD3853BEF5BDF56A27F8031"); break; //EMH.02.07922226 + case 126 : return F("b5344A81594176710020267788C20D7900F002C256A000000E4D7497D043A0EE5B2A47AD7003007102F390C301AD7CB52A6B8633CA25AA26BDE4FDF94F3230E43CCBF66EEC0D4C0C7A6E66310DC6376768FE891C0CA84DD365D3FBF23690BB55E812B"); break; //EMH.02.10671794 + case 127 : return F("b2644AC482711000050378A347201271100AC4850021B0000002F2F0C01C103895936002F2F2F2F2F2F2F2F84F48029"); break; //REL.02.00112701"); break; //REL.37.00001127 + case 128 : return F("b7C44361C120001000002CA9C8C203F7A3F00000004050000000004FBCBA782750000000004FB82F53C000000000412B22A0000000004FB140000000004FB943C59F00000000004FDD9FC010000000004FDD954D8FC020000000004FDD9FC030000000004CF6EFDC8FC012909000004FDC8FC02F10400CDDC0004FDC8FC03F104000002FB2EF40101DC37FD1700DE8C80FE"); break; //GAV.02.00010012 + case 129 : return F("b3244361C373601000102603A8C20457A4500000004050900000004FBDA3482750800000004FB82F53C0000000004C3AE2A2415000001FD17001EB2801B"); break; //GAV.02.00013637 + case 130 : return F("bY394447135523636001027A830000000478232D9D030DFD110E35353332333630363030475A44310602ED8B0000000006823C00000000000004E4802B"); break; //DZG.02.60632355 + case 131 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 + case 132 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672 + case 133 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371 + case 134 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371 + case 135 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917 + case 136 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917 + case 137 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177 + case 138 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177 + case 139 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484 + case 140 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384 + case 141 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384 + case 142 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 143 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 144 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909 + case 145 : return F("b624468501509651494085A758C002A900F002C25DECE080028421E778E39B0665A707ADE0030071080A1255F4529D6628D1BD017B641EF0A8046B680C77DAB285B3C2A943522663000821E48556555C93D048F5DF327EFA4BECCA1914A1C0F9EFCE6532756F5E9BF68B0E01E8802D91270"); break; //TCH.08.14650915 + case 146 : return F("b31449344401892903408DFBB7A9B0000200B6E4800004B6E14010042A7D46CBF2CCB086E480000C2086CDF23326CB9BCFFFF046D0E0BD624CF16800B"); break; //QDS.08.90921840 + case 147 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 + case 148 : return F("b3444EE4D774933221608D1BB7AF7000000046D280DBB24036EEB000059A9426CE1F7436E00000002FF2C000002598620C80B0265F10802FD66A000779E80EA"); break; //SON.08.22334977 + case 149 : return F("b2E44B00971280018540843227A0E0000202F2F036E0000000F1000016F1B8563822A0000E70A812A000000000000AF010000000000FFFF81DD"); break; //BMP.08.18002871 + case 150 : return F("b2E44B00971280018540843227A0E0000002F2F0F0000000000000000718D00000000000000000000000000000000FFFF0B2A00002FF97381DE"); break; //BMP.08.18002871 + case 151 : return F("b26446532392238253508D6A07AAF0000000B6E0000004B6E000000422F866C7F2C326CFFFF046D2D0A83279B6780E8"); break; //LSE.08.25382239 + case 152 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218 + case 153 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218 + case 154 : return F("bY2F44C5145935816213087A080000202F2F046D0F2EBB24036E000000426C9F25436E000000317F00346D00200000F1458737"); break; //EFE.08.62813559 + case 155 : return F("b3E44F536861202000108F54A7A9B0020250A86DF3204D8B7DAFBD62C57159092FFCBB77F612E656C59AD065C96CC24B353B024A6930F800C00010900E10E03005DC1207F1B05074A5380F2"); break; //MWU.08.00021286 + case 156 : return F("b2E446850844616516180E459A0015C284604600A7200003900341D9A5C477E9ABC873D05000000000000000000019BB4000406456E897887DD"); break; //TCH.80.51164684 + case 157 : return F("b2E4468506974806164805122A001DE26EB02900D570200000000040D21820D4E301126353030334B44170B0A01004EE40000000000FFFF83D9"); break; //TCH.80.61807469 + case 158 : return F("b324468500441169269802F7CA0119F272F01600A3B00C8082F09000007730000000500000220140B1807030000007111000000000000000001C29A85D5"); break; //TCH.80.92164104 + case 159 : return F("b33446850710351129480ABE9A20F9F270000D00E03000128090B0900DA3600000000000000000000000100020000274800000000000000000000FFFF80E3"); break; //TCH.80.12510371 + case 160 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002 + case 161 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764 + case 162 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096 + case 163 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 164 : return F("b3644A511621280223837CE5E7241022436931581038C002005A8F458578136DF07A14CE37F82BA702DF936647F231F18C961CF7A6CE31CFAACE57153A8888B"); break; //ELS.03.36240241"); break; //DME.37.22801262 + case 165 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234 + case 166 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419 + case 167 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082 + case 168 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492 + case 169 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105 + case 170 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 + case 171 : return F("b5E44496A973430000D1AFCF97AF7CB50057AB6BD92ED8A65D4DB8A19DDA1B6D3CD164A0F600C93485BCFC48263B255C4FC57033B6114A4DD1590F6E3B22855F7161BBFB100973B49CDB3593DEC5164ECF04F1CAB0311E89B873ECECCD7FBF0D60D125734B551865D12F7D012748025"); break; //ZRI.1A.00303497 + case 172 : return F("b4E449344492512971437674F72090328039344141A7510002081027CB4ED034955230F82026C8927C1027C0354466C2C230FC4026D2C0E892781037C034C412307BC3282036C892702FD171000326CFFFF048C536D2809BA22ECC883DC"); break; //QDS.1A.03280309"); break; //QDS.37.97122549 + case 173 : return F("b4E449344505427971537894372380266079344151A6203002081027C8B4A034955230282026CFFFFC1027C035446B3DE2302C4026D1107632681037C034C41234AE90082036CFFFF02FD170008326CFFFF0469C16D000BB822FEF28024"); break; //QDS.1A.07660238"); break; //QDS.37.97275450 + case 174 : return F("b3E44934435188745211A19F67801FD080D81027C034955230182026CF8CB932381037C034C41230082036CFFFF0359F5FD17300010326CFFFF046D060A8428027576FDAC7E5F0192628012"); break; //QDS.1A.45871835 + case 175 : return F("b3E44934455319745221A29607801FD088A81027C034955230F82026CB7439C2381037C034C41230082036CFFFF037174FD17500010326CFFFF046D260B862B02842AFDAC7E8200271080FB"); break; //QDS.1A.45973155 + case 176 : return F("b3E44934404155047231ABDD57801FD084C81027C034955230082026CCC02FFFF81037C034C41230082036CFFFF03E0CFFD17000000326CFFFF046D080DAC2102EA18FDAC7E11005C3E80E0"); break; //QDS.1A.47501504 + case 177 : return F("bY60442515485001000C1A7A23005025568AED71E43AF834900BEC738E08C4FA2637B8915FB401FD6296F19C3AEECEEBC3164B967CD5445E6AAFE90F416314191CB1839210B7CD2EFE168911FD465DAB56CCDA9C82862B90F29353AB57532B49E67E"); break; //EIE.1A.00015048 + case 178 : return F("bY75442515639716000C1A8C208A900F002C25A7000000DDCE55D203E089D07AA800500710760097C0A7F24E9681882D62EFF802EC33146C9B3828FD5B2026432F40E7098DA78C4538579DDE260B2DCCE933093DA312A2C499D4473F150422121279632724B2FCB44A2110D2A2DA87B8C084512CDD698F"); break; //EIE.1A.00169763 + case 179 : return F("b5E442515954400000C1AA17A7A74005025F6B841CED5F1892796E8217F31F08E864EF5C0BBBDFE640AE3711C34C4B5B8AE3B821F1F0F3FF81C8259CBDABD6D05A0A751305C3399E9450DE8E86BE0BDB2D7AFA79BB10179B7EB1F37983CB8C7F10746888DFA54B4CD95AB8C78018025"); break; //EIE.1A.00004495 + case 180 : return F("b5E442515695800000C1A452E7AF200502547F361FEDBED8040998A4AF106CE368D0D469CFE69DD1983E5D5A84078AB4F4F2AFD97FD57668A660038C14F4B79CC3CAB703C3C4ADB6B19AE027E25C4E157B23E9A4D5A7CF24F4962BD29591A73F3E3BD13C9F26D15826364C2DFB28051"); break; //EIE.1A.00005869 + case 181 : return F("b37449344981002451F1AF6837AC418002081027C034955230082026C7CB5FFFF81037C034C41230282036C6D2A027047FD178400326C962A046D080FA623CD2B8AD1"); break; //QDS.1A.45021098 + case 182 : return F("bY5844972642631092001A727299291897A60030B21340A5C84E203CFF62E039C2A1F61CF679A05B7DA7F31E4F0AAA0F30EFFECF4AC176AC9E173C426799C618D50A4B285CBB9074CDF78FA733C7AEC4F66C45D760FDFEE2327E8016"); break; //ITW.1A.92106342"); break; //ITW.30.18299972 + case 183 : return F("b3E449344981002451F1A2CED7801FD08FB81027C034955230082026C61FAFFFF81037C034C41230282036C6D2A034D22FD17840018326C962A046D0A0EA523028B4BFDAC7EF0002E078AD5"); break; //QDS.1A.45021098 + case 184 : return F("bY5044972694376092001A7A3E1340A553D753D0583A78B2BC306DF80BA1DBF6FC88DAFCD83AF7D955EC6196B643A571494B99AE831F8894A4726042E23EA5C474411A585EEEEE8E84A15F54562C31168159804D"); break; //ITW.1A.92603794 + case 185 : return F("bY5044972625353893071A7AE40040A517031B1A58F312C641E1E45D19F6DD3EA9B61FC929D70F39747F6A8A3BA53D7ED25B2A86C741728467DADC3652D54F40660B397E72F60CE3434006A1D843B3248D9C8016"); break; //ITW.1A.93383525 + case 186 : return F("b1744242349075722754982E471F202000001FD0C6202FD173000CFB38EF8"); break; //HYD.49.22570749 + case 187 : return F("b1744242349075722754982E471F602000001FD0C6202FD17300044308CEF"); break; //HYD.49.22570749 + case 188 : return F("b1744242349075722754982E471FC02000001FD0C6202FD173000B52092F0"); break; //HYD.49.22570749 + case 189 : return F("b1744242349075722754982E4711402000001FD0C6202FD17300084489CF0"); break; //HYD.49.22570749 + case 190 : return F("b63442D2C272951803504DCB48C2064900F002C251664000038AA5E8D66325C253B6B7A6400400710EDD4746D6402CF31496EE7AE09E634270E5701ED9E5D16E7A5A22EBC15B0CB6AC0EF980F73E5AD3BF6E658AFC24F614AFBF844AD1EC8CA21C0FCF8FC2E9E64C0B28542EE8C7EA37B444A8036"); break; //KAM.04.80512927 + case 191 : return F("b5E44A7329022226704041EDE7A7500502599F38B5BB9B53F705A6B158D76D3C33F390AE5F6E48A051680C3B866F317F4DBC781E920051DB2619F768DA54EB632DA8746E483A5569A9D8C0E16905ED61857D1B9A07C6EB6AE501B22E0D55A7DD4AF67DB88D6DDCCA5B78E5B88528014"); break; //LUG.04.67222290 + case 192 : return F("b60446850090510825937364F8C0039900F002C2584340D0063667A2334810387B25C72989280612423FE048500307DA907106591F67C43C0B36FCA410346B6A06E7CAADE06D06CF2911ED2775E3297F20105876C245C9309EC93EA0F68B3F9FA466371C73B4A39B80FEBAD1F9C40758028"); break; //HYD.04.61809298"); break; //TCH.37.82100509 + case 193 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218 + case 194 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218 + case 195 : return F("b48442423240756341200933E7AA0303A31A074C27AB1B79A4BF71E2818DAA788D064B94612F5AED2C06E10F1A022615C7EA6E4E97D999D450C33C358340DCB3D948F7F9B58E2B922ED1A580BA905B4E34388A184D5"); break; //HYD.00.34560724 + case 196 : return F("bY5044972670314082001A7A120040A5AE9E0AFE53E0E5216516C41E94CCEA4220BC25A8A5CCF38635E315900BA6BEB0CB5E343BF419524FFED59CD28D106314AC875F9812782700A8267FFCDB4A24251099804A"); break; //ITW.1A.82403170 + case 197 : return F("bY5044972670314082000A7A7A0040A5CE2F908D1B6C7FAB19CF06F0EE140BB44A84C7FF54BDA05D33D6EE45686D054984A4467283EDA6514E8094E361082D9555BD043A64D3F593BAE29C577984921E3CBC92D7"); break; //ITW.0A.82403170 + case 198 : return F("b3B44931536000000013721968C30AA900F002C25BBD101005A558A6E08987D124F027212334938931581036C0010657B0710B23E83B33BD1C8F5A3AB0DAFCEFB35C1D44718C68011"); break; //ELS.03.38493312"); break; //ELS.37.00000036 + case 199 : return F("b2E44B05C95720000021B556C7A500020059C8692AAFADBDBCAA36875B54901F2E32655B735A59AB899223306CD8402A02D189C816E86BB8061"); break; //WEP.1B.00007295 + case 200 : return F("b3B449526564412004237BD198C20E7900F002C25641C00002B7452AB08B6F3843AA6725644120095264203E700108D660710B78BE5BE2867BD38DCE24619A59D0A6BB9384E3A80E2"); break; //ITU.03.00124456"); break; //ITU.37.00124456 + case 201 : return F("b2E44A5111863054230037FB57A30002105DE7DCE381F74E06136FEB49A5B3D45B688341DDCF387CC0D6344DF5BF60078C7A596B1A0D3BE8020"); break; //DME.03.42056318 + case 202 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 203 : return F("b2644AC488437000050379BF37201843700AC485003210000002F2F0CFB6E16038161002F2F2F2F2F2F2F2F2C138057"); break; //REL.03.00378401"); break; //REL.37.00003784 + case 204 : return F("b3644496A855900500537290F7285590000496A0103CC002025CF5138AEB21021CE8339724700D0AF89B3CDDAAE2BAD28479AC27ADBE1E3C3B849269E632772E54C"); break; //ZRI.03.00005985"); break; //ZRI.37.50005985 + case 205 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 206 : return F("b3B449526404107004237CE7B8C2076900F002C25832C020029B8AE8C5A07F337A5EF72046318043041000375001016F10710C9B050BA32F8730DFCC9465A1C3385BA84D8190D801B"); break; //PIP.03.04186304"); break; //ITU.37.00074140 + case 207 : return F("b3B4493154809000001379A2E8C2028900F002C252907000090080FACAA513D7A7B7B729549120430413A03CB001054A107101672D7C39DD223F7A8D3C4FFF7E9CA294237B1AD83DE"); break; //PIP.03.04124995"); break; //ELS.37.00000948 + case 208 : return F("b1944C418637901000103C9CE7A690000A00414EE75620202FD08EAF2A1B38039"); break; //FFD.03.00017963 + case 209 : return F("b3B449526544412004237036C8C2065900F002C25943B00000CC9351967A937062362723991270492262203650810BBE30710483A983059AFE3C21836C683F23BA2B5A04EBAE38021"); break; //ITR.03.04279139"); break; //ITU.37.00124454 + case 210 : return F("b3B4493154909000001375BA68C20BD900F002C25BE0200004F4E34B29075C9986ACB722009210493150003B10010E2D407103C75563866FAC12EBCE6FC5829323547A3CC9A488057"); break; //ELS.03.04210920"); break; //ELS.37.00000949 + case 211 : return F("b3B44931558130000013727848C20DD900F002C25DE0C0000ADFB2D6B2E02C73267AF72011713419315250339001037AB0710A68555FAD7A29A1A875EE33FD26D13EAE33A7AF08035"); break; //ELS.03.41131701"); break; //ELS.37.00001358 + case 212 : return F("b3644A511870350133837198E7280409628931580038F002105F09DD43A485B949F2AB07122E6A6CB3E4BDF9055A351316D145C3EF8522BDF56D9D12495B281D5B0"); break; //ELS.03.28964080"); break; //DME.37.13500387 + case 213 : return F("b3644A511621280223837CE5E724102243693158103850020050075969BAC2F21DD6DEDFFDBDDCF9F0E9A7E066046DF920DF3BFE4DA1174DE1943F173A93C0E2A5B"); break; //ELS.03.36240241"); break; //DME.37.22801262 + case 214 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 215 : return F("b2E44B05C10130000021B34137A490000002F2F0A6653020AFB1A5105E7D102FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8016"); break; //WEP.1B.00001310 + case 216 : return F("b2E44B05C77010000041B1E0B7AE60000002F2F0A6641020AFB1A8103251802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F9"); break; //WEP.1B.00000177 + case 217 : return F("b4E44333081400004032AF18B7A03004005E5AEF57BA739B9789BD9AAFAE16068AFF9FB85A70613B3800B85D28B409B5BDD9607BE2A1450EEE20137C663FB522F4E6B9E914E8E3795C577BFB32D6E0152C21EB0358C41DD6D9F76498020"); break; //LAS.2A.04004081 + case 218 : return F("b1E443330886102001E1B8E9A7A180000202F2F026584030778281E18ADDC240B0000AB01768017"); break; //LAS.1B.00026188 + case 219 : return F("b2E443330603903003C1B18AF7A77002025BB29E575E63C4AA6174A82CED44D5497FA0207F44D4518D8CFA53CD7024276A675D4E0621C0F80E5"); break; //LAS.1B.00033960 + case 220 : return F("b7044B40908794920101B6F8B7A320000000265490A42651F0A8201650BCEB9072265D70912655F0A62659905526576FB4F0A02FB1ABD0142FB1ABB018201FB1A36B6C60122FB1AB90112FB1ABC0162FB1AC24B1C0152FB1ACF01066D3B1D2EAA25000FFFBE95FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA00FFFFFFFFFFFFFF7B5382DE"); break; //BMT.1B.20497908 + case 221 : return F("b60449615347810630C1BE5957A8C0000202F2F026505094265F908822DF20165880822658C081265680A62655808B1785265680A02FB1A880142FB1A95018201130DFB1AE00122FB1A660112FB1A100262FB491D1A660152FB1A1F0202FD1B60030DFD0F817605302E302E340F6C04"); break; //ELV.1B.63107834 + case 222 : return F("bY2944961565181468201B7A140000202F2F0265E20842659A0802FD1B30030DFD0F05302E302E340F5E46"); break; //ELV.1B.68141865 + case 223 : return F("b1644AF4C40020041011B788C7A0A0000000266F3000266F10026FE800F"); break; //SEO.1B.41000240 + case 224 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869 + case 225 : return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162 + case 226 : return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180 + case 227 : return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346 + case 228 : return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000 + case 229 : return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966 + case 230 : return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036 + case 231 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 232 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039 + case 233 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490 + case 234 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 235 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510 + case 236 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337 + case 237 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 238 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224 + case 239 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199 + case 240 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075 + case 241 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA + case 242 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424 + case 243 : return F("b3C449344454392352337ABFC727972126793442304FF0000200C050321648206004C0530240500426CBF2CCC0805098875630600C2086CDF23326CFFFF046D177FEA11D3242E1F830C"); break; //QDS.04.67127279"); break; //QDS.37.35924345 + case 244 : return F("bY5E449726900634160004728499181897A60030D30040A5D20022C396367C6A868B05FE2E9F70B6878070839C62E69DDC79CB409EBCAD68E950A6958B9B92FE6D1B700065B8BD52CB3035B93653FFDA3C3EF3EF6BBB1B450C7976874216AD188011"); break; //ITW.04.16340690"); break; //ITW.30.18189984 + case 245 : return F("b3444A73228188269040439327A370000202F2F0974040970080C06348AF03500000C14040301000B2D6207000B3BDD4A2322000A5A05090A5E0506F29780E8"); break; //LUG.04.69821828 + case 246 : return F("b9644A732170135700A0476A67A2F00002002747501046D3315952B0C3CF20637000000446D3B177F2C4C06000000F4640084016D3B179F2A8C010636000000CCB85A0106330000008C020633000000CC02067E6A330000008C030633000000CC030600009A1900008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22890200000F001000C94A80E8"); break; //LUG.04.70350117 + case 247 : return F("b5E44496A044550108804533A7A19005005B14A145C0EEC7C6FC18A85582753BE2BD08B6F0F9320809C5F28436A3388BB09145DF69DA51ED00DCCFE7BA2D6F86936D17E654489390BB7511CC4DA27A576A9612B1243151C634899A74D3EBDB2CA578CA4444F5740D74D3EC766628013"); break; //ZRI.04.10504504 + case 248 : return F("bD6440186669601001637060C7215067071010660047400C0251CE72725D9364386C4BF8A15AF4C722115F38B66DDE9F4B800D05A14F870E74492B8F57CB2282388B864D71416FDE0A74CA84735B59D1806BBBF8483B6B14AB580F0D5257E8CC06889EBA80AFB032BCEECD849E7E6A4639B5649B0BF5CBB57BF0993AE16EF7D88D784ADD9CE09284880D14D1502967798901C9E24ED6A04A3B26AE580B5D2AEC46804DEC1CBEDCB1E0B3FE1F84679620FD59E297FE8E67374EE9C34CA5A7F45B4B3709C6BD4E0B546F706DDC571C6469CFA90602C0E339A054CDE1D0A0E071651FD49FEBD485D8E561BB36AF7BED2741D35BBC88051"); break; //APA.04.71700615"); break; //APA.37.00019666 + case 249 : return F("bA944C514381355920004A5FC7A3D0090257403AC17355186FF933BAF0D0820041595970BD168489A746DF337AB01D1DBBA3EF7D486D74CCCEE777E57BD12A15D22B89AB4B4266D84B9C5C1DDBC437CD64807AF0B2B232E3A3C1B4DA568C9F8BA39FDF4565C170ACEF1918655B45D82E8BAAD769B88FA917E82542CAD596AB2145433599FFDDD3FCE889F657A8AC07FE5CED3F184244475B59710CFDCA44D7C3AE00CC021FA6EE3F49B77939529B49279FC526803FD0C05010002FD0B01111E248025"); break; //EFE.04.92551338 + case 250 : return F("b6E44A511485268614004B97F7A86006005B8D6D972C8E06EFB5AEC0FC750DB9DFFE62BB59DA183A5709DFC5029CEB2E44FEAF475DD2F3CF6D90544F29372A5C027DACA08536B2457CDEDEB76E36B281F4A0FD4A203FE9F7DFD337BDEA15D02F53A21CB281A3EF01E15D779B3018A99D0E3EF2FF946A63F419E56644BBDF8FB8010"); break; //DME.04.61685248 + case 251 : return F("b3E44A51159351969A004761E7A030030050F0020FC830AABCA7CEE4EC8E44524D98802ED5CA2EF3C89774FF800730951E02569BF91C156331EC9C29730FE2F437128D7E574D29C4E808047"); break; //DME.04.69193559 + case 252 : return F("b7144685009022004593720EB8C00EE900F002C252CA40100F5CDA57367A4436D244E72791670692423FE040600407D7C07108660CA27A4DD53871B12F96FFEF0F84E0CA88A2201BD794859D35CCD051F34327D04EAAE8772675F8E7F28DE2E515FBC9AF3A40E72C078304158D453511F2CF39DBF98DBE1EAD84F0F2CAD975EDDCC4F"); break; //HYD.04.69701679"); break; //TCH.37.04200209 + case 253 : return F("bA944C514381355920004A5FC7A670090251913406458376E6F9CEB817ABA85D9F26E5867EBA222FDFF496F2C8A312E593C80C0604576D7B5269B4A25BD15B314C0598FE3395611400AA7ADB705EE2120686208A449BDC5EE828FF048F669516585611337839C0A40A308F4DE619997F29D2438CE9EC263574FC60D42197CFA7B47528574792BFFF54AFF9193CA678242653A97DEBCB5E5333687846A875A69C4B7371C762C03A2DB5AB738D92623907896AEBF03FD0C05010002FD0B0111C6E48020"); break; //EFE.04.92551338 + case 254 : return F("b5E44C51475051512010425417A1C004025EEA5F744D756D853BB606F5DBB33DD7B26896D7370BD9C50990E605CE03F8F16DEE8AA36EF2C87E3902F933DDD87E1D383B7C647E8D2236E5FF5E1BE6742D65511FE1629B5CD3F6F0F800C000109002F680300035E2001020A0901C98008"); break; //EFE.04.12150575 + case 255 : return F("b5344A5115158536941043C9D8C00CC900F002C25986B0110B13D74311F4D1ACF88797A7E7031071066DEA0D815DFAC31E07763666D4C46C2DEA856FCEC34F2B5CCF3750F72BD8F924544E73C51D7DBC11A1AE31049E5D0E121CE0053AEB8E018805B"); break; //DME.04.69535851 + case 256 : return F("bA944C514381355920004A5FC7A79009025E37121BAC0ACF1A216281AE7A85DA20DF90613A83417418BD9BA7B0F09C192713A65DECD3ECB2DFC5299683A5B2669EE70851493FC9944D7579F7FCD44CCC839E432D498E40EFDF1614BD9B7E6F1B4AD96CC16939C9D061434790765D3883275B80DE0508393A0F00C1B1590A66F94D920BD5E80125C610F7C0B0346D8D0CFE8DD4E8A14370077D90A7BCFA59345DB13D7134E80D5F2F1D50B3B043075126DF42C2A03FD0C05010002FD0B0111ABC78028"); break; //EFE.04.92551338 + case 257 : return F("b4E44EE4D557648251B048F777ABE004025D84601014EEA5EDCC08B007BC8BB1EBB266BADDEC127897EDA4296EEB8EB05A57DFCA5190F54B41C05B1BF0F5EF82B719CD27EA6242263DFB94F589F38843A1146916B872141977275BD8035"); break; //SON.04.25487655 + case 258 : return F("b2644AC488137000050371BF47201813700AC485000D90000002F2F0C32C60E569405002F2F2F2F2F2F2F2FB90E8059"); break; //REL.00.00378101"); break; //REL.37.00003781 + case 259 : return F("b2644AC488137000050371BF47202813700AC48500CE20000002F2F0C068D16635640002F2F2F2F2F2F2F2F72188058"); break; //REL.0C.00378102"); break; //REL.37.00003781 + case 260 : return F("b36446850790133512243C21BA1009F29793C0088230200007E0E4FFD476370544E2D33280000000000000000000CC8A2608214298C81CD45160105964E203B372E"); break; //TCH.43.51330179 + case 261 : return F("b3744685029069372274351B5A2129F27995400305C1200000007400B69792D4C914447BB7D4A2AC86CB37BFAFD59EEC7971B1F070D00000000002C0040004FD085CC"); break; //TCH.43.72930629 + case 262 : return F("b37446850715221622843FE7BA2129F29F827008800000080800D0000DE060000E00739EF48B3861639BC2044115A10CB0C6283102F1830C0080AD861D24C1E8F80E1"); break; //TCH.43.62215271 + case 263 : return F("b374468508475676739430ECDA2109F27793500287F15000000D34D95907D550ADB32A24C9AAAA9922ED6A76075D51C1F7937DB8D4B95F20E4C63EDC5997041E280FA"); break; //TCH.43.67677584 + case 264 : return F("b36446850442620514543A443A1009F279E180038CC0400803680000286DB08F0C00727FCF0C82F92E8128D31165D722873C60C1568910103062C700107EF3F912E"); break; //TCH.43.51202644 + case 265 : return F("b3744685060478860574395F9A20D9F29820500A00000000100060000E34A00000000000000000000000000000000FFFF0000000000000000000000000000FFFF80D4"); break; //TCH.43.60884760 + case 266 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 + case 267 : return F("b364468504200545145444FE0A1009F29CA6900B0F00D008023001A68BB4748D28D4F7685755095534E09E187ADE5A7B903D03027041000000100008002CF41D570"); break; //TCH.44.51540042 + case 268 : return F("b37446850341929625744B280A20D9F27C9350058395A00000169848B3EDF2ECEF8A17F008A875B80FCB947E0914351334E69E4A14D72A8E296375279E286AB9B8008"); break; //TCH.44.62291934 + case 269 : return F("b36446850060670527144C1AAA0009F29C41F0190DE0F0081A5A1BF2595D38ABA9F512E89A3471D67F8F0C6206308383B6288298BD0A3D68424A228A9907F8B34B2"); break; //TCH.44.52700606 + case 270 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608 + case 271 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984 + case 272 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + case 273 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525 + case 274 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660 + case 275 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + + } + // *INDENT-ON* + count = 0; + return F(""); +} + +# endif // if P094_DEBUG_OPTIONS + bool P094_data_struct::loop() { if (!isInitialized()) { return false; @@ -128,6 +593,7 @@ bool P094_data_struct::loop() { valid = false; } } + if (valid) { fullSentenceReceived = true; } @@ -138,7 +604,8 @@ bool P094_data_struct::loop() { // Ignore LF break; default: - if (c >= 32 && c < 127) { + + if ((c >= 32) && (c < 127)) { sentence_part += c; } else { current_sentence_errored = true; @@ -154,6 +621,23 @@ bool P094_data_struct::loop() { ++sentences_received; length_last_received = sentence_part.length(); } +# if P094_DEBUG_OPTIONS + else { + if (debug_generate_CUL_data && (sentence_part.length() == 0)) { + static uint32_t last_test_sentence = 0; + static int count = 0; + + if (timePassedSince(last_test_sentence) > 1000) { + count++; + + // sentence_part = F("b2644AC48585300005037FAB97201585300AC485003150000202F2F0C0AF314213993002F2F2F2F2F2F2F2FAFCA8046"); + sentence_part = getDebugSentences(count); + fullSentenceReceived = true; + last_test_sentence = millis(); + } + } + } +# endif // if P094_DEBUG_OPTIONS return fullSentenceReceived; } @@ -162,8 +646,9 @@ const String& P094_data_struct::peekSentence() const { } void P094_data_struct::getSentence(String& string, bool appendSysTime) { - string = std::move(sentence_part); + string = std::move(sentence_part); sentence_part = String(); // FIXME TD-er: Should not be needed as move already cleared it. + if (appendSysTime) { // Unix timestamp = 10 decimals + separator if (string.reserve(sentence_part.length() + 11)) { @@ -184,54 +669,8 @@ void P094_data_struct::setMaxLength(uint16_t maxlenght) { max_length = maxlenght; } -void P094_data_struct::setLine(uint8_t varNr, const String& line) { - if (varNr < P94_Nlines) { - _lines[varNr] = line; - } -} - uint32_t P094_data_struct::getFilterOffWindowTime() const { - return _lines[P094_FILTER_OFF_WINDOW_POS].toInt(); -} - -P094_Match_Type P094_data_struct::getMatchType() const { - return static_cast(_lines[P094_MATCH_TYPE_POS].toInt()); -} - -bool P094_data_struct::invertMatch() const { - switch (getMatchType()) { - case P094_Regular_Match: - break; - case P094_Regular_Match_inverted: - return true; - case P094_Filter_Disabled: - break; - } - return false; -} - -bool P094_data_struct::filterUsed(uint8_t lineNr) const -{ - if (valueType_index[lineNr] == P094_Filter_Value_Type::P094_not_used) { return false; } - uint8_t varNr = P094_Get_filter_base_index(lineNr); - return _lines[varNr + 3].length() > 0; -} - -String P094_data_struct::getFilter(uint8_t lineNr, P094_Filter_Value_Type& filterValueType, uint32_t& optional, - P094_Filter_Comp& comparator) const -{ - uint8_t varNr = P094_Get_filter_base_index(lineNr); - - filterValueType = P094_Filter_Value_Type::P094_not_used; - - if ((varNr + 3) >= P94_Nlines) { return ""; } - optional = _lines[varNr + 1].toInt(); - filterValueType = valueType_index[lineNr]; - comparator = filter_comp[lineNr]; - - // filterValueType = static_cast(_lines[varNr].toInt()); - // comparator = static_cast(_lines[varNr + 2].toInt()); - return _lines[varNr + 3]; + return filterOffWindowTime; } void P094_data_struct::setDisableFilterWindowTimer() { @@ -253,175 +692,72 @@ bool P094_data_struct::disableFilterWindowActive() const { return false; } -bool P094_data_struct::parsePacket(const String& received) const { - size_t strlength = received.length(); +bool P094_data_struct::parsePacket(const String& received, mBusPacket_t& packet) { + const size_t strlength = received.length(); if (strlength == 0) { return false; } + const char firstChar = received[0]; - if (getMatchType() == P094_Filter_Disabled) { - return true; - } - - bool match_result = false; - - // FIXME TD-er: For now added '$' to test with GPS. - if ((received[0] == 'b') || (received[0] == '$')) { + if ((firstChar == 'b')) { // Received a data packet in CUL format. if (strlength < 21) { return false; } // Decoded packet + if (!packet.parse(received)) { return false; } - unsigned long packet_header[P094_FILTER_VALUE_Type_NR_ELEMENTS]; - packet_header[P094_packet_length] = hexToUL(received, 1, 2); - packet_header[P094_unknown1] = hexToUL(received, 3, 2); - packet_header[P094_manufacturer] = hexToUL(received, 5, 4); - packet_header[P094_serial_number] = hexToUL(received, 9, 8); - packet_header[P094_unknown2] = hexToUL(received, 17, 2); - packet_header[P094_meter_type] = hexToUL(received, 19, 2); + const mBusPacket_header_t *header = packet.getDeviceHeader(); - // FIXME TD-er: Is this also correct? - packet_header[P094_rssi] = hexToUL(received, strlength - 4, 4); - - // FIXME TD-er: Is this correct? - // match_result = packet_length == (strlength - 21) / 2; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (log.reserve(128)) { - log = F("CUL Reader: "); - log += F(" length: "); - log += packet_header[P094_packet_length]; - log += F(" (header: "); - log += strlength - (packet_header[P094_packet_length] * 2); - log += F(") manu: "); - log += formatToHex_decimal(packet_header[P094_manufacturer]); - log += F(" serial: "); - log += formatToHex_decimal(packet_header[P094_serial_number]); - log += F(" mType: "); - log += formatToHex_decimal(packet_header[P094_meter_type]); - log += F(" RSSI: "); - log += formatToHex_decimal(packet_header[P094_rssi]); - addLogMove(LOG_LEVEL_INFO, log); + if (header == nullptr) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Header "), packet.toString())); } - } - bool filter_matches[P094_NR_FILTERS]; - - for (unsigned int f = 0; f < P094_NR_FILTERS; ++f) { - filter_matches[f] = false; + return false; } - // Do not check for "not used" (0) - for (unsigned int i = 1; i < P094_FILTER_VALUE_Type_NR_ELEMENTS; ++i) { - if (valueType_used[i]) { - for (unsigned int f = 0; f < P094_NR_FILTERS; ++f) { - if (valueType_index[f] == i) { - // Have a matching filter - - uint32_t optional; - P094_Filter_Value_Type filterValueType; - P094_Filter_Comp comparator; - bool match = false; - String inputString; - String valueString; - - if (i == P094_Filter_Value_Type::P094_position) { - valueString = getFilter(f, filterValueType, optional, comparator); - - if (received.length() >= (optional + valueString.length())) { - // received string is long enough to fit the expression. - inputString = received.substring(optional, optional + valueString.length()); - match = inputString.equalsIgnoreCase(valueString); - } - } else { - unsigned long value = hexToUL(getFilter(f, filterValueType, optional, comparator)); - match = (value == packet_header[i]); - inputString = formatToHex_decimal(packet_header[i]); - valueString = formatToHex_decimal(value); - } - - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (log.reserve(64)) { - log += F("CUL Reader: "); - log += P094_FilterValueType_toString(valueType_index[f]); - log += F(": in:"); - log += inputString; - log += ' '; - log += P094_FilterComp_toString(comparator); - log += ' '; - log += valueString; - - switch (comparator) { - case P094_Filter_Comp::P094_Equal_OR: - case P094_Filter_Comp::P094_Equal_MUST: - - if (match) { log += F(" expected MATCH"); } - break; - case P094_Filter_Comp::P094_NotEqual_OR: - case P094_Filter_Comp::P094_NotEqual_MUST: - - if (!match) { log += F(" expected NO MATCH"); } - break; - } - addLogMove(LOG_LEVEL_INFO, log); - } - } - - switch (comparator) { - case P094_Filter_Comp::P094_Equal_OR: - - if (match) { filter_matches[f] = true; } - break; - case P094_Filter_Comp::P094_NotEqual_OR: - - if (!match) { filter_matches[f] = true; } - break; - - case P094_Filter_Comp::P094_Equal_MUST: - - if (!match) { return false; } - break; + if (mute_messages) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Muted "), packet.toString())); + } - case P094_Filter_Comp::P094_NotEqual_MUST: + return false; // Mute all messages + } - if (match) { return false; } - break; - } - } - } + if (!interval_filter.enabled || (_filters.size() == 0)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Filter "), packet.toString())); } + + return true; // No filtering } - // Now we have to check if all rows per filter line in filter_matches[f] are true or not used. - int nrMatches = 0; - int nrNotUsed = 0; + for (unsigned int f = 0; f < _filters.size(); ++f) { + if (_filters[f].matches(*header)) { + const bool res = interval_filter.filter(packet, _filters[f]); - for (unsigned int f = 0; !match_result && f < P094_NR_FILTERS; ++f) { - if (f % P094_AND_FILTER_BLOCK == 0) { - if ((nrMatches > 0) && ((nrMatches + nrNotUsed) == P094_AND_FILTER_BLOCK)) { - match_result = true; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Match "), _filters[f].toString())); + addLogMove(LOG_LEVEL_INFO, concat(res ? F("CUL Filter: Pass ") : F("CUL Filter: Reject "), header->toString())); } - nrMatches = 0; - nrNotUsed = 0; - } - if (filter_matches[f]) { - ++nrMatches; - } else { - if (!filterUsed(f)) { - ++nrNotUsed; - } + return res; } } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Match "), header->toString())); + } + + // No matching filter, so consider fall-through filter to be: + // *.*.*;none + return false; } else { - switch (received[0]) { + switch (firstChar) { case 'C': // CMODE case 'S': // SMODE case 'T': // TMODE @@ -429,51 +765,60 @@ bool P094_data_struct::parsePacket(const String& received) const { case 'V': // Version info // FIXME TD-er: Must test the result of the other possible answers. - match_result = true; - break; + return true; } } - return match_result; + return false; } -const __FlashStringHelper * P094_data_struct::MatchType_toString(P094_Match_Type matchType) { - switch (matchType) - { - case P094_Match_Type::P094_Regular_Match: return F("Regular Match"); - case P094_Match_Type::P094_Regular_Match_inverted: return F("Regular Match inverted"); - case P094_Match_Type::P094_Filter_Disabled: return F("Filter Disabled"); - } - return F(""); +void P094_data_struct::interval_filter_purgeExpired() { + interval_filter.purgeExpired(); } -const __FlashStringHelper * P094_data_struct::P094_FilterValueType_toString(P094_Filter_Value_Type valueType) +void P094_data_struct::html_show_interval_filter_stats() const { - switch (valueType) { - case P094_Filter_Value_Type::P094_not_used: return F("---"); - case P094_Filter_Value_Type::P094_packet_length: return F("Packet Length"); - case P094_Filter_Value_Type::P094_unknown1: return F("unknown1"); - case P094_Filter_Value_Type::P094_manufacturer: return F("Manufacturer"); - case P094_Filter_Value_Type::P094_serial_number: return F("Serial Number"); - case P094_Filter_Value_Type::P094_unknown2: return F("unknown2"); - case P094_Filter_Value_Type::P094_meter_type: return F("Meter Type"); - case P094_Filter_Value_Type::P094_rssi: return F("RSSI"); - case P094_Filter_Value_Type::P094_position: return F("Position"); - - // default: break; + if (interval_filter._mBusFilterMap.empty()) { return; } + + addRowLabel(F("Interval Filter Entries")); + addHtmlInt(interval_filter._mBusFilterMap.size()); + + addFormNote(F("Non expired W-MBus device filters")); +} + +bool P094_data_struct::collect_stats_add(const mBusPacket_t& packet, const String& source) { + if (collect_stats) { + return mBus_stats[firstStatsIndexActive ? 0 : 1].add(packet, source); } - return F("unknown"); + return false; +} + +void P094_data_struct::prepare_dump_stats() { + firstStatsIndexActive = !firstStatsIndexActive; } -const __FlashStringHelper * P094_data_struct::P094_FilterComp_toString(P094_Filter_Comp comparator) +bool P094_data_struct::dump_next_stats(String& str) { + const uint8_t dumpStatsIndex = firstStatsIndexActive ? 1 : 0; + + if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return false; } + + str = concat(F("stats;"), mBus_stats[dumpStatsIndex].getFront()); + + return true; +} + +void P094_data_struct::html_show_mBus_stats() const { - switch (comparator) { - case P094_Filter_Comp::P094_Equal_OR: return F("=="); - case P094_Filter_Comp::P094_NotEqual_OR: return F("!="); - case P094_Filter_Comp::P094_Equal_MUST: return F("== (must)"); - case P094_Filter_Comp::P094_NotEqual_MUST: return F("!= (must)"); - } - return F(""); + const uint8_t dumpStatsIndex = firstStatsIndexActive ? 0 : 1; + + if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return; } + + addRowLabel(F("W-MBus Devices")); + addHtmlInt(mBus_stats[dumpStatsIndex]._mBusStatsMap.size()); + + addFormNote(F("Devices received since last culreader,dumpstats")); + + mBus_stats[dumpStatsIndex].toHtml(); } bool P094_data_struct::max_length_reached() const { @@ -481,12 +826,23 @@ bool P094_data_struct::max_length_reached() const { return sentence_part.length() >= max_length; } -size_t P094_data_struct::P094_Get_filter_base_index(size_t filterLine) { - return filterLine * P094_ITEMS_PER_FILTER + P094_FIRST_FILTER_POS; +bool P094_data_struct::isDuplicate(const P094_filter& other) const +{ + const String f_str = other.toString(); + + for (auto it = _filters.begin(); it != _filters.end(); ++it) { + if (f_str.equals(it->toString())) { + return true; + } + } + return false; } +# if P094_DEBUG_OPTIONS uint32_t P094_data_struct::getDebugCounter() { return debug_counter++; } -#endif // USES_P094 \ No newline at end of file +# endif // if P094_DEBUG_OPTIONS + +#endif // USES_P094 \ No newline at end of file diff --git a/src/src/PluginStructs/P094_data_struct.h b/src/src/PluginStructs/P094_data_struct.h index 9fdcd9db2a..0e6bb12227 100644 --- a/src/src/PluginStructs/P094_data_struct.h +++ b/src/src/PluginStructs/P094_data_struct.h @@ -4,53 +4,63 @@ #include "../../_Plugin_Helper.h" #ifdef USES_P094 -#include -#include +# include "../Helpers/CUL_interval_filter.h" +# include "../Helpers/CUL_stats.h" +# include "../PluginStructs/P094_Filter.h" -# define P094_REGEX_POS 0 -# define P094_NR_CHAR_USE_POS 1 -# define P094_FILTER_OFF_WINDOW_POS 2 -# define P094_MATCH_TYPE_POS 3 +# include +# include -# define P094_FIRST_FILTER_POS 10 +# ifndef P094_DEBUG_OPTIONS +# define P094_DEBUG_OPTIONS 0 +# endif // ifndef P094_DEBUG_OPTIONS -# define P094_ITEMS_PER_FILTER 4 -# define P094_AND_FILTER_BLOCK 3 -# define P094_NR_FILTERS (7 * P094_AND_FILTER_BLOCK) -# define P94_Nlines (P094_FIRST_FILTER_POS + (P094_ITEMS_PER_FILTER * (P094_NR_FILTERS))) -# define P94_Nchars 128 -# define P94_MAX_CAPTURE_INDEX 32 +# define P094_BAUDRATE PCONFIG_LONG(0) +# define P094_BAUDRATE_LABEL PCONFIG_LABEL(0) -enum P094_Match_Type { - P094_Regular_Match = 0, - P094_Regular_Match_inverted = 1, - P094_Filter_Disabled = 2 -}; -# define P094_Match_Type_NR_ELEMENTS 3 - -enum P094_Filter_Value_Type { - P094_not_used = 0, - P094_packet_length = 1, - P094_unknown1 = 2, - P094_manufacturer = 3, - P094_serial_number = 4, - P094_unknown2 = 5, - P094_meter_type = 6, - P094_rssi = 7, - P094_position = 8 -}; -# define P094_FILTER_VALUE_Type_NR_ELEMENTS 9 +# define P094_DEBUG_SENTENCE_LENGTH PCONFIG_LONG(1) +# define P094_DEBUG_SENTENCE_LABEL PCONFIG_LABEL(1) -enum P094_Filter_Comp { - P094_Equal_OR = 0, - P094_NotEqual_OR = 1, - P094_Equal_MUST = 2, - P094_NotEqual_MUST = 3 -}; +# define P094_DISABLE_WINDOW_TIME_MS PCONFIG_LONG(2) + +# define P094_GET_APPEND_RECEIVE_SYSTIME bitRead(PCONFIG(0), 0) +# define P094_SET_APPEND_RECEIVE_SYSTIME(X) bitWrite(PCONFIG(0), 0, X) + +# if P094_DEBUG_OPTIONS +# define P094_GET_GENERATE_DEBUG_CUL_DATA bitRead(PCONFIG(0), 1) +# define P094_SET_GENERATE_DEBUG_CUL_DATA(X) bitWrite(PCONFIG(0), 1, X) +# endif // if P094_DEBUG_OPTIONS + +# define P094_GET_INTERVAL_FILTER bitRead(PCONFIG(0), 2) +# define P094_SET_INTERVAL_FILTER(X) bitWrite(PCONFIG(0), 2, X) + +# define P094_GET_COLLECT_STATS bitRead(PCONFIG(0), 3) +# define P094_SET_COLLECT_STATS(X) bitWrite(PCONFIG(0), 3, X) + +# define P094_GET_MUTE_MESSAGES bitRead(PCONFIG(0), 4) +# define P094_SET_MUTE_MESSAGES(X) bitWrite(PCONFIG(0), 4, X) -# define P094_FILTER_COMP_NR_ELEMENTS 4 +# define P094_NR_FILTERS PCONFIG(1) + +# ifdef ESP8266 +# define P094_MAX_NR_FILTERS 25 +# endif // ifdef ESP8266 +# ifdef ESP32 +# define P094_MAX_NR_FILTERS 100 +# endif // ifdef ESP32 + + +# ifdef ESP8266 +# define P094_MAX_MSG_LENGTH 550 +# endif // ifdef ESP8266 +# ifdef ESP32 +# define P094_MAX_MSG_LENGTH 1024 +# endif // ifdef ESP32 + + +# define P094_DEFAULT_BAUDRATE 38400 struct P094_data_struct : public PluginTaskData_base { @@ -62,85 +72,123 @@ struct P094_data_struct : public PluginTaskData_base { void reset(); - bool init(ESPEasySerialPort port, - const int16_t serial_rx, - const int16_t serial_tx, - unsigned long baudrate); + bool init(ESPEasySerialPort port, + const int16_t serial_rx, + const int16_t serial_tx, + unsigned long baudrate); - void post_init(); + void setFlags(unsigned long filterOffWindowTime_ms, + bool intervalFilterEnabled, + bool mute, + bool collectStats); - bool isInitialized() const; - void sendString(const String& data); + void loadFilters(struct EventStruct *event, + uint8_t nrFilters); - bool loop(); + String saveFilters(struct EventStruct *event) const; - const String& peekSentence() const; - void getSentence(String& string, bool appendSysTime); + void clearFilters(); + + bool addFilter(struct EventStruct *event, const String& filter); - void getSentencesReceived(uint32_t& succes, - uint32_t& error, - uint32_t& length_last) const; + String getFiltersMD5() const; - void setMaxLength(uint16_t maxlenght); + void WebformLoadFilters(uint8_t nrFilters) const; - void setLine(uint8_t varNr, - const String& line); + void WebformSaveFilters(struct EventStruct *event, + uint8_t nrFilters); + bool isInitialized() const; - uint32_t getFilterOffWindowTime() const; + void sendString(const String& data); - P094_Match_Type getMatchType() const; + bool loop(); - bool invertMatch() const; + const String& peekSentence() const; + + void getSentence(String& string, + bool appendSysTime); - bool filterUsed(uint8_t lineNr) const; + void getSentencesReceived(uint32_t& succes, + uint32_t& error, + uint32_t& length_last) const; - String getFilter(uint8_t lineNr, - P094_Filter_Value_Type& capture, - uint32_t & optional, - P094_Filter_Comp & comparator) const; + void setMaxLength(uint16_t maxlenght); - void setDisableFilterWindowTimer(); + void setLine(uint8_t varNr, + const String& line); - bool disableFilterWindowActive() const; + uint32_t getFilterOffWindowTime() const; - bool parsePacket(const String& received) const; + bool filterUsed(uint8_t lineNr) const; - static const __FlashStringHelper * MatchType_toString(P094_Match_Type matchType); - static const __FlashStringHelper * P094_FilterValueType_toString(P094_Filter_Value_Type valueType); - static const __FlashStringHelper * P094_FilterComp_toString(P094_Filter_Comp comparator); + void setDisableFilterWindowTimer(); + bool disableFilterWindowActive() const; - // Made public so we don't have to copy the values when loading/saving. - String _lines[P94_Nlines]; + bool parsePacket(const String& received, + mBusPacket_t& packet); - static size_t P094_Get_filter_base_index(size_t filterLine); + +# if P094_DEBUG_OPTIONS // Get (and increment) debug counter uint32_t getDebugCounter(); + void setGenerate_DebugCulData(bool value) { + debug_generate_CUL_data = value; + } + +# endif // if P094_DEBUG_OPTIONS + + void interval_filter_purgeExpired(); + + void html_show_interval_filter_stats() const; + + + bool collect_stats_add(const mBusPacket_t& packet, const String& source); + void prepare_dump_stats(); + bool dump_next_stats(String& str); + + void html_show_mBus_stats() const; + private: bool max_length_reached() const; + bool isDuplicate(const P094_filter& other) const; + + std::vector_filters; + ESPeasySerial *easySerial = nullptr; String sentence_part; - uint16_t max_length = 550; + uint16_t max_length = P094_MAX_MSG_LENGTH; + uint16_t nrFilters{}; + unsigned long filterOffWindowTime = 0; uint32_t sentences_received = 0; uint32_t sentences_received_error = 0; bool current_sentence_errored = false; uint32_t length_last_received = 0; unsigned long disable_filter_window = 0; - uint32_t debug_counter = 0; - bool valueType_used[P094_FILTER_VALUE_Type_NR_ELEMENTS] = {0}; - P094_Filter_Value_Type valueType_index[P094_NR_FILTERS]; - P094_Filter_Comp filter_comp[P094_NR_FILTERS]; + # if P094_DEBUG_OPTIONS + uint32_t debug_counter = 0; + bool debug_generate_CUL_data = false; + # endif // if P094_DEBUG_OPTIONS + bool collect_stats = false; + bool mute_messages = false; + + bool firstStatsIndexActive = false; + + CUL_interval_filter interval_filter; + + // Alternating stats, one being flushed, the other used to collect new stats + CUL_Stats mBus_stats[2]; }; #endif // USES_P094 -#endif // PLUGINSTRUCTS_P094_DATA_STRUCT_H \ No newline at end of file +#endif // PLUGINSTRUCTS_P094_DATA_STRUCT_H diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 1b8440178e..0497a57627 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -318,7 +318,7 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task Sensor_VType VType = TempEvent.sensorType; if ((pconfigIndex >= 0) && (pconfigIndex < PLUGIN_CONFIGVAR_MAX)) { - VType = static_cast(getFormItemInt(PCONFIG_LABEL(pconfigIndex), 0)); + VType = static_cast(getFormItemInt(sensorTypeHelper_webformID(pconfigIndex), 0)); Settings.TaskDevicePluginConfig[taskIndex][pconfigIndex] = static_cast(VType); } ExtraTaskSettings.clearUnusedValueNames(getValueCountFromSensorType(VType)); @@ -436,8 +436,10 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task // 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 - for (int pconfigIndex = 0; pconfigIndex < PLUGIN_CONFIGVAR_MAX; ++pconfigIndex) { - pconfig_webformSave(&TempEvent, pconfigIndex); + 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();