import gc from collections import defaultdict from ScEpTIC.emulator.custom_devices import CustomDevice from ScEpTIC.emulator.energy import energy_utils from ScEpTIC.emulator.energy.buffer import EnergyBufferModel from ScEpTIC.emulator.energy.energy_source import EnergySourceModel from ScEpTIC.emulator.energy.energy_source.synthetic_energy_source import SyntheticEnergySource from ScEpTIC.emulator.energy.energy_harvester import EnergyHarvesterModel from ScEpTIC.emulator.energy.energy_harvester.generic import GenericEnergyHarvester from ScEpTIC.emulator.energy.memory.physical_memory_energy_model import PhysicalMemoryEnergyModel from ScEpTIC.emulator.energy.mcu import MCUEnergyModel, MCUPowerState, MCUClockCycleAction from ScEpTIC.emulator.energy.mcu_peripheral import MCUPeripheral from ScEpTIC.emulator.energy.options import ComponentVoltageSource, PowerOffCondition from ScEpTIC.emulator.energy.power_state_event import PowerStateEvent from ScEpTIC.emulator.energy.state_retention import StateRetentionEnergyModel from ScEpTIC.emulator.energy.timekeeper import TimekeeperModel from ScEpTIC.emulator.energy.voltage_regulator import NoRegulator, VoltageRegulatorModel from ScEpTIC.exceptions import ConfigurationException class SystemEnergyModel: """ Class to model the system energy consumption """ def __init__(self): # index 0 -> operation mode; index 1 -> MCU_CLOCK_ACTION # {'total': {'total': 0.0, MCU_ACTION_1: 0.0,} , 'op_mode_1': {...}} self.elapsed_time = defaultdict(lambda: defaultdict(float)) self.elapsed_ticks = defaultdict(lambda: defaultdict(int)) # index 0 -> component; index 1 -> operation mode; index 2 -> MCU_CLOCK_ACTION # {'total': {'total': {'total': 0.0, MCU_ACTION1: 0.0,}, 'component1': {...}} self.used_energy = defaultdict(lambda: defaultdict(lambda: defaultdict(float))) self.vreg_energy_loss = defaultdict(lambda: defaultdict(lambda: defaultdict(float))) # index 0 -> operation mode; index 1-> MCUPowerState self.harvested_energy = defaultdict(lambda: defaultdict(float)) # standard components self.energy_buffer = None self.voltage_regulator = None self.voltage_regulator_buffer = None self.voltage_regulator_mcu = None self.mcu = None self.timekeeper = None # additional components of the system self.additional_components = { ComponentVoltageSource.ENERGY_BUFFER: {}, ComponentVoltageSource.VOLTAGE_REGULATOR: {} } # custom devices - elements with their own logic and setup (e.g., the model of the D2VFS board) self.custom_devices = [] # mcu_peripheral self.mcu_peripherals = [] self.physical_memories = [] # state retention model self.state_retention = None # Power off condition self.power_off_condition = PowerOffCondition.POWER_STATE_EVENT self.power_off_events = [] self.power_off_voltage_threshold = 0.0 # Power failure self.automatic_check_for_power_failures = False self.cached_power_failure = False self.cached_energy = 0.0 self.cached_harvested_energy = 0.0 self.power_failures = 0 # Instant values self.instant_V = 0.0 self.instant_E = 0.0 self.instant_EH = 0.0 # Energy source model self.energy_source = None self.energy_harvester = None # Collect signals self.collect_signals = False self.signals = [] self.custom_signals_keys = [] self.custom_signals_strings = [] self.custom_signals = {} self._init_signals() self.total_time = 0.0 self.lpm_precise_ticks = False def enable_lpm_precise_ticks(self): """ Enables LPM simulation of t = mcu tick """ self.lpm_precise_ticks = True def disable_lpm_precise_ticks(self): """ Revers LPM simulation of t = source_sample_time """ self.lpm_precise_ticks = False def get_simulation_time(self): """ Returns total elapsed time in s """ return self.elapsed_time['total']['total'] def reset_simulation_time(self): self.elapsed_time = defaultdict(lambda: defaultdict(float)) def get_elapsed_ticks(self): """ Returns total elapsed clock ticks """ return self.elapsed_ticks['total']['total'] def get_used_energy(self): """ Returns total used energy """ return self.used_energy['total']['total']['total'] def reset_used_energy(self): """ Resets the total energy usage """ self.used_energy['total']['total']['total'] = 0.0 def disable_stats(self): """ Disables statistics collection """ self.__update_stats, self.__disabled_update_stats = self.__disabled_update_stats, self.__update_stats def init_custom_signals(self, data): """ Set the strings and keys of custom signals collected during the simulation :param data: a list of tuples (key, string) """ if not isinstance(data, list): raise ConfigurationException(f"set_custom_signal_strings() requires a list of tuples.") del self.custom_signals_strings del self.custom_signals_keys self.custom_signals_strings = [] self.custom_signals_keys = [] for key, name in data: self.custom_signals_keys.append(key) self.custom_signals_strings.append(name) self.custom_signals[key] = None self._init_signals() def set_custom_signal(self, name, val): """ Set the custom signals collected during the simulation :param name: the signal name :param val: the signal value """ self.custom_signals[name] = val def get_stats(self): """ :return: the simulation statistics and the name of each label """ names = { 'time': 'Elapsed Time', 'ticks': 'Simulated Ticks', 'energy': 'Energy Consumption', 'harvested_energy': 'Harvested Energy', 'voltage_regulator_loss': 'Energy - Voltage Regulator Loss', 'power_failures': 'Simulated Power Failures', 'n_state_saves': 'Number of State Saves', 'n_restores': 'Number of State Restores', 'state_max_regs': 'Max Registers Saved', 'state_max_cells': 'Max Memory Cells Saved', } stats = { 'time': self.elapsed_time, 'ticks': self.elapsed_ticks, 'energy': self.used_energy, 'harvested_energy': self.harvested_energy, 'voltage_regulator_loss': self.vreg_energy_loss, 'power_failures': self.power_failures, 'n_state_saves': self.state_retention.n_state_save, 'n_restores': self.state_retention.n_state_restore, 'state_max_regs': self.state_retention.max_registers, 'state_max_cells': self.state_retention.max_memory_cells, } return names, stats def get_collected_signals(self): """ :return: the collected signals """ return self.signals def set_power_off_condition(self, power_off_condition): """ Sets the power off condition for the MCU :param power_off_condition: a PowerOffCondition """ if not isinstance(power_off_condition, PowerOffCondition): raise ConfigurationException(f"Wrong power off condition: {power_off_condition.__class__.__name__} is not a PowerOffCondition") self.power_off_condition = power_off_condition def set_power_off_voltage(self, voltage): """ Sets the power off voltage (considered only when power off condition = PowerOffCondition.VOLTAGE_THRESHOLD) :param voltage: the power off voltage """ self.power_off_voltage_threshold = float(voltage) def set_power_failures_automatic_check(self, val): """ Sets how power failures are controlled :param val: True/False """ self.automatic_check_for_power_failures = val def add_power_off_event(self, event): """ Adds a PowerStateEvent to the list of events that will cause the system to power off :param event: a PowerStateEvent """ if not isinstance(event, PowerStateEvent): raise ConfigurationException(f"add_power_off_event() requires a PowerStateEvent, {event.__class__.__name__} provided.") self.power_off_events.append(event) def attach_energy_buffer(self, energy_buffer): """ Attaches an energy buffer to the system model :param energy_buffer: the energy buffer (must inherit from EnergyBufferModel) """ if self.energy_buffer is not None: raise ConfigurationException("Energy buffer already atteched!") if not isinstance(energy_buffer, EnergyBufferModel): raise ConfigurationException("Wrong energy buffer model!") self.energy_buffer = energy_buffer if self.voltage_regulator is not None: self.voltage_regulator.attach_voltage_source(self.energy_buffer) for component in self.additional_components[ComponentVoltageSource.ENERGY_BUFFER]: component.attach_voltage_source(self.energy_buffer) def attach_energy_harvester(self, energy_harvester): """ Attaches an energy harvester to the system model :param energy_harvester: the energy harvester """ if self.energy_harvester is not None: raise ConfigurationException("Energy harvester already attached!") if not isinstance(energy_harvester, EnergyHarvesterModel): raise ConfigurationException("Wrong energy harvester model!") self.energy_harvester = energy_harvester if self.energy_source is not None: self.energy_harvester.attach_energy_source(self.energy_source) def attach_mcu(self, mcu): """ Attaches a MCU to the system model :param mcu: the MCU (must inherit from MCUEnergyModel) """ if self.mcu is not None: raise ConfigurationException("MCU already attached!") if not isinstance(mcu, MCUEnergyModel): raise ConfigurationException("Wrong mcu energy model!") self.mcu = mcu if self.voltage_regulator is not None: self.mcu.attach_voltage_source(self.voltage_regulator) for peripheral in self.mcu_peripherals: peripheral.attach_mcu(self.mcu) self.mcu.attach_peripheral(peripheral) def attach_timekeeper(self, timekeeper): """ Attaches a timekeeper to the system model :param timekeeper: the timekeeper """ if self.timekeeper is not None: raise ConfigurationException("Timekeeper already attached!") if not isinstance(timekeeper, TimekeeperModel): raise ConfigurationException("Wrong timekeeper model!") self.timekeeper = timekeeper self.timekeeper.attach_system_model(self) def attach_voltage_regulator(self, voltage_regulator): """ Attaches a voltage regulator to the system model :param voltage_regulator: the voltage regulator (must inherit from VoltageRegulatorModel) """ if self.voltage_regulator is not None: raise ConfigurationException("Voltage regulator already attached!") if voltage_regulator is not None and not isinstance(voltage_regulator, VoltageRegulatorModel): raise ConfigurationException("Wrong voltage regulator energy model!") # Crate dummy regulator to preserve overall logic if voltage_regulator is None: voltage_regulator = NoRegulator() self.voltage_regulator = voltage_regulator if self.energy_buffer is not None: self.voltage_regulator.attach_voltage_source(self.energy_buffer) if self.mcu is not None: self.mcu.attach_voltage_source(self.voltage_regulator) for component in self.additional_components[ComponentVoltageSource.VOLTAGE_REGULATOR]: component.attach_voltage_source(self.voltage_regulator) def attach_component(self, name, component, target): """ Attaches an additional component to the energy buffer, the voltage regulator, or the MCU. :param name: component name for stats :param component: the component :param target: ComponentVoltageSource enum specifying the voltage source for the device """ if not isinstance(target, ComponentVoltageSource): raise ConfigurationException(f"Wrong target {target.__class__.__name__} for component {component.__class__.__name__}") if isinstance(component, PhysicalMemoryEnergyModel): self.physical_memories.append(component) name = component.get_name() self.additional_components[target][name] = component if target == ComponentVoltageSource.ENERGY_BUFFER: if self.energy_buffer is not None: component.attach_voltage_source(self.energy_buffer) elif target == ComponentVoltageSource.VOLTAGE_REGULATOR: if self.voltage_regulator is not None: component.attach_voltage_source(self.voltage_regulator) else: raise ConfigurationException(f"Wrong target '{target.__class__.__name__}' for component {name}") if hasattr(component, 'attach_system_model') and callable(getattr(component, 'attach_system_model')): component.attach_system_model(self) # attach peripheral to MCU if isinstance(component, MCUPeripheral): self.mcu_peripherals.append(component) if self.mcu is not None: component.attach_mcu(self.mcu) self.mcu.attach_peripheral(component) def attach_custom_device(self, custom_device): """ Attaches a custom device to the system model and initializes it. Note that a custom device consists in a model of an ad-hoc device, which may include multiple components and additional logic. :param custom_device: the custom device (must inherit from CustomDevice) """ if not isinstance(custom_device, CustomDevice): raise ConfigurationException(f"Wrong custom device model! {custom_device.__class__.__name__} is not a valid CustomDevice") if custom_device in self.custom_devices: raise ConfigurationException(f"{custom_device.__class__.__name__} already attached!") self.custom_devices.append(custom_device) custom_device.setup(self) def attach_state_retention_model(self, state_retention): """ Attaches a state retention model to the system model :param state_retention: the state retention model (must inherit from StateRetentionEnergyModel) """ if self.state_retention is not None: raise ConfigurationException("State retention model already attached!") if not isinstance(state_retention, StateRetentionEnergyModel): raise ConfigurationException("Wrong state retention energy model!") self.state_retention = state_retention self.state_retention.attach_system_model(self) def attach_energy_source_model(self, energy_source): """ Attaches an energy source model to the system model :param energy_source: the energy source model """ if self.energy_source is not None: raise ConfigurationException("Energy source model already attached!") if energy_source is None: energy_source = SyntheticEnergySource(5.0) if not isinstance(energy_source, EnergySourceModel): raise ConfigurationException("Wrong energy source model!") self.energy_source = energy_source if self.energy_harvester is not None: self.energy_harvester.attach_energy_source(self.energy_source) def get_power_state_events(self): """ :return: a list of the power state events occurred """ events = self.mcu.get_power_state_events() for peripheral in self.mcu_peripherals: events.extend(peripheral.get_power_state_events()) return events def _init_signals(self, run_gc_collect=True): """ Inserts signals names onto the signal collection list :param run_gc_collect: runs the garbage collector after deleting the saved signals """ if self.collect_signals: del self.signals if run_gc_collect: gc.collect() self.signals = [] data = [ 'Time', 'Energy Source Voltage', 'Energy Buffer Voltage', 'Voltage Regulator Output Voltage', 'Energy Buffer Available Energy', 'Energy Harvested', 'Energy Consumed', 'MCU Frequency', 'MCU Power State', 'ADC Power State', ] for device in self.custom_devices: for signal_string in device.get_signals_strings(): data.append(signal_string) for custom_signal in self.custom_signals_strings: data.append(custom_signal) self.signals.append(data) def _collect_signals(self, t): """ Collect the voltage trace :param t: elapsed time """ if self.collect_signals: # Simulation signals data = [ self.total_time, self.energy_source.get_voltage(), self.energy_buffer.get_voltage(), self.voltage_regulator.get_voltage(), self.energy_buffer.get_energy(), self.instant_EH, self.instant_E, self.mcu.frequency, str(self.mcu.mcu_power_state), str(self.mcu.adc_power_state), ] # Devices signals for device in self.custom_devices: for signal in device.get_signals(): data.append(signal) # Custom signals for key in self.custom_signals_keys: data.append(self.custom_signals[key]) self.signals.append(data) self.total_time += t def __disabled_update_stats(self, op_mode_name, additional_op_mode_name, is_wait_cycle, mcu_clock_action, elapsed_time, n_ticks, energy_draws, e_harvested): """ Disabled method """ return def __update_stats(self, op_mode_name, additional_op_mode_name, is_wait_cycle, mcu_clock_action, elapsed_time, n_ticks, energy_draws, e_harvested): """ Updates internal statistics :param op_mode_name: current operation identifier (string) for metrics identification :param additional_op_mode_name: additional operation identifier (string) for metrics identification :param is_wait_cycle: if current cycle is a wait cycle :param mcu_clock_action: Clock cycle action of the MCU :param elapsed_time: time elapsed in the simulation :param n_ticks: number of ticks simulated :param energy_draws: energy drawn by the entire circuit calculated with calculate_energy_draws() :param e_harvested: energy harvested from the environment """ wait_cycle_mask = "_WAIT" if is_wait_cycle else "" op_mode_name = "{}{}".format(str(op_mode_name), wait_cycle_mask) mcu_clock_action = "{}{}".format(str(mcu_clock_action), wait_cycle_mask) mcu_state = "{}{}".format(str(self.mcu.get_mcu_state()), wait_cycle_mask) # Unpack dict energy_drawn = energy_draws['energy_drawn'] vreg_energy_loss = energy_draws['vreg_energy_loss'] energy_components = energy_draws['energy_components'] energy_loss_components = energy_draws['energy_loss_components'] # Operation mode names op_modes = ['total', op_mode_name] if additional_op_mode_name is not None: additional_op_mode_name = "{}{}".format(str(additional_op_mode_name), wait_cycle_mask) op_modes.append(additional_op_mode_name) for op_mode in op_modes: for mcu_action in ['total', mcu_clock_action]: self.elapsed_time[op_mode][mcu_action] += elapsed_time self.elapsed_ticks[op_mode][mcu_action] += n_ticks self.used_energy['total'][op_mode][mcu_action] += energy_drawn self.vreg_energy_loss['total'][op_mode][mcu_action] += vreg_energy_loss for component in energy_components.keys(): energy = energy_components[component] vreg_loss = energy_loss_components[component] self.used_energy[component][op_mode][mcu_action] += energy self.vreg_energy_loss[component][op_mode][mcu_action] += vreg_loss for mcu_st in ['total', mcu_state]: self.harvested_energy[op_mode][mcu_st] += e_harvested def get_elapsed_time(self): """ :return: the time elapsed during a simulation tick / step """ mcu_state = self.mcu.get_mcu_state() # If energy source is set and MCU in LPM / OFF -> recharging energy buffer from harvested energy # -> fast forward to next sampled interval if self.energy_source is not None and (mcu_state == MCUPowerState.OFF or (mcu_state == MCUPowerState.LPM and not self.lpm_precise_ticks)): return self.energy_source.get_current_sample_remaining_time() return self.mcu.get_mcu_cycle_time() def calculate_energy_draws(self, mcu_clock_action, elapsed_time): """ Calculates the energy draw of the simulated circuit and returns it :param mcu_clock_action: Clock cycle action of the MCU :param elapsed_time: the time elapsed :return: a dictionary with all the energy draws types """ energy_components = {} energy_loss_components = {} # Energy consumed from the output of the voltage regulator e = self.mcu.get_drained_energy(mcu_clock_action, elapsed_time) energy_components['mcu'] = e energy_from_regulator = e energy_loss_components['mcu'] = 0.0 # Energy consumed by devices attached to voltage regulator for name, component in self.additional_components[ComponentVoltageSource.VOLTAGE_REGULATOR].items(): e = component.get_drained_energy(elapsed_time) energy_components[name] = e energy_from_regulator += e energy_loss_components[name] = 0.0 # energy drawn by voltage regulator from energy buffer energy_drawn = self.voltage_regulator.get_drained_energy(energy_from_regulator, elapsed_time) # energy loss due to voltage regulator (in)efficiency vreg_energy_loss = energy_drawn - energy_from_regulator if energy_from_regulator > 0.0: # Calculate the energy lost due to voltage regulator for each connected component for name in energy_components.keys(): e_ratio = energy_components[name] / energy_from_regulator energy_loss_components[name] = vreg_energy_loss * e_ratio # Energy consumed by devices attached to energy buffer for name, component in self.additional_components[ComponentVoltageSource.ENERGY_BUFFER].items(): e = component.get_drained_energy(elapsed_time) energy_components[name] = e energy_drawn += e energy_loss_components[name] = 0.0 retval = { 'energy_drawn': energy_drawn, 'vreg_energy_loss': vreg_energy_loss, 'energy_from_regulator': energy_from_regulator, 'energy_components': energy_components, 'energy_loss_components': energy_loss_components, } return retval def run_step(self, mcu_clock_action, op_mode_name, is_wait_cycle=False, from_custom_device=None, additional_op_mode_name=None): """ Runs a simulation step :param mcu_clock_action: Clock cycle action of the MCU :param op_mode_name: current operation identifier (string) for metrics identification :param is_wait_cycle: specifies if the execution happens due to a wait cycle :param from_custom_device: name of the cust device that is triggering the step execution :param additional_op_mode_name: additional op_mode_name for metrics identification :return: the number of clock cycles executed by the MCU """ # Automatic check enabled -> return 0 ticks if power failure occurred if self.automatic_check_for_power_failures: # Check for a possible power failure (do not reset cache) self.cached_power_failure = self.power_failure_occurring(False) if self.cached_power_failure: return 0 devices_ticks = self.run_custom_devices_logic(from_custom_device) n_ticks = 1 # Set ticks to 0 if current operation is a NOP if mcu_clock_action == MCUClockCycleAction.LPM_NOP or mcu_clock_action == MCUClockCycleAction.NOP_OFF_RECHARGE: n_ticks = 0 elapsed_time = self.get_elapsed_time() self._collect_signals(elapsed_time) energy_draws = self.calculate_energy_draws(mcu_clock_action, elapsed_time) energy_drawn = energy_draws['energy_drawn'] # Instantaneous Values (pre-recharge) self.instant_V = self.energy_buffer.get_voltage() self.instant_E = energy_drawn # Energy source voltage intervals -> [(voltage, time),...] voltage_intervals = self.energy_harvester.get_voltage_intervals(elapsed_time) # Get equivalent resistances charge_r = self.energy_harvester.get_equivalent_resistance() e_harvested = self.energy_buffer.update(voltage_intervals, energy_drawn, charge_r, elapsed_time, self.cached_power_failure) # Instant EH = e_harvested if charge + e_drawn self.instant_EH = e_harvested # if a cached power failure occurred, it means that power failures are checked manually, as otherwise the # execution would not reach this point # -> save the consumed energy and decrement it later # Note: self.energy_buffer.update() keeps the voltage constant if self.cached_power_failure is set if self.cached_power_failure: self.cached_energy += energy_drawn self.cached_harvested_energy += e_harvested # Collect metrics self.__update_stats(op_mode_name, additional_op_mode_name, is_wait_cycle, mcu_clock_action, elapsed_time, n_ticks, energy_draws, e_harvested) self.mcu.update_stats(elapsed_time, n_ticks) # Account for wait cycles if not is_wait_cycle: wait_cycles = self.mcu.get_action_wait_cycles(mcu_clock_action) for _ in range(wait_cycles): n_ticks += self.run_step(mcu_clock_action, op_mode_name, True, from_custom_device, additional_op_mode_name) # Physical memory access latency if mcu_clock_action == MCUClockCycleAction.PHYSICAL_MEMORY_ACCESS: for memory in self.physical_memories: name = memory.get_name() wait_cycles = memory.get_wait_cycles(self.mcu.get_frequency()) # simulate wait cycles for _ in range(wait_cycles): n_ticks += self.run_step(mcu_clock_action, op_mode_name, True, from_custom_device, name) # reset memories' operations queue memory.reset_operations_queue() # LPM exit if mcu_clock_action == MCUClockCycleAction.LPM_EXIT: # Turn on MCU and init custom devices n_ticks += self.init() return n_ticks + devices_ticks def get_drained_energy(self): """ :return: the used energy """ return self.used_energy['total']['total']['total'] def init(self, custom_devices_print_enabled=True): """ Initializes the MCU and custom devices :param custom_devices_print_enabled: enables/disables custom devices messages :return: the number of clock cycles executed by the MCU """ n_ticks = 0 self.mcu.set_mcu_state(MCUPowerState.ON) for device in self.custom_devices: n_ticks += device.init(custom_devices_print_enabled) return n_ticks def run_custom_devices_logic(self, skip_device=None): """ Runs all custom device logic :param skip_device: device to skip :return: the number of clock cycles executed by the MCU during custom device operations """ n_ticks = 0 for device in self.custom_devices: if device.name != skip_device: n_ticks += device.run_logic() return n_ticks def power_failure_occurred(self): """ :return: if a power failure occurred during previous operations """ return self.cached_power_failure def power_failure_occurring(self, reset_cache=True): """ :param reset_cache: reset the cache if a cached power failure occurred :return: if the MCU needs to be shut down / a power failure is occurring """ if self.cached_power_failure: # reset cache (the manual control is happening) if reset_cache: # e_harvested - e_drawn -> energy drawn (this is negative if e_harvested < e_drawn) net_e_drawn = self.cached_harvested_energy - self.cached_energy self.energy_buffer.increment_energy(net_e_drawn) self.cached_power_failure = False self.cached_energy = 0.0 self.cached_harvested_energy = 0.0 return True # keep this call here, as it updates also all the power state events events = self.get_power_state_events() # voltage threshold if self.power_off_condition == PowerOffCondition.ENERGY_BUFFER_VOLTAGE_THRESHOLD: voltage = self.energy_buffer.get_voltage() return voltage < self.power_off_voltage_threshold # event-based elif self.power_off_condition == PowerOffCondition.POWER_STATE_EVENT: if len(events) == 0: return False for poff_event in self.power_off_events: if poff_event in events: return True return False else: raise ConfigurationException("Power off condition not set.") def record_power_failure(self): """ Records the occurrence of a power failure """ self.power_failures += 1 self.mcu.power_failure_callback() def reset(self, run_gc_collect=True): """ Resets statistics :param run_gc_collect: runs the garbage collector after deleting the saved signals """ del self.elapsed_time self.elapsed_time = defaultdict(lambda: defaultdict(float)) del self.elapsed_ticks self.elapsed_ticks = defaultdict(lambda: defaultdict(int)) del self.used_energy self.used_energy = defaultdict(lambda: defaultdict(lambda: defaultdict(float))) del self.vreg_energy_loss self.vreg_energy_loss = defaultdict(lambda: defaultdict(lambda: defaultdict(float))) # remove garbage if run_gc_collect: gc.collect() self.power_failures = 0 self.cached_power_failure = False self.cached_energy = 0.0 self.cached_harvested_energy = 0.0 self.instant_V = 0.0 self.instant_E = 0.0 self.instant_EH = 0.0 self._init_signals(run_gc_collect) self.total_time = 0.0 self.state_retention.reset() self.mcu.reset() def circuit_equivalent_resistance(self, mcu_clock_action, energy_draws=None, elapsed_time=None): """ Calculates the circuit equivalent resistance (excluding the energy harvester) :param mcu_clock_action: Clock cycle action of the MCU :param energy_draws: pre-computed energy draws :param elapsed_time: elapsed time in the pre-computed energy draws :return: the circuit equivalent resistance """ if energy_draws is None: energy_drawn = self.calculate_energy_draws(mcu_clock_action, elapsed_time)['energy_drawn'] else: energy_drawn = energy_draws['energy_drawn'] return energy_utils.R_from_energy_t(self.energy_buffer.get_voltage(), energy_drawn, elapsed_time) def execute_full_recharge(self, mcu_clock_action, op_mode_name, v_target, v_supply): """ Executes a full recharge of the energy buffer :param mcu_clock_action: Clock cycle action of the MCU :param op_mode_name: current operation identifier (string) for metrics identification :param v_target: target voltage :param v_supply: voltage of the power supply """ v_buff = self.energy_buffer.get_voltage() # Check non-charging condition and return if v_buff > v_target or v_buff > v_supply or v_target > v_supply: return # Equivalent resistance charge_r = self.energy_harvester.get_equivalent_resistance() # Calculate time required to recharge t_recharge = self.energy_buffer.calculate_recharge_time_to_voltage(v_target, v_supply, charge_r) energy_draws = self.calculate_energy_draws(MCUClockCycleAction.NOP_OFF_RECHARGE, t_recharge) # Calculate harvested energy e_harvested = self.energy_buffer.calculate_recharge_energy_to_voltage(v_target) e_harvested += energy_draws['energy_drawn'] self.__update_stats(op_mode_name, None, False, mcu_clock_action, t_recharge, 0, energy_draws, e_harvested) # Refill energy buffer to v_supply self.energy_buffer.set_voltage(v_target) def get_component(self, name): for components in self.additional_components.values(): for c_name, component in components.items(): if c_name == name: return component