Skip to content

Commit

Permalink
New class for Victron Chargers and first support for sending VE.Direc…
Browse files Browse the repository at this point in the history
…t hex commands

cleanup
  • Loading branch information
philippsandhaus committed Oct 3, 2023
1 parent 7fb26e1 commit bd4744a
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 30 deletions.
14 changes: 14 additions & 0 deletions lib/VeDirectFrameHandler/VeDirectChargerController.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <Arduino.h>
#include "VeDirectChargerController.h"

/*
* setBatteryCurrentLimit
* This function sets battery current limit. Don't call it in a loop because
* Victron is writing battery current limit in non volatile memory.
*/
void VeDirectChargerController::setBatteryCurrentLimit(uint16_t batteryCurrentLimit)
{
//uint16_t veBatCurrentLimit = batteryCurrentLimit * 10;
sendHexCommand(SET,BATTERY_MAXIMUM_CURRENT,DEFAULT_FLAG0, batteryCurrentLimit * 10);
_msgOut->printf("[Victron Charger] Set Victron batteryMaximumCurrent to %dA.\r\n",batteryCurrentLimit);
}
9 changes: 9 additions & 0 deletions lib/VeDirectFrameHandler/VeDirectChargerController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <Arduino.h>
#include "VeDirectFrameHandler.h"

class VeDirectChargerController : public VeDirectFrameHandler {
public:
void setBatteryCurrentLimit(uint16_t batteryCurrentLimit);
};
123 changes: 104 additions & 19 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ enum States {
RECORD_HEX = 6
};



class Silent : public Print {
public:
size_t write(uint8_t c) final { return 0; }
Expand Down Expand Up @@ -101,6 +99,9 @@ void VeDirectFrameHandler::dumpDebugBuffer() {

void VeDirectFrameHandler::loop()
{
if (!_vedirectSerial) {
return;
}
while ( _vedirectSerial->available()) {
rxData(_vedirectSerial->read());
_lastByteMillis = millis();
Expand Down Expand Up @@ -224,7 +225,8 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
* 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 * name, char * value, veStruct& frame) {
void VeDirectFrameHandler::textRxEvent(char * name, char * value, veStruct& frame)
{
if (strcmp(name, "PID") == 0) {
frame.PID = strtol(value, nullptr, 0);
}
Expand All @@ -242,23 +244,105 @@ void VeDirectFrameHandler::textRxEvent(char * name, char * value, veStruct& fram
}
}

bool VeDirectFrameHandler::isDataValid(veStruct frame)
{
if (_lastUpdate == 0) {
return false;
}
if (strlen(frame.SER) == 0) {
return false;
}
return true;
}

unsigned long VeDirectFrameHandler::getLastUpdate()
{
return _lastUpdate;
}

static uint8_t calcHexChecksum(String message)
{
char buffer[2];
uint8_t sum=0;

for (int i=0; i < message.length(); i=i+2) {
buffer[0] = message[i] == ':' ? '0' : message[i];
buffer[1] = message[i+1];
sum += strtoul(buffer,NULL,16);
}

// calculate victron check value
// sum of command + flag + value + check = 0x55
return (0x55 - sum);
}

/*
* isHexFrameValid
* This function computes the checksum and validates a hex frame
*/
#define ascii2hex(v) (v-48-(v>='A'?7:0))
#define hex2byte(b) (ascii2hex(*(b)))*16+((ascii2hex(*(b+1))))
static bool isHexFrameValid(const char* buffer, int size)
{
uint8_t checksum=0x55-ascii2hex(buffer[1]);
for (int i=2; i<size; i+=2) checksum -= hex2byte(buffer+i);
return (checksum==0);
}

// used to convert uint8_t hex into hex string
static char HEX_MAP[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

static String uint4toHexString(uint8_t value)
{
return (String(HEX_MAP[(value & 0x0F)]));
}

static String uint8toHexString(uint8_t value)
{
return (
String(HEX_MAP[(value >> 4)])
+ String(HEX_MAP[(value & 0x0F)])
);
}

static String uint16toHexString(uint16_t value)
{
// victron uses little endian
return (
String(HEX_MAP[((value & 0xF0)>> 4)])
+ String(HEX_MAP[(value & 0x0F)])
+ String(HEX_MAP[((value)>> 12)])
+ String(HEX_MAP[((value & 0xF00)>> 8)])
);
}

/*
* hexRxEvent
* This function records hex answers or async messages
*/
int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte)
{
int ret=RECORD_HEX; // default - continue recording until end of frame

switch (inbyte) {
case '\n':
if (isHexFrameValid(_hexBuffer,_hexSize)) {
// TODO: handle hex frame here
if (_verboseLogging) _msgOut->printf("[VE.Direct] hex frame received: %.*s\r\n", _hexSize, _hexBuffer);
if (_hexBuffer[1]=='A') {
// asynchronous message
// TODO: Handle asynchronous message here
} else {
// answer to previously sent command
// TODO: Handle answer here
}
}
// restore previous state
ret=_prevState;
break;

default:
_hexSize++;
_hexBuffer[_hexSize++]=inbyte;
if (_hexSize>=VE_MAX_HEX_LEN) { // oops -buffer overflow - something went wrong, we abort
_msgOut->println("[VE.Direct] hexRx buffer overflow - aborting read");
_hexSize=0;
Expand All @@ -269,19 +353,19 @@ int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
return ret;
}

bool VeDirectFrameHandler::isDataValid(veStruct frame) {
if (_lastUpdate == 0) {
return false;
}
if (strlen(frame.SER) == 0) {
return false;
}
return true;
}

unsigned long VeDirectFrameHandler::getLastUpdate()
/*
* sendSetHexCommand
* This function uses set command to set uint16 value.
*/
void VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, VeDirectHexId id, VeDirectFlag flag, uint16_t value)
{
return _lastUpdate;
String txData = ":" + uint4toHexString(cmd);
txData += uint16toHexString(id);
txData += uint8toHexString(flag);
txData += uint16toHexString(value);
txData += uint8toHexString(calcHexChecksum(txData));
_vedirectSerial->print(txData+"\n");
if (_verboseLogging) _msgOut->println("[VE.Direct] send hex command: " + txData);
}

/*
Expand Down Expand Up @@ -503,6 +587,9 @@ String VeDirectFrameHandler::getPidAsString(uint16_t pid)
case 0XA116:
strPID = "SmartSolar MPPT VE.Can 250|85 rev2";
break;
case 0XA336:
strPID = "Blue Smart IP22 24/16";
break;
case 0xA381:
strPID = "BMV-712 Smart";
break;
Expand Down Expand Up @@ -530,8 +617,6 @@ String VeDirectFrameHandler::getPidAsString(uint16_t pid)
return strPID;
}



/*
* getErrAsString
* This function returns error state (ERR) as readable text.
Expand Down
44 changes: 35 additions & 9 deletions lib/VeDirectFrameHandler/VeDirectFrameHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,64 @@ typedef struct {
double E = 0; // efficiency in percent (calculated, moving average)
} veStruct;

// hex commands
enum VeDirectHexCommand {
ENTER_BOOT = 0x00,
PING = 0x01,
APP_VERSION = 0x02,
PRODUCT_ID = 0x04,
RESTART = 0x06,
GET = 0x07,
SET = 0x08,
ASYNC = 0x0A
};

// hex ids
enum VeDirectHexId {
BATTERY_MAXIMUM_CURRENT = 0xEDF0,
CHARGER_MAXIMUM_CURRENT = 0xEDDF,
CHARGER_CURRENT = 0xEDD7,
CHARGER_VOLTAGE = 0xEDD5,
CHARGER_ERROR_CODE = 0xEDDA,
};

enum VeDirectFlag {
DEFAULT_FLAG0 = 0x00,
};

class VeDirectFrameHandler {
public:
VeDirectFrameHandler();
void setVerboseLogging(bool verboseLogging);
virtual void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); // timestamp of last successful frame read
bool isDataValid(veStruct frame); // return true if data valid and not outdated
void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); // timestamp of last successful frame read
bool isDataValid(veStruct frame); // return true if data valid and not outdated
String getPidAsString(uint16_t pid); // product id as string
String getErrAsString(uint8_t err); // errer state as string
String getErrAsString(uint8_t err); // errer state as string

protected:
void textRxEvent(char *, char *, veStruct& );

void sendHexCommand(VeDirectHexCommand cmd, VeDirectHexId id, VeDirectFlag flag, uint16_t value);
bool _verboseLogging;
Print* _msgOut;
uint32_t _lastUpdate;

private:
void setLastUpdate(); // set timestampt after successful frame read
//void setLastUpdate(); // set timestampt after successful frame read
void dumpDebugBuffer();
void rxData(uint8_t inbyte); // byte of serial data
virtual void textRxEvent(char *, char *) = 0;
virtual void frameEndEvent(bool) = 0; // copy temp struct to public struct
virtual void frameEndEvent(bool) = 0; // copy temp struct to public struct
int hexRxEvent(uint8_t);

std::unique_ptr<HardwareSerial> _vedirectSerial;
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
int _hexSize; // length of hex buffer
char _hexBuffer[VE_MAX_HEX_LEN] = { }; // buffer for received hex frames
char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
std::array<uint8_t, 512> _debugBuffer;
Expand Down
4 changes: 2 additions & 2 deletions lib/VeDirectFrameHandler/VeDirectMpptController.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <Arduino.h>
#include "VeDirectFrameHandler.h"
#include "VeDirectChargerController.h"

template<typename T, size_t WINDOW_SIZE>
class MovingAverage {
Expand Down Expand Up @@ -35,7 +35,7 @@ class MovingAverage {
size_t _count;
};

class VeDirectMpptController : public VeDirectFrameHandler {
class VeDirectMpptController : public VeDirectChargerController {
public:
VeDirectMpptController();

Expand Down

0 comments on commit bd4744a

Please sign in to comment.