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 DEV_MAX_MAPPING_NAME_STRLEN 31
|
||||
|
||||
#define JSON_BUFFER_SIZE 6144
|
||||
|
||||
struct CHANNEL_CONFIG_T {
|
||||
@ -88,6 +90,8 @@ struct CONFIG_T {
|
||||
|
||||
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
|
||||
bool Security_AllowReadonly;
|
||||
|
||||
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
|
||||
};
|
||||
|
||||
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_eventlog.h"
|
||||
#include "WebApi_firmware.h"
|
||||
#include "WebApi_device.h"
|
||||
#include "WebApi_inverter.h"
|
||||
#include "WebApi_limit.h"
|
||||
#include "WebApi_maintenance.h"
|
||||
@ -35,6 +36,7 @@ private:
|
||||
AsyncEventSource _events;
|
||||
|
||||
WebApiConfigClass _webApiConfig;
|
||||
WebApiDeviceClass _webApiDevice;
|
||||
WebApiDevInfoClass _webApiDevInfo;
|
||||
WebApiDtuClass _webApiDtu;
|
||||
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,
|
||||
PowerSerialZero,
|
||||
PowerInvalidInverter,
|
||||
|
||||
HardwareBase = 12000,
|
||||
HardwarePinMappingLength,
|
||||
};
|
||||
@ -78,3 +78,5 @@
|
||||
#define MQTT_HASS_RETAIN true
|
||||
#define MQTT_HASS_TOPIC "homeassistant/"
|
||||
#define MQTT_HASS_INDIVIDUALPANELS false
|
||||
|
||||
#define DEV_PINMAPPING ""
|
||||
@ -80,6 +80,9 @@ bool ConfigurationClass::write()
|
||||
security["password"] = config.Security_Password;
|
||||
security["allow_readonly"] = config.Security_AllowReadonly;
|
||||
|
||||
JsonObject device = doc.createNestedObject("device");
|
||||
device["pinmapping"] = config.Dev_PinMapping;
|
||||
|
||||
JsonArray inverters = doc.createNestedArray("inverters");
|
||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||
JsonObject inv = inverters.createNestedObject();
|
||||
@ -201,6 +204,9 @@ bool ConfigurationClass::read()
|
||||
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
|
||||
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"];
|
||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||
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);
|
||||
|
||||
_webApiConfig.init(&_server);
|
||||
_webApiDevice.init(&_server);
|
||||
_webApiDevInfo.init(&_server);
|
||||
_webApiDtu.init(&_server);
|
||||
_webApiEventlog.init(&_server);
|
||||
@ -42,6 +43,7 @@ void WebApiClass::init()
|
||||
void WebApiClass::loop()
|
||||
{
|
||||
_webApiConfig.loop();
|
||||
_webApiDevice.loop();
|
||||
_webApiDevInfo.loop();
|
||||
_webApiDtu.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();
|
||||
}
|
||||
19
src/main.cpp
19
src/main.cpp
@ -10,6 +10,7 @@
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "NtpSettings.h"
|
||||
#include "PinMapping.h"
|
||||
#include "Utils.h"
|
||||
#include "WebApi.h"
|
||||
#include "defaults.h"
|
||||
@ -56,6 +57,15 @@ void setup()
|
||||
}
|
||||
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
|
||||
MessageOutput.print(F("Initialize Network... "));
|
||||
NetworkSettings.init();
|
||||
@ -96,10 +106,12 @@ void setup()
|
||||
|
||||
// Initialize inverter communication
|
||||
MessageOutput.print(F("Initialize Hoymiles interface... "));
|
||||
if (PinMapping.isValidNrf24Config()) {
|
||||
SPIClass* spiClass = new SPIClass(HSPI);
|
||||
spiClass->begin(HOYMILES_PIN_SCLK, HOYMILES_PIN_MISO, HOYMILES_PIN_MOSI, HOYMILES_PIN_CS);
|
||||
PinMapping_t& pin = PinMapping.get();
|
||||
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
|
||||
Hoymiles.setMessageOutput(&MessageOutput);
|
||||
Hoymiles.init(spiClass, HOYMILES_PIN_CE, HOYMILES_PIN_IRQ);
|
||||
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
||||
|
||||
MessageOutput.println(F(" Setting radio PA level... "));
|
||||
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
||||
@ -129,6 +141,9 @@ void setup()
|
||||
}
|
||||
}
|
||||
MessageOutput.println(F("done"));
|
||||
} else {
|
||||
MessageOutput.println(F("Invalid pin config"));
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user