def write(infofile, version, serial_number, calibfile, no_calib): if infofile is not None: if serial_number is not None or version is not None: raise click.UsageError(("--infofile and --version/--serial_number" " are mutually exclusive")) cape_data = CapeData.from_yaml(infofile) with EEPROM() as eeprom: eeprom.write_cape_data(cape_data) elif serial_number is not None or version is not None: if version is None or serial_number is None: raise click.UsageError( ("--version and --serial_number are required")) cape_data = CapeData.from_values(serial_number, version) with EEPROM() as eeprom: eeprom.write_cape_data(cape_data) if calibfile is not None: if no_calib: raise click.UsageError( "--no-calib and --calibfile are mutually exclusive") calib = CalibrationData.from_yaml(calibfile) with EEPROM() as eeprom: cape_data = eeprom.write_calibration(calib) if no_calib: calib = CalibrationData.from_default() with EEPROM() as eeprom: eeprom.write_calibration(calib)
def __init__( self, shepherd_mode: str = "emulation", initial_buffers: list = None, calibration_recording: CalibrationData = None, # TODO: make clearer that these is "THE RECORDING" calibration_emulation: CalibrationData = None, set_target_io_lvl_conv: bool = False, sel_target_for_io: bool = True, sel_target_for_pwr: bool = True, aux_target_voltage: float = 0.0, settings_virtsource: VirtualSourceData = None): logger.debug(f"Emulator-Init in {shepherd_mode}-mode") super().__init__(shepherd_mode) self._initial_buffers = initial_buffers if calibration_emulation is None: calibration_emulation = CalibrationData.from_default() logger.warning( "No emulation calibration data provided - using defaults") if calibration_recording is None: calibration_recording = CalibrationData.from_default() logger.warning( "No recording calibration data provided - using defaults") self._cal_recording = calibration_recording self._cal_emulation = calibration_emulation self._settings_virtsource = settings_virtsource self._set_target_io_lvl_conv = set_target_io_lvl_conv self._sel_target_for_io = sel_target_for_io self._sel_target_for_pwr = sel_target_for_pwr self._aux_target_voltage = aux_target_voltage
def get_calibration_data(self) -> CalibrationData: """Reads calibration data from hdf5 file. Returns: Calibration data as CalibrationData object """ nested_dict = lambda: defaultdict(nested_dict) calib = CalibrationData.from_default() for channel, parameter in product(["current", "voltage"], cal_parameter_list): cal_channel = cal_channel_harvest_dict[channel] calib._data["harvesting"][cal_channel][parameter] = self._h5file[ "data"][channel].attrs[parameter] return CalibrationData(calib)
def read_calibration(self) -> CalibrationData: """Reads and returns shepherd calibration data from EEPROM Returns: CalibrationData object containing data extracted from EEPROM """ data = self._read( calibration_data_format["offset"], calibration_data_format["size"] ) try: cal = CalibrationData.from_bytestr(data) except struct.error: cal = CalibrationData.from_default() logger.warning("EEPROM seems to have no usable data - will set calibration from default-values") return cal
def make(filename, output_path): cd = CalibrationData.from_measurements(filename) if output_path is None: print(repr(cd)) else: with open(output_path, "w") as f: f.write(repr(cd))
def write_dac_aux_voltage(calibration_settings: CalibrationData, voltage_V: float) -> NoReturn: """ Sends the auxiliary voltage (dac channel B) to the PRU core. Args: voltage_V: desired voltage in volt """ if voltage_V is None: voltage_V = 0.0 elif voltage_V is False: voltage_V = 0.0 elif voltage_V is True: # set value > 16bit and therefore link both adc-channels write_dac_aux_voltage_raw(2 ** 20 - 1) return if voltage_V < 0.0: raise SysfsInterfaceException(f"sending voltage with negative value: {voltage_V}") if voltage_V > 5.0: raise SysfsInterfaceException(f"sending voltage above limit of 5V: {voltage_V}") if calibration_settings is None: output = calibration_default.dac_ch_b_voltage_to_raw(voltage_V) else: output = calibration_settings.convert_value_to_raw("emulation", "dac_voltage_b", voltage_V) # TODO: currently only an assumption that it is for emulation, could also be for harvesting # TODO: fn would be smoother if it contained the offset/gain-dict of the cal-data. but this requires a general FN for conversion write_dac_aux_voltage_raw(output)
def write_calibration(self, calibration_data: CalibrationData) -> NoReturn: """Writes complete BeagleBone cape data to EEPROM Args: calibration_data (CalibrationData): Calibration data that is going to be stored in EEPROM """ self._write(calibration_data_format["offset"], calibration_data.to_bytestr())
def send_calibration_settings(cal_settings: CalibrationData) -> NoReturn: """Sends calibration settings to PRU core For the virtual source it is required to have the calibration settings. Args: cal_settings (CalibrationData): Contains the device's calibration settings. """ sysfs_interface.write_calibration_settings(cal_settings.export_for_sysfs())
def __init__( self, initial_buffers: list = None, calibration_recording: CalibrationData = None, calibration_emulation: CalibrationData = None, load: str = "node", ldo_voltage: float = 0.0 ): shepherd_mode = "emulation" self.ldo_voltage = ldo_voltage super().__init__(shepherd_mode, load) if calibration_emulation is None: calibration_emulation = CalibrationData.from_default() logger.warning( "No emulation calibration data provided - using defaults" ) if calibration_recording is None: calibration_recording = CalibrationData.from_default() logger.warning( "No recording calibration data provided - using defaults" ) self.transform_coeffs = {"voltage": dict(), "current": dict()} # Values from recording are binary ADC values. We have to send binary # DAC values to the DAC for emulation. To directly convert ADC to DAC # values, we precalculate the 'transformation coefficients' based on # calibration data from the recorder and the emulator. for channel in ["voltage", "current"]: self.transform_coeffs[channel]["gain"] = ( calibration_recording["harvesting"][channel]["gain"] * calibration_emulation["emulation"][channel]["gain"] ) self.transform_coeffs[channel]["offset"] = ( calibration_emulation["emulation"][channel]["gain"] * calibration_recording["harvesting"][channel]["offset"] + calibration_emulation["emulation"][channel]["offset"] ) self._initial_buffers = initial_buffers self._calibration_emulation = calibration_emulation
def calibration_settings(): calibration_emulation = CalibrationData.from_default() current_gain = int(1 / calibration_emulation["load"]["current"]["gain"]) current_offset = int( calibration_emulation["load"]["current"]["offset"] / calibration_emulation["load"]["current"]["gain"] ) voltage_gain = int(1 / calibration_emulation["load"]["voltage"]["gain"]) voltage_offset = int( calibration_emulation["load"]["voltage"]["offset"] / calibration_emulation["load"]["voltage"]["gain"] ) return current_gain, current_offset, voltage_gain, voltage_offset
def get_calibration_data(self): """Reads calibration data from hdf5 file. Returns: Calibration data as CalibrationData object """ nested_dict = lambda: defaultdict(nested_dict) calib = nested_dict() for var, attr in product(["voltage", "current"], ["gain", "offset"]): calib["harvesting"][var][attr] = self._h5file["data"][var].attrs[ attr] return CalibrationData(calib)
def aux_target_power(on: bool, voltage: float, sel_target_for_aux: bool): if not voltage: voltage = 3.0 else: if not on: raise click.UsageError( "Can't set voltage, when Shepherd is switched off") for pin_name in ["en_shepherd"]: pin = GPIO(gpio_pin_nums[pin_name], "out") pin.write(on) for pin_name in ["target_pwr_sel"]: pin = GPIO(gpio_pin_nums[pin_name], "out") pin.write(not sel_target_for_aux) cal = CalibrationData.from_default() sysfs_interface.write_dac_aux_voltage(cal, voltage)
def read_dac_aux_voltage(cal_settings: CalibrationData) -> float: """ Reads the auxiliary voltage (dac channel B) from the PRU core. Args: cal_settings: dict with offset/gain Returns: aux voltage """ value_raw = read_dac_aux_voltage_raw() if cal_settings is None: voltage = calibration_default.dac_ch_a_raw_to_voltage(value_raw) else: voltage = cal_settings.convert_raw_to_value("emulation", "dac_voltage_b", value_raw) return voltage
def __init__(self): super().__init__("debug") self._cal = CalibrationData.from_default() self._io = TargetIO()
def test_dac_aux_voltage(shepherd_up, value): cal_set = CalibrationData.from_default() msb_threshold = cal_set.convert_raw_to_value("emulation", "dac_voltage_b", 2) sysfs_interface.write_dac_aux_voltage(cal_set, value) assert abs(sysfs_interface.read_dac_aux_voltage(cal_set) - value) <= msb_threshold
def calibration_settings(): calibration_emulation = CalibrationData.from_default() return calibration_emulation.export_for_sysfs()
def emulate( input_path: Path, output_path: Path = None, duration: float = None, force_overwrite: bool = False, no_calib: bool = False, load: str = "artificial", ldo_voltage: float = None, start_time: float = None, warn_only: bool = False, ): """ Starts emulation. Args: input_path (Path): path of hdf5 file containing recorded harvesting data output_path (Path): Path of hdf5 file where load measurements should be stored duration (float): Maximum time duration of emulation in seconds force_overwrite (bool): True to overwrite existing file under output, False to store under different name no_calib (bool): True to use default calibration values, False to read calibration data from EEPROM load (str): Type of load. 'artificial' for dummy, 'node' for sensor node ldo_voltage (float): Pre-charge capacitor to this voltage before starting emulation start_time (float): Desired start time of emulation in unix epoch time warn_only (bool): Set true to continue emulation after recoverable error """ if no_calib: calib = CalibrationData.from_default() else: try: with EEPROM() as eeprom: calib = eeprom.read_calibration() except ValueError: logger.warning("Couldn't read calibration from EEPROM (val). Falling back to default values.") calib = CalibrationData.from_default() except FileNotFoundError: logger.warning("Couldn't read calibration from EEPROM (FS). Falling back to default values.") calib = CalibrationData.from_default() if start_time is None: start_time = time.time() + 15 if output_path is not None: if not output_path.is_absolute(): output_path = output_path.absolute() if output_path.is_dir(): timestamp = datetime.datetime.fromtimestamp(start_time) timestamp = timestamp.strftime("%Y-%m-%d_%H-%M-%S") # closest to ISO 8601, avoid ":" store_path = output_path / f"emu_{timestamp}.h5" else: store_path = output_path log_writer = LogWriter( store_path=store_path, force_overwrite=force_overwrite, mode="load", calibration_data=calib, ) if isinstance(input_path, str): input_path = Path(input_path) if input_path is None: raise ValueError("No Input-File configured for emulation") if not input_path.exists(): raise ValueError("Input-File does not exist") log_reader = LogReader(input_path, 10_000) with ExitStack() as stack: if output_path is not None: stack.enter_context(log_writer) stack.enter_context(log_reader) emu = Emulator( calibration_recording=log_reader.get_calibration_data(), calibration_emulation=calib, initial_buffers=log_reader.read_buffers(end=64), ldo_voltage=ldo_voltage, load=load, ) stack.enter_context(emu) emu.start(start_time, wait_blocking=False) logger.info(f"waiting {start_time - time.time():.2f} s until start") emu.wait_for_start(start_time - time.time() + 15) logger.info("shepherd started!") def exit_gracefully(signum, frame): stack.close() sys.exit(0) signal.signal(signal.SIGTERM, exit_gracefully) signal.signal(signal.SIGINT, exit_gracefully) if duration is None: ts_end = sys.float_info.max else: ts_end = time.time() + duration for hrvst_buf in log_reader.read_buffers(start=64): try: idx, emu_buf = emu.get_buffer(timeout=1) except ShepherdIOException as e: logger.error( f"ShepherdIOException(ID={e.id}, val={e.value}): {str(e)}" ) if output_path is not None: err_rec = ExceptionRecord( int(time.time() * 1e9), str(e), e.value ) log_writer.write_exception(err_rec) if not warn_only: raise if output_path is not None: log_writer.write_buffer(emu_buf) emu.return_buffer(idx, hrvst_buf) if time.time() > ts_end: break # Read all remaining buffers from PRU while True: try: idx, emu_buf = emu.get_buffer(timeout=1) if output_path is not None: log_writer.write_buffer(emu_buf) except ShepherdIOException as e: # We're done when the PRU has processed all emulation data buffers if e.id == commons.MSG_DEP_ERR_NOFREEBUF: break else: if not warn_only: raise
def record( output_path: Path, mode: str = "harvesting", duration: float = None, force_overwrite: bool = False, no_calib: bool = False, start_time: float = None, warn_only: bool = False, ): """Starts recording. Args: output_path (Path): Path of hdf5 file where IV measurements should be stored mode (str): 'harvesting' for recording harvesting data duration (float): Maximum time duration of emulation in seconds force_overwrite (bool): True to overwrite existing file under output path, False to store under different name no_calib (bool): True to use default calibration values, False to read calibration data from EEPROM start_time (float): Desired start time of emulation in unix epoch time warn_only (bool): Set true to continue recording after recoverable error """ if no_calib: calib = CalibrationData.from_default() else: try: with EEPROM() as eeprom: calib = eeprom.read_calibration() except ValueError: logger.warning( "Couldn't read calibration from EEPROM (Val). Falling back to default values." ) calib = CalibrationData.from_default() except FileNotFoundError: logger.warning( "Couldn't read calibration from EEPROM (FS). Falling back to default values." ) calib = CalibrationData.from_default() if start_time is None: start_time = round(time.time() + 10) if not output_path.is_absolute(): output_path = output_path.absolute() if output_path.is_dir(): timestamp = datetime.datetime.fromtimestamp(start_time) timestring = timestamp.strftime( "%Y-%m-%d_%H-%M-%S") # closest to ISO 8601, avoid ":" store_path = output_path / f"rec_{timestring}.h5" else: store_path = output_path recorder = Recorder(shepherd_mode=mode) log_writer = LogWriter(store_path=store_path, calibration_data=calib, mode=mode, force_overwrite=force_overwrite) with ExitStack() as stack: stack.enter_context(recorder) stack.enter_context(log_writer) # in_stream has to be disabled to avoid trouble with pytest res = invoke.run("hostname", hide=True, warn=True, in_stream=False) log_writer["hostname"] = res.stdout recorder.start(start_time, wait_blocking=False) logger.info(f"waiting {start_time - time.time():.2f} s until start") recorder.wait_for_start(start_time - time.time() + 15) logger.info("shepherd started!") def exit_gracefully(signum, frame): stack.close() sys.exit(0) signal.signal(signal.SIGTERM, exit_gracefully) signal.signal(signal.SIGINT, exit_gracefully) if duration is None: ts_end = sys.float_info.max else: ts_end = time.time() + duration while time.time() < ts_end: try: idx, buf = recorder.get_buffer() except ShepherdIOException as e: logger.error( f"ShepherdIOException(ID={e.id}, val={e.value}): {str(e)}") err_rec = ExceptionRecord(int(time.time() * 1e9), str(e), e.value) log_writer.write_exception(err_rec) if not warn_only: raise log_writer.write_buffer(buf) recorder.return_buffer(idx)
def emulate( input_path: Path, output_path: Path = None, duration: float = None, force_overwrite: bool = False, no_calib: bool = False, start_time: float = None, set_target_io_lvl_conv: bool = False, sel_target_for_io: bool = True, sel_target_for_pwr: bool = True, aux_target_voltage: float = 0.0, settings_virtsource: VirtualSourceData = None, warn_only: bool = False, ): """ Starts emulation. Args: input_path (Path): path of hdf5 file containing recorded harvesting data output_path (Path): Path of hdf5 file where power measurements should be stored duration (float): Maximum time duration of emulation in seconds force_overwrite (bool): True to overwrite existing file under output, False to store under different name no_calib (bool): True to use default calibration values, False to read calibration data from EEPROM start_time (float): Desired start time of emulation in unix epoch time set_target_io_lvl_conv: Enables or disables the GPIO level converter to targets. sel_target_for_io: choose which targets gets the io-connection (serial, swd, gpio) from beaglebone, True = Target A, False = Target B sel_target_for_pwr: choose which targets gets the supply with current-monitor, True = Target A, False = Target B aux_target_voltage: Sets, Enables or disables the voltage for the second target, 0.0 or False for Disable, True for linking it to voltage of other Target settings_virtsource (VirtualSourceData): Settings which define the behavior of virtsource emulation warn_only (bool): Set true to continue emulation after recoverable error """ if no_calib: calib = CalibrationData.from_default() else: try: with EEPROM() as eeprom: calib = eeprom.read_calibration() except ValueError: logger.warning( "Couldn't read calibration from EEPROM (Val). Falling back to default values." ) calib = CalibrationData.from_default() except FileNotFoundError: logger.warning( "Couldn't read calibration from EEPROM (FS). Falling back to default values." ) calib = CalibrationData.from_default() if start_time is None: start_time = round(time.time() + 10) if set_target_io_lvl_conv is None: set_target_io_lvl_conv = True if sel_target_for_io is None: sel_target_for_io = True if sel_target_for_pwr is None: sel_target_for_pwr = True if aux_target_voltage is None: aux_target_voltage = 0.0 if output_path is not None: if not output_path.is_absolute(): output_path = output_path.absolute() if output_path.is_dir(): timestamp = datetime.datetime.fromtimestamp(start_time) timestring = timestamp.strftime( "%Y-%m-%d_%H-%M-%S") # closest to ISO 8601, avoid ":" store_path = output_path / f"emu_{timestring}.h5" else: store_path = output_path log_writer = LogWriter( store_path=store_path, force_overwrite=force_overwrite, mode="emulation", calibration_data=calib, ) if isinstance(input_path, str): input_path = Path(input_path) if input_path is None: raise ValueError("No Input-File configured for emulation") if not input_path.exists(): raise ValueError("Input-File does not exist") log_reader = LogReader(input_path, 10_000) with ExitStack() as stack: if output_path is not None: stack.enter_context(log_writer) stack.enter_context(log_reader) emu = Emulator( shepherd_mode="emulation", initial_buffers=log_reader.read_buffers(end=64), calibration_recording=log_reader.get_calibration_data(), calibration_emulation=calib, set_target_io_lvl_conv=set_target_io_lvl_conv, sel_target_for_io=sel_target_for_io, sel_target_for_pwr=sel_target_for_pwr, aux_target_voltage=aux_target_voltage, settings_virtsource=settings_virtsource, ) stack.enter_context(emu) emu.start(start_time, wait_blocking=False) logger.info(f"waiting {start_time - time.time():.2f} s until start") emu.wait_for_start(start_time - time.time() + 15) logger.info("shepherd started!") def exit_gracefully(signum, frame): stack.close() sys.exit(0) signal.signal(signal.SIGTERM, exit_gracefully) signal.signal(signal.SIGINT, exit_gracefully) if duration is None: ts_end = sys.float_info.max else: ts_end = time.time() + duration for hrvst_buf in log_reader.read_buffers(start=64): try: idx, emu_buf = emu.get_buffer(timeout=1) except ShepherdIOException as e: logger.error( f"ShepherdIOException(ID={e.id}, val={e.value}): {str(e)}") err_rec = ExceptionRecord(int(time.time() * 1e9), str(e), e.value) if output_path is not None: log_writer.write_exception(err_rec) if not warn_only: raise if output_path is not None: log_writer.write_buffer(emu_buf) emu.return_buffer(idx, hrvst_buf) if time.time() > ts_end: break # Read all remaining buffers from PRU while True: try: idx, emu_buf = emu.get_buffer(timeout=1) if output_path is not None: log_writer.write_buffer(emu_buf) except ShepherdIOException as e: # We're done when the PRU has processed all emulation data buffers if e.id == commons.MSG_DEP_ERR_NOFREEBUF: break else: if not warn_only: raise