move JSON path resolver to Utils class for re-use
This commit is contained in:
parent
673b9f4fa8
commit
ccba7d8036
@ -22,6 +22,7 @@ public:
|
||||
|
||||
bool queryPhase(int phase, PowerMeterHttpConfig const& config);
|
||||
char httpPowerMeterError[256];
|
||||
float getCached(size_t idx) { return _cache[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t _lastPoll;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
@ -12,4 +13,8 @@ public:
|
||||
static void restartDtu();
|
||||
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
|
||||
static void removeAllFiles();
|
||||
|
||||
/* OpenDTU-OnBatter-specific utils go here: */
|
||||
template<typename T>
|
||||
static std::pair<T, String> getJsonValueFromStringByPath(String const& jsonText, String const& path);
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "Utils.h"
|
||||
#include "Configuration.h"
|
||||
#include "PowerMeterHttpJson.h"
|
||||
#include "MessageOutput.h"
|
||||
@ -237,79 +238,16 @@ String PowerMeterHttpJson::getDigestAuth(String& authReq, const String& username
|
||||
|
||||
bool PowerMeterHttpJson::tryGetFloatValueForPhase(int phase, String jsonPath, Unit_t unit, bool signInverted)
|
||||
{
|
||||
JsonDocument root;
|
||||
const DeserializationError error = deserializeJson(root, httpResponse);
|
||||
if (error) {
|
||||
auto pathResolutionResult = Utils::getJsonValueFromStringByPath<float>(httpResponse, jsonPath);
|
||||
if (!pathResolutionResult.second.isEmpty()) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[PowerMeterHttpJson] Unable to parse server response as JSON"));
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr char delimiter = '/';
|
||||
int start = 0;
|
||||
int end = jsonPath.indexOf(delimiter);
|
||||
auto value = root.as<JsonVariantConst>();
|
||||
|
||||
auto getNext = [this, &value, &jsonPath, &start](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<JsonArrayConst>()) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[HttpPowerMeter] Cannot access non-array JSON node "
|
||||
"using array index '%s' (JSON path '%s', position %i)"),
|
||||
key.c_str(), jsonPath.c_str(), start);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto idx = key.substring(1, key.length() - 1).toInt();
|
||||
value = value[idx];
|
||||
|
||||
if (value.isNull()) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[HttpPowerMeter] Unable to access JSON array "
|
||||
"index %li (JSON path '%s', position %i)"),
|
||||
idx, jsonPath.c_str(), start);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
value = value[key];
|
||||
|
||||
if (value.isNull()) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[HttpPowerMeter] Unable to access JSON key "
|
||||
"'%s' (JSON path '%s', position %i)"),
|
||||
key.c_str(), jsonPath.c_str(), start);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 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."
|
||||
while (end != -1) {
|
||||
if (!getNext(jsonPath.substring(start, end))) { return false; }
|
||||
start = end + 1;
|
||||
end = jsonPath.indexOf(delimiter, start);
|
||||
}
|
||||
|
||||
if (!getNext(jsonPath.substring(start))) { return false; }
|
||||
|
||||
if (!value.is<float>()) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[PowerMeterHttpJson] not a float: '%s'"),
|
||||
value.as<String>().c_str());
|
||||
PSTR("[PowerMeterHttpJson] %s"),
|
||||
pathResolutionResult.second.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// this value is supposed to be in Watts and positive if energy is consumed.
|
||||
_cache[phase] = value.as<float>();
|
||||
_cache[phase] = pathResolutionResult.first;
|
||||
|
||||
switch (unit) {
|
||||
case Unit_t::MilliWatts:
|
||||
|
||||
@ -92,3 +92,83 @@ void Utils::removeAllFiles()
|
||||
file = root.getNextFileName();
|
||||
}
|
||||
}
|
||||
|
||||
/* OpenDTU-OnBatter-specific utils go here: */
|
||||
template<typename T>
|
||||
std::pair<T, String> Utils::getJsonValueFromStringByPath(String const& jsonText, String const& path)
|
||||
{
|
||||
JsonDocument root;
|
||||
const DeserializationError error = deserializeJson(root, jsonText);
|
||||
if (error) {
|
||||
return { T(), "Unable to parse server response as JSON" };
|
||||
}
|
||||
|
||||
size_t constexpr kErrBufferSize = 256;
|
||||
char errBuffer[kErrBufferSize];
|
||||
constexpr char delimiter = '/';
|
||||
int start = 0;
|
||||
int end = path.indexOf(delimiter);
|
||||
auto value = root.as<JsonVariant>();
|
||||
|
||||
// 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<JsonArrayConst>()) {
|
||||
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<T>()) {
|
||||
snprintf(errBuffer, kErrBufferSize, "Value '%s' at JSON path '%s' is not "
|
||||
"of the expected type", value.as<String>().c_str(), path.c_str());
|
||||
return { T(), String(errBuffer) };
|
||||
}
|
||||
|
||||
return { value.as<T>(), "" };
|
||||
}
|
||||
|
||||
template std::pair<float, String> Utils::getJsonValueFromStringByPath(String const& jsonText, String const& path);
|
||||
|
||||
@ -258,7 +258,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
||||
auto upMeter = std::make_unique<PowerMeterHttpJson>();
|
||||
if (upMeter->queryPhase(0/*phase*/, phaseConfig)) {
|
||||
retMsg["type"] = "success";
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getPowerTotal());
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getCached(0));
|
||||
} else {
|
||||
snprintf_P(response, sizeof(response), "%s", upMeter->httpPowerMeterError);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user