Skip to content
Open
14 changes: 13 additions & 1 deletion include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ struct BATTERY_CONFIG_T {
};
using BatteryConfig = struct BATTERY_CONFIG_T;

enum GridChargerProviderType { HUAWEI = 0, TRUCKI = 1 };
enum GridChargerProviderType { HUAWEI = 0, TRUCKI = 1, HTTP = 2 };
enum GridChargerHardwareInterface { MCP2515 = 0, TWAI = 1 };

struct GRID_CHARGER_TRUCKI_CONFIG_T {
Expand All @@ -268,6 +268,15 @@ struct GRID_CHARGER_HUAWEI_CONFIG_T {
};
using GridChargerHuaweiConfig = struct GRID_CHARGER_HUAWEI_CONFIG_T;

struct GRID_CHARGER_HTTP_CONFIG_T{
char url[1025];
char uri_on[1025];
char uri_off[1025];
char uri_stats[1025];
char uri_powerparam[256];
float AcPower;
};using GridChargerHTTPConfig = struct GRID_CHARGER_HTTP_CONFIG_T;
Comment thread
Snoopy-HSS marked this conversation as resolved.
Outdated

struct GRID_CHARGER_CONFIG_T {
bool Enabled;
bool AutoPowerEnabled;
Expand All @@ -283,6 +292,7 @@ struct GRID_CHARGER_CONFIG_T {
GridChargerCanConfig Can;
GridChargerHuaweiConfig Huawei;
GridChargerTruckiConfig Trucki;
GridChargerHTTPConfig HTTP;
};
using GridChargerConfig = struct GRID_CHARGER_CONFIG_T;

Expand Down Expand Up @@ -499,6 +509,7 @@ class ConfigurationClass {
static void serializeGridChargerCanConfig(GridChargerCanConfig const& source, JsonObject& target);
static void serializeGridChargerHuaweiConfig(GridChargerHuaweiConfig const& source, JsonObject& target);
static void serializeGridChargerTruckiConfig(GridChargerTruckiConfig const& source, JsonObject& target);
static void serializeGridChargerHTTPConfig(GridChargerHTTPConfig const& source, JsonObject& target);

static void deserializeHttpRequestConfig(JsonObject const& source_http_config, HttpRequestConfig& target);
static void deserializeSolarChargerConfig(JsonObject const& source, SolarChargerConfig& target);
Expand All @@ -517,6 +528,7 @@ class ConfigurationClass {
static void deserializeGridChargerCanConfig(JsonObject const& source, GridChargerCanConfig& target);
static void deserializeGridChargerHuaweiConfig(JsonObject const& source, GridChargerHuaweiConfig& target);
static void deserializeGridChargerTruckiConfig(JsonObject const& source, GridChargerTruckiConfig& target);
static void deserializeGridChargerHTTPConfig(JsonObject const& source, GridChargerHTTPConfig& target);

private:
void loop();
Expand Down
9 changes: 9 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,12 @@
#define GRIDCHARGER_HUAWEI_INPUT_CURRENT_LIMIT 0.0
#define GRIDCHARGER_HUAWEI_FAN_ONLINE_FULL_SPEED false
#define GRIDCHARGER_HUAWEI_FAN_OFFLINE_FULL_SPEED false

#define GRIDCHARGER_HTTP_POWER_ON_THRESHOLD -500
#define GRIDCHARGER_HTTP_POWER_OFF_THRESHOLD -100
#define GRIDCHARGER_HTTP_IPADDRESS "http://192.168.2.100"
#define GRIDCHARGER_HTTP_URION "/relay/0?turn=on"
#define GRIDCHARGER_HTTP_URIOFF "/relay/0?turn=off"
#define GRIDCHARGER_HTTP_URISTATS "/rpc/Switch.GetStatus?id=0"
#define GRIDCHARGER_HTTP_URIPOWERPARAM "apower"
#define GRIDCHARGER_HTTP_ACPOWER 500
33 changes: 33 additions & 0 deletions include/gridcharger/HTTP/DataPoints.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <DataPoints.h>
#include <string>

namespace GridChargers::HTTP {

enum class DataPointLabel : uint8_t {
AcPowerSetpoint,
AcPower,
};

template<DataPointLabel> struct DataPointLabelTraits;

#define LABEL_TRAIT(n, t, u) template<> struct DataPointLabelTraits<DataPointLabel::n> { \
using type = t; \
static constexpr char const name[] = #n; \
static constexpr char const unit[] = u; \
};

LABEL_TRAIT(AcPowerSetpoint, float, "W");
LABEL_TRAIT(AcPower, float, "W");
#undef LABEL_TRAIT

} // namespace GridChargers::HTTP

template class DataPointContainer<DataPoint<float, std::string>,
GridChargers::HTTP::DataPointLabel,
GridChargers::HTTP::DataPointLabelTraits>;

namespace GridChargers::HTTP {
using DataPointContainer = ::DataPointContainer<::DataPoint<float, std::string>, DataPointLabel, DataPointLabelTraits>;
} // namespace GridChargers::HTTP
Comment thread
Snoopy-HSS marked this conversation as resolved.
Outdated
103 changes: 103 additions & 0 deletions include/gridcharger/HTTP/Provider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <atomic>
#include <gridcharger/Provider.h>
#include <gridcharger/HTTP/Stats.h>
#include <HttpGetter.h>
#include <Utils.h>

namespace GridChargers::HTTP {

class Provider : public ::GridChargers::Provider {
public:
bool init() final;
void deinit() final;
void loop() final;
void PowerON();
void PowerOFF();
bool send_http(String Url);
float read_http(String uri);
Comment thread
Snoopy-HSS marked this conversation as resolved.
Outdated
bool powerstate = false;
float acPowerCurrent=0;
String uri_on;
String uri_off;
String uri_stats;
String uri_powerparam;
float maximumAcPower;
Comment thread
Snoopy-HSS marked this conversation as resolved.
Outdated
std::shared_ptr<::GridChargers::Stats> getStats() const final { return _stats; }

bool getAutoPowerStatus() const final {
return _requestedPowerAc > 0
|| _dataCurrent.get<DataPointLabel::AcPowerSetpoint>().value_or(0) > 0.0f;
}

private:
template<DataPointLabel L>
void addStringToDataPoints(const JsonDocument& doc, const char* key)
{
if (doc[key].is<std::string>()) {
_dataCurrent.add<L>(doc[key].as<std::string>());
return;
}

if (doc[key].is<const char*>()) {
_dataCurrent.add<L>(std::string{doc[key].as<const char*>()});
}
}

template<DataPointLabel L>
void addFloatToDataPoints(const JsonDocument& doc, const char* key)
{
std::optional<float> value;

if (doc[key].is<float>()) {
value = doc[key].as<float>();
}

if (doc[key].is<char const*>()) {
value = Utils::getFromString<float>(doc[key].as<char const*>());
}

if (!value.has_value()) { return; }
_dataCurrent.add<L>(*value);
}

void powerControlLoop();
static void dataPollingLoopHelper(void* context);
void dataPollingLoop();
void pollData();

TaskHandle_t _dataPollingTaskHandle = nullptr;
std::atomic<bool> _dataPollingTaskDone = false;
bool _stopPollingData = false;
mutable std::mutex _dataPollingMutex;
std::condition_variable _dataPollingCv;
uint32_t _lastDataPoll = 0;

std::unique_ptr<HttpRequestConfig> _httpRequestConfig;
std::unique_ptr<HttpGetter> _httpGetter;

static constexpr int DATA_POLLING_INTERVAL_MS = 5000; // 3 seconds
Comment thread
Snoopy-HSS marked this conversation as resolved.
Outdated
static constexpr int HTTP_REQUEST_TIMEOUT_MS = 500; // 500ms

float _requestedPowerAc = 0;

void parseControlCommandResponse();

uint32_t _lastControlCommandRequestMillis = 0;
uint32_t _lastparseControlCommandRequestMillis = 0;
static constexpr int CONTROL_COMMAND_INTERVAL_MS = 500; // 500ms

std::shared_ptr<Stats> _stats = std::make_shared<Stats>();

DataPointContainer _dataCurrent;

uint32_t _lastPowerMeterUpdateReceivedMillis = 0; // Timestamp of last seen power meter value
uint32_t _autoModeBlockedTillMillis = 0; // Timestamp to block running auto mode for some time

bool _autoPowerEnabled = false;
bool _batteryEmergencyCharging = false;
};

} // namespace GridChargers::HTTP
50 changes: 50 additions & 0 deletions include/gridcharger/HTTP/Stats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <gridcharger/Stats.h>
#include <gridcharger/HTTP/DataPoints.h>

namespace GridChargers::HTTP {

class Stats : public ::GridChargers::Stats {
friend class Provider;

public:
uint32_t getLastUpdate() const final;

std::optional<float> getInputPower() const final;

void getLiveViewData(JsonVariant& root) const final;

protected:
void mqttPublish() const final {}

void updateFrom(DataPointContainer const& dataPoints) const;

private:
template<DataPointLabel L>
void addValueInSection(JsonVariant& root,
std::string const& section, std::string const& name,
int precision = 2) const
{
auto oVal = _dataPoints.get<L>();
if (!oVal) { return; }

::GridChargers::Stats::addValueInSection(root, section, name, *oVal, DataPointLabelTraits<L>::unit, precision);
}

template<DataPointLabel L>
void addStringInSection(JsonVariant& root,
std::string const& section, std::string const& name,
bool translate = true) const
{
auto oVal = _dataPoints.get<L>();
if (!oVal) { return; }

::GridChargers::Stats::addStringInSection(root, section, name, *oVal, translate);
}

mutable DataPointContainer _dataPoints;
};

} // namespace GridChargers::HTTP
24 changes: 24 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ void ConfigurationClass::serializeGridChargerTruckiConfig(GridChargerTruckiConfi
target["password"] = source.Password;
}

void ConfigurationClass::serializeGridChargerHTTPConfig(GridChargerHTTPConfig const& source, JsonObject& target)
{
target["url"] = source.url;
target["uri_on"] = source.uri_on;
target["uri_off"] = source.uri_off;
target["uri_stats"] = source.uri_stats;
target["uri_powerparam"] = source.uri_powerparam;
target["AcPower"] = source.AcPower;
}

bool ConfigurationClass::write()
{
File f = LittleFS.open(CONFIG_FILENAME, "w");
Expand Down Expand Up @@ -456,6 +466,9 @@ bool ConfigurationClass::write()
JsonObject gridcharger_trucki = gridcharger["trucki"].to<JsonObject>();
serializeGridChargerTruckiConfig(config.GridCharger.Trucki, gridcharger_trucki);

JsonObject gridcharger_HTTP = gridcharger["HTTP"].to<JsonObject>();
serializeGridChargerHTTPConfig(config.GridCharger.HTTP, gridcharger_HTTP);

if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false;
}
Expand Down Expand Up @@ -698,6 +711,16 @@ void ConfigurationClass::deserializeGridChargerTruckiConfig(JsonObject const& so
strlcpy(target.Password, source["password"] | "", sizeof(target.Password));
}

void ConfigurationClass::deserializeGridChargerHTTPConfig(JsonObject const& source, GridChargerHTTPConfig& target)
{
strlcpy(target.url, source["url"] | GRIDCHARGER_HTTP_IPADDRESS, sizeof(target.url));
strlcpy(target.uri_on, source["uri_on"] | GRIDCHARGER_HTTP_URION, sizeof(target.uri_on));
strlcpy(target.uri_off, source["uri_off"] | GRIDCHARGER_HTTP_URIOFF, sizeof(target.uri_off));
strlcpy(target.uri_stats, source["uri_stats"] | GRIDCHARGER_HTTP_URISTATS, sizeof(target.uri_stats));
strlcpy(target.uri_powerparam, source["uri_powerparam"] | GRIDCHARGER_HTTP_URIPOWERPARAM, sizeof(target.uri_powerparam));
target.AcPower = source["AcPower"] | GRIDCHARGER_HTTP_ACPOWER;
}

bool ConfigurationClass::read()
{
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
Expand Down Expand Up @@ -912,6 +935,7 @@ bool ConfigurationClass::read()
deserializeGridChargerCanConfig(gridcharger["can"], config.GridCharger.Can);
deserializeGridChargerHuaweiConfig(gridcharger["huawei"], config.GridCharger.Huawei);
deserializeGridChargerTruckiConfig(gridcharger["trucki"], config.GridCharger.Trucki);
deserializeGridChargerHTTPConfig(gridcharger["HTTP"], config.GridCharger.HTTP);

f.close();

Expand Down
5 changes: 5 additions & 0 deletions src/WebApi_gridcharger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "WebApi_gridcharger.h"
#include <gridcharger/Controller.h>
#include <gridcharger/huawei/Provider.h>
#include <gridcharger/HTTP/Provider.h>
#include "Configuration.h"
#include "PinMapping.h"
#include "WebApi.h"
Expand Down Expand Up @@ -175,6 +176,9 @@ void WebApiGridChargerClass::onAdminGet(AsyncWebServerRequest* request)
auto trucki = root["trucki"].to<JsonObject>();
ConfigurationClass::serializeGridChargerTruckiConfig(config.GridCharger.Trucki, trucki);

auto HTTP = root["HTTP"].to<JsonObject>();
ConfigurationClass::serializeGridChargerHTTPConfig(config.GridCharger.HTTP, HTTP);

response->setLength();
request->send(response);
}
Expand Down Expand Up @@ -242,6 +246,7 @@ void WebApiGridChargerClass::onAdminPost(AsyncWebServerRequest* request)
ConfigurationClass::deserializeGridChargerCanConfig(root["can"].as<JsonObject>(), config.GridCharger.Can);
ConfigurationClass::deserializeGridChargerHuaweiConfig(root["huawei"].as<JsonObject>(), config.GridCharger.Huawei);
ConfigurationClass::deserializeGridChargerTruckiConfig(root["trucki"].as<JsonObject>(), config.GridCharger.Trucki);
ConfigurationClass::deserializeGridChargerHTTPConfig(root["HTTP"].as<JsonObject>(), config.GridCharger.HTTP);
}

WebApi.writeConfig(retMsg);
Expand Down
4 changes: 4 additions & 0 deletions src/gridcharger/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <gridcharger/DummyStats.h>
#include <gridcharger/huawei/Provider.h>
#include <gridcharger/trucki/Provider.h>
#include <gridcharger/HTTP/Provider.h>
#include <Configuration.h>
#include <MqttSettings.h>
#include <LogHelper.h>
Expand Down Expand Up @@ -45,6 +46,9 @@ void Controller::updateSettings()
case GridChargerProviderType::TRUCKI:
_upProvider = std::make_unique<::GridChargers::Trucki::Provider>();
break;
case GridChargerProviderType::HTTP:
_upProvider = std::make_unique<::GridChargers::HTTP::Provider>();
break;
default:
DTU_LOGW("Unknown provider: %d\r\n", config.GridCharger.Provider);
return;
Expand Down
Loading