Skip to content
Open
15 changes: 14 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,16 @@ struct GRID_CHARGER_HUAWEI_CONFIG_T {
};
using GridChargerHuaweiConfig = struct GRID_CHARGER_HUAWEI_CONFIG_T;

struct GRID_CHARGER_HTTP_CONFIG_T {
char Url[HTTP_REQUEST_MAX_URL_STRLEN + 1];
char UriOn[HTTP_REQUEST_MAX_URL_STRLEN + 1];
char UriOff[HTTP_REQUEST_MAX_URL_STRLEN + 1];
char UriStats[HTTP_REQUEST_MAX_URL_STRLEN + 1];
char UriPowerParam[MQTT_MAX_JSON_PATH_STRLEN + 1];
float AcPower;
};
using GridChargerHTTPConfig = struct GRID_CHARGER_HTTP_CONFIG_T;

struct GRID_CHARGER_CONFIG_T {
bool Enabled;
bool AutoPowerEnabled;
Expand All @@ -283,6 +293,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 +510,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 +529,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 @@ -192,3 +192,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

extern 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
97 changes: 97 additions & 0 deletions include/gridcharger/HTTP/Provider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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>
#include <memory>
#include <mutex>
#include <condition_variable>

namespace GridChargers::HTTP {

class Provider : public ::GridChargers::Provider {
public:
bool init() final;
void deinit() final;
void loop() final;
void PowerON();
void PowerOFF();
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);
}

bool send_http(String Url);
float read_http(String uri);
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;

static constexpr int DATA_POLLING_INTERVAL_MS = 5000; // 5 seconds

float _requestedPowerAc = 0;
std::atomic<bool> _powerState{false};

String _uriOn;
String _uriOff;
String _uriStats;
String _uriPowerparam;
float _maximumAcPower = 0.0f;

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

DataPointContainer _dataCurrent;

uint32_t _lastPowerMeterUpdateReceivedMillis = 0;
uint32_t _autoModeBlockedTillMillis = 0;
uint32_t _autowaitTillMillis = 0;

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 @@ -269,6 +269,16 @@ void ConfigurationClass::serializeGridChargerTruckiConfig(GridChargerTruckiConfi
target["password"] = source.Password;
}

void ConfigurationClass::serializeGridChargerHTTPConfig(GridChargerHTTPConfig const& source, JsonObject& target)
{
target["Url"] = source.Url;
target["UriOn"] = source.UriOn;
target["UriOff"] = source.UriOff;
target["UriStats"] = source.UriStats;
target["UriPowerParam"] = source.UriPowerParam;
target["AcPower"] = source.AcPower;
}

bool ConfigurationClass::write()
{
File f = LittleFS.open(CONFIG_FILENAME, "w");
Expand Down Expand Up @@ -459,6 +469,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 @@ -702,6 +715,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.UriOn, source["UriOn"] | GRIDCHARGER_HTTP_URION, sizeof(target.UriOn));
strlcpy(target.UriOff, source["UriOff"] | GRIDCHARGER_HTTP_URIOFF, sizeof(target.UriOff));
strlcpy(target.UriStats, source["UriStats"] | GRIDCHARGER_HTTP_URISTATS, sizeof(target.UriStats));
strlcpy(target.UriPowerParam, source["UriPowerParam"] | GRIDCHARGER_HTTP_URIPOWERPARAM, sizeof(target.UriPowerParam));
target.AcPower = source["AcPower"] | GRIDCHARGER_HTTP_ACPOWER;
}

bool ConfigurationClass::read()
{
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
Expand Down Expand Up @@ -915,6 +938,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
15 changes: 15 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 @@ -215,6 +219,16 @@ void WebApiGridChargerClass::onAdminPost(AsyncWebServerRequest* request)
return;
}

if (root["provider"].as<uint8_t>() == static_cast<uint8_t>(GridChargerProviderType::HTTP)) {
if (!(root["HTTP"]["Url"].is<const char*>()) ||
!(root["HTTP"]["AcPower"].is<float>())) {
retMsg["message"] = "HTTP values are missing or of wrong type!";
retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength();
request->send(response);
return;
}
}
using HuaweiProvider = GridChargers::Huawei::Provider;

auto isValidRange = [&](const char* valueName, float min, float max, WebApiError error) -> bool {
Expand Down Expand Up @@ -242,6 +256,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
7 changes: 7 additions & 0 deletions src/gridcharger/HTTP/DataPoints.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include <gridcharger/HTTP/DataPoints.h>

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