diff --git a/lib/VeDirectFrameHandler/VeDirectChargerController.cpp b/lib/VeDirectFrameHandler/VeDirectChargerController.cpp new file mode 100644 index 000000000..7250d131b --- /dev/null +++ b/lib/VeDirectFrameHandler/VeDirectChargerController.cpp @@ -0,0 +1,14 @@ +#include +#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); +} diff --git a/lib/VeDirectFrameHandler/VeDirectChargerController.h b/lib/VeDirectFrameHandler/VeDirectChargerController.h new file mode 100644 index 000000000..bf24b014f --- /dev/null +++ b/lib/VeDirectFrameHandler/VeDirectChargerController.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "VeDirectFrameHandler.h" + +class VeDirectChargerController : public VeDirectFrameHandler { +public: + void setBatteryCurrentLimit(uint16_t batteryCurrentLimit); +}; \ No newline at end of file diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index 0c0c544e6..a63b512c7 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -254,6 +254,56 @@ bool VeDirectFrameHandler::textRxEvent(std::string const& who, char* name, char* } +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> 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 @@ -264,12 +314,23 @@ int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) { 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; @@ -290,11 +351,24 @@ bool VeDirectFrameHandler::isDataValid(veStruct const& frame) const { return true; } -uint32_t VeDirectFrameHandler::getLastUpdate() const -{ +uint32_t VeDirectFrameHandler::getLastUpdate() const { return _lastUpdate; } +/* + * sendSetHexCommand + * This function uses set command to set uint16 value. + */ +void VeDirectFrameHandler::sendHexCommand(VeDirectHexCommand cmd, VeDirectHexId id, VeDirectFlag flag, uint16_t value) { + 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); +} + template String const& VeDirectFrameHandler::getAsString(std::map const& values, T val) { @@ -311,12 +385,12 @@ template String const& VeDirectFrameHandler::getAsString(std::map const& values, uint16_t val); template String const& VeDirectFrameHandler::getAsString(std::map const& values, uint32_t val); + /* * getPidAsString * This function returns the product id (PID) as readable text. */ -String VeDirectFrameHandler::veStruct::getPidAsString() const -{ +String VeDirectFrameHandler::veStruct::getPidAsString() const { static const std::map values = { { 0x0300, F("BlueSolar MPPT 70|15") }, { 0xA040, F("BlueSolar MPPT 75|50") }, diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index bc6678b53..fcaedab5d 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -19,6 +19,32 @@ #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 + +// 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(); @@ -30,7 +56,6 @@ class VeDirectFrameHandler { bool _verboseLogging; Print* _msgOut; uint32_t _lastUpdate; - typedef struct { uint16_t PID = 0; // product id char SER[VE_MAX_VALUE_LEN]; // serial number @@ -42,6 +67,7 @@ class VeDirectFrameHandler { String getPidAsString() const; // product id as string } veStruct; + void sendHexCommand(VeDirectHexCommand cmd, VeDirectHexId id, VeDirectFlag flag, uint16_t value); bool textRxEvent(std::string const& who, char* name, char* value, veStruct& frame); bool isDataValid(veStruct const& frame) const; // return true if data valid and not outdated @@ -55,13 +81,14 @@ class VeDirectFrameHandler { virtual void textRxEvent(char *, char *) = 0; virtual void frameValidEvent() = 0; int hexRxEvent(uint8_t); - + std::unique_ptr _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 _debugBuffer; diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index 04e0d8ca4..65ec1d17d 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -1,7 +1,7 @@ #pragma once #include -#include "VeDirectFrameHandler.h" +#include "VeDirectChargerController.h" template class MovingAverage { @@ -35,7 +35,7 @@ class MovingAverage { size_t _count; }; -class VeDirectMpptController : public VeDirectFrameHandler { +class VeDirectMpptController : public VeDirectChargerController { public: VeDirectMpptController() = default;