Skip to content

Commit

Permalink
Feature: First very basic support to read the grid profile
Browse files Browse the repository at this point in the history
The parser is still missing and requires community support to collect data.
  • Loading branch information
tbnobody committed Sep 7, 2023
1 parent 1acefd8 commit 9ac6dd6
Show file tree
Hide file tree
Showing 20 changed files with 379 additions and 4 deletions.
2 changes: 2 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "WebApi_dtu.h"
#include "WebApi_eventlog.h"
#include "WebApi_firmware.h"
#include "WebApi_gridprofile.h"
#include "WebApi_inverter.h"
#include "WebApi_limit.h"
#include "WebApi_maintenance.h"
Expand Down Expand Up @@ -43,6 +44,7 @@ class WebApiClass {
WebApiDtuClass _webApiDtu;
WebApiEventlogClass _webApiEventlog;
WebApiFirmwareClass _webApiFirmware;
WebApiGridProfileClass _webApiGridprofile;
WebApiInverterClass _webApiInverter;
WebApiLimitClass _webApiLimit;
WebApiMaintenanceClass _webApiMaintenance;
Expand Down
15 changes: 15 additions & 0 deletions include/WebApi_gridprofile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>

class WebApiGridProfileClass {
public:
void init(AsyncWebServer* server);
void loop();

private:
void onGridProfileStatus(AsyncWebServerRequest* request);

AsyncWebServer* _server;
};
5 changes: 5 additions & 0 deletions lib/Hoymiles/src/Hoymiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ void HoymilesClass::loop()
}
}

// Fetch grid profile
if (iv->Statistics()->getLastUpdate() > 0 && iv->GridProfile()->getLastUpdate() == 0) {
iv->sendGridOnProFileParaRequest();
}

if (++inverterPos >= getNumInverters()) {
inverterPos = 0;
}
Expand Down
40 changes: 40 additions & 0 deletions lib/Hoymiles/src/commands/GridOnProFilePara.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "GridOnProFilePara.h"
#include "Hoymiles.h"
#include "inverters/InverterAbstract.h"

GridOnProFilePara::GridOnProFilePara(uint64_t target_address, uint64_t router_address, time_t time)
: MultiDataCommand(target_address, router_address)
{
setTime(time);
setDataType(0x02);
setTimeout(500);
}

String GridOnProFilePara::getCommandName()
{
return "GridOnProFilePara";
}

bool GridOnProFilePara::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id)
{
// Check CRC of whole payload
if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) {
return false;
}

// Move all fragments into target buffer
uint8_t offs = 0;
inverter->GridProfile()->beginAppendFragment();
inverter->GridProfile()->clearBuffer();
for (uint8_t i = 0; i < max_fragment_id; i++) {
inverter->GridProfile()->appendFragment(offs, fragment[i].fragment, fragment[i].len);
offs += (fragment[i].len);
}
inverter->GridProfile()->endAppendFragment();
inverter->GridProfile()->setLastUpdate(millis());
return true;
}
13 changes: 13 additions & 0 deletions lib/Hoymiles/src/commands/GridOnProFilePara.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include "MultiDataCommand.h"

class GridOnProFilePara : public MultiDataCommand {
public:
explicit GridOnProFilePara(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0);

virtual String getCommandName();

virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id);
};
1 change: 1 addition & 0 deletions lib/Hoymiles/src/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* AlarmDataCommand
* DevInfoAllCommand
* DevInfoSimpleCommand
* GridOnProFilePara
* RealTimeRunDataCommand
* SystemConfigParaCommand
* ParaSetCommand
Expand Down
25 changes: 24 additions & 1 deletion lib/Hoymiles/src/inverters/HM_Abstract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "commands/AlarmDataCommand.h"
#include "commands/DevInfoAllCommand.h"
#include "commands/DevInfoSimpleCommand.h"
#include "commands/GridOnProFilePara.h"
#include "commands/PowerControlCommand.h"
#include "commands/RealTimeRunDataCommand.h"
#include "commands/SystemConfigParaCommand.h"
Expand Down Expand Up @@ -202,4 +203,26 @@ bool HM_Abstract::resendPowerControlRequest()
return false;
break;
}
}
}

bool HM_Abstract::sendGridOnProFileParaRequest()
{
if (!getEnablePolling()) {
return false;
}

struct tm timeinfo;
if (!getLocalTime(&timeinfo, 5)) {
return false;
}

time_t now;
time(&now);

auto cmd = _radio->prepareCommand<GridOnProFilePara>();
cmd->setTime(now);
cmd->setTargetAddress(serial());
_radio->enqueCommand(cmd);

return true;
}
1 change: 1 addition & 0 deletions lib/Hoymiles/src/inverters/HM_Abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class HM_Abstract : public InverterAbstract {
bool sendPowerControlRequest(bool turnOn);
bool sendRestartControlRequest();
bool resendPowerControlRequest();
bool sendGridOnProFileParaRequest();

private:
uint8_t _lastAlarmLogCnt = 0;
Expand Down
6 changes: 6 additions & 0 deletions lib/Hoymiles/src/inverters/InverterAbstract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ InverterAbstract::InverterAbstract(HoymilesRadio* radio, uint64_t serial)

_alarmLogParser.reset(new AlarmLogParser());
_devInfoParser.reset(new DevInfoParser());
_gridProfileParser.reset(new GridProfileParser());
_powerCommandParser.reset(new PowerCommandParser());
_statisticsParser.reset(new StatisticsParser());
_systemConfigParaParser.reset(new SystemConfigParaParser());
Expand Down Expand Up @@ -146,6 +147,11 @@ DevInfoParser* InverterAbstract::DevInfo()
return _devInfoParser.get();
}

GridProfileParser* InverterAbstract::GridProfile()
{
return _gridProfileParser.get();
}

PowerCommandParser* InverterAbstract::PowerCommand()
{
return _powerCommandParser.get();
Expand Down
4 changes: 4 additions & 0 deletions lib/Hoymiles/src/inverters/InverterAbstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "../commands/ActivePowerControlCommand.h"
#include "../parser/AlarmLogParser.h"
#include "../parser/DevInfoParser.h"
#include "../parser/GridProfileParser.h"
#include "../parser/PowerCommandParser.h"
#include "../parser/StatisticsParser.h"
#include "../parser/SystemConfigParaParser.h"
Expand Down Expand Up @@ -71,11 +72,13 @@ class InverterAbstract {
virtual bool sendRestartControlRequest() = 0;
virtual bool resendPowerControlRequest() = 0;
virtual bool sendChangeChannelRequest();
virtual bool sendGridOnProFileParaRequest() = 0;

HoymilesRadio* getRadio();

AlarmLogParser* EventLog();
DevInfoParser* DevInfo();
GridProfileParser* GridProfile();
PowerCommandParser* PowerCommand();
StatisticsParser* Statistics();
SystemConfigParaParser* SystemConfigPara();
Expand All @@ -102,6 +105,7 @@ class InverterAbstract {

std::unique_ptr<AlarmLogParser> _alarmLogParser;
std::unique_ptr<DevInfoParser> _devInfoParser;
std::unique_ptr<GridProfileParser> _gridProfileParser;
std::unique_ptr<PowerCommandParser> _powerCommandParser;
std::unique_ptr<StatisticsParser> _statisticsParser;
std::unique_ptr<SystemConfigParaParser> _systemConfigParaParser;
Expand Down
57 changes: 57 additions & 0 deletions lib/Hoymiles/src/parser/GridProfileParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "GridProfileParser.h"
#include "../Hoymiles.h"
#include <cstring>

#define HOY_SEMAPHORE_TAKE() \
do { \
} while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS)
#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)

GridProfileParser::GridProfileParser()
: Parser()
{
_xSemaphore = xSemaphoreCreateMutex();
HOY_SEMAPHORE_GIVE(); // release before first use
clearBuffer();
}

void GridProfileParser::clearBuffer()
{
memset(_payloadGridProfile, 0, GRID_PROFILE_SIZE);
_gridProfileLength = 0;
}

void GridProfileParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len)
{
if (offset + len > GRID_PROFILE_SIZE) {
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) grid profile packet too large for buffer\r\n", __FILE__, __LINE__);
return;
}
memcpy(&_payloadGridProfile[offset], payload, len);
_gridProfileLength += len;
}

void GridProfileParser::beginAppendFragment()
{
HOY_SEMAPHORE_TAKE();
}

void GridProfileParser::endAppendFragment()
{
HOY_SEMAPHORE_GIVE();
}

std::vector<uint8_t> GridProfileParser::getRawData()
{
std::vector<uint8_t> ret;
HOY_SEMAPHORE_TAKE();
for (uint8_t i = 0; i < GRID_PROFILE_SIZE; i++) {
ret.push_back(_payloadGridProfile[i]);
}
HOY_SEMAPHORE_GIVE();
return ret;
}
24 changes: 24 additions & 0 deletions lib/Hoymiles/src/parser/GridProfileParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Parser.h"
#include <Arduino.h>

#define GRID_PROFILE_SIZE 141

class GridProfileParser : public Parser {
public:
GridProfileParser();
void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);

void beginAppendFragment();
void endAppendFragment();

std::vector<uint8_t> getRawData();

private:
uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {};
uint8_t _gridProfileLength = 0;

SemaphoreHandle_t _xSemaphore;
};
2 changes: 2 additions & 0 deletions src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ void WebApiClass::init()
_webApiDtu.init(&_server);
_webApiEventlog.init(&_server);
_webApiFirmware.init(&_server);
_webApiGridprofile.init(&_server);
_webApiInverter.init(&_server);
_webApiLimit.init(&_server);
_webApiMaintenance.init(&_server);
Expand All @@ -48,6 +49,7 @@ void WebApiClass::loop()
_webApiDtu.loop();
_webApiEventlog.loop();
_webApiFirmware.loop();
_webApiGridprofile.loop();
_webApiInverter.loop();
_webApiLimit.loop();
_webApiMaintenance.loop();
Expand Down
49 changes: 49 additions & 0 deletions src/WebApi_gridprofile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "WebApi_gridprofile.h"
#include "WebApi.h"
#include <AsyncJson.h>
#include <Hoymiles.h>

void WebApiGridProfileClass::init(AsyncWebServer* server)
{
using std::placeholders::_1;

_server = server;

_server->on("/api/gridprofile/status", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileStatus, this, _1));
}

void WebApiGridProfileClass::loop()
{
}

void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}

AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096);
JsonObject root = response->getRoot();

uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}

auto inv = Hoymiles.getInverterBySerial(serial);

if (inv != nullptr) {
auto raw = root.createNestedArray("raw");
auto data = inv->GridProfile()->getRawData();

copyArray(&data[0], data.size(), raw);
}

response->setLength();
request->send(response);
}
Loading

0 comments on commit 9ac6dd6

Please sign in to comment.