Ejemplo n.º 1
0
 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}')
Ejemplo n.º 2
0
    def test_config(self):
        config = OpenWBconfig(self.testfile)
        self.assertIsNone(config['test'])
        config['evseids1'] = 1
        config['evselanips1'] = "10.20.0.180"

        self.assertEqual(config['evseids1'], 1)
        self.assertEqual(config['evselanips1'], "10.20.0.180",
                         "Getting a non-integer setting")

        # Read it another time
        config2 = OpenWBconfig(self.testfile)
        self.assertEqual(config2['evseids1'], 1)
        self.assertEqual(config2['evselanips1'], "10.20.0.180",
                         "Getting a non-integer setting")
Ejemplo n.º 3
0
 def add(self, module: Ladepunkt) -> None:
     module.master = self
     module.configprefix = "lpmodul" + str(module.id)
     self.modules.append(module)
     module.setup(OpenWBconfig())
     self.data.update({
         "lp/%i/boolChargePointConfigured" % module.id:
         1,  # Configured -> vorhanden
         "lp/%i/ChargePointEnabled" % module.id:
         1  # Enabled -> Ladebereit; nicht z.B. nach Ablauf der Lademenge
     })
Ejemplo n.º 4
0
def for_all_modules(prefix, callback: Callable[[Modul], None]):
    """Suche alle Module mit <prefix> und rufe für die erzeugte <Modul>-Instanz den callback auf"""
    instance = 1
    while True:
        modulename = OpenWBconfig()[prefix + 'modul' + str(instance)]
        if modulename is None:
            break
        module = importlib.import_module(f'modules.{prefix}_{modulename}')
        o = module.getClass()(instance)
        callback(o)
        print("Created module: " + o.name)
        instance += 1
Ejemplo n.º 5
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)
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
 def event(self, event: OpenWBEvent):
     if event.type == EventType.resetEnergy and event.info == self.id:
         # Reset invoked from UI
         if self.offsets['chargedW']:
             self.logger.info("Reset Energie LP%i (vorher: %i)" %
                              (self.id, self.offsets['chargedW']))
             from openWB.OpenWBCore import OpenWBCore
             self.logger.info("Reset Energie LP%i (vorher: %i)" %
                              (self.id, self.offsets['chargedW']))
             lademenge = OpenWBconfig().get(
                 'lademkwh%i' % self.id) - self.offsets['chargedW']
             if lademenge < 0:
                 lademenge = 0
             OpenWBCore().setconfig('lademkwh%i' % self.id, lademenge)
         self.offsets['chargedW'] = 0
     if event.type == EventType.resetDaily:
         self.reset_offset('daily', 'kwh')
Ejemplo n.º 8
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")
Ejemplo n.º 9
0
 def __init__(self):
     Scheduler().registerData(
         ['evu/W', 'pv/W', 'global/WAllChargePoints', 'housebattery/W'],
         self)
     self.data = openWBValues()
     self.config = OpenWBconfig()
Ejemplo n.º 10
0
def for_module(prefix, callback: Callable[[Modul], None]):
    """Suche das Modul mit <prefix> und rufe für die erzeugte <Modul>-Instanz den callback auf"""
    modulename = OpenWBconfig()[prefix + 'modul']
    if modulename is not None:
        module = importlib.import_module(f'modules.{prefix}_{modulename}')
        callback(module.getClass()(1))
Ejemplo n.º 11
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)
Ejemplo n.º 12
0
 def maxP(self) -> int:
     """Maximalleistung"""
     return OpenWBconfig().get('maximalstromstaerke') * self.phasen * 230
Ejemplo n.º 13
0
 def add(self, module: Speichermodul) -> None:
     module.master = self
     module.configprefix = "speichermodul" + str(module.id)
     self.modules.append(module)
     module.setup(OpenWBconfig())
     self.data.update({"housebattery/boolHouseBatteryConfigured": 1})
Ejemplo n.º 14
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()
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
 def add(self, module: EVUModul) -> None:
     self.modul = module
     module.master = self
     module.configprefix = "bezugmodul1"
     module.setup(OpenWBconfig())
Ejemplo n.º 17
0
 def add(self, module: Displaymodul) -> None:
    self.modul = module
    module.master = self
    module.configprefix = "displaymodul" + str(module.id)
    module.setup(OpenWBconfig())
Ejemplo n.º 18
0
 def add(self, module: PVModul) -> None:
     module.master = self
     module.configprefix = "wrmodul" + str(module.id)
     self.modules.append(module)
     module.setup(OpenWBconfig())