From 763c2721279d9dfa744c9edf60c3b01e9ec295ce Mon Sep 17 00:00:00 2001 From: Ben Pirt Date: Thu, 30 Nov 2023 08:50:29 +0000 Subject: [PATCH] Add hysteresis on detecting over voltage errors This allows time to reduce the charge current before shutting down --- README.md | 7 ++++--- battery/battery_cell.py | 15 ++++++++++++++- bms/config.py | 2 ++ test/battery/test_BatteryCell.py | 8 ++++++++ test/battery/test_BatteryModule.py | 4 ++++ test/battery/test_BatteryPack.py | 4 ++++ 6 files changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a3d088..7e7b533 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ Which will produce `build/out/tesla-bms-emulator.uf2`. You can drag and drop thi ## To Do +- [ ] Dynamically scale back the charge current to avoid going over voltage +- [x] Add hysteresis on over voltage faults - [x] Simulator for integration testing over socat virtual serial port - [x] Cell balancing - [x] Battery Management System controlling contactors @@ -123,8 +125,7 @@ Which will produce `build/out/tesla-bms-emulator.uf2`. You can drag and drop thi - [x] Handle communication faults - [x] Re-work fault level settings - [x] State of charge from current sensor -- [ ] MQTT Support -- [ ] Design hardware -- [ ] Support for contactors per string +- [x] MQTT Support +- [x] Design hardware - [ ] HTTPS - [ ] Basic Auth diff --git a/battery/battery_cell.py b/battery/battery_cell.py index 329d239..5dd7b7a 100644 --- a/battery/battery_cell.py +++ b/battery/battery_cell.py @@ -1,4 +1,6 @@ from __future__ import annotations + +from hal import get_interval from .constants import OVER_VOLTAGE, UNDER_VOLTAGE from typing import TYPE_CHECKING, List if TYPE_CHECKING: @@ -13,10 +15,21 @@ def __init__(self, config: Config): self.over_voltage_fault_override = False self.under_voltage_fault_override = False self._config = config + self._over_voltage_interval = get_interval() + self._over_voltage_fault = False @property def over_voltage_fault(self) -> bool: - return self.voltage >= self._config.high_voltage_fault_level or self.over_voltage_fault_override + if self.voltage >= self._config.high_voltage_fault_level or self.over_voltage_fault_override: + if not self._over_voltage_fault: + self._over_voltage_fault = True + self._over_voltage_interval.set( + self._config.over_voltage_hysteresis_time) + else: + return self._over_voltage_interval.ready + else: + self._over_voltage_fault = False + return False @property def under_voltage_fault(self) -> bool: diff --git a/bms/config.py b/bms/config.py index 9ed3f76..6624776 100644 --- a/bms/config.py +++ b/bms/config.py @@ -106,6 +106,8 @@ def __init__(self, file="config.json"): self.charge_hysteresis_voltage: float = 0.1 # The hysteresis offset time (in seconds) for enabling and disabling charging and discharging self.charge_hysteresis_time: float = 30.0 + # The hysteresis time for over voltage faults in seconds + self.over_voltage_hysteresis_time: float = 10.0 self.read() diff --git a/test/battery/test_BatteryCell.py b/test/battery/test_BatteryCell.py index f2a7619..dce1979 100644 --- a/test/battery/test_BatteryCell.py +++ b/test/battery/test_BatteryCell.py @@ -1,6 +1,7 @@ from battery import BatteryCell import unittest from battery.constants import OVER_VOLTAGE, UNDER_VOLTAGE +from time import sleep from bms import Config @@ -8,6 +9,7 @@ class BatteryCellTestCase(unittest.TestCase): def setUp(self): c = Config("config.default.json") + c.over_voltage_hysteresis_time = 0.01 self.cell = BatteryCell(c) def test_store_highest_voltage(self): @@ -34,6 +36,8 @@ def test_store_lowest_voltage(self): def test_over_voltage_fault(self): self.cell.voltage = 4.2 + self.assertFalse(self.cell.over_voltage_fault) + sleep(0.01) self.assertTrue(self.cell.over_voltage_fault) def test_over_voltage_alert(self): @@ -54,6 +58,8 @@ def test_fault(self): self.cell.voltage = 3.0 self.assertTrue(self.cell.fault) self.cell.voltage = 5.0 + self.assertFalse(self.cell.fault) + sleep(0.01) self.assertTrue(self.cell.fault) def test_faults(self): @@ -62,6 +68,8 @@ def test_faults(self): self.cell.voltage = 3.0 self.assertEqual(self.cell.faults, [UNDER_VOLTAGE]) self.cell.voltage = 5.0 + self.assertEqual(self.cell.faults, []) + sleep(0.01) self.assertEqual(self.cell.faults, [OVER_VOLTAGE]) def test_alerts(self): diff --git a/test/battery/test_BatteryModule.py b/test/battery/test_BatteryModule.py index ade2dc9..36c4dc2 100644 --- a/test/battery/test_BatteryModule.py +++ b/test/battery/test_BatteryModule.py @@ -2,6 +2,7 @@ from battery.battery_module import BatteryModule import unittest from battery.constants import COMMS, OVER_TEMPERATURE, OVER_VOLTAGE, UNDER_TEMPERATURE, UNDER_VOLTAGE +from time import sleep from bms import Config @@ -9,6 +10,7 @@ class BatteryModuleTestCase(unittest.TestCase): def setUp(self): c = Config("config.default.json") + c.over_voltage_hysteresis_time = 0.01 self.module = BatteryModule(c) for i in range(4): cell = BatteryCell(c) @@ -129,6 +131,8 @@ def test_over_voltage_faults(self): self.module.cells[1].voltage = 4.2 self.module.cells[2].voltage = 4.2 self.module.cells[3].voltage = 4.2 + self.assertEqual(self.module.faults, []) + sleep(0.01) self.assertEqual(self.module.faults, [OVER_VOLTAGE]) self.assertTrue(self.module.fault) diff --git a/test/battery/test_BatteryPack.py b/test/battery/test_BatteryPack.py index 2397f23..e9e1bae 100644 --- a/test/battery/test_BatteryPack.py +++ b/test/battery/test_BatteryPack.py @@ -2,12 +2,14 @@ import unittest from battery.constants import COMMS, OVER_VOLTAGE, OVER_TEMPERATURE, UNDER_VOLTAGE, UNDER_TEMPERATURE, BALANCE from bms import Config +from time import sleep class BatteryPackTestCase(unittest.TestCase): def setUp(self): self.config = Config("config.default.json") self.config.parallel_string_count = 2 + self.config.over_voltage_hysteresis_time = 0.01 self.pack = BatteryPack(self.config) for m in range(4): module = BatteryModule(self.config) @@ -103,6 +105,8 @@ def test_over_voltage_faults(self): self.pack.modules[0].cells[1].voltage = 4.15 self.pack.modules[0].cells[2].voltage = 4.15 self.pack.modules[0].cells[3].voltage = 4.15 + self.assertEqual(self.pack.faults, []) + sleep(0.01) self.assertEqual(self.pack.faults, [OVER_VOLTAGE]) self.assertTrue(self.pack.fault)