diy solar

diy solar

JK-BMS-CAN with new Cut-Off Charging Logic (open-source)

Used ESPHome Web to first flash my esp32 s3 and have OTA support. ( @chaosnature )

ESP-IDF framework, Li-Ion for the battery setting on the inverter, PYLON protocol, here is a copy of my yaml.

yaml file @ pastebin

Today I had my wifi down for a few hours & I even pulled the power on the Atom for a few seconds without any interuption on the inverter, everything resumed just fine.

the app for eg4's 6000xp does report the wrong number of batteries ( 3 in parallel ( just have a 1 x 16s ) other then that, smooth sailing. :)
Are you sure you have ENABLED a setting on the inverter side, in case BMS (ESP32) communication stops ? It seems a bit weird that it didn't trip the inverter when you rebooted the Atom S3 Lite.

On Deye that is BMS-Err_Stop-

Otherwise in case of communication loss it will go full power (max voltage & max current) ...
 
Are you sure you have ENABLED a setting on the inverter side, in case BMS (ESP32) communication stops ? It seems a bit weird that it didn't trip the inverter when you rebooted the Atom S3 Lite.

On Deye that is BMS-Err_Stop-

Otherwise in case of communication loss it will go full power (max voltage & max current) ...

I pulled the Atom power only 3,4 seconds & inverter kept powering my loads ( a few seconds might not be long enough to trigger communication loss but in past, on atom disconnect, the inverter throws a sad smiley face on the display, and stop power output ) .

Never did change any inverter bms error settings.
 
Last edited:
OK, sorry, I didn't know that. Did you talked to him directly or just made that GitHub Issue ? Maybe it slipped through so many other issues, that's why I thought you could have pinged him directly and maybe he could have helped diagnose the problem a bit better since he has quite a bit of experience doing reverse engineering of these things.

Does the JK BMS Reply at all to ANY command ? Or send ANY data out ? Or it's like the port is basically dead ?

Now you are making me a bit scared since I recently purchased 2 new JK BMS ...

Should be this one I ordered (WITHOUT Heat-CAN or HC suffix): https://eu.nkon.nl/jk-bms-b2a20s20p.html

EDIT 1: @paulsteigel did you have a look at https://github.com/syssi/esphome-jk-bms/issues/309#issuecomment-1513618297 ?
ESP32 seems to be working whereas ESP8266 not: https://github.com/syssi/esphome-jk-bms/issues/309#issuecomment-1532177991 and also later posts.

EDIT 2: not sure if it makes a difference (probably not) and assuming the syssi software supports it ... Trying with the RS485 interface using JK BMS RS485 Adapter ?

EDIT 3: What about the "double" RS485 Port described in the GitHub Issues above, did you try both ports and force the CAN/RS485 switch in the APP ?

EDIT 4: https://diysolarforum.com/threads/h...een-and-power-button.33267/page-8#post-535839
I will try another talk to him via discussion! It's weird that a friend of mine has done the reading successfully over hardware serial on esp8266 D1 promini but not on esphome. He did not use SoftwareSerial (of course esp8266 has problem with SoftwareSerial). Esp32-esphome use softwareserial too. I did try to day using arduino but it failed! I will build another Firmware in Arduino IDE to check for whatever message sent back by the JK-BMS in coming days (I gave up working on arduino and now have to be back with it). I have some programing skills and I did try all what you said. my JKBMS is brand new one!
Thanks!
Ngoc
 
This is normal, the PYLON protocol counts one pack per 5kWh of battery.
Pylon or Pylon+?

Are you telling me I will habe problems hitting 100 kwh 🤣?

IIRC when I was going through the rs485 registries for the Deye I also remember battery 1, battery 2,...

But then again in case of multiple batteries the inverter shouldn't really care... The Esp32 master can just send one average/min/max summary for one equivalent battery
 
Pylon or Pylon+?

Are you telling me I will habe problems hitting 100 kwh 🤣?

IIRC when I was going through the rs485 registries for the Deye I also remember battery 1, battery 2,...

But then again in case of multiple batteries the inverter shouldn't really care... The Esp32 master can just send one average/min/max summary for one equivalent battery

PYLON and PYLON+ I don't really know what impact this information can have on the inverter. This makes me think that I could change this in the multi-bms version.

How many packs do you have?
 
PYLON and PYLON+ I don't really know what impact this information can have on the inverter. This makes me think that I could change this in the multi-bms version.

How many packs do you have?
Right now 2 packs. 2 new being built. Space for 8 or maybe 10 in the garage.

Why should there be a limit?

Bms master should just give a high level overview to the inverter.

Or are you thinking about bms master internal CAN/RS485 registries to store the data to process?
 
Right now 2 packs. 2 new being built. Space for 8 or maybe 10 in the garage.

Why should there be a limit?

Bms master should just give a high level overview to the inverter.

Or are you thinking about bms master internal CAN/RS485 registries to store the data to process?

There should be no limit for a multi-bms bms Master Slaves version. (still needs to be developed).

I ask the question because if you wish you could test the multi-bms version (2x JK-BMS BLE) on the same Atom S3.

The first tests could begin in a few days.
 
There should be no limit for a multi-bms bms Master Slaves version. (still needs to be developed).

I ask the question because if you wish you could test the multi-bms version (2x JK-BMS BLE) on the same Atom S3.

The first tests could begin in a few days.
Wow, that's MUCH faster than I expected.

But how exactly do you want me to test on the same Atom S3 Lite ? I don't have a wired connection ...
 
Back with another question.

My 6000xp inverter seems to disable any loads ( and throws a low voltage notice in its logs ) when SOC hits 15% ( and the lowest cell voltage was at 3.16v (16s pack ))

I have 2.64v ( perhaps a little low ) set as 0% SOC in the JK BMS settings. Are there any yaml settings I can edit to push my batteries a little further here?
 
Back with another question.

My 6000xp inverter seems to disable any loads ( and throws a low voltage notice in its logs ) when SOC hits 15% ( and the lowest cell voltage was at 3.16v (16s pack ))

I have 2.64v ( perhaps a little low ) set as 0% SOC in the JK BMS settings. Are there any yaml settings I can edit to push my batteries a little further here?
Are you sure it's a BMS triggered disconnect and not the Inverter Thresholds for Shutdown / Alarm / Warning based on Battery Voltage ?

Cannot remember what I have on the Deye now that I enabled BMS communication, but before I for sure had a Shutdown Voltage, a Warning / Low-Battery Voltage as well as a Restart Voltage (that once recovered re-enables the Inverter to supply the Loads).
 
Are you sure it's a BMS triggered disconnect and not the Inverter Thresholds for Shutdown / Alarm / Warning based on Battery Voltage ?

Cannot remember what I have on the Deye now that I enabled BMS communication, but before I for sure had a Shutdown Voltage, a Warning / Low-Battery Voltage as well as a Restart Voltage (that once recovered re-enables the Inverter to supply the Loads).
My friend, you are completely right. ;)
 
@Sleeper85 , @MrPablo : Somewhat unrelated (and maybe obvious to everybody except me ...)

I tried switching a Test HomeAssistant Instance (basically an empty one where I just setup some MQTT entities logging) for test Purposes.

I converted the SQLite3 Database from this Test HomeAssistant Instance ({DATA_FOLDER}/home-assistant_v2.db) to PostgreSQL then finally to TimescaleDB-HA (PostgreSQL + TimescaleDB Extension + PostGIS Extension + TimescaleDB Toolkit) to see if that would Improve the Performances. This is on a KVM Virtual Machine with ZFS Pool/Dataset inside the Virtual Machine (AMD64 Host with Proxmox VE with ZFS Pool/Dataset also) running with ZFS and Compression set to LZ4.

The Migration was performed using a Script I developed. I lost basically 2 days dealing with ramdom Podman (Docker) Network Hostnames & DNS Failures that occurred on a random Basis due to some Network Configuration that for some Reason was dangling around in my temporary User Folder :( .

Anyways here it is in case anybody is interested:

The Script will run in Podman/Docker directly on the Host where your Docker Container Database is. The Advantage is that will NOT require any Authentication & Data Transfer over UNPROTECTED Network Connection (by default PostgreSQL is NOT using SSL, at least in PostgreSQL Docker Containers). The communication can therefore be done directly "across Containers", with no need to send data over unencrypted connections.

The Script takes care of:
- Migrate the SQLite3 Database (home-assistant_v2.db) to a (temporary) PostgreSQL using pgloader
- Fix Sequences on (temporary) PostgreSQL database using psql (otherwise you'll get lots of errors when HomeAssistant tries to write to the Database, complaining about non-Unique Keys, since the Sequences were NOT updated automatically)
- Backup the (temporary) PostgreSQL Database using pg_dump
- Initialize the (production) TimescaleDB-HA Database according to the Instructions on the TimescaleDB Website
- Import/Migrate (Restore) the (temporary) PostgreSQL Database into the (production) TimescaleDB-HA Database
- Finalize the (production) TimescaleDB-HA Database according to the Instructions on the TimescaleDB Website

Of course there is a bit of Configuration Required and you need to bring down your HomeAssistant Container before starting the actual migration (and prevent it from Starting again until the Migration is finished). On the other side you need to start the (temporary) Postgresql Database and the (production) TimescaleDB-HA Database before starting the Migration.

I plan to test it a bit better by copying the "Production" home-assistant_v2.db File to a Test Virtual Machine and try to do the Conversion with that. That will most likely spot some new Issues, since importing 6GB of Data will probably be a bit more challenging (RAM, CPU, time) than 2MB of an almost empty Database.


If Somebody is Interested in the ZFS (or Other File System) Compression Data Information:
Bash:
#!/bin/bash

# Define Folder / File
target=${1-""}
if [[ -z "$target" ]]
then
   read -p "Enter the Target File or Folder to Analyze: " target
fi

# Check if File or Folder or if doesn't Exist
if [[ -d "${target}" ]]
then
   echo "Analyzing Folder ${target}"
elif [[ -f "${target}" ]]
then
   echo "Analyzing File ${target}"
else
    echo "Target ${target} does NOT Exist. Aborting !"
    exit 9
fi

# Declare Function
get_size() {
   # Target File / Folder
   local ltarget="${1}"

   # The Arguments for "du" Command is passed by nameref
   declare -n largs="$2"  # Reference to output array

   # Run Command and Filter Output
   #local lresult=$(du ${largs[*]} "${ltarget}" | sed -E "s|^([a-zA-Z0-9_-]+)\s*?(.*)$|\1|g")                    # Value + Unit In one Block (e.g. "256M")
   #local lresult=$(du ${largs[*]} "${ltarget}" | sed -E "s|^([0-9]+)([a-zA-Z]*?)\s*?(.*)$|\1 \2|g")             # Value + Unit with Space Between them (e.g. "256 M")
   local lresult=$(du ${largs[*]} "${ltarget}" | sed -E "s|^([0-9]+)(\.?)([0-9]*?)([a-zA-Z]*?)\s*?(.*)$|\1\2\3 \4|g")  # Support for e.g. "5.6G" Integer Value . Decimal Value + Unit with Space Between them (e.g. "256 M")

   # Return Result
   echo $lresult
}

# Get Uncompressed Size
uncompressedOptionsRaw=("--summarize" "--apparent-size")
uncompressedOptionsHuman=("--summarize" "--apparent-size" "--human-readable")
uncompressedSizeRaw=$(get_size "${target}" uncompressedOptionsRaw)
uncompressedSizeHuman=$(get_size "${target}" uncompressedOptionsHuman)

# Get Compressed Size
compressedOptionsRaw=("--summarize")
compressedOptionsHuman=("--summarize" "--human-readable")
compressedSizeRaw=$(get_size "${target}" compressedOptionsRaw)
compressedSizeHuman=$(get_size "${target}" compressedOptionsHuman)

# Compute Compression Ratio
compressedRatioFormatted=$(awk -v u=${uncompressedSizeRaw} -v c=${compressedSizeRaw} 'BEGIN{printf("%.1f\n", c/u*100.0)}')

# Echo
echo "Uncompressed Size: ${uncompressedSizeHuman}B (${uncompressedSizeRaw} B)"
echo "Compressed Size: ${compressedSizeHuman}B (${compressedSizeRaw} B)"
echo "Compression Ratio: ${compressedRatioFormatted} %"

After approx. 12 hours of data Logging from a SINGLE JK BMS I get, for TimescaleDB-HA 16.2 with PostgreSQL 16.2 + PostGIS + TimescaleDB Toolkit under the Hood (which should be very high performance):
Bash:
root@TESTING:/tools# ./getSizeCompressionData.sh "/home/podman/containers/data/homeassistant-timescaledb/"
Analyzing Folder /home/podman/containers/data/homeassistant-timescaledb/
Uncompressed Size: 593 MB (606562 B)
Compressed Size: 250 MB (255859 B)
Compression Ratio: 42.2 %

My Production HomeAssistant Instance running in the Garage and Handling Solar (currently logging 2 x JK BMS + 3 x Inverters + 1 x Meteo Station) still running only the "default" SQLite3 Database home-assistant_v2.db but also on ZFS with LZ4 Compression:
Bash:
root@SOLAR:/tools# ./getSizeCompressionData.sh "/home/podman/data/homeassistant/"
Analyzing Folder /home/podman/data/homeassistant01/
Uncompressed Size: 5.7 GB (5894054 B)
Compressed Size: 3.2 GB (3293903 B)
Compression Ratio: 55.9 %

So I assume that the big File Size (approx. 0.5 GB / 12h / 1 BMS = 1GB / day / BMS) is due to the Update Frequency of the JK BMS (every 1 second, so that the controller for Automatic Charge Voltage / Automatic Charge Current can work Correctly).

But 1GB / BMS / day is A LOT of data without much use IMHO. I am also looking into using HomeAssistant "LTSS" addon to replace the Recorder Section (overall that will make HomeAssistant use 2 databases: 1 for LTSS [Long Term Data Storage] where ALL Data will be periodically dumped to and 1 for Normal Operation [only Store Data for a few Days])].

It seems like Home Assistant Recorder Purges Data older than 10 days by Default ...

But ... if the Value does NOT Change, I do not see why that value should be re-inserted into the Database.

I didn't see any "Global" HomeAssistant Configuration for the "recorder:" Section that can limit the Sample Rate / Frequency of Sensors. I saw some Posts suggesting the Definition of "Virtual" Sensors.

One Option is maybe to Specify a "delta" Filter for each JK BMS Sensor based on:

E.g. for a Cell Voltage specify Maybe "delta: 0.002" [V] so that any "Measurement Noise / Tolerances" of 2mV or so will be Skipped.
Or do the RAW Sensor Value as "Internal" and only Publish the Filtered Sensor Value to API/MQTT ?

Any other Ideas ?


EDIT 1: on second thought, maybe "delta" is not very good. If every "step" is less than 0.002 V (as in my example) it will NEVER log anything :( . Worst it will NEVER use that for the actual controller.

EDIT 2:
Maybe we therefore need to split EACH sensor into:
- ${sensorName} (as it is now but with internal: true)
- ${sensorName}_publish (as it is now but with internal: false and throttle: ${maximumUpdateFrequency}s

Any other ideas ?

EDIT 3:
*In theory* for a **text file** ZFS should be able to handle the compression of similar Data quite well.

But Home Assistant SQLite3 Database is **not** a text file, but it's more like a binary File. So I'm not so sure how effective ZFS Compression is in this case (I'm pretty sure that it also depends on Block Size, Ashift Value). After all ZFS Compression is NOT the same as ZFS De-Duplication Feature (the latter being VERY Memory Intensive and therefore VERY rarely used).

TimescaleDB .. Not Sure.

This is what can be found on the Internet quite Easily regarding PostgreSQL Tuning on ZFS:

And this very extensive Analysis which, among other stuff, suggests using recordsize=8K or recordsize=16K:

Apparently I'm using the "Defaults" of recordsize=128K :( .

The "official" OpenZFS Documentation suggests recordsize=32K/64K/128K:
 
Last edited:
The default behavior of the api: and wifi: components is a reboot every 15 minutes in the event of loss or no connection.

If you don't use Home Assistant the api: line must be configured like this to avoid reboots.

YAML:
api:
  reboot_timeout: 0s

Same for wifi:

YAML:
wifi:
  reboot_timeout: 0s
I just (by accident) "tested" this.

The ESP32 Rebooted within 15 seconds of losing connection to WiFi / API / MQTT. Not 15 minutes ...
 
Last edited:
The documentation says 15min. Could be another reason?
Uhm difficult to tell yesterday was a bit of a mess with something like 20 trips.

First checked network connectivity that is also sketchy inside the garage apparently.

And of course when I put back the Esp32 which I had to flash using usb, I connected it to the inverter but the USB cable that I used was the wrong one. Not connected to the AC (just a USB cable not connected to anything) 🙄. I tidied up quite a bit to avoid it happening again.

But that also makes no sense...BMS_Err-Stop is ENABLED. Why it attempted to start, then trip immediately, if the ESP32 was clearly not working?

I got lots of BMS_Communication_Err, F41_Parallel_Error and some F18_AC_Overcurrent. Even without any load, just the 3 inverters together... Once I got overcurrent without load and only one inverter comected. It was driving me crazy.

After connecting usb power it worked almost flawlessly. Weird that it attempted to start-stop-start etc though. What could that be?

At first I though the USB cable for the Esp32 was connected to the inverter output (off grid, hence a trip of the inverter would cause a trip of Esp32 and vice versa). But it wasn't connected at all...

I'm particularly worried about the AC overcurrent fault... I swear I heard a sound of a spark coming from one of the inverters when that message popped upthe first time 😭. There should be some overvoltage protection in place in that output filter, even in an event of an IGBT/MOSFET turn off 🙄.

And during those periods for some reason, even with bms enabled, one lifepo4 cell hit 3.69 V with a few spikes (not ultra long, but I am worried that it could habe been damaged).

EDIT 1: that's the reason I was afraid to raise OVP to 3.65 V @MrPablo. There doesn't seem to be so great headroom in case BMS stops talking to Inverter (and there is always a bit of delay for the inverter to detect the "Heartbeat" on the CANbus.
1713602555725.png

EDIT 2: And of course at the end of the day (besides the USB Cable Issue) I also discovered that I never added the ESPHome to Home Assistant :rolleyes: . Stupid me 🫣. I thought I did but actually I didn't. Damn ....
 
Last edited:
Back
Top