def validate_infinite_bus(**kwargs):
    validate_commercial_producer(**kwargs)
    if "energy_rate_profile" in kwargs and kwargs["energy_rate_profile"] is not None and \
            ("energy_rate_profile_uuid" not in kwargs or
             kwargs["energy_rate_profile_uuid"] is None):
        raise D3ADeviceException(
            {"misconfiguration": [f"energy_rate_profile must have a uuid."]})
    if key_in_dict_and_not_none_and_not_str_type(kwargs,
                                                 "energy_rate_profile_uuid"):
        raise D3ADeviceException({
            "misconfiguration":
            [f"energy_rate_profile_uuid must have a string type."]
        })

    if key_in_dict_and_not_none(kwargs, "energy_buy_rate"):
        _validate_rate(kwargs['energy_buy_rate'])
    if key_in_dict_and_not_none(kwargs, "buying_rate_profile") and \
            ("buying_rate_profile_uuid" not in kwargs or
             kwargs["buying_rate_profile_uuid"] is None):
        raise D3ADeviceException(
            {"misconfiguration": [f"buying_rate_profile must have a uuid."]})
    if key_in_dict_and_not_none_and_not_str_type(kwargs,
                                                 "buying_rate_profile_uuid"):
        raise D3ADeviceException({
            "misconfiguration":
            [f"buying_rate_profile_uuid must have a string type."]
        })
Example #2
0
def validate_pv_device_price(**kwargs):
    if "final_selling_rate" in kwargs and kwargs[
            "final_selling_rate"] is not None:
        error_message = {
            "misconfiguration": [
                f"final_selling_rate should be in between "
                f"{PvSettings.FINAL_SELLING_RATE_LIMIT.min} & "
                f"{PvSettings.FINAL_SELLING_RATE_LIMIT.max}"
            ]
        }
        validate_range_limit(PvSettings.FINAL_SELLING_RATE_LIMIT.min,
                             kwargs["final_selling_rate"],
                             PvSettings.FINAL_SELLING_RATE_LIMIT.max,
                             error_message)

    if "initial_selling_rate" in kwargs and kwargs[
            "initial_selling_rate"] is not None:
        error_message = {
            "misconfiguration": [
                f"initial_selling_rate should be in between "
                f"{PvSettings.INITIAL_SELLING_RATE_LIMIT.min} & "
                f"{PvSettings.INITIAL_SELLING_RATE_LIMIT.max}"
            ]
        }
        validate_range_limit(PvSettings.INITIAL_SELLING_RATE_LIMIT.min,
                             kwargs["initial_selling_rate"],
                             PvSettings.INITIAL_SELLING_RATE_LIMIT.max,
                             error_message)

    if ("initial_selling_rate" in kwargs and kwargs["initial_selling_rate"] is not None) and \
            ("final_selling_rate" in kwargs and kwargs["final_selling_rate"] is not None) and \
            (kwargs["initial_selling_rate"] < kwargs["final_selling_rate"]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"initial_selling_rate/market_maker_rate should be greater "
                f"than or equal to final_selling_rate. Please adapt the "
                f"market_maker_rate of the configuration or the "
                f"initial_selling_rate"
            ]
        })
    if ("fit_to_limit" in kwargs and kwargs["fit_to_limit"] is True) and \
            ("energy_rate_decrease_per_update" in kwargs and
             kwargs["energy_rate_decrease_per_update"] is not None):
        raise D3ADeviceException({
            "misconfiguration": [
                f"fit_to_limit & energy_rate_decrease_per_update "
                f"can't be set together."
            ]
        })
    if "energy_rate_decrease_per_update" in kwargs and \
            kwargs["energy_rate_decrease_per_update"] is not None:
        error_message = \
            {"misconfiguration": [f"energy_rate_decrease_per_update should be in between "
                                  f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min} & "
                                  f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max}"]}
        validate_range_limit(GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min,
                             kwargs["energy_rate_decrease_per_update"],
                             GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max,
                             error_message)
Example #3
0
    def __init__(
            self,
            panel_count: int = 1,
            initial_selling_rate: float = ConstSettings.GeneralSettings.
        DEFAULT_MARKET_MAKER_RATE,
            final_selling_rate: float = ConstSettings.PVSettings.
        FINAL_SELLING_RATE,
            fit_to_limit: bool = True,
            update_interval=duration(
                minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
            energy_rate_decrease_per_update: float = ConstSettings.
        GeneralSettings.ENERGY_RATE_DECREASE_PER_UPDATE,
            max_panel_power_W: float = None,
            use_market_maker_rate: bool = False):
        """
        :param panel_count: Number of solar panels for this PV plant
        :param initial_selling_rate: Upper Threshold for PV offers
        :param final_selling_rate: Lower Threshold for PV offers
        :param fit_to_limit: Linear curve following initial_selling_rate & initial_selling_rate
        :param update_interval: Interval after which PV will update its offer
        :param energy_rate_decrease_per_update: Slope of PV Offer change per update
        :param max_panel_power_W:
        """

        # If use_market_maker_rate is true, overwrite initial_selling_rate to market maker rate
        if use_market_maker_rate:
            initial_selling_rate = GlobalConfig.market_maker_rate

        try:
            validate_pv_device(panel_count=panel_count,
                               max_panel_power_W=max_panel_power_W)
        except D3ADeviceException as e:
            raise D3ADeviceException(str(e))

        if isinstance(update_interval, int):
            update_interval = duration(minutes=update_interval)

        BaseStrategy.__init__(self)
        self.offer_update = UpdateFrequencyMixin(
            initial_selling_rate, final_selling_rate, fit_to_limit,
            energy_rate_decrease_per_update, update_interval)
        for time_slot in generate_market_slot_list():
            try:
                validate_pv_device(
                    initial_selling_rate=self.offer_update.
                    initial_rate[time_slot],
                    final_selling_rate=self.offer_update.final_rate[time_slot])
            except D3ADeviceException as e:
                raise D3ADeviceException(str(e))
        self.panel_count = panel_count
        self.final_selling_rate = final_selling_rate
        self.max_panel_power_W = max_panel_power_W
        self.energy_production_forecast_kWh = {}  # type: Dict[Time, float]
        self.state = PVState()
def validate_market_maker(**kwargs):
    validate_energy_rate(**kwargs)
    if "energy_rate_profile" in kwargs and kwargs["energy_rate_profile"] is not None and \
            ("energy_rate_profile_uuid" not in kwargs or
             kwargs["energy_rate_profile_uuid"] is None):
        raise D3ADeviceException(
            {"misconfiguration": [f"energy_rate_profile must have a uuid."]})
    if "grid_connected" in kwargs and kwargs["grid_connected"] is not None and \
            not isinstance(kwargs["grid_connected"], bool):
        raise D3ADeviceException(
            {"misconfiguration": [f"grid_connected must be a boolean value."]})
def validate_finite_diesel_generator(**kwargs):
    if "max_available_power_kW" in kwargs and kwargs[
            "max_available_power_kW"] is not None:
        if isinstance(kwargs["max_available_power_kW"], (int, float)):
            error_message = \
                {"misconfiguration": [f"max_available_power_kW should be in between "
                                      f"{CepSettings.MAX_POWER_KW_LIMIT.min} & "
                                      f"{CepSettings.MAX_POWER_KW_LIMIT.max}."]}
            validate_range_limit(CepSettings.MAX_POWER_KW_LIMIT.min,
                                 kwargs["max_available_power_kW"],
                                 CepSettings.MAX_POWER_KW_LIMIT.max,
                                 error_message)
        elif isinstance(kwargs["max_available_power_kW"], dict):
            error_message = \
                {"misconfiguration": [f"max_available_power_kW should be in between "
                                      f"{CepSettings.MAX_POWER_KW_LIMIT.min} & "
                                      f"{CepSettings.MAX_POWER_KW_LIMIT.max}."]}
            for date, value in kwargs["max_available_power_kW"].items():
                validate_range_limit(CepSettings.MAX_POWER_KW_LIMIT.min, value,
                                     CepSettings.MAX_POWER_KW_LIMIT.max,
                                     error_message)
        else:
            raise D3ADeviceException({
                "misconfiguration":
                [f"max_available_power_kW has an "
                 f"invalid type. "]
            })

    validate_commercial_producer(**kwargs)
def validate_pv_device_energy(**kwargs):
    if "panel_count" in kwargs and kwargs["panel_count"] is not None:
        error_message = \
            {"misconfiguration": [f"PV panel count should be in between "
                                  f"{PvSettings.PANEL_COUNT_LIMIT.min} & "
                                  f"{PvSettings.PANEL_COUNT_LIMIT.max}"]}
        validate_range_limit(PvSettings.PANEL_COUNT_LIMIT.min,
                             kwargs["panel_count"],
                             PvSettings.PANEL_COUNT_LIMIT.max, error_message)
    if "max_panel_power_W" in kwargs and kwargs[
            "max_panel_power_W"] is not None:
        error_message = \
            {"misconfiguration": [f"max_panel_power_W should be in between "
                                  f"{PvSettings.MAX_PANEL_OUTPUT_W_LIMIT.min} & "
                                  f"{PvSettings.MAX_PANEL_OUTPUT_W_LIMIT.max}"]}
        validate_range_limit(PvSettings.MAX_PANEL_OUTPUT_W_LIMIT.min,
                             kwargs["max_panel_power_W"],
                             PvSettings.MAX_PANEL_OUTPUT_W_LIMIT.max,
                             error_message)
    if "cloud_coverage" in kwargs and kwargs["cloud_coverage"] is not None:
        if (kwargs["cloud_coverage"] != 4) and \
           ("power_profile" in kwargs and kwargs["power_profile"] is not None):
            raise D3ADeviceException({
                "misconfiguration": [
                    f"cloud_coverage (if values 0-3) & "
                    f"power_profile can't be set together."
                ]
            })
def validate_energy_rate(**kwargs):
    if "energy_rate" in kwargs and kwargs["energy_rate"] is not None:
        if isinstance(kwargs["energy_rate"], (float, int)):
            _validate_rate(kwargs["energy_rate"])
        elif isinstance(kwargs["energy_rate"], str):
            _validate_rate_profile(ast.literal_eval(kwargs["energy_rate"]))
        elif isinstance(kwargs["energy_rate"], dict):
            _validate_rate_profile(kwargs["energy_rate"])
        else:
            raise D3ADeviceException(
                {"misconfiguration": [f"energy_rate has an invalid type."]})
Example #8
0
    def area_reconfigure_event(self, **kwargs):
        assert all(k in self.parameters for k in kwargs.keys())
        try:
            validate_pv_device(**kwargs)
        except D3ADeviceException as e:
            raise D3ADeviceException(str(e))
        for name, value in kwargs.items():
            setattr(self, name, value)

        if 'initial_selling_rate' in kwargs and kwargs[
                'initial_selling_rate'] is not None:
            self.offer_update.initial_rate = read_arbitrary_profile(
                InputProfileTypes.IDENTITY, kwargs['initial_selling_rate'])
        if 'final_selling_rate' in kwargs and kwargs[
                'final_selling_rate'] is not None:
            self.offer_update.final_rate = read_arbitrary_profile(
                InputProfileTypes.IDENTITY, kwargs['final_selling_rate'])

        self.produced_energy_forecast_kWh()
        self.offer_update.update_offer(self)
Example #9
0
    def __init__(
            self,
            avg_power_W,
            hrs_per_day=None,
            hrs_of_day=None,
            fit_to_limit=True,
            energy_rate_increase_per_update=1,
            update_interval=duration(
                minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
            initial_buying_rate: Union[
                float, dict,
                str] = ConstSettings.LoadSettings.INITIAL_BUYING_RATE,
            final_buying_rate: Union[
                float, dict,
                str] = ConstSettings.LoadSettings.FINAL_BUYING_RATE,
            balancing_energy_ratio: tuple = (
                ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
                ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO),
            use_market_maker_rate: bool = False):
        """
        Constructor of LoadHoursStrategy
        :param avg_power_W: Power rating of load device
        :param hrs_per_day: Daily energy usage
        :param hrs_of_day: hours of day energy is needed
        :param fit_to_limit: if set to True, it will make a linear curve
        following following initial_buying_rate & final_buying_rate
        :param energy_rate_increase_per_update: Slope of Load bids change per update
        :param update_interval: Interval after which Load will update its offer
        :param initial_buying_rate: Starting point of load's preferred buying rate
        :param final_buying_rate: Ending point of load's preferred buying rate
        :param use_market_maker_rate: If set to True, Load would track its final buying rate
        as per utility's trading rate
        """

        # If use_market_maker_rate is true, overwrite final_buying_rate to market maker rate
        if use_market_maker_rate:
            final_buying_rate = GlobalConfig.market_maker_rate

        if isinstance(update_interval, int):
            update_interval = duration(minutes=update_interval)

        BidEnabledStrategy.__init__(self)
        self.bid_update = \
            UpdateFrequencyMixin(initial_rate=initial_buying_rate,
                                 final_rate=final_buying_rate,
                                 fit_to_limit=fit_to_limit,
                                 energy_rate_change_per_update=energy_rate_increase_per_update,
                                 update_interval=update_interval, rate_limit_object=min)
        try:
            validate_load_device(avg_power_W=avg_power_W,
                                 hrs_per_day=hrs_per_day,
                                 hrs_of_day=hrs_of_day)
        except D3ADeviceException as e:
            raise D3ADeviceException(str(e))

        for time_slot in generate_market_slot_list():
            rate_change = self.bid_update.energy_rate_change_per_update[
                time_slot]
            try:
                validate_load_device(
                    initial_buying_rate=self.bid_update.
                    initial_rate[time_slot],
                    final_buying_rate=self.bid_update.final_rate[time_slot],
                    energy_rate_increase_per_update=rate_change)
            except D3ADeviceException as e:
                raise D3ADeviceException(str(e))
        self.state = LoadState()
        self.avg_power_W = avg_power_W

        # consolidated_cycle is KWh energy consumed for the entire year
        self.daily_energy_required = None
        # Energy consumed during the day ideally should not exceed daily_energy_required
        self.energy_per_slot_Wh = None
        self.energy_requirement_Wh = {}  # type: Dict[Time, float]
        self.hrs_per_day = {}  # type: Dict[int, int]

        self.assign_hours_of_per_day(hrs_of_day, hrs_per_day)
        self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio)
def validate_range_limit(initial_limit, value, final_limit, error_message):
    if not initial_limit <= value <= final_limit:
        raise D3ADeviceException(error_message)
    def _validate_home_meter_consumption_rates(**kwargs):
        """Validate rates related to the consumption activity of the device."""
        if kwargs.get("final_buying_rate") is not None:
            error_message = {
                "misconfiguration": [
                    "final_buying_rate should be in between "
                    f"{HomeMeterSettings.FINAL_BUYING_RATE_LIMIT.min} & "
                    f"{HomeMeterSettings.FINAL_BUYING_RATE_LIMIT.max}."
                ]
            }

            validate_range_limit(HomeMeterSettings.FINAL_BUYING_RATE_LIMIT.min,
                                 kwargs["final_buying_rate"],
                                 HomeMeterSettings.FINAL_BUYING_RATE_LIMIT.max,
                                 error_message)

        if kwargs.get("initial_buying_rate") is not None:
            error_message = {
                "misconfiguration": [
                    "initial_buying_rate should be in between "
                    f"{HomeMeterSettings.INITIAL_BUYING_RATE_LIMIT.min} & "
                    f"{HomeMeterSettings.INITIAL_BUYING_RATE_LIMIT.max}"
                ]
            }

            validate_range_limit(
                HomeMeterSettings.INITIAL_BUYING_RATE_LIMIT.min,
                kwargs["initial_buying_rate"],
                HomeMeterSettings.INITIAL_BUYING_RATE_LIMIT.max, error_message)

        if (kwargs.get("initial_buying_rate") is not None
                and kwargs.get("final_buying_rate") is not None and
                kwargs["initial_buying_rate"] > kwargs["final_buying_rate"]):
            raise D3ADeviceException({
                "misconfiguration": [
                    "initial_buying_rate should be less than or equal to final_buying_rate/"
                    "market_maker_rate. Please adapt the market_maker_rate of the configuration "
                    "or the initial_buying_rate."
                ]
            })

        if kwargs.get("energy_rate_increase_per_update") is not None:
            error_message = {
                "misconfiguration": [
                    "energy_rate_increase_per_update should be in between "
                    f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min} & "
                    f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max}."
                ]
            }

            validate_range_limit(
                GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min,
                kwargs["energy_rate_increase_per_update"],
                GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max,
                error_message)

        if (kwargs.get("fit_to_limit") is True
                and kwargs.get("energy_rate_increase_per_update") is not None):
            raise D3ADeviceException({
                "misconfiguration": [
                    "fit_to_limit & energy_rate_increase_per_update can't be set together."
                ]
            })
def validate_storage_device(**kwargs):
    if "initial_soc" in kwargs and kwargs["initial_soc"] is not None:
        error_message = \
            {"misconfiguration": [f"initial_soc should be in between "
                                  f"{StorageSettings.INITIAL_CHARGE_LIMIT.min} & "
                                  f"{StorageSettings.INITIAL_CHARGE_LIMIT.max}."]}
        validate_range_limit(StorageSettings.INITIAL_CHARGE_LIMIT.min,
                             kwargs["initial_soc"],
                             StorageSettings.INITIAL_CHARGE_LIMIT.max,
                             error_message)

    if "min_allowed_soc" in kwargs and kwargs["min_allowed_soc"] is not None:
        error_message = \
            {"misconfiguration": [f"min_allowed_soc should be in between "
                                  f"{StorageSettings.MIN_SOC_LIMIT.min} & "
                                  f"{StorageSettings.MIN_SOC_LIMIT.max}."]}
        validate_range_limit(StorageSettings.MIN_SOC_LIMIT.min,
                             kwargs["min_allowed_soc"],
                             StorageSettings.MIN_SOC_LIMIT.max, error_message)

    if ("initial_soc" in kwargs and kwargs["initial_soc"] is not None) and \
            ("min_allowed_soc" in kwargs and kwargs["min_allowed_soc"] is not None) and \
            (kwargs["initial_soc"] < kwargs["min_allowed_soc"]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"initial_soc should be greater "
                f"than or equal to min_allowed_soc."
            ]
        })

    if "battery_capacity_kWh" in kwargs and kwargs[
            "battery_capacity_kWh"] is not None:
        error_message = \
            {"misconfiguration": [f"battery_capacity_kWh should be in between "
                                  f"{StorageSettings.CAPACITY_LIMIT.min} & "
                                  f"{StorageSettings.CAPACITY_LIMIT.max}."]}
        validate_range_limit(StorageSettings.CAPACITY_LIMIT.min,
                             kwargs["battery_capacity_kWh"],
                             StorageSettings.CAPACITY_LIMIT.max, error_message)
    if "max_abs_battery_power_kW" in kwargs and kwargs[
            "max_abs_battery_power_kW"] is not None:
        error_message = \
            {"misconfiguration": [f"max_abs_battery_power_kW should be in between "
                                  f"{StorageSettings.MAX_ABS_POWER_RANGE.initial} & "
                                  f"{StorageSettings.MAX_ABS_POWER_RANGE.final}."]}
        validate_range_limit(StorageSettings.MAX_ABS_POWER_RANGE.initial,
                             kwargs["max_abs_battery_power_kW"],
                             StorageSettings.MAX_ABS_POWER_RANGE.final,
                             error_message)

    if "initial_selling_rate" in kwargs and kwargs[
            "initial_selling_rate"] is not None:
        error_message = \
            {"misconfiguration": [f"initial_selling_rate should be in between "
                                  f"{StorageSettings.INITIAL_SELLING_RATE_LIMIT.min} & "
                                  f"{StorageSettings.INITIAL_SELLING_RATE_LIMIT.max}."]}
        validate_range_limit(StorageSettings.INITIAL_SELLING_RATE_LIMIT.min,
                             kwargs["initial_selling_rate"],
                             StorageSettings.INITIAL_SELLING_RATE_LIMIT.max,
                             error_message)

    if "final_selling_rate" in kwargs and kwargs[
            "final_selling_rate"] is not None:
        error_message = \
            {"misconfiguration": [f"final_selling_rate should be in between "
                                  f"{StorageSettings.FINAL_SELLING_RATE_LIMIT.min} & "
                                  f"{StorageSettings.FINAL_SELLING_RATE_LIMIT.max}."]}
        validate_range_limit(StorageSettings.FINAL_SELLING_RATE_LIMIT.min,
                             kwargs["final_selling_rate"],
                             StorageSettings.FINAL_SELLING_RATE_LIMIT.max,
                             error_message)

    if ("initial_selling_rate" in kwargs and kwargs["initial_selling_rate"] is not None) and \
            ("final_selling_rate" in kwargs and kwargs["final_selling_rate"] is not None) and \
            (kwargs["initial_selling_rate"] < kwargs["final_selling_rate"]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"initial_selling_rate should be greater "
                f"than or equal to final_selling_rate."
            ]
        })

    if "initial_buying_rate" in kwargs and kwargs[
            "initial_buying_rate"] is not None:
        error_message = \
            {"misconfiguration": [f"initial_buying_rate should be in between "
                                  f"{StorageSettings.INITIAL_BUYING_RATE_LIMIT.min} & "
                                  f"{StorageSettings.INITIAL_BUYING_RATE_LIMIT.max}."]}
        validate_range_limit(StorageSettings.INITIAL_BUYING_RATE_LIMIT.min,
                             kwargs["initial_buying_rate"],
                             StorageSettings.INITIAL_BUYING_RATE_LIMIT.max,
                             error_message)

    if "final_buying_rate" in kwargs and kwargs[
            "final_buying_rate"] is not None:
        error_message = {
            "misconfiguration": [
                f"final_buying_rate should be in between "
                f"{StorageSettings.FINAL_BUYING_RATE_LIMIT.min} & "
                f"{StorageSettings.FINAL_BUYING_RATE_LIMIT.max}."
            ]
        }
        validate_range_limit(StorageSettings.FINAL_BUYING_RATE_LIMIT.min,
                             kwargs["final_buying_rate"],
                             StorageSettings.FINAL_BUYING_RATE_LIMIT.max,
                             error_message)
    if ("initial_buying_rate" in kwargs and kwargs["initial_buying_rate"] is not None) and \
            ("final_buying_rate" in kwargs and kwargs["final_buying_rate"] is not None) and \
            (kwargs["initial_buying_rate"] > kwargs["final_buying_rate"]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"initial_buying_rate should be less "
                f"than or equal to final_buying_rate."
            ]
        })
    if ("final_selling_rate" in kwargs and kwargs["final_selling_rate"] is not None) and \
            ("final_buying_rate" in kwargs and kwargs["final_buying_rate"] is not None) and \
            (kwargs["final_buying_rate"] > kwargs["final_selling_rate"]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"final_buying_rate should be less "
                f"than or equal to final_selling_rate."
            ]
        })

    if "energy_rate_increase_per_update" in kwargs and \
            kwargs["energy_rate_increase_per_update"] is not None:
        error_message = \
            {"misconfiguration": [f"energy_rate_increase_per_update should be in between "
                                  f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min} & "
                                  f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max}."]}
        validate_range_limit(GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min,
                             kwargs["energy_rate_increase_per_update"],
                             GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max,
                             error_message)

    if "energy_rate_decrease_per_update" in kwargs and \
            kwargs["energy_rate_decrease_per_update"] is not None:
        error_message = \
            {"misconfiguration": [f"energy_rate_decrease_per_update should be in between "
                                  f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min} & "
                                  f"{GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max}."]}
        validate_range_limit(GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.min,
                             kwargs["energy_rate_decrease_per_update"],
                             GeneralSettings.RATE_CHANGE_PER_UPDATE_LIMIT.max,
                             error_message)
    if ("fit_to_limit" in kwargs and kwargs["fit_to_limit"] is True) and \
            (("energy_rate_increase_per_update" in kwargs and
              kwargs["energy_rate_increase_per_update"] is not None) or
             ("energy_rate_decrease_per_update" in kwargs and
              kwargs["energy_rate_decrease_per_update"] is not None)):
        raise D3ADeviceException({
            "misconfiguration": [
                f"fit_to_limit & energy_rate_change_per_update "
                f"can't be set together."
            ]
        })

    if key_in_dict_and_not_none(kwargs, "loss_function"):
        error_message = {
            "misconfiguration": [
                f"loss_function should either be "
                f"{StorageSettings.LOSS_FUNCTION_LIMIT.min} or "
                f"{StorageSettings.LOSS_FUNCTION_LIMIT.max}."
            ]
        }
        validate_range_limit(StorageSettings.LOSS_FUNCTION_LIMIT.min,
                             kwargs["loss_function"],
                             StorageSettings.LOSS_FUNCTION_LIMIT.max,
                             error_message)
        if key_in_dict_and_not_none(kwargs, "loss_per_hour"):
            if kwargs["loss_function"] == 1:
                error_message = {
                    "misconfiguration": [
                        f"loss_per_hour should be in between "
                        f"{StorageSettings.LOSS_PER_HOUR_RELATIVE_LIMIT.min} & "
                        f"{StorageSettings.LOSS_PER_HOUR_RELATIVE_LIMIT.max}."
                    ]
                }
                validate_range_limit(
                    StorageSettings.LOSS_PER_HOUR_RELATIVE_LIMIT.min,
                    kwargs["loss_per_hour"],
                    StorageSettings.LOSS_PER_HOUR_RELATIVE_LIMIT.max,
                    error_message)
            else:
                error_message = {
                    "misconfiguration": [
                        f"loss_per_hour should be in between "
                        f"{StorageSettings.LOSS_PER_HOUR_ABSOLUTE_LIMIT.min} & "
                        f"{StorageSettings.LOSS_PER_HOUR_ABSOLUTE_LIMIT.max}."
                    ]
                }
                validate_range_limit(
                    StorageSettings.LOSS_PER_HOUR_ABSOLUTE_LIMIT.min,
                    kwargs["loss_per_hour"],
                    StorageSettings.LOSS_PER_HOUR_ABSOLUTE_LIMIT.max,
                    error_message)
def validate_load_device_energy(**kwargs):
    if "avg_power_W" in kwargs and kwargs["avg_power_W"] is not None:
        error_message = {
            "misconfiguration": [
                f"avg_power_W should be in between "
                f"{LoadSettings.AVG_POWER_LIMIT.min} & "
                f"{LoadSettings.AVG_POWER_LIMIT.max}."
            ]
        }
        validate_range_limit(LoadSettings.AVG_POWER_LIMIT.min,
                             kwargs["avg_power_W"],
                             LoadSettings.AVG_POWER_LIMIT.max, error_message)

    if (("avg_power_W" in kwargs and kwargs["avg_power_W"] is not None) or
        ("hrs_per_day" in kwargs and kwargs["hrs_per_day"] is not None) or
        ("hrs_of_day" in kwargs and kwargs["hrs_of_day"] is not None)) \
            and ("daily_load_profile" in kwargs and kwargs["daily_load_profile"] is not None):
        raise D3ADeviceException({
            "misconfiguration": [
                f"daily_load_profile shouldn't be set with "
                f"avg_power_W, hrs_per_day & hrs_of_day."
            ]
        })
    if "hrs_per_day" in kwargs and kwargs["hrs_per_day"] is not None:
        error_message = {
            "misconfiguration": [
                f"hrs_per_day should be in between "
                f"{LoadSettings.HOURS_LIMIT.min} & "
                f"{LoadSettings.HOURS_LIMIT.max}."
            ]
        }
        validate_range_limit(LoadSettings.HOURS_LIMIT.min,
                             kwargs["hrs_per_day"],
                             LoadSettings.HOURS_LIMIT.max, error_message)
    if ("hrs_of_day" in kwargs and kwargs["hrs_of_day"] is not None) and \
            any([not LoadSettings.HOURS_LIMIT.min <= h <= LoadSettings.HOURS_LIMIT.max
                 for h in kwargs["hrs_of_day"]]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"hrs_of_day should be less between "
                f"{LoadSettings.HOURS_LIMIT.min} & "
                f"{LoadSettings.HOURS_LIMIT.max}."
            ]
        })
    if ("hrs_of_day" in kwargs and kwargs["hrs_of_day"] is not None) and \
            ("hrs_per_day" in kwargs and kwargs["hrs_per_day"] is not None) and \
            (len(kwargs["hrs_of_day"]) < kwargs["hrs_per_day"]):
        raise D3ADeviceException({
            "misconfiguration": [
                f"length of hrs_of_day list should be "
                f"greater than or equal hrs_per_day."
            ]
        })

    if (("avg_power_W" in kwargs and kwargs["avg_power_W"] is not None) or
            ("hrs_per_day" in kwargs and kwargs["hrs_per_day"] is not None) or
            ("hrs_of_day" in kwargs and kwargs["hrs_of_day"] is not None)) and \
            ("daily_load_profile" in kwargs and kwargs["daily_load_profile"] is not None):
        raise D3ADeviceException({
            "misconfiguration": [
                f"daily_load_profile and all or one [hrs_per_day, hrs_of_day, "
                f"avg_power_W] can't be set together."
            ]
        })