diff --git a/delegates/dvcc.py b/delegates/dvcc.py index b5b850f..0c53290 100644 --- a/delegates/dvcc.py +++ b/delegates/dvcc.py @@ -25,6 +25,9 @@ VEDIRECT_FIRMWARE_REQUIRED = 0x129 VECAN_FIRMWARE_REQUIRED = 0x10200 # 1.02, 24-bit version +class Flags(object): + InverterSwitchedOff = 1 # inverter was switched off when discharge was disabled + # This is a place to account for some BMS quirks where we may have to ignore # the BMS value and substitute our own. @@ -244,7 +247,8 @@ def update_values(self): class Inverter(object): """ Encapsulates an inverter object. """ - def __init__(self, monitor, service): + def __init__(self, dvcc, monitor, service): + self.dvcc = dvcc self.monitor = monitor self.service = service @@ -259,9 +263,16 @@ def mode(self, m): def set_maxdischargecurrent(self, limit): """ For plain inverters, a limit of zero turns it off, and a non-zero limit turns it back on. """ - m = 2 if limit > 0 else 4 # 2 == invert-only, 4 == off - if self.mode != m: - self.mode = m + if limit == 0 and self.mode != 4: + self.dvcc.flags |= Flags.InverterSwitchedOff + self.mode = 4 # off + elif limit > 0 and self.mode != 2: + # If the inverter was turned off by us, switch it back on + # This avoids turning the inverter on if it was previously + # soft-switched off by something other than DVCC. + if self.dvcc.flags & Flags.InverterSwitchedOff: + self.dvcc.flags &= ~Flags.InverterSwitchedOff + self.mode = 2 # inverter only class InverterCharger(SolarCharger, Inverter): """ Encapsulates an inverter/charger object, currently the inverter RS, @@ -269,7 +280,7 @@ class InverterCharger(SolarCharger, Inverter): charger, but is also an inverter. """ def __init__(self, monitor, service): - super(InverterCharger, self).__init__(monitor, service) + SolarCharger.__init__(self, monitor, service) @property def has_externalcontrol_support(self): @@ -294,7 +305,8 @@ def set_maxdischargecurrent(self, limit): class InverterSubsystem(object): """ Encapsulate collection of inverters. """ - def __init__(self, monitor): + def __init__(self, dvcc, monitor): + self.dvcc = dvcc self.monitor = monitor self._inverters = {} @@ -303,7 +315,7 @@ def _add_inverter(self, ob): return ob def add_inverter(self, service): - return self._add_inverter(Inverter(self.monitor, service)) + return self._add_inverter(Inverter(self.dvcc, self.monitor, service)) def remove_inverter(self, service): del self._inverters[service] @@ -756,12 +768,14 @@ def get_input(self): '/Link/NetworkMode']), ('com.victronenergy.settings', [ '/Settings/CGwacs/OvervoltageFeedIn', + '/Settings/Dvcc/Flags', '/Settings/Services/Bol'])] def get_settings(self): return [ ('maxchargecurrent', '/Settings/SystemSetup/MaxChargeCurrent', -1, -1, 10000), ('maxchargevoltage', '/Settings/SystemSetup/MaxChargeVoltage', 0.0, 0.0, 80.0), + ('dvcc_flags', '/Settings/Dvcc/Flags', 0, 0, 0), ('bol', '/Settings/Services/Bol', 0, 0, 7) ] @@ -769,7 +783,7 @@ def set_sources(self, dbusmonitor, settings, dbusservice): SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) self._batterysystem = BatterySubsystem(dbusmonitor) self._solarsystem = SolarChargerSubsystem(dbusmonitor) - self._inverters = InverterSubsystem(dbusmonitor) + self._inverters = InverterSubsystem(self, dbusmonitor) self._multi = Multi(dbusmonitor, dbusservice) self._dbusservice.add_path('/Control/SolarChargeVoltage', value=0) @@ -848,6 +862,14 @@ def has_dvcc(self): v = self._settings['bol'] return bool(v & 1) + @property + def flags(self): + return self._settings['dvcc_flags'] + + @flags.setter + def flags(self, v): + self._settings['dvcc_flags'] = int(v) + @property def bms(self): bmses = sorted(self._batterysystem.bmses, diff --git a/tests/hub_test.py b/tests/hub_test.py index f25b49a..c2a00e4 100644 --- a/tests/hub_test.py +++ b/tests/hub_test.py @@ -1755,6 +1755,7 @@ def test_inverter_rs_discharge_current(self): '/Link/DischargeCurrent': 0.0}}) def test_inverter_switchoff_bms(self): + from delegates.dvcc import Dvcc # Add inverter self._add_device('com.victronenergy.inverter.ttyO1', { '/Ac/Out/L1/P': 2000, @@ -1791,9 +1792,58 @@ def test_inverter_switchoff_bms(self): self._check_external_values({ 'com.victronenergy.inverter.ttyO1': { '/Mode': 4}}) # OFF + self.assertTrue(Dvcc.instance.flags & 1) # InverterSwitchedOff flag set self._monitor.set_value('com.victronenergy.battery.ttyO2', '/Info/MaxDischargeCurrent', 100.0) self._update_values(interval=3000) self._check_external_values({ 'com.victronenergy.inverter.ttyO1': { '/Mode': 2}}) # InvertOnly + + self.assertFalse(Dvcc.instance.flags & 1) # InverterSwitchedOff flag unset + + def test_inverter_switchoff_bms2(self): + # Leave inverter off and don't turn it back on if it wasn't turned off + # by us. + # Add inverter + self._add_device('com.victronenergy.inverter.ttyO1', { + '/Ac/Out/L1/P': 2000, + '/Ac/Out/L1/V': 234.2, + '/Dc/0/Voltage': 53.1, + '/Dc/0/Current': 40.0, + '/DeviceInstance': 278, + '/Soc': 53.2, + '/State': 9, + '/Mode': 4, # Off + '/IsInverterCharger': 0, # Dumb inverter + '/Link/NetworkMode': 0, + '/Link/ChargeVoltage': None, + '/Link/ChargeCurrent': None, + '/Link/DischargeCurrent': None, + '/Settings/ChargeCurrentLimit': 100}, + product_name='Inverter RS', connection='VE.Direct') + + # Battery + self._add_device('com.victronenergy.battery.ttyO2', + product_name='battery', + values={ + '/Dc/0/Voltage': 53.2, + '/Dc/0/Current': 80.0, + '/Dc/0/Power': 4000, + '/Soc': 43.2, + '/DeviceInstance': 512, + '/Info/BatteryLowVoltage': 47, + '/Info/MaxChargeCurrent': 100, + '/Info/MaxChargeVoltage': 58.2, + '/Info/MaxDischargeCurrent': 0.0}) + + self._update_values(interval=3000) + self._check_external_values({ + 'com.victronenergy.inverter.ttyO1': { + '/Mode': 4}}) # OFF + + self._monitor.set_value('com.victronenergy.battery.ttyO2', '/Info/MaxDischargeCurrent', 100.0) + self._update_values(interval=3000) + self._check_external_values({ + 'com.victronenergy.inverter.ttyO1': { + '/Mode': 4}}) # Remains OFF