Skip to content

Commit

Permalink
Implement go-e Controller integration
Browse files Browse the repository at this point in the history
  • Loading branch information
CommanderRedYT committed Oct 6, 2024
1 parent b7f830f commit 4239a0d
Show file tree
Hide file tree
Showing 23 changed files with 440 additions and 6 deletions.
10 changes: 10 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

#define DEV_MAX_MAPPING_NAME_STRLEN 63

#define INTEGRATIONS_GOE_MAX_HOSTNAME_STRLEN 128

struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower;
char Name[CHAN_MAX_NAME_STRLEN];
Expand Down Expand Up @@ -157,6 +159,14 @@ struct CONFIG_T {

INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];

struct {
// go-e Controller
bool GoeControllerEnabled;
bool GoeControllerPublishHomeCategory;
char GoeControllerHostname[INTEGRATIONS_GOE_MAX_HOSTNAME_STRLEN + 1];
uint32_t GoeControllerUpdateInterval;
} Integrations;
};

class ConfigurationClass {
Expand Down
23 changes: 23 additions & 0 deletions include/IntegrationsGoeController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <HTTPClient.h>
#include "NetworkSettings.h"
#include <TaskSchedulerDeclarations.h>

class IntegrationsGoeControllerClass {
public:
IntegrationsGoeControllerClass();
void init(Scheduler& scheduler);

private:
void loop();
void NetworkEvent(network_event event);

Task _loopTask;

bool _networkConnected = false;
HTTPClient _http;
};

extern IntegrationsGoeControllerClass IntegrationsGoeController;
2 changes: 2 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "WebApi_eventlog.h"
#include "WebApi_firmware.h"
#include "WebApi_gridprofile.h"
#include "WebApi_integrations.h"
#include "WebApi_inverter.h"
#include "WebApi_limit.h"
#include "WebApi_maintenance.h"
Expand Down Expand Up @@ -66,6 +67,7 @@ class WebApiClass {
WebApiWebappClass _webApiWebapp;
WebApiWsConsoleClass _webApiWsConsole;
WebApiWsLiveClass _webApiWsLive;
WebApiIntegrationsClass _webApiIntegrations;
};

extern WebApiClass WebApi;
4 changes: 4 additions & 0 deletions include/WebApi_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,8 @@ enum WebApiError {

HardwareBase = 12000,
HardwarePinMappingLength,

IntegrationsBase = 13000,
IntegrationsGoeControllerHostnameLength,
IntegrationsGoeControllerUpdateInterval,
};
14 changes: 14 additions & 0 deletions include/WebApi_integrations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

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

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

private:
void onIntegrationsAdminGet(AsyncWebServerRequest* request);
void onIntegrationsAdminPost(AsyncWebServerRequest* request);
};
5 changes: 5 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@
#define LED_BRIGHTNESS 100U

#define MAX_INVERTER_LIMIT 2250

#define INTEGRATIONS_GOE_CTRL_HOSTNAME ""
#define INTEGRATIONS_GOE_CTRL_ENABLED false
#define INTEGRATIONS_GOE_CTRL_ENABLE_HOME_CATEGORY false
#define INTEGRATIONS_GOE_CTRL_UPDATE_INTERVAL 3U
12 changes: 12 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ bool ConfigurationClass::write()
}
}

JsonObject integrations = doc["integrations"].to<JsonObject>();
integrations["goe_ctrl_hostname"] = config.Integrations.GoeControllerHostname;
integrations["goe_ctrl_enabled"] = config.Integrations.GoeControllerEnabled;
integrations["goe_ctrl_publish_home_category"] = config.Integrations.GoeControllerPublishHomeCategory;
integrations["goe_ctrl_update_interval"] = config.Integrations.GoeControllerUpdateInterval;

if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false;
}
Expand Down Expand Up @@ -317,6 +323,12 @@ bool ConfigurationClass::read()
}
}

JsonObject integrations = doc["integrations"];
strlcpy(config.Integrations.GoeControllerHostname, integrations["goe_ctrl_hostname"] | INTEGRATIONS_GOE_CTRL_HOSTNAME, sizeof(config.Integrations.GoeControllerHostname));
config.Integrations.GoeControllerEnabled = integrations["goe_ctrl_enabled"] | INTEGRATIONS_GOE_CTRL_ENABLED;
config.Integrations.GoeControllerPublishHomeCategory = integrations["goe_ctrl_publish_home_category"] | INTEGRATIONS_GOE_CTRL_ENABLE_HOME_CATEGORY;
config.Integrations.GoeControllerUpdateInterval = integrations["goe_ctrl_update_interval"] | INTEGRATIONS_GOE_CTRL_UPDATE_INTERVAL;

f.close();
return true;
}
Expand Down
94 changes: 94 additions & 0 deletions src/IntegrationsGoeController.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023-2024 Thomas Basler and others
*/
#include "IntegrationsGoeController.h"
#include "Configuration.h"
#include "Datastore.h"
#include "MessageOutput.h"
#include "NetworkSettings.h"
#include <Hoymiles.h>

IntegrationsGoeControllerClass IntegrationsGoeController;

IntegrationsGoeControllerClass::IntegrationsGoeControllerClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&IntegrationsGoeControllerClass::loop, this))
{
}

void IntegrationsGoeControllerClass::init(Scheduler& scheduler)
{
using std::placeholders::_1;

NetworkSettings.onEvent(std::bind(&IntegrationsGoeControllerClass::NetworkEvent, this, _1));

scheduler.addTask(_loopTask);
_loopTask.setInterval(Configuration.get().Integrations.GoeControllerUpdateInterval * TASK_SECOND);
_loopTask.enable();
}

void IntegrationsGoeControllerClass::NetworkEvent(network_event event)
{
switch (event) {
case network_event::NETWORK_GOT_IP:
_networkConnected = true;
break;
case network_event::NETWORK_DISCONNECTED:
_networkConnected = false;
break;
default:
break;
}
}

void IntegrationsGoeControllerClass::loop()
{
const auto& integrationsConfig = Configuration.get().Integrations;

const bool reachable = Datastore.getIsAllEnabledReachable();

_loopTask.setInterval((reachable ? integrationsConfig.GoeControllerUpdateInterval : std::min(integrationsConfig.GoeControllerUpdateInterval, 5U)) * TASK_SECOND);

if (!integrationsConfig.GoeControllerEnabled) {
return;
}

if (!_networkConnected || !Hoymiles.isAllRadioIdle()) {
_loopTask.forceNextIteration();
return;
}

const auto value = reachable ? Datastore.getTotalAcPowerEnabled() : 0;

// home, grid, car, relais, solar
// ecp is an array of numbers or null: [{power}, null, null, null, {power}]
// setting the home category to the power should be configurable
// url is this: http://{hostname}/api/set?ecp=

auto url = "http://" + String(integrationsConfig.GoeControllerHostname) + "/api/set?ecp=";

url += "[";
url += integrationsConfig.GoeControllerPublishHomeCategory ? String(value) : "null";
url += ",null,null,null,";
url += value;
url += "]";

const auto timeout = std::max(2U, std::min(integrationsConfig.GoeControllerUpdateInterval-1, 3U)) * 1000U;

_http.setConnectTimeout(timeout);
_http.setTimeout(timeout);
_http.setReuse(true);
_http.begin(url);

int httpCode = _http.GET();

if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
MessageOutput.println("go-e Controller updated");
} else {
MessageOutput.printf("HTTP error: %d\n", httpCode);
}
} else {
MessageOutput.println("HTTP error");
}
}
2 changes: 1 addition & 1 deletion src/NetworkSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ void NetworkSettingsClass::applyConfig()
MessageOutput.print("existing credentials... ");
WiFi.begin();
}
MessageOutput.println("done");
MessageOutput.println("done. Connecting to " + String(Configuration.get().WiFi.Ssid));
setStaticIp();
}

Expand Down
1 change: 1 addition & 0 deletions src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ void WebApiClass::init(Scheduler& scheduler)
_webApiWebapp.init(_server, scheduler);
_webApiWsConsole.init(_server, scheduler);
_webApiWsLive.init(_server, scheduler);
_webApiIntegrations.init(_server, scheduler);

_server.begin();
}
Expand Down
90 changes: 90 additions & 0 deletions src/WebApi_integrations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_integrations.h"
#include "Configuration.h"
#include "WebApi.h"
#include "WebApi_errors.h"
#include "helper.h"
#include <AsyncJson.h>

void WebApiIntegrationsClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;

server.on("/api/integrations/config", HTTP_GET, std::bind(&WebApiIntegrationsClass::onIntegrationsAdminGet, this, _1));
server.on("/api/integrations/config", HTTP_POST, std::bind(&WebApiIntegrationsClass::onIntegrationsAdminPost, this, _1));
}

void WebApiIntegrationsClass::onIntegrationsAdminGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {
return;
}

AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get();

root["goe_ctrl_hostname"] = config.Integrations.GoeControllerHostname;
root["goe_ctrl_enabled"] = config.Integrations.GoeControllerEnabled;
root["goe_ctrl_publish_home_category"] = config.Integrations.GoeControllerPublishHomeCategory;
root["goe_ctrl_update_interval"] = config.Integrations.GoeControllerUpdateInterval;

WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
}

void WebApiIntegrationsClass::onIntegrationsAdminPost(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {
return;
}

AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}

auto& retMsg = response->getRoot();

if (!(root["goe_ctrl_hostname"].is<String>()
&& root["goe_ctrl_enabled"].is<bool>()
&& root["goe_ctrl_publish_home_category"].is<bool>()
&& root["goe_ctrl_update_interval"].is<uint32_t>())) {
retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing;
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
return;
}

if (root["goe_ctrl_enabled"].as<bool>()) {
if (root["goe_ctrl_hostname"].as<String>().length() == 0 || root["goe_ctrl_hostname"].as<String>().length() > INTEGRATIONS_GOE_MAX_HOSTNAME_STRLEN) {
retMsg["message"] = "go-e Controller hostname must between 1 and " STR(INTEGRATIONS_GOE_MAX_HOSTNAME_STRLEN) " characters long!";
retMsg["code"] = WebApiError::IntegrationsGoeControllerHostnameLength;
retMsg["param"]["max"] = INTEGRATIONS_GOE_MAX_HOSTNAME_STRLEN;
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
return;
}

if (root["goe_ctrl_update_interval"].as<uint32_t>() < 3 || root["goe_ctrl_update_interval"].as<uint32_t>() > 65535) {
retMsg["message"] = "go-e Controller update interval must between 3 and 65535!";
retMsg["code"] = WebApiError::IntegrationsGoeControllerUpdateInterval;
retMsg["param"]["min"] = 3;
retMsg["param"]["max"] = 65535;
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
return;
}
}

CONFIG_T& config = Configuration.get();
strlcpy(config.Integrations.GoeControllerHostname, root["goe_ctrl_hostname"].as<String>().c_str(), sizeof(config.Integrations.GoeControllerHostname));
config.Integrations.GoeControllerEnabled = root["goe_ctrl_enabled"].as<bool>();
config.Integrations.GoeControllerPublishHomeCategory = root["goe_ctrl_publish_home_category"].as<bool>();
config.Integrations.GoeControllerUpdateInterval = root["goe_ctrl_update_interval"].as<uint32_t>();

WebApi.writeConfig(retMsg);

WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
}
6 changes: 6 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Configuration.h"
#include "Datastore.h"
#include "Display_Graphic.h"
#include "IntegrationsGoeController.h"
#include "InverterSettings.h"
#include "Led_Single.h"
#include "MessageOutput.h"
Expand Down Expand Up @@ -120,6 +121,11 @@ void setup()
MqttHandleHass.init(scheduler);
MessageOutput.println("done");

// Initialize go-e Integration
MessageOutput.print("Initialize go-e Integration... ");
IntegrationsGoeController.init(scheduler);
MessageOutput.println("done");

// Initialize WebApi
MessageOutput.print("Initialize WebApi... ");
WebApi.init(scheduler);
Expand Down
1 change: 1 addition & 0 deletions webapp/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.9.0
3 changes: 3 additions & 0 deletions webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
"vite-plugin-css-injected-by-js": "^3.5.2",
"vue-tsc": "^2.1.6"
},
"engines": {
"node": ">=21.1.0"
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
2 changes: 1 addition & 1 deletion webapp/src/components/InputElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:class="[wide ? 'col-sm-4' : 'col-sm-2', isCheckbox ? 'form-check-label' : 'col-form-label']"
>
{{ label }}
<BIconInfoCircle v-if="tooltip !== undefined" v-tooltip :title="tooltip" />
<BIconInfoCircle v-if="tooltip !== undefined" v-tooltip :title="tooltip" class="ms-1" />
</label>
<div :class="[wide ? 'col-sm-8' : 'col-sm-10']">
<div v-if="!isTextarea" :class="{ 'form-check form-switch': isCheckbox, 'input-group': postfix || prefix }">
Expand Down
5 changes: 5 additions & 0 deletions webapp/src/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
$t('menu.DeviceManager')
}}</router-link>
</li>
<li>
<router-link @click="onClick" class="dropdown-item" to="/settings/integrations">{{
$t('menu.Integrations')
}}</router-link>
</li>
<li>
<hr class="dropdown-divider" />
</li>
Expand Down
Loading

0 comments on commit 4239a0d

Please sign in to comment.