Beispiel #1
0
def amp2amp(amp: Union[float, int]) -> int:
    """Limitiere Ampere auf min/max und runde ab auf Ganze"""
    config = OpenWBconfig()
    if amp < 1:
        return 0
    elif amp < config.get('minimalstromstaerke'):
        amp = config.get('minimalstromstaerke')
    elif amp > config.get('maximalstromstaerke'):
        amp = config.get('maximalstromstaerke')
    return int(amp)
Beispiel #2
0
class DependentData:
    priority = 1  # Dependent data has highest data dependency priority

    def __init__(self):
        Scheduler().registerData(
            ['evu/W', 'pv/W', 'global/WAllChargePoints', 'housebattery/W'],
            self)
        self.data = openWBValues()
        self.config = OpenWBconfig()

    def _speicherreserve(self) -> int:
        """Passe Überschuss an um die Reserven, die der Speicher zur Verfügung stellt"""
        Pbatt = self.data.get('housebattery/W')
        speicherprio = self.config.get('speicherpveinbeziehen')
        if Pbatt < 0:
            return Pbatt  # Speicherentnahme ist negativer Überschuss
        if speicherprio == 0:  # Speicher hat Priorität
            return 0
        elif speicherprio == 1:  # EV hat unbedingte Priorität
            return Pbatt
        elif speicherprio == 2:  # Auto -> Je leerer der Speicher, desto mehr reservierte Leistung
            speicher_reserve = int(
                ((100 - self.data.get('housebattery/%Soc')) *
                 self.config.get('speichermaxp', 3000)) / 100)
            return max(Pbatt - speicher_reserve, 0)

    def newdata(self, updated: dict) -> None:
        packet = DataPackage(
            self, {
                'global/uberschuss':
                self._speicherreserve() - self.data.get('evu/W'),
                'global/WHouseConsumption':
                self.data.get('evu/W') + self.data.get('pv/W') -
                self.data.get('global/WAllChargePoints') -
                self.data.get('housebattery/W')
            })
        self.data.update(packet)
Beispiel #3
0
 def test_default(self):
     config = OpenWBconfig(self.testfile)
     self.assertEqual(config.get("offsetpvpeak"), 6500,
                      "Shall return the default")
     self.assertEqual(config.get("offsetpvpeak", 200), 200,
                      "Given default has precedence over global default")
Beispiel #4
0
class Regler:
    """Eine Reglerinstanz"""
    def __init__(self, wallbox: "Ladepunkt"):
        self.wallbox = wallbox
        self.oncount = 0
        self.offcount = 0
        self.blockcount = 0
        self.state = 'idle'
        self.config = OpenWBconfig()
        self.request = self.req_idle
        self.logger = logging.getLogger(self.__class__.__name__ +
                                        f'-{self.wallbox.id}')

    def __repr__(self):
        return f"<Regler #{self.wallbox.id}>"

    @property
    def blocked(self) -> bool:
        return self.wallbox.is_blocked

    def get_props(self) -> Request:
        props = self.wallbox.powerproperties()
        request = Request(self.wallbox.id, prio=self.wallbox.prio)
        debugstr = f'Wallbox setP: {self.wallbox.setP} minP: {props.minP}'
        if self.wallbox.is_charging:
            request.flags.add('on')
            debugstr += " (on)"
            if props.inc > 0 and self.wallbox.setP - props.inc >= props.minP:  # Verringerung noch möglich
                request += RequestPoint('min-P', props.inc)
                request += RequestPoint('max-P',
                                        self.wallbox.setP - props.minP)
            elif not self.config[self.wallbox.configprefix +
                                 '_alwayson']:  # Nein, nur ganz ausschalten
                request.flags.add('min')
                # Wenn noch über Min, lasse Reduzierung noch zu
                if self.wallbox.setP > props.minP:
                    request += RequestPoint('min-P',
                                            self.wallbox.setP - props.minP)
                else:
                    request += RequestPoint('min-P', self.wallbox.setP)
                request += RequestPoint('max-P', self.wallbox.setP)
            if self.blocked:  # Blockierter Ladepunkt bietet keine Leistungserhöhung an
                request.flags.add('blocked')  # Nicht wirklich benötigt
                debugstr += " (blocked)"
            elif self.wallbox.actP + props.inc <= props.maxP:  # Erhöhung noch möglich
                request += RequestPoint('min+P', props.inc)
                request += RequestPoint('max+P',
                                        props.maxP - self.wallbox.setP)
            else:
                request.flags.add('max')  # Nicht wirklich benötigt
                debugstr += " (max)"
        else:
            request.flags.add('off')
            debugstr += " (off)"
            if self.wallbox.setP < props.minP:  # Noch nicht eingeschaltet
                request += RequestPoint('min+P',
                                        props.minP - self.wallbox.setP)
                request += RequestPoint('max+P',
                                        props.maxP - self.wallbox.setP)
            else:  # Ladeende - versuche Leistung freizugeben
                request += RequestPoint('min-P', self.wallbox.setP)
                request += RequestPoint('max-P', self.wallbox.setP)

        self.logger.debug(debugstr)
        return request

    def req_idle(self, increment: int) -> None:
        """set function of PV/idle and PV/init mode"""
        # WB soll an sein
        if self.config[self.wallbox.configprefix + '_alwayson']:
            self.wallbox.set(self.wallbox.powerproperties().minP)
            self.request = self.req_charging
        elif self.oncount >= self.config.get('einschaltverzoegerung', 10):
            self.state = 'init'
            self.oncount = 0
            self.request = self.req_charging
            power = self.wallbox.setP + increment
            self.wallbox.set(power)
        elif increment == 0:  # Keine Anforderung
            self.oncount = 0
            self.state = 'idle'
        elif increment < 0:  # Negative Anforderung ist Reduzierung von Restleistung
            self.oncount = 0
            self.wallbox.set(self.wallbox.setP + increment)
        else:
            self.oncount += 1

    def req_charging(self, increment: int) -> None:
        """Set the given power"""
        power = self.wallbox.setP + increment
        self.logger.info("WB %i requested %iW" % (self.wallbox.id, power))
        if power < 100:
            self.offcount += 1
            if self.offcount >= self.config.get('abschaltverzoegerung', 20):
                self.state = 'idle'
                self.offcount = 0
                self.request = self.req_idle
                self.wallbox.set(power)
        else:
            self.offcount = 0
            self.wallbox.set(power)
            self.wallbox.zaehle_phasen()
Beispiel #5
0
class Regelgruppe:
    """
   Eine Regelungsgruppe, charakterisiert durch eine Regelungsstrategie:
   - "pv" - Überschuss regler
   - "peak" - Peak shaving
   - "sofort" - Sofortladen
   """
    priority = 500  # Regelung hat eine mittlere Priorität

    def __init__(self, mode: str):
        self.mode = mode
        self.regler = dict()
        self.config = OpenWBconfig()
        self.data = openWBValues()
        self.hysterese = int(self.config.get('hysterese'))
        self.logger = logging.getLogger(self.__class__.__name__ + "_" + mode)
        if self.mode == 'pv':
            """
            PV-Modus: Limit darf nicht unterschritten werden.
            - P > Limit: iO, aber erhöhe solange > Limit
            - P < Limit: reduziere bis P>limit
         """
            def get_increment(r: Request, deltaP: int) -> Optional[int]:
                if r['min+P'].value < deltaP:  # Akzeptiert
                    if r['max+P'].value < deltaP:  # Sogar Pmax
                        return r['max+P'].value
                    else:  # Dann liegt deltaP dazwischen
                        return deltaP

            def get_decrement(r: Request, deltaP: int) -> Optional[int]:
                if deltaP <= 0:  # Kein Bedarf
                    return None
                elif r['min-P'].value > deltaP:  # min+P reicht
                    return r['min-P'].value
                elif r['max-P'].value < deltaP:  # Pmax reicht noch nicht
                    return r['max-P'].value
                else:  # deltaP liegt zwischen beidem
                    return deltaP

            self.get_increment = get_increment
            self.get_decrement = get_decrement
            self.limit = int(self.config.get('offsetpv'))
        elif self.mode == 'peak':
            """
            Peak-Modus: Limit darf nicht überschritten werden.
            - P > Limit: erhöhe bis < Limit
            - P < Limit: reduziere solange < Limit
         """
            self.limit = int(self.config.get('offsetpvpeak'))

            def get_increment(r: Request, deltaP: int) -> Optional[int]:
                if deltaP <= 0:  # Kein Bedarf
                    return None
                elif r['min+P'].value > deltaP:  # min+P reicht
                    return r['min+P'].value
                elif r['max+P'].value < deltaP:  # Pmax reicht noch nicht
                    return r['max+P'].value
                else:  # deltaP liegt zwischen beidem
                    return deltaP

            def get_decrement(r: Request, deltaP: int) -> Optional[int]:
                if r['min-P'].value < deltaP:  # Akzeptiert
                    if r['max-P'].value < deltaP:  # Sogar Pmax
                        return r['max-P'].value
                    else:  # Dann liegt deltaP dazwischen
                        return deltaP

            self.get_increment = get_increment
            self.get_decrement = get_decrement

        elif self.mode == 'sofort':
            """
         Sofort-Lade-Modus:
         Keine Regelung,
         - setze auf Ladestrom lpmodul%i_sofortll (A)
         - bis zu kwH: lademkwh%i
         """
            def get_delta(r: Request, deltaP: int) -> int:
                return 0

            self.limit = 1  # dummy
            self.get_increment = get_delta
            self.get_decrement = get_delta

    def add(self, ladepunkt: "Ladepunkt") -> None:
        """Füge Ladepunkt hinzu"""
        self.regler[ladepunkt.id] = Regler(ladepunkt)

    def pop(self, id: int) -> "Ladepunkt":
        """Lösche Ladepunkt mit der ID <id>"""
        # TODO: Beibehaltung aktiver Lademodus
        if id in self.regler:
            return self.regler.pop(id).wallbox

    def __repr__(self):
        return "<Regelgruppe " + str(list(self.regler.keys())) + ">"

    @property
    def isempty(self) -> bool:
        """
      Gebe an, ob diese Gruppe leer ist
      :return:
      """
        return len(self.regler) == 0

    def schieflast_nicht_erreicht(self, wallbox_id: int) -> bool:
        return self.regler[wallbox_id].wallbox.phasen == 3 or \
           self.data.get('lp/%i/AConfigured' % wallbox_id) < 20 or \
           self.data.get('evu/ASchieflast') < int(self.config.get('schieflastmaxa'))

    def loop(self) -> None:
        properties = [lp.get_props() for lp in self.regler.values()]
        arbitriert = dict([(id, 0) for id in self.regler.keys()])
        self.logger.debug(f"Reglergruppe {self.mode} LP Props: {properties!r}")
        uberschuss = self.data.get('global/uberschuss')
        for id, regler in self.regler.items():
            prefix = 'lp/%i/' % id
            limitierung = self.config.get('msmoduslp%i' % id)
            if self.mode not in ['standby', 'stop']:
                if limitierung == 1 and self.config.get(
                        'lademkwh%i' % id):  # Limitierung: kWh
                    if self.data.get(prefix + 'W') == 0:
                        restzeit = "---"
                    else:
                        restzeit = int(
                            (self.config.get('lademkwh%i' % id) -
                             self.data.get(prefix + 'kWhActualCharged', 0)) *
                            1000 * 60 / self.data.get('lp/%i/W' % id))
                    # print(f"LP{id} Ziel: {self.config.get('lademkwh%i' % id)} Akt: {self.data.get(prefix + 'kWhActualCharged')} Leistung: {self.data.get(prefix + 'W')} Restzeit: {restzeit}")
                    self.data.update(
                        DataPackage(
                            regler.wallbox,
                            {prefix + 'TimeRemaining': f"{restzeit} min"}))
                    if self.config.get(
                            'lademkwh%i' %
                            id) <= self.data.get(prefix + 'kWhActualCharged'):
                        self.logger.info(
                            f"Lademenge erreicht: LP{id} {self.config.get('lademkwh%i' % id)}kwh"
                        )
                        from openWB.OpenWBCore import OpenWBCore
                        OpenWBCore().setconfig(
                            regler.wallbox.configprefix + '_mode', "standby")
                        Scheduler().signalEvent(
                            OpenWBEvent(EventType.resetEnergy, id))
                elif limitierung == 2:  # Limitierung: SOC
                    pass  # TODO
        if self.mode == 'sofort':
            for id, regler in self.regler.items():
                power = amp2power(
                    self.config.get("lpmodul%i_sofortll" % id, 6),
                    regler.wallbox.phasen)
                if regler.wallbox.setP != power:
                    regler.wallbox.set(power)

        elif self.mode in ['stop', 'standby']:
            for regler in self.regler.values():
                if regler.wallbox.setP != 0 or regler.wallbox.is_charging:
                    self.logger.info(
                        f"(LP {regler.wallbox.id}: {regler.wallbox.setP}W -> Reset"
                    )
                    regler.wallbox.set(0)
        elif uberschuss > self.limit:  # Leistungserhöhung
            deltaP = uberschuss - self.limit
            # Erhöhe eingeschaltete LPs
            for r in sorted(filter(lambda r: 'min+P' in r and 'on' in r.flags,
                                   properties),
                            key=lambda r: r['min+P'].priority):
                p = self.get_increment(r, deltaP)
                if p is not None and self.schieflast_nicht_erreicht(r.id):
                    # Erhöhe bei Asymmetrie nur 3-phasen-Boxen
                    self.logger.debug(f"LP {r.id} bekommt +{p}W von {deltaP}W")
                    arbitriert[r.id] = p
                    deltaP -= p
                    if deltaP <= 0:
                        break
            # Schalte LPs mit höchster Prio ein
            candidates = unroll(
                groupby(filter(lambda r: 'min+P' in r and 'off' in r.flags,
                               properties),
                        key=lambda r: r['min+P'].priority))
            if candidates:
                highest_prio = max(candidates.keys())
                candidates = candidates[highest_prio]
                # Budget zum Einschalten: Erstmal der Überschuss
                budget = uberschuss - self.limit
                if self.mode == "pv":
                    budget -= self.hysterese
                # Zusätzliches Budget kommt vom Regelpotential eingeschalteter LPs gleicher oder niedrigerer Prio
                budget += sum(r['max-P'].value for r in filter(
                    lambda r: 'max-P' in r and 'min' not in r.flags and r[
                        'max-P'].priority <= highest_prio, properties))
                for r in candidates:
                    if self.get_increment(
                            r, budget
                    ) is not None and self.schieflast_nicht_erreicht(r.id):
                        self.logger.info(
                            f"Budget: {budget}; LP {r.id} min+P {r['min+P'].value} passt noch"
                        )
                        arbitriert[r.id] = r['min+P'].value
                        budget -= r['min+P'].value
        elif uberschuss < self.limit:  # Leistungsreduktion
            deltaP = self.limit - uberschuss
            for r in sorted(filter(
                    lambda r: 'min-P' in r and 'min' not in r.flags,
                    properties),
                            key=lambda r: r['min-P'].priority):
                p = self.get_decrement(r, deltaP)
                if p is not None:
                    self.logger.debug(
                        f"LP {r.id} Reduzierung -{p}W von -{deltaP}W")
                    arbitriert[r.id] = -p
                    deltaP -= p
                    if deltaP <= 0:
                        break
            # Schalte LPs aus
            deltaP = self.limit - uberschuss
            if self.mode == "peak":
                deltaP -= self.hysterese
            for r in sorted(filter(lambda r: 'min' in r.flags, properties),
                            key=lambda r: r['min-P'].priority):
                if self.get_decrement(r, deltaP) is not None:
                    self.logger.info(
                        f"Abschalten LP {r.id} soll {r['min-P'].value}W freigeben."
                    )
                    arbitriert[r.id] = -r['min-P'].value
                    deltaP -= r['min-P'].value

        for ID, inc in arbitriert.items():
            self.regler[ID].request(inc)