Added Device Profiles
Allows the pin assignment during runtime. Pin settings will be read from a json file called "pin_mapping.json"
This commit is contained in:
parent
587de2e3be
commit
5f699f4927
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#define CHAN_MAX_NAME_STRLEN 31
|
#define CHAN_MAX_NAME_STRLEN 31
|
||||||
|
|
||||||
|
#define DEV_MAX_MAPPING_NAME_STRLEN 31
|
||||||
|
|
||||||
#define JSON_BUFFER_SIZE 6144
|
#define JSON_BUFFER_SIZE 6144
|
||||||
|
|
||||||
struct CHANNEL_CONFIG_T {
|
struct CHANNEL_CONFIG_T {
|
||||||
@ -88,6 +90,8 @@ struct CONFIG_T {
|
|||||||
|
|
||||||
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
|
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
|
||||||
bool Security_AllowReadonly;
|
bool Security_AllowReadonly;
|
||||||
|
|
||||||
|
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConfigurationClass {
|
class ConfigurationClass {
|
||||||
|
|||||||
33
include/PinMapping.h
Normal file
33
include/PinMapping.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define PINMAPPING_FILENAME "/pin_mapping.json"
|
||||||
|
|
||||||
|
#define MAPPING_NAME_STRLEN 31
|
||||||
|
|
||||||
|
struct PinMapping_t {
|
||||||
|
char name[MAPPING_NAME_STRLEN + 1];
|
||||||
|
int8_t nrf24_miso;
|
||||||
|
int8_t nrf24_mosi;
|
||||||
|
int8_t nrf24_clk;
|
||||||
|
int8_t nrf24_irq;
|
||||||
|
int8_t nrf24_en;
|
||||||
|
int8_t nrf24_cs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PinMappingClass {
|
||||||
|
public:
|
||||||
|
PinMappingClass();
|
||||||
|
bool init(const String& deviceMapping);
|
||||||
|
PinMapping_t& get();
|
||||||
|
|
||||||
|
bool isValidNrf24Config();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PinMapping_t _pinMapping;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern PinMappingClass PinMapping;
|
||||||
@ -6,6 +6,7 @@
|
|||||||
#include "WebApi_dtu.h"
|
#include "WebApi_dtu.h"
|
||||||
#include "WebApi_eventlog.h"
|
#include "WebApi_eventlog.h"
|
||||||
#include "WebApi_firmware.h"
|
#include "WebApi_firmware.h"
|
||||||
|
#include "WebApi_device.h"
|
||||||
#include "WebApi_inverter.h"
|
#include "WebApi_inverter.h"
|
||||||
#include "WebApi_limit.h"
|
#include "WebApi_limit.h"
|
||||||
#include "WebApi_maintenance.h"
|
#include "WebApi_maintenance.h"
|
||||||
@ -35,6 +36,7 @@ private:
|
|||||||
AsyncEventSource _events;
|
AsyncEventSource _events;
|
||||||
|
|
||||||
WebApiConfigClass _webApiConfig;
|
WebApiConfigClass _webApiConfig;
|
||||||
|
WebApiDeviceClass _webApiDevice;
|
||||||
WebApiDevInfoClass _webApiDevInfo;
|
WebApiDevInfoClass _webApiDevInfo;
|
||||||
WebApiDtuClass _webApiDtu;
|
WebApiDtuClass _webApiDtu;
|
||||||
WebApiEventlogClass _webApiEventlog;
|
WebApiEventlogClass _webApiEventlog;
|
||||||
|
|||||||
16
include/WebApi_device.h
Normal file
16
include/WebApi_device.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
class WebApiDeviceClass {
|
||||||
|
public:
|
||||||
|
void init(AsyncWebServer* server);
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onDeviceAdminGet(AsyncWebServerRequest* request);
|
||||||
|
void onDeviceAdminPost(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
AsyncWebServer* _server;
|
||||||
|
};
|
||||||
@ -81,4 +81,7 @@ enum WebApiError {
|
|||||||
PowerBase = 11000,
|
PowerBase = 11000,
|
||||||
PowerSerialZero,
|
PowerSerialZero,
|
||||||
PowerInvalidInverter,
|
PowerInvalidInverter,
|
||||||
|
|
||||||
|
HardwareBase = 12000,
|
||||||
|
HardwarePinMappingLength,
|
||||||
};
|
};
|
||||||
@ -78,3 +78,5 @@
|
|||||||
#define MQTT_HASS_RETAIN true
|
#define MQTT_HASS_RETAIN true
|
||||||
#define MQTT_HASS_TOPIC "homeassistant/"
|
#define MQTT_HASS_TOPIC "homeassistant/"
|
||||||
#define MQTT_HASS_INDIVIDUALPANELS false
|
#define MQTT_HASS_INDIVIDUALPANELS false
|
||||||
|
|
||||||
|
#define DEV_PINMAPPING ""
|
||||||
@ -80,6 +80,9 @@ bool ConfigurationClass::write()
|
|||||||
security["password"] = config.Security_Password;
|
security["password"] = config.Security_Password;
|
||||||
security["allow_readonly"] = config.Security_AllowReadonly;
|
security["allow_readonly"] = config.Security_AllowReadonly;
|
||||||
|
|
||||||
|
JsonObject device = doc.createNestedObject("device");
|
||||||
|
device["pinmapping"] = config.Dev_PinMapping;
|
||||||
|
|
||||||
JsonArray inverters = doc.createNestedArray("inverters");
|
JsonArray inverters = doc.createNestedArray("inverters");
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
JsonObject inv = inverters.createNestedObject();
|
JsonObject inv = inverters.createNestedObject();
|
||||||
@ -201,6 +204,9 @@ bool ConfigurationClass::read()
|
|||||||
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
|
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
|
||||||
config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
|
config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
|
||||||
|
|
||||||
|
JsonObject device = doc["device"];
|
||||||
|
strlcpy(config.Dev_PinMapping, device["pinmapping"] | DEV_PINMAPPING, sizeof(config.Dev_PinMapping));
|
||||||
|
|
||||||
JsonArray inverters = doc["inverters"];
|
JsonArray inverters = doc["inverters"];
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
JsonObject inv = inverters[i].as<JsonObject>();
|
JsonObject inv = inverters[i].as<JsonObject>();
|
||||||
|
|||||||
72
src/PinMapping.cpp
Normal file
72
src/PinMapping.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 - 2023 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "PinMapping.h"
|
||||||
|
#include "MessageOutput.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define JSON_BUFFER_SIZE 6144
|
||||||
|
|
||||||
|
PinMappingClass PinMapping;
|
||||||
|
|
||||||
|
PinMappingClass::PinMappingClass()
|
||||||
|
{
|
||||||
|
memset(&_pinMapping, 0x0, sizeof(_pinMapping));
|
||||||
|
_pinMapping.nrf24_clk = HOYMILES_PIN_SCLK;
|
||||||
|
_pinMapping.nrf24_cs = HOYMILES_PIN_CS;
|
||||||
|
_pinMapping.nrf24_en = HOYMILES_PIN_CE;
|
||||||
|
_pinMapping.nrf24_irq = HOYMILES_PIN_IRQ;
|
||||||
|
_pinMapping.nrf24_miso = HOYMILES_PIN_MISO;
|
||||||
|
_pinMapping.nrf24_mosi = HOYMILES_PIN_MOSI;
|
||||||
|
}
|
||||||
|
|
||||||
|
PinMapping_t& PinMappingClass::get()
|
||||||
|
{
|
||||||
|
return _pinMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool PinMappingClass::init(const String& deviceMapping)
|
||||||
|
{
|
||||||
|
File f = LittleFS.open(PINMAPPING_FILENAME, "r", false);
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||||
|
// Deserialize the JSON document
|
||||||
|
DeserializationError error = deserializeJson(doc, f);
|
||||||
|
if (error) {
|
||||||
|
MessageOutput.println(F("Failed to read file, using default configuration"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 1; i <= doc.size(); i++) {
|
||||||
|
String devName = doc[i]["name"] | "";
|
||||||
|
if (devName == deviceMapping) {
|
||||||
|
strlcpy(_pinMapping.name, devName.c_str(), sizeof(_pinMapping.name));
|
||||||
|
_pinMapping.nrf24_clk = doc[i]["nrf24"]["clk"] | HOYMILES_PIN_SCLK;
|
||||||
|
_pinMapping.nrf24_cs = doc[i]["nrf24"]["cs"] | HOYMILES_PIN_CS;
|
||||||
|
_pinMapping.nrf24_en = doc[i]["nrf24"]["en"] | HOYMILES_PIN_CE;
|
||||||
|
_pinMapping.nrf24_irq = doc[i]["nrf24"]["irq"] | HOYMILES_PIN_IRQ;
|
||||||
|
_pinMapping.nrf24_miso = doc[i]["nrf24"]["miso"] | HOYMILES_PIN_MISO;
|
||||||
|
_pinMapping.nrf24_mosi = doc[i]["nrf24"]["mosi"] | HOYMILES_PIN_MOSI;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinMappingClass::isValidNrf24Config()
|
||||||
|
{
|
||||||
|
return _pinMapping.nrf24_clk > 0
|
||||||
|
&& _pinMapping.nrf24_cs > 0
|
||||||
|
&& _pinMapping.nrf24_en > 0
|
||||||
|
&& _pinMapping.nrf24_irq > 0
|
||||||
|
&& _pinMapping.nrf24_miso > 0
|
||||||
|
&& _pinMapping.nrf24_mosi > 0;
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ void WebApiClass::init()
|
|||||||
_server.addHandler(&_events);
|
_server.addHandler(&_events);
|
||||||
|
|
||||||
_webApiConfig.init(&_server);
|
_webApiConfig.init(&_server);
|
||||||
|
_webApiDevice.init(&_server);
|
||||||
_webApiDevInfo.init(&_server);
|
_webApiDevInfo.init(&_server);
|
||||||
_webApiDtu.init(&_server);
|
_webApiDtu.init(&_server);
|
||||||
_webApiEventlog.init(&_server);
|
_webApiEventlog.init(&_server);
|
||||||
@ -42,6 +43,7 @@ void WebApiClass::init()
|
|||||||
void WebApiClass::loop()
|
void WebApiClass::loop()
|
||||||
{
|
{
|
||||||
_webApiConfig.loop();
|
_webApiConfig.loop();
|
||||||
|
_webApiDevice.loop();
|
||||||
_webApiDevInfo.loop();
|
_webApiDevInfo.loop();
|
||||||
_webApiDtu.loop();
|
_webApiDtu.loop();
|
||||||
_webApiEventlog.loop();
|
_webApiEventlog.loop();
|
||||||
|
|||||||
113
src/WebApi_device.cpp
Normal file
113
src/WebApi_device.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "WebApi_device.h"
|
||||||
|
#include "Configuration.h"
|
||||||
|
#include "WebApi.h"
|
||||||
|
#include "WebApi_errors.h"
|
||||||
|
#include "helper.h"
|
||||||
|
#include <AsyncJson.h>
|
||||||
|
|
||||||
|
void WebApiDeviceClass::init(AsyncWebServer* server)
|
||||||
|
{
|
||||||
|
using std::placeholders::_1;
|
||||||
|
|
||||||
|
_server = server;
|
||||||
|
|
||||||
|
_server->on("/api/device/config", HTTP_GET, std::bind(&WebApiDeviceClass::onDeviceAdminGet, this, _1));
|
||||||
|
_server->on("/api/device/config", HTTP_POST, std::bind(&WebApiDeviceClass::onDeviceAdminPost, this, _1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiDeviceClass::loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
|
root[F("dev_pinmapping")] = config.Dev_PinMapping;
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||||
|
JsonObject retMsg = response->getRoot();
|
||||||
|
retMsg[F("type")] = F("warning");
|
||||||
|
|
||||||
|
if (!request->hasParam("data", true)) {
|
||||||
|
retMsg[F("message")] = F("No values found!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
|
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||||
|
retMsg[F("message")] = F("Data too large!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicJsonDocument root(MQTT_JSON_DOC_SIZE);
|
||||||
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
retMsg[F("message")] = F("Failed to parse data!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericParseError;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(root.containsKey("dev_pinmapping"))) {
|
||||||
|
retMsg[F("message")] = F("Values are missing!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root[F("dev_pinmapping")].as<String>().length() == 0 || root[F("dev_pinmapping")].as<String>().length() > DEV_MAX_MAPPING_NAME_STRLEN) {
|
||||||
|
retMsg[F("message")] = F("Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!");
|
||||||
|
retMsg[F("code")] = WebApiError::HardwarePinMappingLength;
|
||||||
|
retMsg[F("param")][F("max")] = DEV_MAX_MAPPING_NAME_STRLEN;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_T& config = Configuration.get();
|
||||||
|
strlcpy(config.Dev_PinMapping, root[F("dev_pinmapping")].as<String>().c_str(), sizeof(config.Dev_PinMapping));
|
||||||
|
Configuration.write();
|
||||||
|
|
||||||
|
retMsg[F("type")] = F("success");
|
||||||
|
retMsg[F("message")] = F("Settings saved!");
|
||||||
|
retMsg[F("code")] = WebApiError::GenericSuccess;
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
|
||||||
|
yield();
|
||||||
|
delay(1000);
|
||||||
|
yield();
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
63
src/main.cpp
63
src/main.cpp
@ -10,6 +10,7 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "NtpSettings.h"
|
#include "NtpSettings.h"
|
||||||
|
#include "PinMapping.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
@ -56,6 +57,15 @@ void setup()
|
|||||||
}
|
}
|
||||||
MessageOutput.println(F("done"));
|
MessageOutput.println(F("done"));
|
||||||
|
|
||||||
|
// Load PinMapping
|
||||||
|
MessageOutput.print(F("Reading PinMapping... "));
|
||||||
|
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
||||||
|
MessageOutput.print(F("found valid mapping "));
|
||||||
|
} else {
|
||||||
|
MessageOutput.print(F("using default config "));
|
||||||
|
}
|
||||||
|
MessageOutput.println(F("done"));
|
||||||
|
|
||||||
// Initialize WiFi
|
// Initialize WiFi
|
||||||
MessageOutput.print(F("Initialize Network... "));
|
MessageOutput.print(F("Initialize Network... "));
|
||||||
NetworkSettings.init();
|
NetworkSettings.init();
|
||||||
@ -96,39 +106,44 @@ void setup()
|
|||||||
|
|
||||||
// Initialize inverter communication
|
// Initialize inverter communication
|
||||||
MessageOutput.print(F("Initialize Hoymiles interface... "));
|
MessageOutput.print(F("Initialize Hoymiles interface... "));
|
||||||
SPIClass* spiClass = new SPIClass(HSPI);
|
if (PinMapping.isValidNrf24Config()) {
|
||||||
spiClass->begin(HOYMILES_PIN_SCLK, HOYMILES_PIN_MISO, HOYMILES_PIN_MOSI, HOYMILES_PIN_CS);
|
SPIClass* spiClass = new SPIClass(HSPI);
|
||||||
Hoymiles.setMessageOutput(&MessageOutput);
|
PinMapping_t& pin = PinMapping.get();
|
||||||
Hoymiles.init(spiClass, HOYMILES_PIN_CE, HOYMILES_PIN_IRQ);
|
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
|
||||||
|
Hoymiles.setMessageOutput(&MessageOutput);
|
||||||
|
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
||||||
|
|
||||||
MessageOutput.println(F(" Setting radio PA level... "));
|
MessageOutput.println(F(" Setting radio PA level... "));
|
||||||
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
||||||
|
|
||||||
MessageOutput.println(F(" Setting DTU serial... "));
|
MessageOutput.println(F(" Setting DTU serial... "));
|
||||||
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
|
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
|
||||||
|
|
||||||
MessageOutput.println(F(" Setting poll interval... "));
|
MessageOutput.println(F(" Setting poll interval... "));
|
||||||
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
if (config.Inverter[i].Serial > 0) {
|
if (config.Inverter[i].Serial > 0) {
|
||||||
MessageOutput.print(F(" Adding inverter: "));
|
MessageOutput.print(F(" Adding inverter: "));
|
||||||
MessageOutput.print(config.Inverter[i].Serial, HEX);
|
MessageOutput.print(config.Inverter[i].Serial, HEX);
|
||||||
MessageOutput.print(F(" - "));
|
MessageOutput.print(F(" - "));
|
||||||
MessageOutput.print(config.Inverter[i].Name);
|
MessageOutput.print(config.Inverter[i].Name);
|
||||||
auto inv = Hoymiles.addInverter(
|
auto inv = Hoymiles.addInverter(
|
||||||
config.Inverter[i].Name,
|
config.Inverter[i].Name,
|
||||||
config.Inverter[i].Serial);
|
config.Inverter[i].Serial);
|
||||||
|
|
||||||
if (inv != nullptr) {
|
if (inv != nullptr) {
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
inv->Statistics()->setChannelMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
|
inv->Statistics()->setChannelMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MessageOutput.println(F(" done"));
|
||||||
}
|
}
|
||||||
MessageOutput.println(F(" done"));
|
|
||||||
}
|
}
|
||||||
|
MessageOutput.println(F("done"));
|
||||||
|
} else {
|
||||||
|
MessageOutput.println(F("Invalid pin config"));
|
||||||
}
|
}
|
||||||
MessageOutput.println(F("done"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user