First version
This commit is contained in:
parent
217fddf405
commit
c7c1506e42
@ -73,7 +73,10 @@ struct CONFIG_T {
|
||||
char Mqtt_Hass_Topic[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
bool Mqtt_Hass_IndividualPanels;
|
||||
bool Mqtt_Tls;
|
||||
char Mqtt_RootCaCert[MQTT_MAX_ROOT_CA_CERT_STRLEN + 1];
|
||||
char Mqtt_RootCaCert[MQTT_MAX_ROOT_CA_CERT_STRLEN +1];
|
||||
|
||||
bool Vedirect_Enabled;
|
||||
bool Vedirect_UpdatesOnly;
|
||||
|
||||
char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1];
|
||||
};
|
||||
|
||||
27
include/MqttVedirectPublishing.h
Normal file
27
include/MqttVedirectPublishing.h
Normal file
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "Configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
|
||||
#ifndef VICTRON_PIN_RX
|
||||
#define VICTRON_PIN_RX 22
|
||||
#endif
|
||||
|
||||
#ifndef VICTRON_PIN_TX
|
||||
#define VICTRON_PIN_TX 21
|
||||
#endif
|
||||
|
||||
class MqttVedirectPublishingClass {
|
||||
public:
|
||||
void init();
|
||||
void loop();
|
||||
private:
|
||||
std::map<String, String> _kv_map;
|
||||
VeDirectFrameHandler _myve;
|
||||
uint32_t _lastPublish;
|
||||
};
|
||||
|
||||
extern MqttVedirectPublishingClass MqttVedirectPublishing;
|
||||
@ -12,6 +12,7 @@
|
||||
#include "WebApi_sysstatus.h"
|
||||
#include "WebApi_webapp.h"
|
||||
#include "WebApi_ws_live.h"
|
||||
#include "WebApi_vedirect.h"
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
class WebApiClass {
|
||||
@ -35,6 +36,7 @@ private:
|
||||
WebApiSysstatusClass _webApiSysstatus;
|
||||
WebApiWebappClass _webApiWebapp;
|
||||
WebApiWsLiveClass _webApiWsLive;
|
||||
WebApiVedirectClass _webApiVedirect;
|
||||
};
|
||||
|
||||
extern WebApiClass WebApi;
|
||||
17
include/WebApi_vedirect.h
Normal file
17
include/WebApi_vedirect.h
Normal file
@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
class WebApiVedirectClass {
|
||||
public:
|
||||
void init(AsyncWebServer* server);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void onVedirectStatus(AsyncWebServerRequest* request);
|
||||
void onVedirectAdminGet(AsyncWebServerRequest* request);
|
||||
void onVedirectAdminPost(AsyncWebServerRequest* request);
|
||||
|
||||
AsyncWebServer* _server;
|
||||
};
|
||||
@ -75,3 +75,6 @@
|
||||
#define MQTT_HASS_RETAIN true
|
||||
#define MQTT_HASS_TOPIC "homeassistant/"
|
||||
#define MQTT_HASS_INDIVIDUALPANELS false
|
||||
|
||||
#define VEDIRECT_ENABLED false
|
||||
#define VEDIRECT_UPDATESONLY true
|
||||
BIN
lib/.DS_Store
vendored
Normal file
BIN
lib/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
lib/Hoymiles/.DS_Store
vendored
Normal file
BIN
lib/Hoymiles/.DS_Store
vendored
Normal file
Binary file not shown.
210
lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp
Normal file
210
lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
/* framehandler.cpp
|
||||
*
|
||||
* Arduino library to read from Victron devices using VE.Direct protocol.
|
||||
* Derived from Victron framehandler reference implementation.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Victron Energy BV
|
||||
* Portions Copyright (C) 2020 Chris Terwilliger
|
||||
* https://github.com/cterwilliger/VeDirectFrameHandler
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* 2020.05.05 - 0.2 - initial release
|
||||
* 2020.06.21 - 0.2 - add MIT license, no code changes
|
||||
* 2020.08.20 - 0.3 - corrected #include reference
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "VeDirectFrameHandler.h"
|
||||
|
||||
char MODULE[] = "VE.Frame"; // Victron seems to use this to find out where logging messages were generated
|
||||
|
||||
// The name of the record that contains the checksum.
|
||||
static constexpr char checksumTagName[] = "CHECKSUM";
|
||||
|
||||
VeDirectFrameHandler::VeDirectFrameHandler() :
|
||||
//mStop(false), // don't know what Victron uses this for, not using
|
||||
veName(),
|
||||
veValue(),
|
||||
frameIndex(0),
|
||||
veEnd(0),
|
||||
mState(IDLE),
|
||||
mChecksum(0),
|
||||
mTextPointer(0),
|
||||
tempName(),
|
||||
tempValue()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* rxData
|
||||
* This function is called by the application which passes a byte of serial data
|
||||
* It is unchanged from Victron's example code
|
||||
*/
|
||||
void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
||||
{
|
||||
//if (mStop) return;
|
||||
if ( (inbyte == ':') && (mState != CHECKSUM) ) {
|
||||
mState = RECORD_HEX;
|
||||
}
|
||||
if (mState != RECORD_HEX) {
|
||||
mChecksum += inbyte;
|
||||
}
|
||||
inbyte = toupper(inbyte);
|
||||
|
||||
switch(mState) {
|
||||
case IDLE:
|
||||
/* wait for \n of the start of an record */
|
||||
switch(inbyte) {
|
||||
case '\n':
|
||||
mState = RECORD_BEGIN;
|
||||
break;
|
||||
case '\r': /* Skip */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case RECORD_BEGIN:
|
||||
mTextPointer = mName;
|
||||
*mTextPointer++ = inbyte;
|
||||
mState = RECORD_NAME;
|
||||
break;
|
||||
case RECORD_NAME:
|
||||
// The record name is being received, terminated by a \t
|
||||
switch(inbyte) {
|
||||
case '\t':
|
||||
// the Checksum record indicates a EOR
|
||||
if ( mTextPointer < (mName + sizeof(mName)) ) {
|
||||
*mTextPointer = 0; /* Zero terminate */
|
||||
if (strcmp(mName, checksumTagName) == 0) {
|
||||
mState = CHECKSUM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mTextPointer = mValue; /* Reset value pointer */
|
||||
mState = RECORD_VALUE;
|
||||
break;
|
||||
default:
|
||||
// add byte to name, but do no overflow
|
||||
if ( mTextPointer < (mName + sizeof(mName)) )
|
||||
*mTextPointer++ = inbyte;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case RECORD_VALUE:
|
||||
// The record value is being received. The \r indicates a new record.
|
||||
switch(inbyte) {
|
||||
case '\n':
|
||||
// forward record, only if it could be stored completely
|
||||
if ( mTextPointer < (mValue + sizeof(mValue)) ) {
|
||||
*mTextPointer = 0; // make zero ended
|
||||
textRxEvent(mName, mValue);
|
||||
}
|
||||
mState = RECORD_BEGIN;
|
||||
break;
|
||||
case '\r': /* Skip */
|
||||
break;
|
||||
default:
|
||||
// add byte to value, but do no overflow
|
||||
if ( mTextPointer < (mValue + sizeof(mValue)) )
|
||||
*mTextPointer++ = inbyte;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CHECKSUM:
|
||||
{
|
||||
bool valid = mChecksum == 0;
|
||||
if (!valid)
|
||||
logE(MODULE,"[CHECKSUM] Invalid frame");
|
||||
mChecksum = 0;
|
||||
mState = IDLE;
|
||||
frameEndEvent(valid);
|
||||
break;
|
||||
}
|
||||
case RECORD_HEX:
|
||||
if (hexRxEvent(inbyte)) {
|
||||
mChecksum = 0;
|
||||
mState = IDLE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* textRxEvent
|
||||
* This function is called every time a new name/value is successfully parsed. It writes the values to the temporary buffer.
|
||||
*/
|
||||
void VeDirectFrameHandler::textRxEvent(char * mName, char * mValue) {
|
||||
strcpy(tempName[frameIndex], mName); // copy name to temporary buffer
|
||||
strcpy(tempValue[frameIndex], mValue); // copy value to temporary buffer
|
||||
frameIndex++;
|
||||
}
|
||||
|
||||
/*
|
||||
* frameEndEvent
|
||||
* This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line.
|
||||
* If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry
|
||||
* is created in the public buffer.
|
||||
*/
|
||||
void VeDirectFrameHandler::frameEndEvent(bool valid) {
|
||||
if ( valid ) {
|
||||
for ( int i = 0; i < frameIndex; i++ ) { // read each name already in the temp buffer
|
||||
bool nameExists = false;
|
||||
for ( int j = 0; j <= veEnd; j++ ) { // compare to existing names in the public buffer
|
||||
if ( strcmp(tempName[i], veName[j]) == 0 ) {
|
||||
strcpy(veValue[j], tempValue[i]); // overwrite tempValue in the public buffer
|
||||
nameExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !nameExists ) {
|
||||
strcpy(veName[veEnd], tempName[i]); // write new Name to public buffer
|
||||
strcpy(veValue[veEnd], tempValue[i]); // write new Value to public buffer
|
||||
veEnd++; // increment end of public buffer
|
||||
if ( veEnd >= buffLen ) { // stop any buffer overrun
|
||||
veEnd = buffLen - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
frameIndex = 0; // reset frame
|
||||
}
|
||||
|
||||
/*
|
||||
* logE
|
||||
* This function included for continuity and possible future use.
|
||||
*/
|
||||
void VeDirectFrameHandler::logE(const char * module, const char * error) {
|
||||
Serial.print("MODULE: ");
|
||||
Serial.println(module);
|
||||
Serial.print("ERROR: ");
|
||||
Serial.println(error);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* hexRxEvent
|
||||
* This function included for continuity and possible future use.
|
||||
*/
|
||||
bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
|
||||
return true; // stubbed out for future
|
||||
}
|
||||
63
lib/VeDirectFrameHandler/VeDirectFrameHandler.h
Normal file
63
lib/VeDirectFrameHandler/VeDirectFrameHandler.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* frameHandler.h
|
||||
*
|
||||
* Arduino library to read from Victron devices using VE.Direct protocol.
|
||||
* Derived from Victron framehandler reference implementation.
|
||||
*
|
||||
* 2020.05.05 - 0.2 - initial release
|
||||
* 2021.02.23 - 0.3 - change frameLen to 22 per VE.Direct Protocol version 3.30
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FRAMEHANDLER_H_
|
||||
#define FRAMEHANDLER_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const byte frameLen = 22; // VE.Direct Protocol: max frame size is 18
|
||||
const byte nameLen = 9; // VE.Direct Protocol: max name size is 9 including /0
|
||||
const byte valueLen = 33; // VE.Direct Protocol: max value size is 33 including /0
|
||||
const byte buffLen = 40; // Maximum number of lines possible from the device. Current protocol shows this to be the BMV700 at 33 lines.
|
||||
|
||||
|
||||
class VeDirectFrameHandler {
|
||||
|
||||
public:
|
||||
VeDirectFrameHandler();
|
||||
void rxData(uint8_t inbyte); // byte of serial data to be passed by the application
|
||||
|
||||
char veName[buffLen][nameLen] = { }; // public buffer for received names
|
||||
char veValue[buffLen][valueLen] = { }; // public buffer for received values
|
||||
|
||||
int frameIndex; // which line of the frame are we on
|
||||
int veEnd; // current size (end) of the public buffer
|
||||
|
||||
private:
|
||||
//bool mStop; // not sure what Victron uses this for, not using
|
||||
|
||||
enum States { // state machine
|
||||
IDLE,
|
||||
RECORD_BEGIN,
|
||||
RECORD_NAME,
|
||||
RECORD_VALUE,
|
||||
CHECKSUM,
|
||||
RECORD_HEX
|
||||
};
|
||||
|
||||
int mState; // current state
|
||||
|
||||
uint8_t mChecksum; // checksum value
|
||||
|
||||
char * mTextPointer; // pointer to the private buffer we're writing to, name or value
|
||||
|
||||
char mName[9]; // buffer for the field name
|
||||
char mValue[33]; // buffer for the field value
|
||||
char tempName[frameLen][nameLen]; // private buffer for received names
|
||||
char tempValue[frameLen][valueLen]; // private buffer for received values
|
||||
|
||||
void textRxEvent(char *, char *);
|
||||
void frameEndEvent(bool);
|
||||
void logE(const char *, const char *);
|
||||
bool hexRxEvent(uint8_t);
|
||||
};
|
||||
|
||||
#endif // FRAMEHANDLER_H_
|
||||
@ -34,13 +34,11 @@ monitor_filters = time, colorize, log2file, esp32_exception_decoder
|
||||
monitor_speed = 115200
|
||||
upload_protocol = esptool
|
||||
|
||||
|
||||
[env:generic]
|
||||
board = esp32dev
|
||||
monitor_port = COM4
|
||||
upload_port = COM4
|
||||
|
||||
|
||||
[env:olimex_esp32_poe]
|
||||
; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware
|
||||
|
||||
@ -57,9 +55,7 @@ build_flags = ${env.build_flags}
|
||||
monitor_port = COM3
|
||||
upload_port = COM3
|
||||
|
||||
|
||||
[env:d1 mini esp32]
|
||||
board = wemos_d1_mini32
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-DHOYMILES_PIN_MISO=19
|
||||
@ -68,6 +64,8 @@ build_flags =
|
||||
-DHOYMILES_PIN_IRQ=16
|
||||
-DHOYMILES_PIN_CE=17
|
||||
-DHOYMILES_PIN_CS=5
|
||||
|
||||
-DVICTRON_PIN_TX=21
|
||||
-DVICTRON_PIN_RX=22
|
||||
board = wemos_d1_mini32
|
||||
monitor_port = /dev/cu.usbserial-01E68DD0
|
||||
upload_port = /dev/cu.usbserial-01E68DD0
|
||||
|
||||
@ -56,6 +56,9 @@ void ConfigurationClass::init()
|
||||
config.Mqtt_Hass_Retain = MQTT_HASS_RETAIN;
|
||||
strlcpy(config.Mqtt_Hass_Topic, MQTT_TOPIC, sizeof(config.Mqtt_Hass_Topic));
|
||||
config.Mqtt_Hass_IndividualPanels = MQTT_HASS_INDIVIDUALPANELS;
|
||||
|
||||
config.Vedirect_Enabled = VEDIRECT_ENABLED;
|
||||
config.Vedirect_UpdatesOnly = VEDIRECT_UPDATESONLY;
|
||||
}
|
||||
|
||||
bool ConfigurationClass::write()
|
||||
@ -145,6 +148,8 @@ void ConfigurationClass::migrate()
|
||||
if (config.Cfg_Version < 0x00011300) {
|
||||
config.Mqtt_Tls = MQTT_TLS;
|
||||
strlcpy(config.Mqtt_RootCaCert, MQTT_ROOT_CA_CERT, sizeof(config.Mqtt_RootCaCert));
|
||||
config.Vedirect_Enabled = VEDIRECT_ENABLED;
|
||||
config.Vedirect_UpdatesOnly = VEDIRECT_UPDATESONLY;
|
||||
}
|
||||
|
||||
if (config.Cfg_Version < 0x00011400) {
|
||||
|
||||
74
src/MqttVedirectPublishing.cpp
Normal file
74
src/MqttVedirectPublishing.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Helge Erbe and others
|
||||
*/
|
||||
#include "MqttVedirectPublishing.h"
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "MqttSettings.h"
|
||||
|
||||
|
||||
|
||||
MqttVedirectPublishingClass MqttVedirectPublishing;
|
||||
|
||||
void MqttVedirectPublishingClass::init()
|
||||
{
|
||||
Serial2.begin(19200, SERIAL_8N1, VICTRON_PIN_RX, VICTRON_PIN_TX);
|
||||
Serial2.flush();
|
||||
}
|
||||
|
||||
void MqttVedirectPublishingClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!MqttSettings.getConnected() && !config.Vedirect_Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
|
||||
String key;
|
||||
String value;
|
||||
bool bChanged;
|
||||
|
||||
while ( Serial2.available() ) {
|
||||
_myve.rxData(Serial2.read());
|
||||
}
|
||||
yield();
|
||||
|
||||
String topic = "";
|
||||
for ( int i = 0; i < _myve.veEnd; i++ ) {
|
||||
key = _myve.veName[i];
|
||||
value = _myve.veValue[i];
|
||||
|
||||
// just for debug
|
||||
Serial.print(key.c_str());
|
||||
Serial.print("= ");
|
||||
Serial.println(value.c_str());
|
||||
|
||||
// Add new key, value pairs to map and update changed values.
|
||||
// Mark changed values
|
||||
auto a = _kv_map.find(key);
|
||||
if (a != _kv_map.end()) {
|
||||
if (_kv_map[key] == value) {
|
||||
bChanged = false;
|
||||
}
|
||||
else {
|
||||
_kv_map[key] = value;
|
||||
bChanged = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_kv_map.insert(std::make_pair(key, value));
|
||||
bChanged = true;
|
||||
}
|
||||
|
||||
// publish only changed key, values pairs
|
||||
if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) {
|
||||
topic = "victron/";
|
||||
topic.concat(key);
|
||||
topic.replace("#",""); // # is a no go in mqtt topic
|
||||
MqttSettings.publish(topic.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
_lastPublish = millis();
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,7 @@ void WebApiClass::init()
|
||||
_webApiSysstatus.init(&_server);
|
||||
_webApiWebapp.init(&_server);
|
||||
_webApiWsLive.init(&_server);
|
||||
_webApiVedirect.init(&_server);
|
||||
|
||||
_server.begin();
|
||||
}
|
||||
@ -47,6 +48,7 @@ void WebApiClass::loop()
|
||||
_webApiSysstatus.loop();
|
||||
_webApiWebapp.loop();
|
||||
_webApiWsLive.loop();
|
||||
_webApiVedirect.loop();
|
||||
}
|
||||
|
||||
WebApiClass WebApi;
|
||||
103
src/WebApi_vedirect.cpp
Normal file
103
src/WebApi_vedirect.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_vedirect.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "helper.h"
|
||||
|
||||
void WebApiVedirectClass::init(AsyncWebServer* server)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
|
||||
_server = server;
|
||||
|
||||
_server->on("/api/vedirect/status", HTTP_GET, std::bind(&WebApiVedirectClass::onVedirectStatus, this, _1));
|
||||
_server->on("/api/vedirect/config", HTTP_GET, std::bind(&WebApiVedirectClass::onVedirectAdminGet, this, _1));
|
||||
_server->on("/api/vedirect/config", HTTP_POST, std::bind(&WebApiVedirectClass::onVedirectAdminPost, this, _1));
|
||||
}
|
||||
|
||||
void WebApiVedirectClass::loop()
|
||||
{
|
||||
}
|
||||
|
||||
void WebApiVedirectClass::onVedirectStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("vedirect_enabled")] = config.Vedirect_Enabled;
|
||||
root[F("vedirect_updatesonly")] = config.Vedirect_UpdatesOnly;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebApiVedirectClass::onVedirectAdminGet(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("vedirect_enabled")] = config.Vedirect_Enabled;
|
||||
root[F("vedirect_updatesonly")] = config.Vedirect_UpdatesOnly;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("vedirect_enabled"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
config.Vedirect_Enabled = root[F("vedirect_enabled")].as<bool>();
|
||||
config.Vedirect_UpdatesOnly = root[F("vedirect_updatesonly")].as<bool>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
#include "Hoymiles.h"
|
||||
#include "MqttHassPublishing.h"
|
||||
#include "MqttPublishing.h"
|
||||
#include "MqttVedirectPublishing.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NtpSettings.h"
|
||||
#include "WebApi.h"
|
||||
@ -68,6 +69,7 @@ void setup()
|
||||
Serial.print(F("Initialize MqTT... "));
|
||||
MqttSettings.init();
|
||||
MqttPublishing.init();
|
||||
MqttVedirectPublishing.init();
|
||||
MqttHassPublishing.init();
|
||||
Serial.println(F("done"));
|
||||
|
||||
@ -106,6 +108,8 @@ void loop()
|
||||
yield();
|
||||
MqttPublishing.loop();
|
||||
yield();
|
||||
MqttVedirectPublishing.loop();
|
||||
yield();
|
||||
MqttHassPublishing.loop();
|
||||
yield();
|
||||
WebApi.loop();
|
||||
|
||||
@ -32,6 +32,9 @@
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/dtu">DTU Settings</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/vedirect">Ve.direct Settings</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider" />
|
||||
@ -59,6 +62,9 @@
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/info/mqtt">MqTT</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/info/vedirect">Ve.direct</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
|
||||
107
webapp/src/components/VedirectAdminView.vue
Normal file
107
webapp/src/components/VedirectAdminView.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="container-xxl" role="main">
|
||||
<div class="page-header">
|
||||
<h1>Ve.direct Settings</h1>
|
||||
</div>
|
||||
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||
{{ alertMessage }}
|
||||
</BootstrapAlert>
|
||||
|
||||
<div class="text-center" v-if="dataLoading">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="!dataLoading">
|
||||
<form @submit="saveVedirectConfig">
|
||||
<div class="card">
|
||||
<div class="card-header text-white bg-primary">Ve.direct Configuration</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-4 form-check-label" for="inputVedirect">Enable Ve.direct</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="inputVedirect"
|
||||
v-model="vedirectConfigList.vedirect_enabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3" v-show="vedirectConfigList.vedirect_enabled">
|
||||
<label class="col-sm-2 form-check-label" for="inputTls">Send only updates</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="inputTls"
|
||||
v-model="vedirectConfigList.vedirect_updatesonly" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-3">Save</button>
|
||||
</form>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BootstrapAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataLoading: true,
|
||||
vedirectConfigList: {
|
||||
vedirect_enabled: false,
|
||||
vedirect_updatesonly: true,
|
||||
},
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getVedirectConfig();
|
||||
},
|
||||
methods: {
|
||||
getVedirectConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/vedirect/config")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.vedirectConfigList = data;
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
saveVedirectConfig(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(this.vedirectConfigList));
|
||||
|
||||
fetch("/api/vedirect/config", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
})
|
||||
.then(function (response) {
|
||||
if (response.status != 200) {
|
||||
throw response.status;
|
||||
} else {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
this.alertType = response.type;
|
||||
this.showAlert = true;
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
77
webapp/src/components/VedirectInfoView.vue
Normal file
77
webapp/src/components/VedirectInfoView.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="container-xxl" role="main">
|
||||
<div class="page-header">
|
||||
<h1>Ve.direct Info</h1>
|
||||
</div>
|
||||
|
||||
<div class="text-center" v-if="dataLoading">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!dataLoading">
|
||||
<div class="card">
|
||||
<div class="card-header text-white bg-primary">Configuration Summary</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td class="badge" :class="{
|
||||
'bg-danger': !vedirectDataList.vedirect_enabled,
|
||||
'bg-success': vedirectDataList.vedirect_enabled,
|
||||
}">
|
||||
<span v-if="vedirectDataList.vedirect_enabled">enabled</span>
|
||||
<span v-else>disabled</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="vedirectDataList.vedirect_enabled">
|
||||
<th>Updates Only</th>
|
||||
<td class="badge" :class="{
|
||||
'bg-danger': !vedirectDataList.vedirect_updatesonly,
|
||||
'bg-success': vedirectDataList.vedirect_updatesonly,
|
||||
}">
|
||||
<span v-if="vedirectDataList.vedirect_updatesonly">enabled</span>
|
||||
<span v-else>disabled</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
dataLoading: true,
|
||||
vedirectDataList: {
|
||||
vedirect_enabled: false,
|
||||
vedirect_updatesonly: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getVedirectInfo();
|
||||
},
|
||||
methods: {
|
||||
getVedirectInfo() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/vedirect/status")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.vedirectDataList = data;
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -11,6 +11,8 @@ import MqttInfoView from '@/components/MqttInfoView.vue'
|
||||
import InverterAdminView from '@/components/InverterAdminView.vue'
|
||||
import DtuAdminView from '@/components/DtuAdminView.vue'
|
||||
import FirmwareUpgradeView from '@/components/FirmwareUpgradeView.vue'
|
||||
import VedirectAdminView from '@/components/VedirectAdminView.vue'
|
||||
import VedirectInfoView from '@/components/VedirectInfoView.vue'
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
@ -43,6 +45,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'MqTT',
|
||||
component: MqttInfoView
|
||||
},
|
||||
{
|
||||
path: '/info/vedirect',
|
||||
name: 'Ve.direct',
|
||||
component: VedirectInfoView
|
||||
},
|
||||
{
|
||||
path: '/settings/network',
|
||||
name: 'Network Settings',
|
||||
@ -53,6 +60,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'NTP Settings',
|
||||
component: NtpAdminView
|
||||
},
|
||||
{
|
||||
path: '/settings/vedirect',
|
||||
name: 'Ve.direct Settings',
|
||||
component: VedirectAdminView
|
||||
},
|
||||
{
|
||||
path: '/settings/mqtt',
|
||||
name: 'MqTT Settings',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user