def _update_battery_operational_limits(self, bms_service): """ This function writes the bms parameters across to the Multi if it exists. The parameters may be modified before being copied across. The modified current value is returned to be used elsewhere. """ cv = safeadd(bms_service.chargevoltage, self.invertervoltageoffset) mcc = safeadd(bms_service.maxchargecurrent, self.currentoffset) cv, mcc = self._adjust_battery_operational_limits(bms_service, cv, mcc) if self._multi.active: try: if cv is not None: self._multi.bol.chargevoltage = cv if mcc is not None: self._multi.bol.maxchargecurrent = mcc # Copy the rest unmodified copy_dbus_value(self._dbusmonitor, bms_service.service, '/Info/BatteryLowVoltage', self._multi.service, '/BatteryOperationalLimits/BatteryLowVoltage') copy_dbus_value( self._dbusmonitor, bms_service.service, '/Info/MaxDischargeCurrent', self._multi.service, '/BatteryOperationalLimits/MaxDischargeCurrent') return 1, cv, mcc except DBusException: logging.debug(traceback.format_exc()) return 0, cv, mcc
def _on_timer(self): # If there is more than one BYD battery, we can't do this. if len(self.batteries) != 1: return True battery = next(iter(self.batteries)) # If this battery is not the selected battery service, there is no # point in improving the SOC. Only do this if the BYD battery is # the battery service. if battery != self.systemcalc._batteryservice: return True # We cannot use the current from the battery monitor, because # 1) that is probably ourselves, 2) it could be another battery, 3) if # it is a BMV, we already have good SOC tracking and this is pointless. # So calculate the battery current by taking the vebus DC current and # adding solarcharger current. battery_current = safeadd(self._dbusservice['/Dc/Vebus/Current'], self._dbusservice['/Dc/Pv/Current']) if battery_current is not None: self._dbusmonitor.set_value_async(battery, '/Sense/Current', float(battery_current)) return True
def _adjust_battery_operational_limits(self, bms_service, feedback_allowed): """ Take the charge voltage and maximum charge current from the BMS and adjust it as necessary. For now we only implement quirks for batteries known to have them. """ cv = bms_service.chargevoltage mcc = bms_service.maxchargecurrent quirk = QUIRKS.get(bms_service.product_id) if quirk is not None: # If any quirks are registered for this battery, use that # instead. cv, mcc, feedback_allowed = quirk(self, bms_service, cv, mcc, feedback_allowed) # Add debug offsets if cv is not None: cv = safeadd(cv, self.invertervoltageoffset) if mcc is not None: mcc = safeadd(mcc, self.currentoffset) return cv, mcc, feedback_allowed
def update_values(self, newvalues): # Sync SOC with all non-VE.Bus inverter-chargers if self.systemcalc.batteryservice is not None: origin = self._dbusmonitor.get_value( self.systemcalc.batteryservice, '/Mgmt/Connection') soc = newvalues.get('/Dc/Battery/Soc', None) if soc is not None and origin and origin != 'VE.Can': for service in self.vecan: # In case service goes down while we write, ignore # exception try: self._dbusmonitor.set_value_async( service, '/Link/Soc', soc) except DBusException: pass # Sync ExtraBatteryCurrent, but only consider currents from # VE.Direct chargers and the Multi pv_current = 0 for service in self.solarchargers: pv_current = safeadd( pv_current, self._dbusmonitor.get_value(service, '/Dc/0/Current', 0), -(self._dbusmonitor.get_value(service, '/Load/I', 0) or 0)) # Add current from Multi multi = Multi.instance.multi pv_current = safeadd(pv_current, getattr(multi, 'dc_current', None)) for service in self.vecan: try: self._dbusmonitor.set_value_async(service, '/Link/ExtraBatteryCurrent', pv_current) except DBusException: pass else: # This control flag is created by the VebusSocWriter delegate. # We set the same one in case the extra battery current was # successfully written to an Inverter RS. newvalues['/Control/ExtraBatteryCurrent'] |= 1
def test_safe_add(self): from sc_utils import safeadd self.assertTrue(safeadd() is None) self.assertTrue(safeadd(None, None) is None) self.assertTrue(safeadd(1, None) == 1) self.assertTrue(safeadd(1, 2, 3) == 6) self.assertTrue(safeadd(1, 2, 3, None) == 6) self.assertTrue(safeadd(0) == 0) self.assertTrue(safeadd(0, None) == 0)
def _legacy_update_solarchargers(self): """ This is the old implementation we used before DVCC. It is kept here so we can fall back to it where DVCC is not fully supported, and to avoid maintaining two copies of systemcalc. """ max_charge_current = None for battery in self._batterysystem: max_charge_current = safeadd(max_charge_current, \ self._dbusmonitor.get_value(battery.service, '/Info/MaxChargeCurrent')) # Workaround: copying the max charge current from BMS batteries to the solarcharger leads to problems: # excess PV power is not fed back to the grid any more, and loads on AC-out are not fed with PV power. # PV power is used for charging the batteries only. # So we removed this feature, until we have a complete solution for solar charger support. Until then # we set a 'high' max charge current to avoid 'BMS connection lost' alarms from the solarcharger. if max_charge_current is not None: max_charge_current = 1000 vebus_path = self._multi.service if self._multi.active else None charge_voltage = None if vebus_path is None else \ self._dbusmonitor.get_value(vebus_path, '/Hub/ChargeVoltage') if charge_voltage is None and max_charge_current is None: return (0, 0) # Network mode: # bit 0: Operated in network environment # bit 2: Remote Hub-1 control # bit 3: Remote BMS control network_mode = 1 | (0 if charge_voltage is None else 4) | (0 if max_charge_current is None else 8) voltage_written = 0 current_written = 0 for charger in self._solarsystem: try: # We use /Link/NetworkMode to detect Hub support in the solarcharger. Existence of this item # implies existence of the other /Link/* fields. if charger.networkmode is None: continue charger.networkmode = network_mode if charge_voltage is not None: charger.chargevoltage = charge_voltage voltage_written = 1 if max_charge_current is not None: charger.maxchargecurrent = max_charge_current current_written = 1 except DBusException: # If the charger for whatever reason doesn't have the /Link # path, ignore it. This is the legacy implementation and # better to keep it for the moment. pass if charge_voltage is not None and self._solarsystem.has_vecan_chargers: # Charge voltage cannot by written directly to the CAN-bus solar chargers, we have to use # the com.victronenergy.vecan.* service instead. # Writing charge current to CAN-bus solar charger is not supported yet. for service in self._vecan_services: try: # Note: we don't check the value of charge_voltage_item because it may be invalid, # for example if the D-Bus path has not been written for more than 60 (?) seconds. # In case there is no path at all, the set_value below will raise an DBusException # which we will ignore cheerfully. self._dbusmonitor.set_value(service, '/Link/ChargeVoltage', charge_voltage) voltage_written = 1 except DBusException: pass return (voltage_written, current_written)
def _calculate_battery_operational_limits(self, bms_service): """ This function obtains and calculates the charge voltage and charge current specified by the BMS. """ cv = safeadd(bms_service.chargevoltage, self.invertervoltageoffset) mcc = safeadd(bms_service.maxchargecurrent, self.currentoffset) return self._adjust_battery_operational_limits(bms_service, cv, mcc)
def smoothed_current(self): """ Total smoothed current, calculated by adding the smoothed current of the individual chargers. """ return safeadd(*(c.smoothed_current for c in self._solarchargers.values())) or 0
def capacity(self): """ Total capacity if all chargers are running at full power. """ return safeadd(*(c.currentlimit for c in self._solarchargers.values()))
def _legacy_update_solarchargers(self): """ This is the old implementation we used before DVCC. It is kept here so we can fall back to it where DVCC is not fully supported, and to avoid maintaining two copies of systemcalc. """ max_charge_current = None for battery in self._batterysystem: max_charge_current = safeadd(max_charge_current, \ self._dbusmonitor.get_value(battery.service, '/Info/MaxChargeCurrent')) # Workaround: copying the max charge current from BMS batteries to the solarcharger leads to problems: # excess PV power is not fed back to the grid any more, and loads on AC-out are not fed with PV power. # PV power is used for charging the batteries only. # So we removed this feature, until we have a complete solution for solar charger support. Until then # we set a 'high' max charge current to avoid 'BMS connection lost' alarms from the solarcharger. if max_charge_current is not None: max_charge_current = 1000 vebus_path = self._multi.service if self._multi.active else None charge_voltage = None if vebus_path is None else \ self._dbusmonitor.get_value(vebus_path, '/Hub/ChargeVoltage') if charge_voltage is None and max_charge_current is None: return (0, 0) # Network mode: # bit 0: Operated in network environment # bit 2: Remote Hub-1 control # bit 3: Remote BMS control network_mode = 1 | (0 if charge_voltage is None else 4) | (0 if max_charge_current is None else 8) voltage_written = 0 current_written = 0 for charger in self._solarsystem: try: # We use /Link/NetworkMode to detect Hub support in the solarcharger. Existence of this item # implies existence of the other /Link/* fields. if charger.networkmode is None: continue charger.networkmode = network_mode if charge_voltage is not None: charger.chargevoltage = charge_voltage voltage_written = 1 if max_charge_current is not None: charger.maxchargecurrent = max_charge_current current_written = 1 except DBusException: # If the charger for whatever reason doesn't have the /Link # path, ignore it. This is the legacy implementation and # better to keep it for the moment. pass if charge_voltage is not None and self._solarsystem.has_vecan_chargers: # Charge voltage cannot by written directly to the CAN-bus solar chargers, we have to use # the com.victronenergy.vecan.* service instead. # Writing charge current to CAN-bus solar charger is not supported yet. for service in self._vecan_services: try: # Note: we don't check the value of charge_voltage_item because it may be invalid, # for example if the D-Bus path has not been written for more than 60 (?) seconds. # In case there is no path at all, the set_value below will raise an DBusException # which we will ignore cheerfully. self._dbusmonitor.set_value_async(service, '/Link/ChargeVoltage', charge_voltage) voltage_written = 1 except DBusException: pass return (voltage_written, current_written)
def _update_solarchargers(self): max_charge_current = None for battery_service in self._battery_services: max_charge_current = safeadd(max_charge_current, \ self._dbusmonitor.get_value(battery_service, '/Info/MaxChargeCurrent')) # Workaround: copying the max charge current from BMS batteries to the solarcharger leads to problems: # excess PV power is not fed back to the grid any more, and loads on AC-out are not fed with PV power. # PV power is used for charging the batteries only. # So we removed this feature, until we have a complete solution for solar charger support. Until then # we set a 'high' max charge current to avoid 'BMS connection lost' alarms from the solarcharger. if max_charge_current is not None: max_charge_current = 1000 vebus_path = self._get_vebus_path() charge_voltage = None if vebus_path is None else \ self._dbusmonitor.get_value(vebus_path, '/Hub/ChargeVoltage') if charge_voltage is None and max_charge_current is None: return (0, 0) # Network mode: # bit 0: Operated in network environment # bit 2: Remote Hub-1 control # bit 3: Remote BMS control network_mode = 1 | (0 if charge_voltage is None else 4) | (0 if max_charge_current is None else 8) has_vecan_charger = False voltage_written = 0 current_written = 0 for service in self._solarchargers: try: has_vecan_charger = has_vecan_charger or \ (self._dbusmonitor.get_value(service, '/Mgmt/Connection') == 'VE.Can') # We use /Link/NetworkMode to detect Hub support in the solarcharger. Existence of this item # implies existence of the other /Link/* fields. if self._dbusmonitor.get_value(service, '/Link/NetworkMode') is None: continue self._dbusmonitor.set_value(service, '/Link/NetworkMode', network_mode) if charge_voltage is not None: self._dbusmonitor.set_value(service, '/Link/ChargeVoltage', charge_voltage) voltage_written = 1 # solarcharger firmware v1.17 does not support link items. Version v1.17 itself requires # the vebus state to be copied to the solarcharger (otherwise the charge voltage would be # ignored). v1.18 and later do not have this requirement. firmware_version = self._dbusmonitor.get_value(service, '/FirmwareVersion') if firmware_version is not None and (firmware_version & 0x0FFF) == 0x0117: state = self._dbusmonitor.get_value(vebus_path, '/State') if state is not None: self._dbusmonitor.set_value(service, '/State', state) if max_charge_current is not None: self._dbusmonitor.set_value(service, '/Link/ChargeCurrent', max_charge_current) current_written = 1 except dbus.exceptions.DBusException: pass if has_vecan_charger and charge_voltage is not None: # Charge voltage cannot by written directly to the CAN-bus solar chargers, we have to use # the com.victronenergy.vecan.* service instead. # Writing charge current to CAN-bus solar charger is not supported yet. for service in self._vecan_services: try: # Note: we don't check the value of charge_voltage_item because it may be invalid, # for example if the D-Bus path has not been written for more than 60 (?) seconds. # In case there is no path at all, the set_value below will raise an DBusException # which we will ignore cheerfully. self._dbusmonitor.set_value(service, '/Link/ChargeVoltage', charge_voltage) voltage_written = 1 except dbus.exceptions.DBusException: pass return (voltage_written, current_written)