import math from ScEpTIC.emulator.energy import energy_utils from ScEpTIC.emulator.energy.buffer import EnergyBufferModel class CapacitorModel(EnergyBufferModel): """ Capacitor model E = 1/2 C V^2 """ def __init__(self, capacitance, voltage_upper_bound, energy_upper_bound): """ :param capacitance: Capacitance of the capacitor :param voltage_upper_bound: maximum voltage of the capacitor :param energy_upper_bound: maximum energy level of the capacitor """ self._capacitance_str = capacitance self._capacitance = energy_utils.str_to_float(capacitance) super().__init__(voltage_upper_bound, energy_upper_bound) def _calculate_energy_from_voltage(self, v): """ :return: the energy level at the voltage v """ return 0.5 * self._capacitance * math.pow(float(v), 2) def _calculate_voltage_from_energy(self, e): """ :return: the voltage at the energy level e """ return math.sqrt((2 * e) / self._capacitance) def get_size(self): """ :return: the capacitance """ return self._capacitance def get_nominal_size(self): """ :return: the energy buffer size in textual representation """ return self._capacitance_str def update_size(self, capacitance, keep_energy_level=False): """ Updates the capacitor size :param capacitance: new capacitance :param keep_energy_level: updates the voltage of the capacitor (keeps the energy level) """ self._capacitance = energy_utils.str_to_float(capacitance) self._capacitance_str = capacitance if keep_energy_level: self._update_voltage_from_energy() else: self._update_energy_from_voltage() # Update energy upper bound self._energy_upper_bound = self._calculate_energy_from_voltage(self._voltage_upper_bound) 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 """ # Current voltage level > source level -> no charge if self._current_voltage >= v_supply: return 0.0 # Cap RC constant RC = R * self._capacitance # Note: v_new may be higher than voltage_upper_bound. # This is ok, as here we are considering the energy recharged from the energy source. # Vnew = Vsupply + (Vcurrent - Vsupply) * e^(-t/RC) v_new = v_supply + (self._current_voltage - v_supply) * math.pow(math.e, -t_elapsed / RC) #t_current_charge = -1 * RC * math.log((v_supply - v_current) / v_supply) #t = t_current_charge + t_elapsed #v_new = v_supply * (1 - math.pow(math.e, -1 * t / RC)) # Charged energy = new voltage energy - old voltage energy charged_energy = self._calculate_energy_from_voltage(v_new) - self._calculate_energy_from_voltage(self._current_voltage) return charged_energy 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 """ if not self.discharge_enabled: return 0.0 e_harvested = 0.0 e_drawn_time = e_drawn / elapsed_time tmp_voltage = self._current_voltage for v_supply, e_max, t_elapsed in voltage_intervals: # MCU/circuitry energy consumption interval_e_drawn = e_drawn_time * t_elapsed # Charge (V supply higher than current capacitor voltage) # The energy source provides energy to both the capacitor and the MCU/components. # The MCU/components draw energy from the energy harvester only, as the energy buffer is in parallel. if v_supply >= self._current_voltage: # Energy available for capacitor's charge e_available = e_max - interval_e_drawn # Energy harvester supplies insufficient energy. Therefore, the circuit draws energy from both if e_available <= 0: # harvest maximum energy (used by system) e_harvested += e_max # Decrement available energy (available_energy is negative) self.increment_energy(e_available) # Energy harvester supplies sufficient energy to recharge the capacitor and power the MCU/circuitry else: # Capacitor's maximum charged energy (limit by energy source residual energy after system energy consumption) e_charge = min(self.get_energy_charge(v_supply, t_elapsed, charge_r), e_available) e_harvested += interval_e_drawn + e_charge self.increment_energy(e_charge) # Discharge (V supply lower than current capacitor voltage) else: # The system drains energy from the capacitor until it reaches v_supply. Then, the energy is drawn from v_supply # Energy available from the capacitor from its current voltage to v_supply cap_energy = self._calculate_energy_from_voltage(self._current_voltage) - self._calculate_energy_from_voltage(v_supply) # The system drains all the energy from the capacitor if interval_e_drawn <= cap_energy: self.increment_energy(-interval_e_drawn) # The system drains energy also from the energy source else: # Residual energy to be drawn from the energy source diff_energy = interval_e_drawn - cap_energy # Energy drawn from the energy source (capped to e_max) energy_from_harvester = min(e_max, diff_energy) e_harvested += energy_from_harvester # Energy from the capacitor: cap_energy + (diff_energy-energy_from_harvester) cap_energy += (diff_energy - energy_from_harvester) self.increment_energy(-cap_energy) # preserve voltage if preserve_voltage: self.set_voltage(tmp_voltage) return e_harvested 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 """ RC = self._capacitance * eq_r ratio = (v_supply - self._current_voltage) / (v_supply - v_target) return RC * math.log(ratio, math.e) 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 """ return self._calculate_energy_from_voltage(v_target) - self._calculate_energy_from_voltage(self._current_voltage)