Esempio n. 1
0
    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
Esempio n. 3
0
	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 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)
Esempio n. 7
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)
Esempio n. 8
0
	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)
Esempio n. 9
0
	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
Esempio n. 10
0
	def capacity(self):
		""" Total capacity if all chargers are running at full power. """
		return safeadd(*(c.currentlimit for c in self._solarchargers.values()))
Esempio n. 11
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_async(service, '/Link/ChargeVoltage', charge_voltage)
					voltage_written = 1
				except DBusException:
					pass

		return (voltage_written, current_written)
Esempio n. 12
0
	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
Esempio n. 13
0
	def capacity(self):
		""" Total capacity if all chargers are running at full power. """
		return safeadd(*(c.currentlimit for c in self._solarchargers.values()))
Esempio n. 14
0
	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)