48V Charger

Andy
Posts: 467
Joined: Sun Aug 29, 2021 12:16 pm

Re: 48V Charger

#31

Post by Andy »

Oh well, I've ordered one off eBay for £60. An ELTEK FLATPACK2 48/2000 HE. Now I have to work out how to control the thing.

Tinbum, I was wondering if I have to constantly set the voltage in the same manner that the Victron does. Ie have a few volts higher than static voltage until float voltage is reached in order to charge. Or can I just set it to 50V say and let it do its thing?

The efficiency is great so I might use it permanently plugged in to do lower level charges.
Tinbum
Posts: 1031
Joined: Mon May 31, 2021 9:55 pm

Re: 48V Charger

#32

Post by Tinbum »

Arduino e.g. Uno with a CAN shield or Teesnsy for more complex..

You can set a default voltage or you can be clever and intercept the CAN messages from the battery and use them.

See my thread https://camelot-forum.co.uk/phpBB3/view ... f=14&t=221
85no 58mm solar thermal tubes, 28.5Kw PV, 3x Sunny Island 5048, 2795 Ah (135kWh) (c20) Rolls batteries 48v, 8kWh Growatt storage, 22 x US3000C Pylontech, Sofar ME3000's, Brosley wood burner and 250lt DHW
Andy
Posts: 467
Joined: Sun Aug 29, 2021 12:16 pm

Re: 48V Charger

#33

Post by Andy »

Tinbum wrote: Fri Nov 03, 2023 10:45 pm Arduino e.g. Uno with a CAN shield or Teesnsy for more complex..

You can set a default voltage or you can be clever and intercept the CAN messages from the battery and use them.

See my thread https://camelot-forum.co.uk/phpBB3/view ... f=14&t=221
Cool, thanks. I'll order the Canbus shield. I've a ton of esp32 lying around so should be able to get going fairly quickly. I should be able to get a lot of data from the Cerbo without bothering to interface with the pylon tech hopefully.

If anyone else wants one then there are a few more here https://www.ebay.co.uk/itm/276025543703. These are rev 3 so a bit cheaper £54. Don't get less than rev 3 or you can't permanently set the max voltage.

Tinbum, I've sent you a PM on the scrounge for those great connectors you made up if you have any left that you are willing to part with :P
Last edited by Andy on Sat Nov 04, 2023 10:21 pm, edited 1 time in total.
Andy
Posts: 467
Joined: Sun Aug 29, 2021 12:16 pm

Re: 48V Charger

#34

Post by Andy »

I'm slowly assembling all the bits I'll need for the Eltek project. The calculators I have seen seem to vary on their opinion for the wire size. I have some left over 6mm solar cable I thought I could use. I only intend to charge around 1.2-1.4 kW so that should be ok. Is it acceptable to use black cable and just wrap some appropriate coloured tape around the ends?

I'm going to use an ESP 32 board linked to one of these transceivers from piHut. I like the esp32 as I don't have to mess around with extra shields and can just use the wifi to communicate via MQTT or set up a web server easily.

for Tinbum if he'd be so kind. I haven't managed to work out the wiring between the ESP32 and the Eltek . Is it a case of a twisted pair between the CANL and CANH at either side? I've read about linking the Can ground to the negative DC output. Is this necessary with just one unit? Where are you powering the teensy board from?
Tinbum
Posts: 1031
Joined: Mon May 31, 2021 9:55 pm

Re: 48V Charger

#35

Post by Tinbum »

Andy wrote: Sat Nov 04, 2023 10:16 pm I'm slowly assembling all the bits I'll need for the Eltek project. The calculators I have seen seem to vary on their opinion for the wire size. I have some left over 6mm solar cable I thought I could use. I only intend to charge around 1.2-1.4 kW so that should be ok. Is it acceptable to use black cable and just wrap some appropriate coloured tape around the ends?

I'm going to use an ESP 32 board linked to one of these transceivers from piHut. I like the esp32 as I don't have to mess around with extra shields and can just use the wifi to communicate via MQTT or set up a web server easily.

for Tinbum if he'd be so kind. I haven't managed to work out the wiring between the ESP32 and the Eltek . Is it a case of a twisted pair between the CANL and CANH at either side? I've read about linking the Can ground to the negative DC output. Is this necessary with just one unit? Where are you powering the teensy board from?
Yes twisted pair. You should be ok without the ground. I just use a power adapter or the laptop if I'm tinkering.

I do find you can get some strange behaviour sometimes with power supplies. I've just set up 3 Sofar ME3000Sp's with RS485 to Wesmos D1 mini's and if I power them with the same power supply and they are charging the data sent out from the Sofar's gets corrupted. They also seem ok if not charging!!
85no 58mm solar thermal tubes, 28.5Kw PV, 3x Sunny Island 5048, 2795 Ah (135kWh) (c20) Rolls batteries 48v, 8kWh Growatt storage, 22 x US3000C Pylontech, Sofar ME3000's, Brosley wood burner and 250lt DHW
Andy
Posts: 467
Joined: Sun Aug 29, 2021 12:16 pm

Re: 48V Charger

#36

Post by Andy »

I dug out an old Particle Photon and purchased a CANbus transceiver from pi hut. Tinbum very kindly provided me with the adapter for the eltek so a quick bit of soldering later I have the following set up.
Image
Image
The communications has been established and I have the voltage turned down to 51V. I did actually request 48V through code so I have to work out why it is so high. At least it is safe for the battery stack. Pins D1 & D2 connects TX and RX respectively on the transceiver. The transceiver has a buck converter to take the 3.3V up to 5V without messing around with voltages. The canbus is CAN H -> H and L ->L respectively. The neutral is also linked. I'll post the code later as it is still work in progress. It took a wee while to get the environment up and running correctly. However the libraries are quite nice to work with.

I'm just going to try and work on a ramp up of current/voltage so as not to cause the generator to trip out on start. I also need to find some 6mm cable and ring crimps M8 which I'm sure I have knocking about somewhere :xx:
Andy
Posts: 467
Joined: Sun Aug 29, 2021 12:16 pm

Re: 48V Charger

#37

Post by Andy »

EDIT:
Added MQTT control. Discovered max voltage wasn't working as the canbus wasn't logged in. This has been fixed and it does recognise the maximum voltage.

Here is the code for the particle board. It is based on a lot of research by others before me. I'll put the forums in below. It sets a default voltage during the setup. Then in the keep alive it will also set the desired voltage and current. There is a built in ramp up to new settings by all accounts. I haven't connected to my battery stack as I want more logging and external control before I do that. The next step is to add some MQTT communication. If you change the default voltage I didn't get the changes sticking until I powered down the Eltek and rebooted it. Also it won't retain a default voltage unless the firmware is v3 or greater.

There does appear to be a difference in the output voltage from that requested. Tinbum mentioned that he has an offset in the code. I will see what I get when I get some victron readings.

I will update this post as the code evolves.

EDIT : the default saved voltage will not take unless you log out (ie don't respond for 15s?). So the code would need to be modified to delay(30000) at the end of startup if wanting to modify the default voltage permanently.

EDIT: added MQTT. it publishes JSON to EltekCharger/JSONStatus, and a binary blob to EltekCharger/Status. Publishing to EltekCharger/setVoltage and setCurrent will set the current running settings. Max voltage is set in the file and cannot be changed via MQTT. The library used can be found here

Code: Select all

#include "Particle.h"

#include "MQTT.h"

#include <stdio.h>

// #include <Adafruit_GFX_RK.h>
// #include <Adafruit_SSD1306_RK.h>

// Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32);
 
void MQTTcallback(char* topic, byte* payload, unsigned int length);

MQTT client("xx.xx.xx.xx", 1883, MQTTcallback);


#define lowByte(w) ((uint8_t)((w)&0xff))
#define highByte(w) ((uint8_t)((w) >> 8))

// Let Device OS manage the connection to the Particle Cloud
SYSTEM_MODE(AUTOMATIC);

// Run the application and system concurrently in separate threads
SYSTEM_THREAD(ENABLED);

// Show system, cloud connectivity, and application logs over USB
// View logs with CLI using 'particle serial monitor --follow'
SerialLogHandler logHandler(115200, LOG_LEVEL_INFO);

CANChannel can(CAN_D1_D2);
bool loggedIn=false;
// This will be filled with your serial number from the data coming in. It can be left blank if desired
unsigned char serial[8] = {0x14, 0x15, 0x72, 0x11, 0x06, 0x93, 0x00, 0x00};

#define VOLTAGE 4830  //    48,3 V

#define MINVOLTAGE 4830 // 48.3V
#define MAXVOLTAGE 5150 //  51 V  this is used throughout the code. I've given a good healthy margin for the pylontech until i've had a play.
#define CURRENT 270 //      27 A

int setCurrent = CURRENT;
int setVoltage = VOLTAGE;

// Struct to hold current status returned from the
struct EltekStatus { 
  uint16_t current;
  uint8_t temperatureIn;
  uint8_t temperatureOut;
  uint8_t voltageIn;
  uint16_t voltageOut;
};

EltekStatus currentStatus;



//Helper function to view the raw data in hex from the Eltek
void dump(uint8_t *data, int len,int spacing)
{
  
  int spaceRequired = len*2+1+len/spacing+1;
  // Allocate space for the hex representation (twice the size + 1 for null terminator)
  char hexRepresentation[spaceRequired];

  // Iterate through each element and convert to hex
  for (int i = 0; i < len; i++)
  {    
    int position = 2* i + i/spacing;
    sprintf(hexRepresentation + position, "%02x", data[i]);
    if (i%spacing==(spacing-1)) {
      hexRepresentation[ position+2]=32;
    }
  }

  // Null-terminate the string
  int termination = len*2+(len/spacing);
  hexRepresentation[termination] = '\0';
  // Print the result
  Log.info("%s", hexRepresentation);
}




CANMessage message;

// Transmit the id and data packet to the Eltek

void transmit(int32_t id, uint8_t *data, int len)
{
  // Log.info("Transmitting to the Eltek: %x",(int) id);
  // dump(data, len, 2);
  message.id = id;
  message.extended = true;
  message.len = len;
  memcpy(message.data, data, len);
  can.transmit(message);
}

void loginCAN()
{
  loggedIn=true;
  transmit(0x05004804, serial, 8);
}

// Voltage x 100 This is the voltage that it will start at when switched on.
void setDefaultVoltage(int voltage)
{
  unsigned char setdefaultvolt[5] = {0x29, 0x15, 0x00, lowByte(voltage), highByte(voltage)}; // 48V
  transmit(0x05019C00, setdefaultvolt, 5);
}

void setParameters(int current,int voltage, int overVoltage) {
  unsigned char parameters[8] = {lowByte(current), highByte(current), lowByte(voltage), highByte(voltage),lowByte(voltage), highByte(voltage), lowByte(overVoltage),highByte(overVoltage)};
  transmit(0x05FF4004,parameters,8);
}

// can be called in response to request to login or forced.  forced is only in the loop.
void keepLoginAlive(){
      static bool firstTime= true;
      if (firstTime){
        memcpy(serial, message.data, 8);
        firstTime=false;
      }
      loginCAN();
      delay(10);
      setParameters(setCurrent,setVoltage,MAXVOLTAGE);
}

void requestWarnings() {
  uint8_t buffer[]={0x08,0x04,0x00};
  transmit(0x0501BFFC,buffer,0);
}

void requestAlarms() {
   uint8_t buffer[]={0x08,0x08,0x00};
  transmit(0x0501BFFC,buffer,0);
}

struct WarningStruct1{
  bool OVS_Lockout:1;
  bool Mod_Fail_Primary:1;
  bool Mod_Fail_Sec:1;
  bool High_Mains:1;
  bool Low_Mains:1;
  bool High_Temp:1;
  bool Low_Temp:1;
  bool Current_Limit:1;
};
struct WarningStruct2{
  bool Internal_Voltage:1;
  bool Module_Fail:1;
  bool Module_Fail_Secondary:1;
  bool Fan_1_Speed_Low:1;
  bool Fan_2_Speed_Low:1;
  bool Sub_Mob_1_Fail:1;
  bool Fan_3_Speed_Low:1;
  bool Inner_Vold:1;
};

void parseAlarmStatus(uint8_t* data){
//   bytes 0-6
// 0x0e,0x04 or 0x08,0x00, bit field 0, bitfield 1,0x00,0x00
  bool isWarning = true;    // warning or alarm

  if (data[1]==0x08)
   isWarning= false; 

  struct WarningStruct1 bytes1;
  struct WarningStruct2 bytes2;

  uint8_t *ptr=(uint8_t*) &bytes1;
  *ptr=data[3];
  uint8_t *ptr2=(uint8_t*) &bytes2;
  *ptr2=data[4];
  WarningStruct1 *bytesPtr = &bytes1;
  
  // bool a = bytesPtr[1];
   //Warning status 
    // 0 0x0e  1 - 0x04 (warning) 0x08 alarm 2 - 0x00 3 - bit field warnings 4- bitfield alarm warnings 5 - 0x00 6 - 0x00
   
}


uint16_t decodeData(uint8_t*data){
  // Log.info("a: %x,b:%x",data[0],data[1]);
  return (data[1] << 8) + data[0];
}

void receiveStatus(uint8_t*data){
  currentStatus.temperatureIn= data[0];
  currentStatus.current= decodeData(data+1);
  currentStatus.voltageOut = decodeData(data+3);
  currentStatus.voltageIn = data[5];
  currentStatus.temperatureOut = data[7];
  // Log.info("temp in:%i,temp out: %i,current %i,voltage out: %i,input voltage:%i,",t1,t2,current,voltageOut,voltageIn);
}

uint32_t readMessage (){
if (can.receive(message))
  {
    // message received
    uint32_t canId = message.id;

    // Log.info("id=%x len=%u", (int) message.id, message.len);

  switch (canId) {
    case 0x05014400:// Please login message
      keepLoginAlive();
    break;
    case 0x0501400C: // Start up and shutdown below 43V during walking. or ALARM
      requestAlarms();
    break;
    case 0x05014010: //Walkin busy
    break;
    case 0x05014004: // At normal voltage status message
    // Log.info("normal voltage status");
      receiveStatus(message.data);
    break;
    case 0x05014008: // Warnings need to be read. eg curren limited see below
      requestWarnings();

    break;
  
    case 0x0501BFFC: 
      parseAlarmStatus(message.data);
    
    break;
    case 0x5000693:  //unknown
    break;
    
    default:
      Log.info("id=%x len=%u", (int) message.id, message.len);
    break;
    
  }

    return canId;
  } 
  return 0;
}


void connectToMQTT(){
  // connect to the server
  client.connect("EltekCharger");
  client.subscribe("EltekCharger/setVoltage");
  client.subscribe("EltekCharger/setCurrent");
}

// recieve message from MQTT
void MQTTcallback(char* topic, byte* payload, unsigned int length) {
  

    float payloadFloat = atof((char*)payload);
    Log.info("Topic:%s,Payload:%s,pf:%f",topic,payload,payloadFloat);

    if (strcmp(topic,"EltekCharger/setVoltage")==0){
    // whatever you want for this topic
    int vtmp = (int)(payloadFloat * 100);

    if (vtmp>MINVOLTAGE && vtmp<MAXVOLTAGE){
      setVoltage = vtmp;
    }

    if (strcmp(topic,"EltekCharger/setCurrent")==0){
      int ct=(int)(payloadFloat * 10);
      if (ct>0 &&ct < 418)
      {
      setCurrent  = ct;
      }
    // whatever you want for this topic
    }
  }

  setParameters(setCurrent,setVoltage,MAXVOLTAGE);
  Log.info("Set voltage:%i,current:%i",setVoltage,setCurrent);
}

void logMQTT() {
  char buffer[200];
  sprintf(buffer,"{\"current\":%f,\"voltageIn\":%f,\"voltageOut\":%f,\"temperatureIn\":%f,\"temperatureOut\":%f}",(float)currentStatus.current/10.0,(float)currentStatus.voltageIn,(float)currentStatus.voltageOut/100,(float)currentStatus.temperatureIn,(float)currentStatus.temperatureOut);

  client.publish("EltekCharger/JSONStatus",buffer);   //JSON
  client.publish("EltekCharger/Status",(uint8_t*) &currentStatus, sizeof(currentStatus)); //Binary blob
}


// Helper function to run functions at defined intervals in the main loop
void runAtInterval(unsigned long *previous,long interval,void(*functionToCall)()){

  int timeSinceLast = millis()-*previous;
  if (timeSinceLast>interval){
      // Log.info("running interval");
    *previous=millis();
    functionToCall();
  }
}

void runOnce(bool *hasRun,void(*functionToCall)()){
  if (!*hasRun) {
    *hasRun=true;
    functionToCall();
  }
}

void setup()
{
  can.begin(125000); // pick the baud rate for your network
  waitFor(can.isEnabled,15000);
  waitFor(Serial.isConnected, 15000);
  delay(1000);

  Log.info("Starting setup");
  uint32_t messageId = 0;
  do {
    messageId=readMessage();
    Log.info("Messageid %x",(int) messageId);
    delay(100);
  } while ((messageId == 0) || (messageId != 0x05014400 && messageId!=0x5000693)); // wait for login


  
    connectToMQTT();

    // publish/subscribe
    if (!client.isConnected()) {
      Log.info("failed to connect to MQTT");
    }
    Log.info("Set up complete.");
}


bool hasRunSetDefault = false;
void runDefaultVoltage(){
  delay(1000);
  setDefaultVoltage(MAXVOLTAGE);
}

void loop()
{
  delay(100);

 // Clear the CANBUS message queue
  while (readMessage()!=0){};

// variables to hold the time the functions where last run at.
  static unsigned long previousKeepAlive = 0;
  static unsigned long previousMQTTLogging = 0;

  runAtInterval(&previousKeepAlive,5000,keepLoginAlive);  //5 second interval
  if (loggedIn){
    runOnce(&hasRunSetDefault,runDefaultVoltage);
  }

  if (client.isConnected()){
      runAtInterval(&previousMQTTLogging,1000,logMQTT); // 1 second interval
      client.loop();
    } else {
      Log.info("Trying to connet to MQTT");
      // connect to the server
      connectToMQTT();
    }
}

protocol: https://github.com/The6P4C/Flatpack2/bl ... rotocol.md

https://www.thebackshed.com/forum/ViewT ... &TID=12035

https://community.home-assistant.io/t/r ... r/420788/9

https://openinverter.org/forum/viewtopic.php?t=1351

https://github.com/neggles/esp32-cantroller

Detailed exploration using uno and Leonardo
https://endless-sphere.com/forums/viewt ... ce854514ff

https://github.com/the6p4c/Flatpack2

https://www.thebackshed.com/forum/ViewT ... =12035&P=1
Andy
Posts: 467
Joined: Sun Aug 29, 2021 12:16 pm

Re: 48V Charger

#38

Post by Andy »

I've been pushed to finish my Eltek Flatpack project as I'm now on GO and only have 4 hours of cheap electricity. The Quattro 10000 couldn't charge the stack in that time so I've enlisted the help of the Flatpack 2.

After a few hours of running it to make sure none of crimps will catch fire and also testing it will stop at the requested voltage, it seems to be doing nicely. I was wondering what are considered to be acceptable temperatures for wires etc. Having ordered a fancy (for me) connector rated at 50A from eBay I'm finding the DC wires are reaching nearly 48ºC with only 30A passing through them. Obviously the cooler the wires are the better, but what do people aim for when wiring? The hottest bit seems to be the midi fuse I bought from amazon. That's about 10º or more hotter than the wire.

Also, for Tinbum, my traces are getting to 58ºC. What is considered acceptable on the PCBs? I'm considering getting chunkier wires in and it was nearly 70º when passing 42A. The soldering seems to be ok and of course the air blowing directly over the traces is at 50ºC as reported by the Eltek so it's starting from warm.

I've got it connected to a smart plug running Tasmota. When it comes to turn it off I set the current to zero and then a few seconds later I turn off the smart plug to try and help prolong the life of the relay. That seems to be working nicely as well.
Post Reply