194 lines
7.7 KiB
Python
194 lines
7.7 KiB
Python
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)
|