// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2022 - 2024 Thomas Basler and others */ #include "Utils.h" #include "MessageOutput.h" #include "PinMapping.h" #include uint32_t Utils::getChipId() { uint32_t chipId = 0; for (uint8_t i = 0; i < 17; i += 8) { chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; } return chipId; } uint64_t Utils::generateDtuSerial() { uint32_t chipId = getChipId(); uint64_t dtuId = 0; // Product category (char 1-4): 1 = Micro Inverter, 999 = Dummy dtuId |= 0x199900000000; // Year of production (char 5): 1 equals 2015 so hard code 8 = 2022 dtuId |= 0x80000000; // Week of production (char 6-7): Range is 1-52 s hard code 1 = week 1 dtuId |= 0x0100000; // Running Number (char 8-12): Derived from the ESP chip id for (uint8_t i = 0; i < 5; i++) { dtuId |= (chipId % 10) << (i * 4); chipId /= 10; } return dtuId; } int Utils::getTimezoneOffset() { // see: https://stackoverflow.com/questions/13804095/get-the-time-zone-gmt-offset-in-c/44063597#44063597 time_t gmt, rawtime = time(NULL); struct tm* ptm; struct tm gbuf; ptm = gmtime_r(&rawtime, &gbuf); // Request that mktime() looksup dst in timezone database ptm->tm_isdst = -1; gmt = mktime(ptm); return static_cast(difftime(rawtime, gmt)); } bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line) { if (doc.overflowed()) { MessageOutput.printf("Alloc failed: %s, %" PRId16 "\r\n", function, line); return false; } return true; } /// @brief Remove all files but the PINMAPPING_FILENAME void Utils::removeAllFiles() { auto root = LittleFS.open("/"); auto file = root.getNextFileName(); while (file != "") { if (file != PINMAPPING_FILENAME) { LittleFS.remove(file); } file = root.getNextFileName(); } } /* OpenDTU-OnBatter-specific utils go here: */ template std::optional getFromString(char const* val); template<> std::optional getFromString(char const* val) { float res = 0; try { res = std::stof(val); } catch (std::invalid_argument const& e) { return std::nullopt; } return res; } template char const* getTypename(); template<> char const* getTypename() { return "float"; } template std::pair Utils::getJsonValueByPath(JsonDocument const& root, String const& path) { size_t constexpr kErrBufferSize = 256; char errBuffer[kErrBufferSize]; constexpr char delimiter = '/'; int start = 0; int end = path.indexOf(delimiter); auto value = root.as(); // NOTE: "Because ArduinoJson implements the Null Object Pattern, it is // always safe to read the object: if the key doesn't exist, it returns an // empty value." auto getNext = [&](String const& key) -> bool { // handle double forward slashes and paths starting or ending with a slash if (key.isEmpty()) { return true; } if (key[0] == '[' && key[key.length() - 1] == ']') { if (!value.is()) { snprintf(errBuffer, kErrBufferSize, "Cannot access non-array " "JSON node using array index '%s' (JSON path '%s', " "position %i)", key.c_str(), path.c_str(), start); return false; } auto idx = key.substring(1, key.length() - 1).toInt(); value = value[idx]; if (value.isNull()) { snprintf(errBuffer, kErrBufferSize, "Unable to access JSON " "array index %li (JSON path '%s', position %i)", idx, path.c_str(), start); return false; } return true; } value = value[key]; if (value.isNull()) { snprintf(errBuffer, kErrBufferSize, "Unable to access JSON key " "'%s' (JSON path '%s', position %i)", key.c_str(), path.c_str(), start); return false; } return true; }; while (end != -1) { if (!getNext(path.substring(start, end))) { return { T(), String(errBuffer) }; } start = end + 1; end = path.indexOf(delimiter, start); } if (!getNext(path.substring(start))) { return { T(), String(errBuffer) }; } if (value.is()) { return { value.as(), "" }; } if (!value.is()) { snprintf(errBuffer, kErrBufferSize, "Value '%s' at JSON path '%s' is " "neither a string nor of type %s", value.as().c_str(), path.c_str(), getTypename()); return { T(), String(errBuffer) }; } auto res = getFromString(value.as()); if (!res.has_value()) { snprintf(errBuffer, kErrBufferSize, "String '%s' at JSON path '%s' cannot " "be converted to %s", value.as().c_str(), path.c_str(), getTypename()); return { T(), String(errBuffer) }; } return { *res, "" }; } template std::pair Utils::getJsonValueByPath(JsonDocument const& root, String const& path); template std::optional Utils::getNumericValueFromMqttPayload(char const* client, std::string const& src, char const* topic, char const* jsonPath) { std::string logValue = src.substr(0, 32); if (src.length() > logValue.length()) { logValue += "..."; } auto log = [client,topic](char const* format, auto&&... args) -> std::optional { MessageOutput.printf("[%s] Topic '%s': ", client, topic); MessageOutput.printf(format, args...); MessageOutput.println(); return std::nullopt; }; if (strlen(jsonPath) == 0) { auto res = getFromString(src.c_str()); if (!res.has_value()) { return log("cannot parse payload '%s' as float", logValue.c_str()); } return res; } JsonDocument json; const DeserializationError error = deserializeJson(json, src); if (error) { return log("cannot parse payload '%s' as JSON", logValue.c_str()); } if (json.overflowed()) { return log("payload too large to process as JSON"); } auto pathResolutionResult = getJsonValueByPath(json, jsonPath); if (!pathResolutionResult.second.isEmpty()) { return log("%s", pathResolutionResult.second.c_str()); } return pathResolutionResult.first; } template std::optional Utils::getNumericValueFromMqttPayload(char const* client, std::string const& src, char const* topic, char const* jsonPath);