from ScEpTIC.emulator.energy import energy_utils from ScEpTIC.emulator.energy.voltage_source import VoltageSource from ScEpTIC.exceptions import ConfigurationException class EnergyBufferModel(VoltageSource): """ Model of an energy buffer """ def __init__(self, voltage_upper_bound, energy_upper_bound): """ :param voltage_upper_bound: maximum voltage of the energy buffer (string with unit multiplier or float) :param energy_upper_bound: maximum energy level of the energy buffer (string with unit multiplier or float) """ # current data self._current_voltage = 0.0 self._current_energy = 0.0 # lower bounds self._voltage_lower_bound = 0.0 self._energy_lower_bound = 0.0 # upper bounds if voltage_upper_bound is None and energy_upper_bound is None: raise ConfigurationException(f"{self.__class__.__name__} configuration error: voltage_upper_bound or energy_upper_bound must be specified!") if voltage_upper_bound is not None: self._voltage_upper_bound = energy_utils.str_to_float(voltage_upper_bound) self._energy_upper_bound = self._calculate_energy_from_voltage(self._voltage_upper_bound) else: self._energy_upper_bound = energy_utils.str_to_float(energy_upper_bound) self._voltage_upper_bound = self._calculate_voltage_from_energy(self._energy_upper_bound) self.discharge_enabled = True def get_max_voltage(self): """ :return: the maximum voltage supported by the energy buffer """ return self._voltage_upper_bound def get_voltage(self): """ :return: current voltage level """ return self._current_voltage def get_energy(self): """ :return: current energy level """ return self._current_energy def enable_discharge(self): """ Enable the energy buffer discharge """ self.discharge_enabled = True def disable_discharge(self): """ Enable the energy buffer discharge """ self.discharge_enabled = False def get_usable_voltage(self): """ :return: The difference between current voltage and the voltage lower bound """ return self._current_voltage - self._voltage_lower_bound def get_usable_energy(self): """ :return: The remaining usable energy (i.e., the one from current voltage level to the voltage lower bound) """ return self._current_energy - self._energy_lower_bound def set_voltage_lower_bound(self, v_min): """ Sets the voltage lower bound to v_min and updates the energy lower bound. The voltage lower bound usually corresponds to the voltage at which the MCU shuts down and specifies the level of the energy buffer that cannot be used for useful computation :param v_min: minimum voltage (string with multiplier or float) """ self._voltage_lower_bound = energy_utils.str_to_float(v_min) self._energy_lower_bound = self._calculate_energy_from_voltage(self._voltage_lower_bound) def set_energy_lower_bound(self, e_min): """ Sets the energy lower bound to e_min and updates the voltage lower bound. The energy lower bound corresponds to the energy that cannot be used for useful computation due to a voltage level below the MCU minimum operating voltage :param e_min: minimum energy (string with multiplier or float) """ self._energy_lower_bound = energy_utils.str_to_float(e_min) self._voltage_lower_bound = self._calculate_voltage_from_energy(self._energy_lower_bound) def increment_voltage(self, v): """ Increments the energy buffer voltage by v and updates the energy level :param v: voltage to increment """ new_voltage = self._current_voltage + float(v) self.set_voltage(new_voltage) def set_voltage(self, new_voltage): """ Sets the energy buffer voltage to v and updates the energy level Note: if new_voltage > voltage_upper_bound, new_voltage = voltage_upper_bound """ # enforce upper bound and min 0V self._current_voltage = max(0.0, min(float(new_voltage), self._voltage_upper_bound)) self._update_energy_from_voltage() def execute_full_refill(self): """ Sets the energy buffer to the maximum level """ self.set_voltage(self._voltage_upper_bound) def _update_voltage_from_energy(self): """ Updates the voltage with respect to the current energy level """ self._current_voltage = self._calculate_voltage_from_energy(self._current_energy) def increment_energy(self, e): """ Increments the energy buffer energy by e and updates the voltage :param e: energy to increment """ new_energy = self._current_energy + float(e) self.set_energy(new_energy) def set_energy(self, new_energy): """ Sets the energy buffer energy to e and updates the voltage Note: if new_energy > energy_upper_bound, new_energy = energy_upper_bound """ # enforce upper bound and min 0J self._current_energy = max(0.0, min(float(new_energy), self._energy_upper_bound)) self._update_voltage_from_energy() def get_energy_charge(self, v_supply, t_elapsed, R): """ Returns the amount of energy that the energy buffer would recharge from v_supply :param v_supply: voltage of the energy source :param t_elapsed: time elapsed :param R: circuit equivalent resistance """ raise NotImplementedError(f'{self.__class__.__name__} must implement get_energy_charge()') def get_size(self): """ :return: the energy buffer size """ raise NotImplementedError(f'{self.__class__.__name__} must implement get_size()') def get_nominal_size(self): """ :return: the energy buffer size in textual representation """ raise NotImplementedError(f'{self.__class__.__name__} must implement get_nominal_size()') def update_size(self, size): """ Updates the energy buffer size :param size: new size """ raise NotImplementedError(f'{self.__class__.__name__} must implement update_size()') def update(self, voltage_intervals, e_drawn, charge_r, elapsed_time, preserve_voltage=False): """ Updates the voltage of the energy buffer (discharge / charge cycles) Note: if discharge is not enabled, the energy buffer level remains constant :param voltage_intervals: list of (voltage, time) :param e_drawn: energy drawn :param charge_r: equivalent resistance of the system when the energy buffer is going to be charged :param elapsed_time: elapsed time :param preserve_voltage: do not update the voltage :return: the energy harvested """ raise NotImplementedError(f'{self.__class__.__name__} must implement update()') def calculate_recharge_time_to_voltage(self, v_target, v_supply, eq_r): """ Calculates the time required to reach the voltage v_target :param v_target: the target voltage :param v_supply: the voltage of the power supply :param eq_r: the equivalent resistance of the circuit :return: the time required to recharge the energy buffer to v_target """ raise NotImplementedError(f'{self.__class__.__name__} must implement calculate_recharge_time_to_voltage()') def calculate_recharge_energy_to_voltage(self, v_target): """ Calculates the energy required to reach the voltage v_target :param v_target: the target voltage :return: the energy required to recharge the energy buffer to v_target """ raise NotImplementedError(f'{self.__class__.__name__} must implement calcualte_recharged_energy_to_voltage()') def _update_energy_from_voltage(self): """ Updates the energy level with respect to the current voltage """ self._current_energy = self._calculate_energy_from_voltage(self._current_voltage) def _calculate_energy_from_voltage(self, v): """ :return: the energy level at the voltage v """ raise NotImplementedError(f'{self.__class__.__name__} must implement _calculate_energy_from_voltage()') def _calculate_voltage_from_energy(self, e): """ :return: the voltage at the energy level e """ raise NotImplementedError(f'{self.__class__.__name__} must implement _calculate_voltage_from_energy()')