Beispiel #1
0
    def __read_registers(self,
                         read_register_method: Callable,
                         address: int,
                         types: Union[Iterable[ModbusDataType],
                                      ModbusDataType],
                         byteorder: Endian = Endian.Big,
                         wordorder: Endian = Endian.Big,
                         **kwargs):
        try:
            multi_request = isinstance(types, Iterable)
            if not multi_request:
                types = [types]

            def divide_rounding_up(numerator: int, denominator: int):
                return -(-numerator // denominator)

            number_of_addresses = sum(
                divide_rounding_up(t.bits, _MODBUS_HOLDING_REGISTER_SIZE)
                for t in types)
            response = read_register_method(address, number_of_addresses,
                                            **kwargs)
            if response.isError():
                raise FaultState.error(__name__ + " " + str(response))
            decoder = BinaryPayloadDecoder.fromRegisters(
                response.registers, byteorder, wordorder)
            result = [
                struct.unpack(">e",
                              struct.pack(">H", decoder.decode_16bit_uint()))
                if t == ModbusDataType.FLOAT_16 else getattr(
                    decoder, t.decoding_method)() for t in types
            ]
            return result if multi_request else result[0]
        except pymodbus.exceptions.ConnectionException as e:
            raise FaultState.error(
                "TCP-Client konnte keine Verbindung zu " + str(self.address) +
                ":" + str(self.port) +
                " aufbauen. Bitte Einstellungen (IP-Adresse, ..) und " +
                "Hardware-Anschluss prüfen.") from e
        except pymodbus.exceptions.ModbusIOException as e:
            raise FaultState.warning(
                "TCP-Client " + str(self.address) + ":" + str(self.port) +
                " konnte keinen Wert abfragen. Falls vorhanden, parallele Verbindungen, zB. node red,"
                +
                "beenden und bei anhaltender Fehlermeldung Zähler neustarten."
            ) from e
        except Exception as e:
            raise FaultState.error(__name__ + " " + str(type(e)) + " " +
                                   str(e)) from e
Beispiel #2
0
    def __update_variant_0_1(
            self, session: Session) -> Tuple[CounterState, MeterLocation]:
        variant = self.component_config["configuration"]["variant"]
        meter_id = self.device_config["meter_id"]
        if variant == 0:
            params = (
                ('Scope', 'Device'),
                ('DeviceId', meter_id),
            )
        elif variant == 1:
            params = (
                ('Scope', 'Device'),
                ('DeviceId', meter_id),
                ('DataCollection', 'MeterRealtimeData'),
            )
        else:
            raise FaultState.error("Unbekannte Generation: " + str(variant))
        response = session.get('http://' + self.device_config["ip_address"] +
                               '/solar_api/v1/GetMeterRealtimeData.cgi',
                               params=params,
                               timeout=5)
        response_json_id = response.json()["Body"]["Data"]
        # old request for variant == 1
        # params = (
        #     ('Scope', 'System'),
        # )
        # response = req.get_http_session().get(
        #     'http://'+self.device_config["ip_address"]+'/solar_api/v1/GetMeterRealtimeData.cgi',
        #  params=params, timeout=5)
        # response_json_id = response["Body"]["Data"][meter_id]
        meter_location = MeterLocation(
            response_json_id["Meter_Location_Current"])
        log.MainLogger().debug("Einbauort: " + str(meter_location))

        power = response_json_id["PowerReal_P_Sum"]
        voltages = [
            response_json_id["Voltage_AC_Phase_" + str(num)]
            for num in range(1, 4)
        ]
        powers = [
            response_json_id["PowerReal_P_Phase_" + str(num)]
            for num in range(1, 4)
        ]
        currents = [powers[i] / voltages[i] for i in range(0, 3)]
        power_factors = [
            response_json_id["PowerFactor_Phase_" + str(num)]
            for num in range(1, 4)
        ]
        frequency = response_json_id["Frequency_Phase_Average"]
        imported = response_json_id["EnergyReal_WAC_Sum_Consumed"]
        exported = response_json_id["EnergyReal_WAC_Sum_Produced"]

        return CounterState(voltages=voltages,
                            currents=currents,
                            powers=powers,
                            imported=imported,
                            exported=exported,
                            power=power,
                            frequency=frequency,
                            power_factors=power_factors), meter_location
Beispiel #3
0
    def __enter__(self) -> Iterator[dict]:
        ip_bind = "0.0.0.0"
        multicast_group = "239.12.255.254"
        multicast_port = 9522
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
                             socket.IPPROTO_UDP)
        try:
            sock.settimeout(self.__timeout_seconds)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('', multicast_port))
            mreq = struct.pack("4s4s", socket.inet_aton(multicast_group),
                               socket.inet_aton(ip_bind))
            sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
        except BaseException:
            sock.close()
            raise FaultState.error(
                "could not connect to multicast group or bind to given interface"
            )
        self.__socket = sock

        def generator() -> Iterator[dict]:
            while True:
                datagram = sock.recv(608)
                if len(datagram) >= 18 and datagram[16:18] == b'\x60\x69':
                    yield decode_speedwire(datagram)

        return generator()
Beispiel #4
0
 def set(self, inverter_state: InverterState):
     try:
         pub_to_broker("openWB/set/pv/" + str(self.num) + "/get/power", inverter_state.power, 2)
         pub_to_broker("openWB/set/pv/" + str(self.num) + "/get/counter", inverter_state.counter, 3)
         pub_to_broker("openWB/set/pv/" + str(self.num) + "/get/currents", inverter_state.currents, 1)
     except Exception as e:
         raise FaultState.from_exception(e)
Beispiel #5
0
 def close_connection(self) -> None:
     try:
         log.MainLogger().debug("Close Modbus TCP connection")
         self.delegate.close()
     except Exception as e:
         raise FaultState.error(__name__ + " " + str(type(e)) + " " +
                                str(e)) from e
Beispiel #6
0
    def update(self) -> None:
        log.MainLogger().debug("Komponente "+self.component_config["name"]+" auslesen.")

        vc_count = self.component_config["configuration"]["vc_count"]
        vc_type = self.component_config["configuration"]["vc_type"]

        if vc_type == 'VS':
            mb_unit = 40
            mb_register = 20  # MB:20; ID: 15010; PV power kW
        elif vc_type == 'VT':
            mb_unit = 20
            mb_register = 8  # MB:8; ID: 11004; Power of the PV generator kW
        else:
            raise FaultState.error("Unbekannter VC-Typ: "+str(vc_type))
        power = 0
        for i in range(1, vc_count+1):
            mb_unit_dev = mb_unit+i
            power += self.__tcp_client.read_input_registers(mb_register, ModbusDataType.FLOAT_32, unit=mb_unit_dev)
        power = power * -1000

        if vc_type == 'VS':
            mb_register = 46  # MB:46; ID: 15023; Desc: Total PV produced energy MWh
        elif vc_type == 'VT':
            mb_register = 18  # MB:18; ID: 11009; Desc: Total produced energy MWh
        counter = 0
        for i in range(1, vc_count + 1):
            mb_unit_dev = mb_unit + i
            counter += self.__tcp_client.read_input_registers(mb_register, ModbusDataType.FLOAT_32, unit=mb_unit_dev)
        counter = counter * 1000000

        inverter_state = InverterState(
            power=power,
            counter=counter
        )
        self.__store.set(inverter_state)
Beispiel #7
0
    def update(self) -> None:
        vc_count = self.component_config.configuration.vc_count
        vc_type = self.component_config.configuration.vc_type

        with self.__tcp_client:
            if vc_type == 'VS':
                mb_unit = 40
                mb_register = 20  # MB:20; ID: 15010; PV power kW
            elif vc_type == 'VT':
                mb_unit = 20
                mb_register = 8  # MB:8; ID: 11004; Power of the PV generator kW
            else:
                raise FaultState.error("Unbekannter VC-Typ: " + str(vc_type))
            power = 0
            for i in range(1, vc_count + 1):
                mb_unit_dev = mb_unit + i
                power += self.__tcp_client.read_input_registers(
                    mb_register, ModbusDataType.FLOAT_32, unit=mb_unit_dev)
            power = power * -1000

            if vc_type == 'VS':
                mb_register = 46  # MB:46; ID: 15023; Desc: Total PV produced energy MWh
            elif vc_type == 'VT':
                mb_register = 18  # MB:18; ID: 11009; Desc: Total produced energy MWh
            exported = 0
            for i in range(1, vc_count + 1):
                mb_unit_dev = mb_unit + i
                exported += self.__tcp_client.read_input_registers(
                    mb_register, ModbusDataType.FLOAT_32, unit=mb_unit_dev)
            exported = exported * 1000000

        inverter_state = InverterState(power=power, exported=exported)
        self.__store.set(inverter_state)
Beispiel #8
0
    def update(self, bat: bool) -> Tuple[CounterState, MeterLocation]:
        variant = self.component_config["configuration"]["variant"]
        log.MainLogger().debug("Komponente " + self.component_config["name"] +
                               " auslesen.")

        session = req.get_http_session()

        if variant == 0 or variant == 1:
            counter_state, meter_location = self.__update_variant_0_1(session)
        elif variant == 2:
            counter_state, meter_location = self.__update_variant_2(session)
        else:
            raise FaultState.error("Unbekannte Variante: " + str(variant))

        if meter_location == MeterLocation.load:
            response = session.get(
                'http://' + self.device_config["ip_address"] +
                '/solar_api/v1/GetPowerFlowRealtimeData.fcgi',
                params=(('Scope', 'System'), ),
                timeout=5)
            counter_state.power = float(
                response.json()["Body"]["Data"]["Site"]["P_Grid"])
            topic_str = "openWB/set/system/device/{}/component/{}/".format(
                self.__device_id, self.component_config["id"])
            # Beim Energiebezug ist nicht klar, welcher Anteil aus dem Netz bezogen wurde, und was aus
            # dem Wechselrichter kam.
            # Beim Energieexport ist nicht klar, wie hoch der Eigenverbrauch während der Produktion war.
            counter_state.imported, counter_state.exported = self.__sim_count.sim_count(
                counter_state.power,
                topic=topic_str,
                data=self.simulation,
                prefix="bezug")

        return counter_state, meter_location
Beispiel #9
0
 def __exit__(self, exception_type, exception, exception_traceback) -> bool:
     fault_state = FaultState.from_exception(exception)
     for component in self.__device_components:
         component_info = component.component_info
         if component_info not in self.__ignored_components:
             fault_state.store_error(component_info)
     delattr(MultiComponentUpdateContext.__thread_local, "active_context")
     return True
Beispiel #10
0
 def set(self, inverter_state: InverterState):
     try:
         self.__pv.power.write(inverter_state.power)
         self.__pv.energy.write(inverter_state.exported)
         self.__pv.energy_k.write(inverter_state.exported / 1000)
         if inverter_state.currents:
             self.__pv.currents.write(inverter_state.currents)
     except Exception as e:
         raise FaultState.from_exception(e)
Beispiel #11
0
    def __update_variant_0_1(self, session: Session) -> CounterState:
        variant = self.component_config.configuration.variant
        meter_id = self.component_config.configuration.meter_id
        if variant == 0:
            params = (
                ('Scope', 'Device'),
                ('DeviceId', meter_id),
            )
        elif variant == 1:
            params = (
                ('Scope', 'Device'),
                ('DeviceId', meter_id),
                ('DataCollection', 'MeterRealtimeData'),
            )
        else:
            raise FaultState.error("Unbekannte Generation: " + str(variant))
        response = session.get('http://' + self.device_config.ip_address +
                               '/solar_api/v1/GetMeterRealtimeData.cgi',
                               params=params,
                               timeout=5)
        response_json_id = response.json()["Body"]["Data"]

        meter_location = MeterLocation.get(
            response_json_id["Meter_Location_Current"])
        log.debug("Einbauort: " + str(meter_location))

        powers = [
            response_json_id["PowerReal_P_Phase_" + str(num)]
            for num in range(1, 4)
        ]
        if meter_location == MeterLocation.load:
            power, power_inverter = self.__get_flow_power(session)
            # wenn SmartMeter im Verbrauchszweig sitzt sind folgende Annahmen getroffen:
            # PV Leistung wird gleichmäßig auf alle Phasen verteilt
            # Spannungen und Leistungsfaktoren sind am Verbrauchszweig == Einspeisepunkt
            # Hier gehen wir mal davon aus, dass der Wechselrichter seine PV-Leistung gleichmäßig
            # auf alle Phasen aufteilt.
            powers = [-1 * power - power_inverter / 3 for power in powers]
        else:
            power = response_json_id["PowerReal_P_Sum"]
        voltages = [
            response_json_id["Voltage_AC_Phase_" + str(num)]
            for num in range(1, 4)
        ]
        currents = [powers[i] / voltages[i] for i in range(0, 3)]
        power_factors = [
            response_json_id["PowerFactor_Phase_" + str(num)]
            for num in range(1, 4)
        ]
        frequency = response_json_id["Frequency_Phase_Average"]

        return CounterState(voltages=voltages,
                            currents=currents,
                            powers=powers,
                            power=power,
                            frequency=frequency,
                            power_factors=power_factors)
Beispiel #12
0
 def set(self, inverter_state: InverterState):
     try:
         self.__pv.power.write(inverter_state.power)
         self.__pv.energy.write(inverter_state.counter)
         self.__pv.energy_k.write(inverter_state.counter / 1000)
         self.__pv.currents.write(inverter_state.currents)
         log.MainLogger().info('PV Watt: ' + str(inverter_state.power))
     except Exception as e:
         raise FaultState.from_exception(e)
Beispiel #13
0
def kit_counter_inverter_version_factory(
        version: int) -> Type[Union[mpm3pm.Mpm3pm, lovato.Lovato, sdm.Sdm630]]:
    if version == 0:
        return mpm3pm.Mpm3pm
    elif version == 1:
        return lovato.Lovato
    elif version == 2:
        return sdm.Sdm630
    else:
        raise FaultState.error("Version " + str(version) + " unbekannt.")
Beispiel #14
0
def kit_bat_version_factory(
        version: int) -> Type[Union[mpm3pm.Mpm3pm, sdm.Sdm630, sdm.Sdm120]]:
    if version == 0:
        return mpm3pm.Mpm3pm
    elif version == 1:
        return sdm.Sdm120
    elif version == 2:
        return sdm.Sdm630
    else:
        raise FaultState.error("Version " + str(version) + " unbekannt.")
Beispiel #15
0
    def update(self) -> None:
        log.debug("Beginning update")
        with MultiComponentUpdateContext(self._components):
            if not self._components:
                raise FaultState.warning("Keine Komponenten konfiguriert")

            with SpeedwireListener(timeout_seconds) as speedwire:
                self.__read_speedwire(speedwire)

        log.debug("Update complete")
Beispiel #16
0
    def override_subcomponent_state(component_info: ComponentInfo, exception,
                                    update_always: bool):
        active_context = getattr(
            MultiComponentUpdateContext.__thread_local, "active_context",
            None)  # type: Optional[MultiComponentUpdateContext]
        if active_context:
            # If a MultiComponentUpdateContext is active, we need make sure that it will not override
            # the value for the individual component
            active_context.ignore_subcomponent_state(component_info)

        if exception:
            fault_state = FaultState.from_exception(exception)
        else:
            # Fehlerstatus nicht überschreiben
            if update_always:
                fault_state = FaultState.no_error()
            else:
                return
        fault_state.store_error(component_info)
Beispiel #17
0
    def override_subcomponent_state(component_info: ComponentInfo, exception):
        active_context = getattr(
            MultiComponentUpdateContext.__thread_local, "active_context",
            None)  # type: Optional[MultiComponentUpdateContext]
        if active_context:
            # If a MultiComponentUpdateContext is active, we need make sure that it will not override
            # the value for the individual component
            active_context.ignore_subcomponent_state(component_info)

        fault_state = FaultState.from_exception(exception)
        fault_state.store_error(component_info)
Beispiel #18
0
 def __init__(self, device_config: dict) -> None:
     self.device_config = device_config
     self._components = {}  # type: Dict[str, bat.BatKit]
     version = self.device_config["configuration"]["version"]
     if version == 0 or version == 1:
         ip_address = '192.168.193.19'
     elif version == 2:
         ip_address = '192.168.193.15'
     else:
         raise FaultState.error("Version " + str(version) + " unbekannt.")
     self.client = modbus.ModbusClient(ip_address, 8899)
Beispiel #19
0
 def update(self) -> None:
     log.debug("Variante: " + str(self.__device_variant))
     if self.__device_variant == 0:
         state = self.__update_variant_0()
     elif self.__device_variant == 1:
         state = self.__update_variant_1()
     elif self.__device_variant == 2:
         state = self.__update_variant_2()
     else:
         raise FaultState.error("Unbekannte Variante: " +
                                str(self.__device_variant))
     self.__store.set(state)
Beispiel #20
0
    def __init__(self, device_id: int, component_config: dict,
                 tcp_client: modbus.ModbusClient) -> None:
        self.data = {"config": component_config}
        version = self.data["config"]["configuration"]["version"]
        if version == 0 or version == 1:
            id = 0x08
        elif version == 2:
            id = 116
        else:
            raise FaultState.error("Version " + str(version) + " unbekannt.")
        self.data["config"]["configuration"]["id"] = id

        super().__init__(device_id, self.data["config"], tcp_client)
Beispiel #21
0
def get_topic(prefix: str) -> str:
    """ ermittelt das zum Präfix gehörende Topic."""
    try:
        if prefix == "bezug":
            topic = "evu"
        elif prefix == "pv" or prefix == "pv2":
            topic = prefix
        elif prefix == "speicher":
            topic = "housebattery"
        else:
            raise FaultState.error("Fehler im Modul simcount: Unbekannter Präfix")
        return topic
    except Exception as e:
        process_error(e)
Beispiel #22
0
 def update(self) -> None:
     log.MainLogger().debug("Komponente '" + str(self.component_config["id"]) + "' "
                            + self.component_config["name"] + " wird auslesen.")
     log.MainLogger().debug("Variante: " + str(self.__device_variant))
     if self.__device_variant == 0:
         state = self.__update_variant_0()
     elif self.__device_variant == 1:
         state = self.__update_variant_1()
     elif self.__device_variant == 2:
         state = self.__update_variant_2()
     else:
         raise FaultState.error("Unbekannte Variante: " + str(self.__device_variant))
     self.__store.set(state)
     log.MainLogger().debug("Komponente "+self.component_config["name"]+" wurde erfolgreich auslesen.")
Beispiel #23
0
    def update(self, response) -> None:
        power = float(response["statistics"]["batconv_power"])
        if response["direction"]["is_battery_discharging_"] == "1":
            power = power * -1
        try:
            soc = float(response["statistics"]["bat_user_soc"])
        except ValueError:
            FaultState.warning(
                'Speicher-SOC ist nicht numerisch und wird auf 0 gesetzt.').store_error(self.component_info)
            soc = 0

        topic_str = "openWB/set/system/device/" + str(
            self.__device_id)+"/component/"+str(self.component_config.id)+"/"
        imported, exported = self.__sim_count.sim_count(
            power, topic=topic_str, data=self.__simulation, prefix="speicher"
        )
        bat_state = BatState(
            power=power,
            soc=soc,
            imported=imported,
            exported=exported
        )
        self.__store.set(bat_state)
Beispiel #24
0
    def __init__(self, device_id: int, component_config: dict) -> None:
        self.data = {"config": component_config}
        version = self.data["config"]["configuration"]["version"]
        if version == 0:
            id = 5
        elif version == 1:
            id = 2
        elif version == 2:
            id = 115
        else:
            raise FaultState.error("Version " + str(version) + " unbekannt.")
        self.data["config"]["configuration"]["id"] = id

        super().__init__(device_id, self.data["config"],
                         modbus.ModbusClient("192.168.193.15", 8899))
Beispiel #25
0
    def __init__(self, device_id: int,
                 component_config: Union[Dict, PvKitInverterSetup],
                 tcp_client: modbus.ModbusTcpClient_) -> None:
        self.component_config = dataclass_from_dict(PvKitInverterSetup,
                                                    component_config)
        version = self.component_config.configuration.version
        if version == 0 or version == 1:
            id = 0x08
        elif version == 2:
            id = 116
        else:
            raise FaultState.error("Version " + str(version) + " unbekannt.")

        super().__init__(device_id,
                         convert_to_flex_setup(self.component_config, id),
                         tcp_client)
Beispiel #26
0
    def __init__(self, device_id: int, component_config: dict) -> None:
        try:
            self.data = {"config": component_config}
            version = self.data["config"]["configuration"]["version"]
            if version == 0 or version == 1:
                id = 0x08
            elif version == 2:
                id = 116
            else:
                raise FaultState.error("Version "+str(version) +
                                       " unbekannt.")
            self.data["config"]["configuration"]["id"] = id

            super().__init__(device_id, self.data["config"], modbus.ModbusClient("192.168.193.15", 8899))
        except Exception:
            log.MainLogger().exception("Fehler im Modul " +
                                       self.data["config"]["components"]["component0"]["name"])
Beispiel #27
0
 def _update_session_key(self, session: Session):
     try:
         headers = {
             'Content-Type': 'application/json',
         }
         data = json.dumps(
             {"password": self.device_config.configuration.password})
         response = session.put(
             "https://" + self.device_config.configuration.ip_address +
             '/v1/login',
             headers=headers,
             data=data,
             verify=False,
             timeout=5).json()
         self.session_key = response["auth_key"]
     except (HTTPError, KeyError):
         raise FaultState.error("login failed! check password!")
Beispiel #28
0
 def __read_speedwire(self, speedwire: Iterator[dict]):
     stop_time = time.time() + timeout_seconds
     components_todo = self._components
     try:
         for sma_data in speedwire:
             components_todo = [
                 component for component in components_todo
                 if not component.read_datagram(sma_data)
             ]
             if not components_todo:
                 log.debug("All components updated")
                 return
             if time.time() > stop_time:
                 break
     except socket.timeout:
         pass
     raise FaultState.error(
         "Kein passendes Datagramm innerhalb des %ds timeout empfangen" %
         timeout_seconds)
Beispiel #29
0
    def update(self) -> None:

        session = req.get_http_session()
        variant = self.component_config.configuration.variant
        if variant == 0 or variant == 1:
            counter_state = self.__update_variant_0_1(session)
        elif variant == 2:
            counter_state = self.__update_variant_2(session)
        else:
            raise FaultState.error("Unbekannte Variante: " + str(variant))

        topic_str = "openWB/set/system/device/{}/component/{}/".format(
            self.__device_id, self.component_config.id)
        counter_state.imported, counter_state.exported = self.__sim_count.sim_count(
            counter_state.power,
            topic=topic_str,
            data=self.simulation,
            prefix="bezug")
        self.__store.set(counter_state)
Beispiel #30
0
    def update(self) -> Tuple[CounterState, MeterLocation]:
        variant = self.component_config["configuration"]["variant"]
        log.MainLogger().debug("Komponente " + self.component_config["name"] +
                               " auslesen.")

        session = req.get_http_session()

        if variant == 0 or variant == 1:
            counter_state, meter_location = self.__update_variant_0_1(session)
        elif variant == 2:
            counter_state, meter_location = self.__update_variant_2(session)
        else:
            raise FaultState.error("Unbekannte Variante: " + str(variant))

        topic_str = "openWB/set/system/device/{}/component/{}/".format(
            self.__device_id, self.component_config["id"])
        counter_state.imported, counter_state.exported = self.__sim_count.sim_count(
            counter_state.power,
            topic=topic_str,
            data=self.simulation,
            prefix="bezug")

        return counter_state, meter_location