2026-07-10 10:38:57 +02:00

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)