Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AC Charger über Shelly Plug S plus einschalten #1330

Open
wants to merge 13 commits into
base: development
Choose a base branch
from
Open
13 changes: 13 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,19 @@ struct CONFIG_T {
float Auto_Power_Target_Power_Consumption;
} Huawei;

struct {
bool Enabled;
bool VerboseLogging;
bool Auto_Power_BatterySoC_Limits_Enabled;
bool Emergency_Charge_Enabled;
uint8_t stop_batterysoc_threshold;
char url[1025];
int32_t POWER_ON_threshold;
int32_t POWER_OFF_threshold;
bool POWER_ON;
bool POWER_OFF;
} Shelly;


INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
Expand Down
33 changes: 33 additions & 0 deletions include/ShellyACPlug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <condition_variable>
#include "HttpGetter.h"
#include "Configuration.h"
#include <TaskSchedulerDeclarations.h>
#include <stdint.h>
#include <atomic>
#include <array>
#include <variant>
#include <mutex>

class ShellyACPlugClass {
public:
bool init(Scheduler& scheduler);
void loop();
void PowerON();
void PowerOFF();
float _readpower;
private:
bool _initialized = false;
Task _loopTask;
const uint16_t _period = 2000;
float _acPower;
float _SoC;
bool _emergcharge;
bool send_http(String uri);
float read_http(String uri);
std::unique_ptr<HttpGetter> _HttpGetter;
};

extern ShellyACPlugClass ShellyACPlug;
5 changes: 5 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
#include "WebApi_ws_Huawei.h"
#include "WebApi_Huawei.h"
#include "WebApi_ws_battery.h"
#include "WebApi_Shelly.h"
#include "WebApi_ws_Shelly.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>


class WebApiClass {
public:
WebApiClass();
Expand Down Expand Up @@ -82,6 +85,8 @@ class WebApiClass {
WebApiHuaweiClass _webApiHuaweiClass;
WebApiWsHuaweiLiveClass _webApiWsHuaweiLive;
WebApiWsBatteryLiveClass _webApiWsBatteryLive;
WebApiShellyClass _webApiShellyClass;
WebApiWsShellyLiveClass _webApiWsShellyLive;
};

extern WebApiClass WebApi;
17 changes: 17 additions & 0 deletions include/WebApi_Shelly.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <AsyncJson.h>

class WebApiShellyClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);

AsyncWebServer* _server;
};
31 changes: 31 additions & 0 deletions include/WebApi_ws_Shelly.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>

class WebApiWsShellyLiveClass {
public:
WebApiWsShellyLiveClass();
void init(AsyncWebServer& server, Scheduler& scheduler);
void reload();

private:
void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

AsyncWebServer* _server;
AsyncWebSocket _ws;
AuthenticationMiddleware _simpleDigestAuth;

std::mutex _mutex;

Task _wsCleanupTask;
void wsCleanupTaskCb();

Task _sendDataTask;
void sendDataTaskCb();
};
2 changes: 1 addition & 1 deletion include/WebApi_ws_live.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class WebApiWsLiveClass {
uint32_t _lastPublishHuawei = 0;
uint32_t _lastPublishBattery = 0;
uint32_t _lastPublishPowerMeter = 0;

uint32_t _lastPublishShelly = 0;
uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 };

std::mutex _mutex;
Expand Down
8 changes: 8 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,12 @@
#define HUAWEI_AUTO_POWER_STOP_BATTERYSOC_THRESHOLD 95
#define HUAWEI_AUTO_POWER_TARGET_POWER_CONSUMPTION 0

#define SHELLY_ENABLED false
#define SHELLY_POWER_ON_THRESHOLD -500
#define SHELLY_POWER_OFF_THRESHOLD -100
#define SHELLY_STOP_BATTERYSOC_THRESHOLD 95
#define SHELLY_IPADDRESS "http://192.168.2.100"
#define SHELLY_POWER_ON false
#define SHELLY_POWER_OFF false

#define VERBOSE_LOGGING true
24 changes: 24 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ bool ConfigurationClass::write()
huawei["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold;
huawei["target_power_consumption"] = config.Huawei.Auto_Power_Target_Power_Consumption;

JsonObject shelly = doc["shelly"].to<JsonObject>();
shelly["enabled"] = config.Shelly.Enabled;
shelly["verbose_logging"] = config.Shelly.VerboseLogging;
shelly["auto_power_batterysoc_limits_enabled"]= config.Shelly.Auto_Power_BatterySoC_Limits_Enabled ;
shelly["emergency_charge_enabled"]= config.Shelly.Emergency_Charge_Enabled;
shelly["stop_batterysoc_threshold"] = config.Shelly.stop_batterysoc_threshold;
shelly["url"] = config.Shelly.url;
shelly["power_on_threshold"] = config.Shelly.POWER_ON_threshold;
shelly["power_off_threshold"] = config.Shelly.POWER_OFF_threshold;
shelly["power_on"] = config.Shelly.POWER_ON;
shelly["power_off"] = config.Shelly.POWER_OFF;

if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false;
}
Expand Down Expand Up @@ -657,6 +669,18 @@ bool ConfigurationClass::read()
config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = huawei["stop_batterysoc_threshold"] | HUAWEI_AUTO_POWER_STOP_BATTERYSOC_THRESHOLD;
config.Huawei.Auto_Power_Target_Power_Consumption = huawei["target_power_consumption"] | HUAWEI_AUTO_POWER_TARGET_POWER_CONSUMPTION;

JsonObject shelly = doc["shelly"];
config.Shelly.Enabled = shelly["enabled"] | SHELLY_ENABLED;
config.Shelly.VerboseLogging = shelly["verbose_logging"] | VERBOSE_LOGGING;
config.Shelly.Auto_Power_BatterySoC_Limits_Enabled = shelly["auto_power_batterysoc_limits_enabled"] | false;
config.Shelly.Emergency_Charge_Enabled = shelly["emergency_charge_enabled"] | false;
config.Shelly.stop_batterysoc_threshold = shelly["stop_batterysoc_threshold"] | SHELLY_STOP_BATTERYSOC_THRESHOLD;
strlcpy(config.Shelly.url, shelly["url"] | SHELLY_IPADDRESS, sizeof(config.Shelly.url));
config.Shelly.POWER_ON_threshold = shelly["power_on_threshold"] | SHELLY_POWER_ON_THRESHOLD;
config.Shelly.POWER_OFF_threshold = shelly["power_off_threshold"] | SHELLY_POWER_OFF_THRESHOLD;
config.Shelly.POWER_ON = shelly["power_on"] | false;
config.Shelly.POWER_OFF = shelly["power_off"] | false;

f.close();
return true;
}
Expand Down
154 changes: 154 additions & 0 deletions src/ShellyACPlug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Utils.h"
#include "ShellyACPlug.h"
#include "MessageOutput.h"
#include <WiFiClientSecure.h>
#include <base64.h>
#include <ESPmDNS.h>
#include "Configuration.h"
#include "Datastore.h"
#include "PowerMeter.h"
#include "Battery.h"

ShellyACPlugClass ShellyACPlug;


bool ShellyACPlugClass::init(Scheduler& scheduler)
{
MessageOutput.printf("ShellyACPlug Initializing ... \r\n");
_initialized = true;
scheduler.addTask(_loopTask);
_loopTask.setCallback([this] { loop(); });
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(_period);
_loopTask.enable();
return false;
}

void ShellyACPlugClass::loop()
{
const CONFIG_T& config = Configuration.get();
if (!config.Shelly.Enabled || !_initialized || !Configuration.get().PowerMeter.Enabled ) {
return;
}
_loopTask.setInterval(_period);
_acPower = PowerMeter.getPowerTotal();
_SoC = Battery.getStats()->getSoC();
_emergcharge = Battery.getStats()->getImmediateChargingRequest();
_readpower = read_http("/rpc/Switch.GetStatus?id=0");
if ((_acPower < config.Shelly.POWER_ON_threshold && !config.Shelly.POWER_ON && _SoC < config.Shelly.stop_batterysoc_threshold) || (_emergcharge && config.Shelly.Emergency_Charge_Enabled))
{
PowerON();
}
else if ((_acPower > config.Shelly.POWER_OFF_threshold && !config.Shelly.POWER_OFF) || (_SoC >= config.Shelly.stop_batterysoc_threshold && !config.Shelly.POWER_OFF))
{
PowerOFF();
}
if (config.Shelly.VerboseLogging) {
MessageOutput.print("[ShellyACPlug] Loop \r\n");
MessageOutput.printf("[ShellyACPlug] %f W \r\n", _acPower );
MessageOutput.printf("[ShellyACPlug] ON: %d OFF: %d \r\n", config.Shelly.POWER_ON, config.Shelly.POWER_OFF );
MessageOutput.printf("[ShellyACPlug] Battery SoC %f \r\n", _SoC);
MessageOutput.printf("[ShellyACPlug] Verbrauch %f W \r\n", _readpower );
}
}

void ShellyACPlugClass::PowerON()
{
if (!send_http("/relay/0?turn=on"))
{
return;
}
CONFIG_T& config = Configuration.get();
config.Shelly.POWER_ON = true;
config.Shelly.POWER_OFF = false;
Configuration.write();
if (config.Shelly.VerboseLogging) {
MessageOutput.print("[ShellyACPlug] Power ON \r\n");
}
}

void ShellyACPlugClass::PowerOFF()
{
if (!send_http("/relay/0?turn=off"))
{
return;
};
CONFIG_T& config = Configuration.get();
config.Shelly.POWER_ON = false;
config.Shelly.POWER_OFF = true;
Configuration.write();
if (config.Shelly.VerboseLogging) {
MessageOutput.print("[ShellyACPlug] Power OFF \r\n");
}
}

bool ShellyACPlugClass::send_http(String uri)
{
CONFIG_T& config = Configuration.get();
String url = config.Shelly.url;
url += uri;
HttpRequestConfig HttpRequest;
strlcpy(HttpRequest.Url, url.c_str(), sizeof(HttpRequest.Url));
HttpRequest.Timeout = 60;
_HttpGetter = std::make_unique<HttpGetter>(HttpRequest);
if (config.Shelly.VerboseLogging) {
MessageOutput.printf("[ShellyACPlug] send_http Initializing: %s\r\n",url.c_str());
}
if (!_HttpGetter->init()) {
MessageOutput.printf("[ShellyACPlug] INIT %s\r\n", _HttpGetter->getErrorText());
return false;
}
if (!_HttpGetter->performGetRequest()) {
MessageOutput.printf("[ShellyACPlug] GET %s\r\n", _HttpGetter->getErrorText());
return false;
}
_HttpGetter = nullptr;
return true;
}
float ShellyACPlugClass::read_http(String uri)
{
CONFIG_T& config = Configuration.get();
String url = config.Shelly.url;
url += uri;
HttpRequestConfig HttpRequest;
JsonDocument jsonResponse;
strlcpy(HttpRequest.Url, url.c_str(), sizeof(HttpRequest.Url));
HttpRequest.Timeout = 60;
_HttpGetter = std::make_unique<HttpGetter>(HttpRequest);
if (config.Shelly.VerboseLogging) {
MessageOutput.printf("[ShellyACPlug] read_http Initializing: %s\r\n",url.c_str());
}
if (!_HttpGetter->init()) {
MessageOutput.printf("[ShellyACPlug] INIT %s\r\n", _HttpGetter->getErrorText());
return 0;
}
_HttpGetter->addHeader("Content-Type", "application/json");
_HttpGetter->addHeader("Accept", "application/json");
auto res = _HttpGetter->performGetRequest();
if (!res) {
MessageOutput.printf("[ShellyACPlug] GET %s\r\n", _HttpGetter->getErrorText());
return 0;
}
auto pStream = res.getStream();
if (!pStream) {
MessageOutput.printf("Programmer error: HTTP request yields no stream");
return 0;
}

const DeserializationError error = deserializeJson(jsonResponse, *pStream);
if (error) {
String msg("[ShellyACPlug] Unable to parse server response as JSON: ");
MessageOutput.printf((msg + error.c_str()).c_str());
return 0;
}
auto pathResolutionResult = Utils::getJsonValueByPath<float>(jsonResponse, "apower");
if (!pathResolutionResult.second.isEmpty()) {
MessageOutput.printf("[ShellyACPlug] second %s\r\n",pathResolutionResult.second.c_str());
}

_HttpGetter = nullptr;
return pathResolutionResult.first;
}


4 changes: 3 additions & 1 deletion src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ void WebApiClass::init(Scheduler& scheduler)
_webApiWsHuaweiLive.init(_server, scheduler);
_webApiHuaweiClass.init(_server, scheduler);
_webApiWsBatteryLive.init(_server, scheduler);

_webApiShellyClass.init(_server, scheduler);
_webApiWsShellyLive.init(_server, scheduler);
_server.begin();
}

Expand All @@ -54,6 +55,7 @@ void WebApiClass::reload()
_webApiWsBatteryLive.reload();
_webApiWsVedirectLive.reload();
_webApiWsHuaweiLive.reload();
_webApiWsShellyLive.reload();
}

bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
Expand Down
Loading