diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index 1cc76de58e..76ee2d0031 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -30,7 +30,8 @@ def set_additional_current(self) -> None: available_currents, limit = Loadmanagement().get_available_currents(missing_currents, counter, cp) log.debug(f"cp {cp.num} available currents {available_currents} missing currents " f"{missing_currents} limit {limit.message}") - cp.data.control_parameter.limit = limit + if limit.limiting_value is not None: + cp.data.control_parameter.limit = limit available_for_cp = common.available_current_for_cp(cp, counts, available_currents, missing_currents) current = common.get_current_to_set( cp.data.set.current, available_for_cp, cp.data.set.target_current) diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index 8dada55ce0..48ce9eba6a 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -27,7 +27,8 @@ def set_min_current(self) -> None: if max(missing_currents) > 0: available_currents, limit = Loadmanagement().get_available_currents( missing_currents, counter, cp) - cp.data.control_parameter.limit = limit + if limit.limiting_value is not None: + cp.data.control_parameter.limit = limit available_for_cp = common.available_current_for_cp( cp, counts, available_currents, missing_currents) current = common.get_current_to_set( diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index a357b57574..d7239b9696 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -61,7 +61,9 @@ def _set(self, cp, feed_in=feed_in_yield ) - cp.data.control_parameter.limit = limit + # im PV-Laden wird der Strom immer durch die Leistung begrenzt + if limit.limiting_value is not None and limit.limiting_value != LimitingValue.POWER: + cp.data.control_parameter.limit = limit available_for_cp = common.available_current_for_cp(cp, counts, available_currents, missing_currents) if counter.get_control_range_state(feed_in_yield) == ControlRangeState.MIDDLE: pv_charging = data.data.general_data.data.chargemode_config.pv_charging diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index 47005c40b3..c71fd831a5 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -276,8 +276,9 @@ def _check_phase_switch_conditions(self, all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus(feed_in_yield) required_surplus = control_parameter.min_current * max_phases_ev * 230 - get_power unbalanced_load_limit_reached = limit.limiting_value == LimitingValue.UNBALANCED_LOAD - condition_1_to_3 = (((get_medium_charging_current(get_currents) > max_current_range and - all_surplus > required_surplus) or unbalanced_load_limit_reached) and + current_limit_reached = limit.limiting_value == LimitingValue.CURRENT + condition_1_to_3 = ((((get_medium_charging_current(get_currents) > max_current_range or current_limit_reached) + and all_surplus > required_surplus) or unbalanced_load_limit_reached) and phases_in_use == 1) condition_3_to_1 = get_medium_charging_current( get_currents) < min_current_range and all_surplus <= 0 and phases_in_use > 1 diff --git a/packages/dataclass_utils/_dataclass_asdict_test.py b/packages/dataclass_utils/_dataclass_asdict_test.py index 3469c283c3..b02f33302d 100644 --- a/packages/dataclass_utils/_dataclass_asdict_test.py +++ b/packages/dataclass_utils/_dataclass_asdict_test.py @@ -1,6 +1,7 @@ import pytest from dataclass_utils import asdict +from dataclass_utils.conftest import MyDataclass class SingleValue: @@ -37,3 +38,38 @@ def test_asdict(object, expected_dict: dict): # evaluation assert actual == expected_dict + + +MY_DATACLASS_AS_DICT = { + "str_value": "string_value", + "float_value": 5.2, + "int_value": 6, + "enum_value": "value1", + "nested_dataclass": { + "nested_str": "nested string", + "nested_int": 42 + }, + "nested_dataclass_enum_value": { + "D1": "value1", + "D2": "value2" + }, + "dict_of_dataclass_value": {"a": {"nested_int": 42, + "nested_str": "nested string"}, + "b": {"nested_int": 42, + "nested_str": "nested string"}}, + "dict_value": {"a": "a", "b": 2}, + "dict2_value": {"a": 1, "b": 2}, + "list_value": ["a", 2, None], + "list2_value": ["a", 2, None], + # JSON kennt keine Tupel + "tuple_value": [None, "a", 2], + "tuple2_value": [None, "a", 2] +} + + +def test_dataclass_as_dict(): + # execution + actual_dict = asdict(MyDataclass()) + + # evaluation + assert actual_dict == MY_DATACLASS_AS_DICT diff --git a/packages/dataclass_utils/_dataclass_from_dict.py b/packages/dataclass_utils/_dataclass_from_dict.py index 6980d761c7..9d97689fe9 100644 --- a/packages/dataclass_utils/_dataclass_from_dict.py +++ b/packages/dataclass_utils/_dataclass_from_dict.py @@ -1,7 +1,6 @@ from enum import Enum import inspect from inspect import FullArgSpec, isclass -import typing from typing import TypeVar, Type, Union, get_args, get_origin T = TypeVar('T') @@ -20,8 +19,9 @@ def dataclass_from_dict(cls: Type[T], args: Union[dict, T]) -> T: if isinstance(args, cls): return args elif get_origin(cls): - # Generische Typen wie Dict[int, float] - if isinstance(args, get_origin(cls)): + # Generische Typen wie Dict[int, float] - aber nicht Union, da isinstance mit Union fehlschlägt + origin = get_origin(cls) + if origin != Union and isinstance(args, origin): return args elif isinstance(args, type(cls)): return args @@ -46,26 +46,25 @@ def _get_argument_value(arg_spec: FullArgSpec, index: int, parameters: dict): def _dataclass_from_dict_recurse(value, requested_type: Type[T]): - if get_origin(requested_type) == list: + # Handle Optional types (Union[X, None]) - extract the actual type + actual_type = requested_type + if get_origin(requested_type) == Union: + args = get_args(requested_type) + if len(args) == 2 and isinstance(args[1], type(None)): + actual_type = args[0] # Extract X from Optional[X] + + if get_origin(actual_type) == list: # Extrahiere den generischen Typ der Liste - if get_args(requested_type): - generic_type = get_args(requested_type)[0] + if get_args(actual_type): + generic_type = get_args(actual_type)[0] # Konvertiere jedes Element der Liste in den generischen Typ return [_dataclass_from_dict_recurse(item, generic_type) for item in value] - if isinstance(value, dict) and not ( - _is_optional_of_dict(requested_type) or - issubclass(requested_type if isclass(requested_type) else type(bool), dict)): - return dataclass_from_dict(requested_type, value) - if isinstance(requested_type, type) and issubclass(requested_type, Enum): - return requested_type(value) - return value - + # Handle dict types (both direct and Optional[dict]) + if isinstance(value, dict) and isclass(actual_type) and not issubclass(actual_type, dict): + return dataclass_from_dict(actual_type, value) -def _is_optional_of_dict(requested_type): - # Optional[dict] is an alias for Union[dict, None] - if typing.get_origin(requested_type) == Union: - args = typing.get_args(requested_type) - if len(args) == 2: - return issubclass(args[0], dict) and issubclass(args[1], type(None)) - return False + # Handle Enum types (both direct and Optional[Enum]) + if isinstance(actual_type, type) and issubclass(actual_type, Enum): + return actual_type(value) + return value diff --git a/packages/dataclass_utils/_dataclass_from_dict_test.py b/packages/dataclass_utils/_dataclass_from_dict_test.py index 012b76f8ff..47ffc7e674 100644 --- a/packages/dataclass_utils/_dataclass_from_dict_test.py +++ b/packages/dataclass_utils/_dataclass_from_dict_test.py @@ -3,6 +3,7 @@ import pytest from dataclass_utils import dataclass_from_dict +from dataclass_utils.conftest import MyDataclass T = TypeVar('T') @@ -125,3 +126,33 @@ def test_from_dict_without_optional(): # evaluation assert actual.a == "aValue" assert actual.o is None + + +MY_DATACLASS_AS_DICT = { + "str_value": "string_value", + "float_value": 5.2, + "int_value": 6, + "enum_value": "value1", + "nested_dataclass": { + "nested_str": "nested string", + "nested_int": 42 + }, + "nested_dataclass_enum_value": { + "D1": "value1", + "D2": "value2" + }, + "dict_value": {"a": "a", "b": 2}, + "dict2_value": {"a": 1, "b": 2}, + "list_value": ["a", 2, None], + "list2_value": ["a", 2, None], + "tuple_value": (None, "a", 2), + "tuple2_value": (None, "a", 2) +} + + +def test_dataclass_from_dict(): + # execution + actual_dict = dataclass_from_dict(MyDataclass, MY_DATACLASS_AS_DICT) + + # evaluation + assert vars(actual_dict) == vars(MyDataclass()) diff --git a/packages/dataclass_utils/conftest.py b/packages/dataclass_utils/conftest.py new file mode 100644 index 0000000000..fdabf7c2a8 --- /dev/null +++ b/packages/dataclass_utils/conftest.py @@ -0,0 +1,46 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, List, Tuple + + +class EnumValues(Enum): + VALUE1 = "value1" + VALUE2 = "value2" + + +@dataclass +class DataclassEnumValue(): + D1: EnumValues = EnumValues.VALUE1 + D2: EnumValues = EnumValues.VALUE2 + + +def dataclass_enum_value_factory() -> DataclassEnumValue: + return DataclassEnumValue() + + +@dataclass +class NestedDataclass: + nested_str: str = "nested string" + nested_int: int = 42 + + +def nested_dataclass_factory() -> NestedDataclass: + return NestedDataclass() + + +@dataclass +class MyDataclass: + str_value: str = "string_value" + float_value: float = 5.2 + int_value: int = 6 + enum_value: EnumValues = EnumValues.VALUE1 + nested_dataclass: NestedDataclass = field(default_factory=nested_dataclass_factory) + nested_dataclass_enum_value: DataclassEnumValue = field(default_factory=dataclass_enum_value_factory) + dict_of_dataclass_value: Dict[str, NestedDataclass] = field( + default_factory=lambda: {"a": NestedDataclass(), "b": NestedDataclass()}) + dict_value: Dict = field(default_factory=lambda: {"a": "a", "b": 2}) + dict2_value: dict = field(default_factory=lambda: {"a": 1, "b": 2}) + list_value: List = field(default_factory=lambda: ["a", 2, None]) + list2_value: list = field(default_factory=lambda: ["a", 2, None]) + tuple_value: tuple = field(default_factory=lambda: (None, "a", 2)) + tuple2_value: Tuple = field(default_factory=lambda: (None, "a", 2))