OpenDTU-old/lib/VeDirectFrameHandler/VeDirectFrameHandler.h
Bernhard Kirchen 9995c1172e
VE.Direct live view enhancements (#269)
* add calculated values to VE.Direct data

solar current, battery output power, and the charger's efficiency can be
calculated from the values reported by the charger. the efficiency must
be taken with a grain of salt. it seems that the solar power value and
the battery output voltage/current are not always in sync. for that
reason a moving average is used to smooth out the calculated efficiency
value.

* show calculated VE.Direct values in web live view

order the values and translations similarly for the input and output,
starting with power at the top, then voltage, then current as the last
of these three.

* VE.Direct live view: use 'd' as unit for days

'd' is the SI unit symbol for days and does not need translation, which
is desirable as units are not translated throughout the project.

* refactor VE.Direct live view

* move Dynamic Power Limiter data into its own type.
* split VE.Direct data into three types: "device", "input", and
  "output". hence all input and output values are now ValueObject, which
  allows to iterate over them using a loop without typing issues.
* generate the tables with input and output values using a loop, rather
  than defining each row individually.
* localize numbers using $n (vue method), which fixes switching the
  number format (dot vs. comma) when switching the language.
* use no decimal point for power values (they are integers), three
  decimal points for kWh values (charger only reports two decimal
  places, but three are easier to read since the unit is *kilo* Wh), one
  decimal point for the efficiency, and two for voltage and current.
* update language tokens to avoid mapping JSON keys to language keys
  (use the JSON keys to access the language tokens).
* re-structure language tokes so the brief keys took over from
  VeDirectFrameHandler always make sense (nest into "input" and
  "output").
* order values similarly from top to bottom: power, then voltage, then
  current. this is following the order of the inverters' details.
* group values by type/unit (yield and max. power) and order them
  "newest" to "oldest" from top to bottom.
* increase the DynamicJsonDocument as it was too small to hold the newly
  added data.

* update webapp_dist to include VE.Direct live view refactoring
2023-06-22 21:32:20 +02:00

127 lines
5.0 KiB
C++

/* 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
* 2022.08.20 - 0.4 - changes for OpenDTU
*
*/
#pragma once
#include <Arduino.h>
#ifndef VICTRON_PIN_TX
#define VICTRON_PIN_TX 21 // HardwareSerial TX Pin
#endif
#ifndef VICTRON_PIN_RX
#define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
#endif
#define VE_MAX_NAME_LEN 9 // VE.Direct Protocol: max name size is 9 including /0
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
typedef struct {
uint16_t PID; // product id
char SER[VE_MAX_VALUE_LEN]; // serial number
char FW[VE_MAX_VALUE_LEN]; // firmware release number
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
uint8_t CS; // current state of operation e. g. OFF or Bulk
uint8_t ERR; // error code
uint32_t OR; // off reason
uint8_t MPPT; // state of MPP tracker
uint32_t HSDS; // day sequence number 1...365
int32_t P; // battery output power in W (calculated)
double V; // battery voltage in V
double I; // battery current in A
double E; // efficiency in percent (calculated, moving average)
int32_t PPV; // panel power in W
double VPV; // panel voltage in V
double IPV; // panel current in A (calculated)
double H19; // yield total kWh
double H20; // yield today kWh
int32_t H21; // maximum power today W
double H22; // yield yesterday kWh
int32_t H23; // maximum power yesterday W
} veStruct;
template<typename T, size_t WINDOW_SIZE>
class MovingAverage {
public:
MovingAverage()
: _sum(0)
, _index(0)
, _count(0) { }
void addNumber(T num) {
if (_count < WINDOW_SIZE) {
_count++;
} else {
_sum -= _window[_index];
}
_window[_index] = num;
_sum += num;
_index = (_index + 1) % WINDOW_SIZE;
}
double getAverage() const {
if (_count == 0) { return 0.0; }
return static_cast<double>(_sum) / _count;
}
private:
std::array<T, WINDOW_SIZE> _window;
T _sum;
size_t _index;
size_t _count;
};
class VeDirectFrameHandler {
public:
VeDirectFrameHandler();
void init(int8_t rx, int8_t tx); // initialize HardewareSerial
void setPollInterval(unsigned long interval); // set poll intervall in seconds
void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); // timestamp of last successful frame read
bool isDataValid(); // return true if data valid and not outdated
String getPidAsString(uint16_t pid); // product id as string
String getCsAsString(uint8_t cs); // current state as string
String getErrAsString(uint8_t err); // errer state as string
String getOrAsString(uint32_t offReason); // off reason as string
String getMpptAsString(uint8_t mppt); // state of mppt as string
veStruct veFrame{}; // public struct for received name and value pairs
private:
void setLastUpdate(); // set timestampt after successful frame read
void rxData(uint8_t inbyte); // byte of serial data
void textRxEvent(char *, char *);
void frameEndEvent(bool); // copy temp struct to public struct
void logE(const char *, const char *);
int hexRxEvent(uint8_t);
//bool mStop; // not sure what Victron uses this for, not using
int _state; // current state
int _prevState; // previous state
uint8_t _checksum; // checksum value
char * _textPointer; // pointer to the private buffer we're writing to, name or value
int _hexSize; // length of hex buffer
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
veStruct _tmpFrame{}; // private struct for received name and value pairs
MovingAverage<double, 5> _efficiency;
unsigned long _pollInterval;
unsigned long _lastPoll;
};
extern VeDirectFrameHandler VeDirect;