Skip to content

Commit

Permalink
Prepare Release 2024.05.06 (merge development into master)
Browse files Browse the repository at this point in the history
  • Loading branch information
schlimmchen authored May 6, 2024
2 parents d2990bd + 2f7e1f3 commit 35491ca
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 38 deletions.
2 changes: 1 addition & 1 deletion include/HttpPowerMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class HttpPowerMeterClass {
String extractParam(String& authReq, const String& param, const char delimit);
String getcNonce(const int len);
String getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter);
bool tryGetFloatValueForPhase(int phase, const char* jsonPath, Unit_t unit, bool signInverted);
bool tryGetFloatValueForPhase(int phase, String jsonPath, Unit_t unit, bool signInverted);
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
String sha256(const String& data);
};
Expand Down
2 changes: 0 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ lib_deps =
https://github.com/coryjfowler/MCP_CAN_lib
plerup/EspSoftwareSerial @ ^8.0.1
https://github.com/dok-net/ghostl @ ^1.0.1
mobizt/FirebaseJson @ ^3.0.6
rweather/Crypto@^0.4.0

extra_scripts =
pre:pio-scripts/auto_firmware_version.py
Expand Down
4 changes: 2 additions & 2 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ bool ConfigurationClass::write()
powerlimiter["enabled"] = config.PowerLimiter.Enabled;
powerlimiter["verbose_logging"] = config.PowerLimiter.VerboseLogging;
powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled;
powerlimiter["solar_passtrough_losses"] = config.PowerLimiter.SolarPassThroughLosses;
powerlimiter["solar_passthrough_losses"] = config.PowerLimiter.SolarPassThroughLosses;
powerlimiter["battery_always_use_at_night"] = config.PowerLimiter.BatteryAlwaysUseAtNight;
powerlimiter["interval"] = config.PowerLimiter.Interval;
powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter;
Expand Down Expand Up @@ -438,7 +438,7 @@ bool ConfigurationClass::read()
config.PowerLimiter.Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED;
config.PowerLimiter.VerboseLogging = powerlimiter["verbose_logging"] | VERBOSE_LOGGING;
config.PowerLimiter.SolarPassThroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED;
config.PowerLimiter.SolarPassThroughLosses = powerlimiter["solar_passthrough_losses"] | POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES;
config.PowerLimiter.SolarPassThroughLosses = powerlimiter["solar_passthrough_losses"] | powerlimiter["solar_passtrough_losses"] | POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES; // solar_passthrough_losses was previously saved as solar_passtrough_losses. Be nice and also try mistyped key.
config.PowerLimiter.BatteryAlwaysUseAtNight = powerlimiter["battery_always_use_at_night"] | POWERLIMITER_BATTERY_ALWAYS_USE_AT_NIGHT;
if (powerlimiter["battery_drain_strategy"].as<uint8_t>() == 1) { config.PowerLimiter.BatteryAlwaysUseAtNight = true; } // convert legacy setting
config.PowerLimiter.Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL;
Expand Down
75 changes: 48 additions & 27 deletions src/HttpPowerMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
#include "HttpPowerMeter.h"
#include "MessageOutput.h"
#include <WiFiClientSecure.h>
#include <FirebaseJson.h>
#include <Crypto.h>
#include <SHA256.h>
#include <ArduinoJson.h>
#include "mbedtls/sha256.h"
#include <base64.h>
#include <memory>
#include <ESPmDNS.h>
Expand Down Expand Up @@ -219,18 +218,43 @@ String HttpPowerMeterClass::getDigestAuth(String& authReq, const String& usernam
return authorization;
}

bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, const char* jsonPath, Unit_t unit, bool signInverted)
bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, String jsonPath, Unit_t unit, bool signInverted)
{
FirebaseJson json;
json.setJsonData(httpResponse);
FirebaseJsonData value;
if (!json.get(value, jsonPath)) {
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("[HttpPowerMeter] Couldn't find a value for phase %i with Json query \"%s\""), phase, jsonPath);
JsonDocument root;
const DeserializationError error = deserializeJson(root, httpResponse);
if (error) {
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
PSTR("[HttpPowerMeter] 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>();

// 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) {
String key = jsonPath.substring(start, end);
value = value[key];
start = end + 1;
end = jsonPath.indexOf(delimiter, start);
}

String lastKey = jsonPath.substring(start);
value = value[lastKey];

if (value.isNull()) {
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
PSTR("[HttpPowerMeter] Unable to find a value for phase %i with JSON path \"%s\""),
phase+1, jsonPath.c_str());
return false;
}

// this value is supposed to be in Watts and positive if energy is consumed.
power[phase] = value.to<float>();
power[phase] = value.as<float>();

switch (unit) {
case Unit_t::MilliWatts:
Expand Down Expand Up @@ -299,27 +323,24 @@ bool HttpPowerMeterClass::extractUrlComponents(String url, String& _protocol, St
return true;
}

#define HASH_SIZE 32

String HttpPowerMeterClass::sha256(const String& data) {
SHA256 sha256;
uint8_t hash[HASH_SIZE];

sha256.reset();
sha256.update(data.c_str(), data.length());
sha256.finalize(hash, HASH_SIZE);

String hashStr = "";
for (int i = 0; i < HASH_SIZE; i++) {
String hex = String(hash[i], HEX);
if (hex.length() == 1) {
hashStr += "0";
}
hashStr += hex;
uint8_t hash[32];

mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0); // select SHA256
mbedtls_sha256_update(&ctx, reinterpret_cast<const unsigned char*>(data.c_str()), data.length());
mbedtls_sha256_finish(&ctx, hash);
mbedtls_sha256_free(&ctx);

char res[sizeof(hash) * 2 + 1];
for (int i = 0; i < sizeof(hash); i++) {
snprintf(res + (i*2), sizeof(res) - (i*2), "%02x", hash[i]);
}

return hashStr;
return res;
}

void HttpPowerMeterClass::prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue) {
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
httpClient.setUserAgent("OpenDTU-OnBattery");
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@
"httpHeaderKeyDescription": "Ein individueller HTTP request header kann hier definiert werden. Das kann z.B. verwendet werden um einen eigenen Authorization header mitzugeben.",
"httpHeaderValue": "Optional: HTTP request header - Wert",
"httpJsonPath": "JSON Pfad",
"httpJsonPathDescription": "JSON Pfad um den Leistungswert zu finden. Es verwendet die Selektions-Syntax von mobizt/FirebaseJson. Beispiele gibt es oben.",
"httpJsonPathDescription": "Anwendungsspezifischer JSON-Pfad um den Leistungswert in the HTTP(S) Antwort zu finden, z.B. 'power/total/watts' oder nur 'total'.",
"httpUnit": "Einheit",
"httpSignInverted": "Vorzeichen umkehren",
"httpSignInvertedHint": "Positive Werte werden als Leistungsabnahme aus dem Netz interpretiert. Diese Option muss aktiviert werden, wenn das Vorzeichen des Wertes die gegenteilige Bedeutung hat.",
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@
"httpHeaderKeyDescription": "A custom HTTP request header can be defined. Might be useful if you have to send something like a custom Authorization header.",
"httpHeaderValue": "Optional: HTTP request header - Value",
"httpJsonPath": "JSON path",
"httpJsonPathDescription": "JSON path to find the power value in the response. This uses the JSON path query syntax from mobizt/FirebaseJson. See above for examples.",
"httpJsonPathDescription": "Application specific JSON path to find the power value in the HTTP(S) response, e.g., 'power/total/watts' or simply 'total'.",
"httpUnit": "Unit",
"httpSignInverted": "Change Sign",
"httpSignInvertedHint": "Is is expected that positive values denote power usage from the grid. Check this option if the sign of this value has the opposite meaning.",
Expand Down
6 changes: 2 additions & 4 deletions webapp/src/views/PowerMeterAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,9 @@

<h2>JSON path examples:</h2>
<ul>
<li>total_power - { "othervalue": "blah", "total_power": 123.4 }</li>
<li>testarray/[2]/myvalue - { "testarray": [ {}, { "power": 123.4 } ] }</li>
<li><code>power/total/watts</code> - Finds 123.4 in <code>{ "power": { "phase1": { "factor": 0.98, "watts": 42 }, "total": { "watts": 123.4 } } }</code></li>
<li><code>total</code> - Finds 123.4 in <code>{ "othervalue": 66, "total": 123.4 }</code></li>
</ul>

More info: <a href="https://github.com/mobizt/FirebaseJson">https://github.com/mobizt/FirebaseJson</a>
</div>

<CardElement
Expand Down

0 comments on commit 35491ca

Please sign in to comment.