This software enables EV batteries to be used together with inverters.
Find a file
2025-12-29 08:35:53 +01:00
extra fixed sungrow tcp control 2023-06-06 17:33:35 +02:00
images added bms status to smarthome, some related images 2023-07-22 20:58:48 +02:00
structures moved things around a bit 2023-02-10 16:07:07 +01:00
systemd improved inverter handling 2023-10-22 07:19:48 +02:00
www added links for inverter handlers on modbus and can, to enable more brands using same code 2024-01-18 20:15:52 +01:00
batt2gen24.png added some doc on sungrow 2023-06-04 20:47:20 +02:00
bms_batrium.py massive mods in bms-world, juntek bms added 2023-06-06 17:20:04 +02:00
bms_can_common.py moved handling of static soh to bms handler 2024-04-21 13:37:19 +02:00
bms_comms_batrium.py reworked min and max voltage, now only bms-config is taken into account 2024-01-17 17:54:57 +01:00
bms_comms_juntek.py juntek mods from mikko setup 2023-10-22 15:41:49 +02:00
bms_comms_leaf.py use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
bms_comms_tesla_model3.py reworked min and max voltage, now only bms-config is taken into account 2024-01-17 17:54:57 +01:00
bms_config_batrium.yaml.example moved handling of static soh to bms handler 2024-04-21 13:37:19 +02:00
bms_config_juntek.yaml.example renamed yaml-files to .example to avoid overwriting when updating 2024-01-21 16:44:26 +01:00
bms_config_leaf.yaml.example use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
bms_config_tesla_model3.yaml.example add values for Tesla Gen4 Batteries (refreh S/X Plaid start 2020 to now) 2024-04-21 22:17:25 +02:00
bms_handler.py massive mods in bms-world, juntek bms added 2023-06-06 17:20:04 +02:00
bms_juntek.py reworked min and max voltage, now only bms-config is taken into account 2024-01-17 17:54:57 +01:00
bms_leaf.py massive mods in bms-world, juntek bms added 2023-06-06 17:20:04 +02:00
bms_tesla_model3.py massive mods in bms-world, juntek bms added 2023-06-06 17:20:04 +02:00
config.yaml.example use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
external_handler.py Added usbrelay2 to external_handler 2024-04-29 18:56:16 +02:00
install.sh add values for Tesla Gen4 Batteries (refreh S/X Plaid start 2020 to now) 2024-04-21 22:17:25 +02:00
inverter_can_byd.py use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
inverter_gen24.py added links for inverter handlers on modbus and can, to enable more brands using same code 2024-01-18 20:15:52 +01:00
inverter_goodwe.py added links for inverter handlers on modbus and can, to enable more brands using same code 2024-01-18 20:15:52 +01:00
inverter_handler.py improved inverter handling 2023-10-22 07:19:48 +02:00
inverter_modbus_byd.py added links for inverter handlers on modbus and can, to enable more brands using same code 2024-01-18 20:15:52 +01:00
inverter_sungrow.py added links for inverter handlers on modbus and can, to enable more brands using same code 2024-01-18 20:15:52 +01:00
logic.py use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
logic_handler.py use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
Pipfile update from pymodbus 2.x.x to pymodbus 3.2.2 2023-04-13 23:05:59 +02:00
Pipfile.lock moved things around a bit 2023-02-10 16:07:07 +01:00
README.md add values for Tesla Gen4 Batteries (refreh S/X Plaid start 2020 to now) 2024-04-21 22:17:25 +02:00
requirements.txt use charge/discharge limits from bms, if present 2025-12-29 08:35:53 +01:00
restart-services.sh add stop inverter handler in restart script 2023-12-10 15:35:10 +01:00
start.sh added option to avoid limiting charge/discharge current 2024-01-18 18:51:27 +01:00
stats_collector.py error handling when dir not exists 2024-01-18 18:51:50 +01:00
update.sh change names and add missing service 2023-12-10 15:34:01 +01:00
usb-devices.md add info on how to get consistent usb devices 2023-10-22 16:09:39 +02:00

Looking for BYD can/modbus registers, scripts (passive modbus rtu listener, modbus tcp client, etc), wiring of Tesla M3 battery....head over to: https://gitlab.com/pelle8/inverter_resources

Or, if you fancy LilyGo T-CAN485 instead of RPi/Rock - https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24

BatteryEmulator

The emulator takes an EV battery and makes it look like a BYD battery, be it over modbus RTU or CAN towards the inverter.

The solution is based on different handlers with a brain in the middle, using json over mqtt to commuicate with each other. The BMS is connected to the RPi with a can-interface. A generic bms-handler with the help of a configurable specific bms-decoder gets can-data into json-formats, later pubilshed over mqtt. The brain(logic) is subscribing on bms-data and inverter-data (the registers that the inverter writes), making sure that everything is fine, and turns all required data into modbus register, in json and publishes this. The inverter will get these registers over mqtt and put them in local registers, to be read by the inverter. A stats module and a web-page shows an overview of the cells, soc, energy etc.

Status

Current BMS support:

  • Batrium: OK
  • TeslaM3: OK. Contactor closing implemented.
  • Juntek (VAT4300, shunt): OK
  • Leaf: OK. Contactors closing implemented (with external pwm drivers).

Current Inverter support (tested): It's very likely that more inverters are supported. If an inverter claims support for BYD over modbus or can, it shoud work.

  • Fronius Gen24 (byd modbus): OK
  • Sungrow (byd can): OK
  • GoodWe (byd can): OK

Known limitiations

  • Sungrow: target values for current seem to be ignored when running in compulsory mode.
  • Juntek: Since Juntek shunt isn't truly a BMS, it will not do any balancing. SoC is based on voltage if Ah-counter differs too much from voltage-calculation.

Batt2gen24, architecture

Battery2Gen24

Gen24, faking a BYD over modbus RTU

Gen24

Sungrow, faking a BYD over CAN

Gen24 Since the GUI is hmmm, not so useful, a sample script that can control power over modbus tcp is here.... (./sungrow_control.py)

Batt2gen24 - stats, screenshot from my old disassembled Leaf pack with Batrium BMS.

Leaf/Batrium

Batt2gen24 - stats, screenshot from a Tesla M3 pack.

Tesla

Batt2gen24 - stats, screenshot from my re-assembled Leaf pack.

Leaf

The modules

bms_handler:

  • A module per type of bms (batrium, leaf, tesla...)
  • Receives can messages from bms, decodes and publishes them in a generic format
  • Subscribes on control data, if any, that needs to be sent to bms (close contactor etc)

ui_stats:

  • subscribes and show statistics on web-page

ui_config:

  • web/file: choice of bms/inverter
  • web/file: extra parameters for logic
  • publishes configuration

logic:

  • subscribes on bms data, transforms this into modbus registers
  • subscribes on configuration, transforms this into information that inverters require + control logic
  • subscribes on inverter_registers, handles state transitions (startup)
  • publishes modifications to modbus registers
  • publishes can control, if any
  • adds functionality in addition to just transforming messages, like limiting current if cell_voltage is low or disabling charging/discharging if outside limits (in addition to inverter limits)

inverter_handler:

  • A module per type of inverter, only Gen24 and Sungrow SHxxRT for now. Gen24 talks modbus RTU, while Sungrow is on CAN.
  • Keeps the register content in memory
  • Receives modbus messages from inverter. "Reads" - just responds with the content of the local registers. "Writes" - writes to local registers.
  • Publishes registers
  • Subscribes on inverter_register (from logic) and updates local registers
  • Checks that the soc has been updated recently (trap errors in logic or bms_handler), if not put the "battery" in FAULT.

mqtt_broker:

  • handles pub/sub

JSON Data over MQTT

From BMS

This is data from the bms that different modules will need.

Example - subscribing on bms/stat: $ mosquitto_sub -h localhost -t bms/stat -u "batt2gen24" -P "batt2gen24_pass"

The following will be published from bms_handler (not 100% updated):

To BMS

This is commands to the bms (if needed). Control like "close_contactor"...on Tesla: $ mosquitto_pub -h localhost -m "close_contactor" -t "bms/control" -u "batt2gen24" -P "batt2gen24_pass"

bms_handler will subscribe to:

  • bms/control ["close_contactor",....]

From inverter

Content of modus register from inverter to logic:

To inverter

Content of modus registers to inverter from logic:

To external

This is commands to that can close relays/contactors connected to the pi. Control like "close relay 1"...: $ mosquitto_pub -h localhost -m "{\"type\":\"relay\",\"id\":0,\"state\":\"on\"}" -t "external/control" -u "batt2gen24" -P "batt2gen24_pass" This is implemented in the logic module for Tesla, enabling precharge with Gen24 disconnected from the Tesla battery.

Todo/Status

general:

  • code cleaning and reusing....as always external_handler:
  • Relay: implemented, not tested

config:

  • No ui for now, only static config in files

Installation and configuration

Running LilyGo/RPI

If you are using Lily for the bms-inverter emulation, but want some nice stats in your Home Assistant. Fine, be sure to not use a terminator on the can-hat. And go with the install below, you can omit enabling the following services (but they will not do any harm): batt2gen24_inverter_handler and batt2gen24_external_handler.

Hardware

Follow the instructions that apply for any interface boards (CAN/RS485-hats). If you have USB-interfaces you may need permissions to access these as a non-privileged user. To do so, add your user to the dialout group sudo usermod -G dialout -a [username] and then logout/login.

Inverter

Configure your inverter, probably involves configuring a smartmeter and a battery...

via install script (Debian/Ubuntu)

sudo apt install wget
sudo wget https://gitlab.com/pelle8/batteryemulator/-/raw/main/install.sh
sudo chmod +x install.sh
sudo ./install.sh

by hand:

MQTT broker

Install and configure, allow mqtt over websockets.

sudo apt install mosquitto mosquitto-clients
sudo systemctl enable mosquitto
sudo mosquitto_passwd -c /etc/mosquitto/.passwd batt2gen24   # type "batt2gen24_pass" when asked for password
sudo nano /etc/mosquitto/conf.d/auth.conf
...content:
listener 1883
allow_anonymous false
password_file /etc/mosquitto/.passwd

sudo nano /etc/mosquitto/conf.d/websockets.conf 
...content:
listener 9001
protocol websockets

sudo systemctl restart mosquitto

batt2gen24

  • not required, but good for troubleshooting (candump): sudo apt install can-utils
  • not required, but good for troubleshooting (mqtt subscribe): sudo apt install mosquitto-clients
  • install and configure mqtt broker (see above)
  • create dir on pi: mkdir /opt/batt2gen24
  • install python3 apt install python3-full can-utils pip apache2 tmux
  • install the python requirements: /opt/batt2gen24$ pip install -r requirements.txt
  • copy all .py and .yaml to dir above
  • copy config.yaml.example to config.yaml
  • modify configuration files (yaml) to fit your setup - doublecheck interfaces and mqtt settings
  • make sure can-interface is up: sudo ifconfig can0 txqueuelen 100
  • make sure can-interface is up: sudo ip link set can0 type can bitrate 500000
  • repeat the two steps above if you also have a can1
  • copy www/stats.html to your web-server and make sure your user (the user that runs the stats_collector) can write to the file (chmod/chown)
  • either run all scripts manually or as systemd services
  • Manual: bring up three windows (or tmux sessions) and start a python process in each session:
pi@pi(1):/opt/batt2gen24 $ python3 bms_handler.py
pi@pi(2):/opt/batt2gen24 $ python3 logic_handler.py
pi@pi(3):/opt/batt2gen24 $ python3 inverter_handler.py
pi@pi(4):/opt/batt2gen24 $ python3 stats_collector.py
pi@pi(5):/opt/batt2gen24 $ python3 external_handler.py
  • Services:copy the files in systemd to pi:/etc/systemd/system
pi@pi:/opt/batt2gen24 $ sudo systemctl daemon-reload
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_bms_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_inverter_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_logic_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_stats_collector
pi@pi:/opt/batt2gen24 $ sudo systemctl enable batt2gen24_external_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_bms_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_inverter_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_logic_handler
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_stats_collector
pi@pi:/opt/batt2gen24 $ sudo systemctl start batt2gen24_external_handler

  • Logs will end up in syslog, but normally a bit delayed. You can also change the service so it starts in a tmux to be able to get the logs closer to real time, check out batt2gen24_inverter_handler.service which uses tmux (to attach: tmux a -t inverter_handler)

Testing bms fault

To trigger a bms fault (temperature or voltage out of bounds), you can this over MQTT:

mosquitto_pub -h 127.0.0.1 -t bms/specific -m "{\"test_bms_fault\": 1}" -u "batt2gen24" -P "batt2gen24_pass"

Troubleshooting

All components are logging, to start by "tailing" the bms:

$ journalctl -fu batt2gen24_bms_handler
-- Logs begin at Mon 2023-06-26 07:16:48 CEST. --
jun 26 08:30:50 hassbian systemd[1]: Stopped BMS handler for Batt2Gen24.
jun 26 08:30:50 hassbian systemd[1]: Starting BMS handler for Batt2Gen24...

So, debugging is off. Looks good though, nothing critical. Continue by "tailing" the logic:

$ journalctl -fu batt2gen24_logic_handler
-- Logs begin at Mon 2023-06-26 06:54:04 CEST. --
jun 26 08:15:48 hassbian logic[19135]: Received message on topic 'bms/cellstat
jun 26 08:15:48 hassbian logic[19135]: Target charge I: 22A, discharge I: 22A, Actual P: 398W , Umin: 3.764, soc: 49.1%, mode: Normal (b:128), reason:
jun 26 08:15:48 hassbian logic[19135]: all keys in place
jun 26 08:15:48 hassbian logic[19135]: Received message on topic 'bms/stat
jun 26 08:15:48 hassbian logic[19135]: Target charge I: 22A, discharge I: 22A, Actual P: 362W , Umin: 3.764, soc: 49.1%, mode: Normal (b:128), reason:
jun 26 08:15:48 hassbian logic[19135]: new data to inverter...
jun 26 08:15:48 hassbian logic[19135]: Publishing inverter/data
jun 26 08:15:48 hassbian logic[19135]: {'static_nominal_capacity': 60000, 'static_max_power': 6000, 'static_max_voltage': 402.56, 'static_min_voltage': 242.14000000000001, 'status': 3, 'mode': 129, 'soc': 49.1, 'soh': 88.30694275274057, 'target_discharge_power': 7969, 'target_charge_power': 7969, 'batt_voltage': 362.26, 'batt_power': 362.26, 'cell_temp_min': 23, 'cell_temp_max': 27, 'cell_voltage_min': 3.764, 'cell_voltage_max': 3.78, 'last_updated': 1687760148.3783154, 'last_updated_soc': 1687760148.288296}
jun 26 08:15:48 hassbian logic[19135]: all keys in place
jun 26 08:15:48 hassbian logic[19135]: Received message on topic 'bms/cellstat

The above looks good - we are receiving messages from the bms (bms/stat and bms/cellstat) and are also publishing "inverter/data".

By default, the inverter doesn't log but puts its output in a tmux window. Attach to it:

$ tmux a -t inverter_handler

 Received message b'{"static_nominal_capacity": 60000, "static_max_power": 6000, "static_max_voltage": 402.56, "static_min_voltage": 242.14000000000001, "status": 3, "mode": 128, "soc": 49.1, "soh": 88.
30694275274057, "target_discharge_power": 7962, "target_charge_power": 7962, "batt_voltage": 361.92, "batt_power": 0.0, "cell_temp_min": 23, "cell_temp_max": 27, "cell_voltage_min": 3.764, "cell_voltage
_max": 3.778, "last_updated": 1687761586.444323, "last_updated_soc": 1687761586.3737097}' on topic 'inverter/data' with QoS 0
Got mqtt update                                                                                                                                                                                           data received: {'static_nominal_capacity': 60000, 'static_max_power': 6000, 'static_max_voltage': 402.56, 'static_min_voltage': 242.14000000000001, 'status': 3, 'mode': 128, 'soc': 49.1, 'soh': 88.30694
275274057, 'target_discharge_power': 7962, 'target_charge_power': 7962, 'batt_voltage': 361.92, 'batt_power': 0.0, 'cell_temp_min': 23, 'cell_temp_max': 27, 'cell_voltage_min': 3.764, 'cell_voltage_max'
: 3.778, 'last_updated': 1687761586.444323, 'last_updated_soc': 1687761586.3737097}                            
Registers updated from mqtt data                                     
Check register - start - diff: 0                                                                                                                                                                         
101: [21321, 1, 16985, 17408, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16985, 17440, 16993, 29812, 25970, 31021, 17007, 30752, 20594, 25965, 26997, 27936, 18518, 0, 0, 0, 13614, 12288, 0, 0, 0, 0, 0, 0
, 13102, 12598, 0, 0, 0, 0, 0, 0, 20581, 27756, 25856, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
201: [0, 0, 32000, 6000, 6000, 4025, 2421, 53248, 10, 53248, 10, 0, 0]                                                                                                                                    
301: [3, 0, 128, 4910, 32000, 15712, 7962, 7962, 3619, 0, 3619, 0, 230, 270, 0, 0, 0, 0, 0, 0, 0, 0, 270, 8830]
401: [1, 65280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 
1001: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Here we can see that the inverter_handler is receiving mqtt updates and that the registers are filled. Good.

Now, if it's not working it might be easier to start each component in different terminals....

(terminal 1) $ python3 bms_handler.py


(terminal 2) $ python3 logic_handler.py
Reading from: config.yaml

Starting logic for gen24 and tesla_model3
Loading module for inverter:  gen24
MQTT connection problem


(terminal 3) $ python3 inverter_handler.py

"MQTT connection problem" could be several things...

  • mosquitto-server is not running or misconfigured
  • the mqtt hostname/IP address in config.yaml is incorrect
  • the mqtt username/password in config.yaml is incorrect

Verify that mqtt messages are published by subscribing:

$ mosquitto_sub -h localhost -t bms/stat -u "batt2gen24" -P "batt2gen24_pass"
{"stat_batt_voltage": 362.23, "last_updated": 1687763169.5818655, "stat_batt_current": 0.5, "stat_batt_power": 181.115, "stat_soc": 49.1, "last_updated_soc": 1687763169.5565157, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
{"stat_batt_voltage": 362.23, "last_updated": 1687763169.5818655, "stat_batt_current": 0.5, "stat_batt_power": 181.115, "stat_soc": 49.1, "last_updated_soc": 1687763169.5565157, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
{"stat_batt_voltage": 362.34000000000003, "last_updated": 1687763171.613073, "stat_batt_current": 0.5, "stat_batt_power": 181.17000000000002, "stat_soc": 49.1, "last_updated_soc": 1687763171.5606484, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
{"stat_batt_voltage": 362.34000000000003, "last_updated": 1687763171.613073, "stat_batt_current": 0.5, "stat_batt_power": 181.17000000000002, "stat_soc": 49.1, "last_updated_soc": 1687763171.5606484, "stat_bat_beginning_of_life": 82.1, "stat_min_cell_t": 23, "stat_max_cell_t": 27, "stat_nominal_full_pack_energy": 72.5, "stat_min_cell_u": 3.764, "stat_max_cell_u": 3.78}
^C

$ mosquitto_sub -h localhost -t inverter/data -u "batt2gen24" -P "batt2gen24_pass"
{"static_nominal_capacity": 60000, "static_max_power": 6000, "static_max_voltage": 402.56, "static_min_voltage": 242.14000000000001, "status": 3, "mode": 129, "soc": 49.1, "soh": 88.30694275274057, "target_discharge_power": 7974, "target_charge_power": 7974, "batt_voltage": 362.48, "batt_power": 181.24, "cell_temp_min": 23, "cell_temp_max": 27, "cell_voltage_min": 3.764, "cell_voltage_max": 3.78, "last_updated": 1687762796.7101412, "last_updated_soc": 1687762796.6628082}
{"static_nominal_capacity": 60000, "static_max_power": 6000, "static_max_voltage": 402.56, "static_min_voltage": 242.14000000000001, "status": 3, "mode": 129, "soc": 49.1, "soh": 88.30694275274057, "target_discharge_power": 7971, "target_charge_power": 7971, "batt_voltage": 362.33, "batt_power": 181.165, "cell_temp_min": 23, "cell_temp_max": 27, "cell_voltage_min": 3.764, "cell_voltage_max": 3.78, "last_updated": 1687762798.762247, "last_updated_soc": 1687762798.6626687}
^C

Integrating with Home assistant

If you wish to get all nice data from your bms (more than one maybe?) into you home automation system, here is how to do it for Home Assistant:

  • edit config.yaml, head over to "GLOBAL CONFIG - MQTT FOR SMARTHOME" section and modify the parameters to match you HA setup
  • restart the bms_handler
  • in HA, make sure that the MQTT integration is enabled, and add a couple of sensors in HA configuration.yaml:
mqtt:
  sensor:
    - name: tesla_soc
      state_topic: "tele/batt2gen24_hassbian/STATE"
      value_template: '{{ value_json["bms/stat"]["stat_soc"] }}'
      qos: 0
    - name: tesla_power
      state_topic: "tele/batt2gen24_hassbian/STATE"
      value_template: '{{ value_json["bms/stat"]["stat_batt_power"] }}'
      qos: 0
    - name: leaf_soc
      state_topic: "tele/batt2gen24_batt2inverter-pi/STATE"
      value_template: '{{ value_json["bms/stat"]["stat_soc"] }}'
      qos: 0
    - name: leaf_power
      state_topic: "tele/batt2gen24_batt2inverter-pi/STATE"
      value_template: '{{ value_json["bms/stat"]["stat_batt_power"] }}'
      qos: 0

In the example above, there are two sources, one coming from host: "batt2inverter-pi", and another from: "hassbian".

Virtual can interface

When playing around with the bms_handler, it's quite convenient to replay saved dumps instead of always being connected to real hardware. To get vcan up and running...

$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set up vcan0
$ canplayer -I m3-michael.log