示例#1
0
class Camera(Instrument):
    # ? https://pypi.org/project/simple-pyspin/

    _config = configuration.from_yml(
        r'W:\Test Data Backup\instruments\config\camera.yml')
    display_name = _config.field(str)
    EXPOSURE_TIME_US = _config.field(float)
    TX_WAIT_S = 0.

    def _instrument_cleanup(self) -> None:
        self.interface.close()

    def _instrument_setup(self) -> None:
        # TODO: gain settings? AOI settings? neither are used in labview version
        self.interface = Spinnaker()
        self.interface.init()
        self.interface.PixelFormat = 'RGB8'
        self.interface.ExposureAuto = 'Off'
        self.interface.ExposureMode = 'Timed'
        self.interface.ExposureTime = self.EXPOSURE_TIME_US

    @proxy.exposed
    def capture(self):
        """
        takes 212ms including start and stop
        this camera model is specced as 7.5FPS
        """
        self.interface.start()
        img = self.interface.get_array()
        self.interface.stop()
        return img

    def _instrument_check(self) -> None:
        _ = self.interface.initialized

    def _instrument_debug(self) -> None:
        from cv2 import cv2
        cv2.imshow('debug image', self.capture())
        cv2.waitKey(0)
示例#2
0
class Pixie2pt0Station(TestStation):
    bk = TestInstrument(BKPowerSupply(), logging.INFO)
    lm = TestInstrument(LightMeter(), logging.INFO)
    ftdi = TestInstrument(RS485(), logging.DEBUG)
    nfc = TestInstrument(NFC(), logging.INFO)

    _config = configuration.from_yml('pixie_hack/.yml')
    TESTING_FW_PATH = _config.field(str)
    PRODUCTION_FW_PATH = _config.field(str)
    RESULT_PATH = _config.field(str)
    PARAMS_PATH = _config.field(str)
    ASSOCIATION_TABLE_PATH = _config.field(str)
    POWER_ON_WAIT_S = _config.field(float)
    CH_SETTLE_WAIT_S = _config.field(float)
    AFTER_ERASE_WAIT_S = _config.field(float)
    PROGRAMMING_V = _config.field(float)
    PROGRAMMING_I = _config.field(float)

    def set_power_supply_for_programming(self) -> None:
        self.bk.write_settings(DCLevel(self.PROGRAMMING_V, self.PROGRAMMING_I))

    def get_params(self) -> None:
        sheet = pd.read_excel(self.PARAMS_PATH,
                              comment='#',
                              sheet_name='params')
        rows = cast(List[Dict], sheet.to_dict('records'))
        rows.sort(key=itemgetter('row'))
        self.params = [messages.Param(**row) for row in rows]
        list(map(self.put, self.params))

    def __init__(self, to_view: ThreadConnection = None) -> None:
        self.q = to_view
        self.put = to_view.put if to_view is not None else self.info
        self.instrument_setup()
        self.set_power_supply_for_programming()
        self.bk.write_settings(output_state=False)
        self.masks = [1 << ch for ch in range(4)] + [15]
        self.string_results = list()
        self.params: List[messages.Param] = list()
        self.uid_table = dict()
        self.dut_id_table = dict()

    def pixie_ch_command(self, mask: int) -> None:
        self.ftdi.ser.send_ascii(f'p{mask}\n')

    def programming_message_adapter(self, msg) -> None:
        self.put(getattr(messages, type(msg).__name__)(**asdict(msg)))

    def program(self, fp: str) -> None:
        self.set_power_supply_for_programming()
        sleep(.1)
        self.ftdi.ser.send_ascii('U')
        sleep(self.AFTER_ERASE_WAIT_S)
        self.ftdi.dta_program_firmware(fp, self.programming_message_adapter)

    def string_check(self, param: messages.Param) -> None:
        self.bk.write_settings(DCLevel(param.v, param.i))
        [self.pixie_ch_command(param.ch_mask) for _ in range(20)]
        sleep(self.CH_SETTLE_WAIT_S)
        light = self.lm.measure()
        power = self.bk.measure()
        dist = light.distance_from(param)
        dist_pf = dist <= param.color_dist_max
        fcd_pf = (param.fcd_nom - param.fcd_tol) <= light.fcd <= (
            param.fcd_nom + param.fcd_tol)
        p_pf = (param.p_nom - param.p_tol) <= power.P <= (param.p_nom +
                                                          param.p_tol)
        self.pixie_ch_command(0)
        result = messages.Result(param.row, light.x, light.y, dist, light.fcd,
                                 power.P, dist_pf, fcd_pf, p_pf)
        self.put(result)
        self.string_results.append(result)
        sleep(.1)

    def test(self, dut: messages.DUT) -> None:
        self.put(dut)
        self.bk.write_settings(output_state=True)
        sleep(self.AFTER_ERASE_WAIT_S)
        self.program(self.TESTING_FW_PATH)
        winsound.Beep(1500, 250)

        sleep(self.AFTER_ERASE_WAIT_S)
        self.put(messages.StringsStart())
        self.string_results.clear()
        [self.string_check(param) for param in self.params]
        test_pf = all(result.row_pf for result in self.string_results)

        with open(self.RESULT_PATH, 'a', newline='') as wf:
            writer = csv.DictWriter(wf, result_header)
            writer.writerows([
                dict(dut_id=dut.dut_id,
                     t=datetime.now(),
                     test_pf=test_pf,
                     **asdict(result)) for result in self.string_results
            ])

        self.program(self.PRODUCTION_FW_PATH)
        self.bk.write_settings(output_state=False)
        for _ in range(2):
            winsound.Beep(1500, 250)
            sleep(.250)

        winsound.Beep(2500 if test_pf else 1000, 1000)

        self.dut_id_table[dut.dut_id] = dict(test_pf=test_pf, **asdict(dut))
        self.write_association_table()
        self.put(messages.TestResult(test_pf))

    # noinspection PyMethodMayBeStatic
    def nfc_present(self) -> bool:  # TODO:
        return self.nfc.is_present()

    # noinspection PyMethodMayBeStatic
    def get_uid(self) -> str:  # TODO:
        [self.nfc.interface.reset_input_buffer() for _ in range(5)]
        first = self.nfc.read_uid()
        if self.nfc.read_uid() == first and len(first) > 5:
            return first
        return ''

    def read_association_table(self) -> None:
        self.uid_table.clear()
        self.dut_id_table.clear()
        with open(self.ASSOCIATION_TABLE_PATH, newline='') as rf:
            for row in csv.DictReader(rf):
                row['dut_id'] = int(row['dut_id'])
                self.dut_id_table[row['dut_id']] = row
                self.uid_table[row['uid']] = row

    def write_association_table(self) -> None:
        with open(self.ASSOCIATION_TABLE_PATH, 'w+', newline='') as wf:
            writer = csv.DictWriter(wf, assoc_table_header)
            writer.writeheader()
            writer.writerows(list(self.dut_id_table.values()))

    # noinspection PyMethodMayBeStatic
    def get_dut_id(self, uid: str) -> int:  # TODO:
        self.read_association_table()
        if not self.uid_table:
            return 1
        row = self.uid_table.get(uid, None)
        if row is None:
            return max(self.dut_id_table.keys()) + 1
        return row['dut_id']

    def should_stop(self) -> bool:  # TODO:
        try:
            self.q.get_nowait()
        except queue.Empty:
            pass
        except (SentinelReceived, ConnectionClosed):
            return True

    def mainloop(self) -> None:
        try:
            self.get_params()
            while 1:
                if self.nfc_present():
                    uid = self.get_uid()
                    if uid:
                        winsound.Beep(1000, 500)
                        self.read_association_table()
                        self.test(messages.DUT(self.get_dut_id(uid), uid))

                if self.should_stop():
                    return self.q.put_sentinel()
        except Exception:
            self.q.put_sentinel()
            raise
示例#3
0
文件: nfc.py 项目: pcreagan/test_old
class NFC(Serial):
    _config = configuration.from_yml(r'instruments\nfc_probe.yml')
    display_name = _config.field(str)
    TIMEOUT = _config.field(float)
    TX_WAIT_S = _config.field(float)
    COMMAND_RETRIES = _config.field(int)
    ACTION_RETRIES = _config.field(int)
    READ_BLOCK_ATTEMPTS = _config.field(int)
    READ_BLOCK_SUCCESSIVE = _config.field(int)
    REDUNDANT_ATTEMPTS = _config.field(int)
    WRITE_BLOCKS = _config.field(tuple)
    SN_BLOCKS = _config.field(dict)
    MN_BLOCKS = _config.field(dict)

    # DEVICE_NAME = "Arduino LilyPad USB"
    HWID = r'5&33EB7B70&0&7'
    BAUDRATE = 57600
    XON_X_OFF = False
    ENCODING = 'utf-8'
    TERM_CHAR = '\n'

    class __Commands:
        is_present = "X"
        read_uid = "U"
        set_block = "B%02x"
        write_block = "W%08x"
        read_block = "R"

    BLOCKS: List[List[int]]

    @register.before('__init__')
    def _make_nfc_attrs(self) -> None:
        self.BLOCKS = [[v for k, v in d.items() if k in self.WRITE_BLOCKS] for d in
                       (self.SN_BLOCKS, self.MN_BLOCKS)]
        if not all(self.BLOCKS):
            raise AttributeError('must specify at least one of [new, old] in write_blocks')
        self._read_tail = deque(maxlen=self.READ_BLOCK_SUCCESSIVE)

    def _command(self, command: str, arg: Optional[int]) -> str:
        if '%' in command:
            if arg is None:
                raise NFCError(f'{command} takes no argument')
            command = command % arg
        for i in range(self.COMMAND_RETRIES):
            self.proxy_check_cancelled()
            self.write(command)
            rx = self.read()
            if rx:
                return rx
        raise NFCError(f'no response to command "{command}"')

    def _action(self, command_string, arg: Optional[int] = None):
        for i in range(self.ACTION_RETRIES):
            # noinspection PyBroadException
            try:
                return self._command(command_string, arg)
            except NFCError:
                self.instrument_setup()
                continue
        raise NFCError(f'failed: {command_string}({arg})')

    def _set_block(self, index: int) -> None:
        if not index == int(self._action(self.__Commands.set_block, index)[:-1], 16):
            raise NFCError(f'failed to set block to {index}')

    def _write_block(self, payload: int) -> None:
        if not 'Y' == self._action(self.__Commands.write_block, payload):
            raise NFCError(f'failed to write block to {payload}')

    def _read_block(self) -> int:
        for _ in range(self.REDUNDANT_ATTEMPTS):
            self._read_tail.clear()
            for _ in range(self.READ_BLOCK_ATTEMPTS):
                value = int(self._action(self.__Commands.read_block)[:-1], 16)
                self._read_tail.append(value)
                if self._read_tail.count(value) == self.READ_BLOCK_SUCCESSIVE:
                    return value
        raise NFCError('inconsistent _read_block iterations')

    @proxy.exposed
    def read_register(self, index: int):
        self._set_block(index)
        return self._read_block()

    @proxy.exposed
    def write_register(self, index: int, payload: int):
        self._set_block(index)
        self._write_block(payload)
        if self._read_block() != payload:
            raise NFCError(f'failed to confirm write_register({index}, {payload})')

    @proxy.exposed
    def is_present(self):
        return 'Y' == self._action(self.__Commands.is_present)

    @proxy.exposed
    def read_uid(self):
        return self._action(self.__Commands.read_uid)

    @proxy.exposed
    def write_unit_identity(self, sn: int, mn: int):
        for blocks, payload in zip(self.BLOCKS, (sn, mn)):
            blocks: List[int]
            [self.write_register(block, payload) for block in blocks]

    @proxy.exposed
    def get_registers(self):
        response = {}
        for blocks in self.BLOCKS:
            for index in blocks:
                response[index] = self.read_register(index)
        return response

    def _instrument_check(self) -> None:
        self._action(self.__Commands.is_present)

    @proxy.exposed
    def test(self) -> None:
        [self.info(f'is_present = {self.is_present()}') for _ in range(25)]

    def _instrument_debug(self) -> None:
        self.test()
示例#4
0
class LambdaPowerSupply(Serial, _DCPowerSupply):
    def calculate_knee(self, percent_of_max: float) -> DCLevel:
        raise NotImplementedError

    _config = configuration.from_yml(r'config\lambda_power_supply.yml')
    display_name = _config.field(str)
    PORT = _config.field(str)
    BAUDRATE = _config.field(int)
    TIMEOUT = _config.field(float)
    TX_WAIT_S = _config.field(float)

    SET_SUCCESS_MARGIN = _config.field(float)
    COMMAND_RETRIES = _config.field(int)

    XON_X_OFF = False
    ENCODING = 'utf-8'
    TERM_CHAR = ''

    _set_address = Command(r'ADR01')
    _clear_error_registers = Command('DCL')
    _set_uvp_level = Command('UVP%04.1f')
    _set_ovp_level = Command('OVP%04.1f')
    _get_measurement = Command(
        'STT?',
        'AV{v_meas:f}SV{v_set:f}AA{i_meas:f}SA{i_set:f}OS{os}AL{al}PS{ps}')
    _set_output_state = Command('OUT%d')
    _set_voltage = Command('VOL%05.2f')
    _set_current = Command('CUR%06.3f')

    def __read_settings(self) -> Tuple[DCLevel, DCLevel]:
        """
        returns settings obj, measurement obj
        """
        results = cast(STTResponse, self._get_measurement())
        self.alarms = AlarmStatusRegister(results.al)
        self.operations_status = OperationStatusRegister(results.os)
        self.error_codes = ErrorCodesRegister(results.ps)
        return DCLevel(results.v_meas,
                       results.i_meas), DCLevel(results.v_set, results.i_set)

    @proxy.exposed
    def measure(self, fresh: bool = True) -> DCLevel:
        _ = fresh
        return self.__read_settings()[0]

    @proxy.exposed
    def set_settings(self, dc_level: DCLevel) -> None:
        self._set_voltage(dc_level.V)
        self._set_current(dc_level.A)

    @proxy.exposed
    def set_output(self, output_state: bool) -> None:
        self._set_output_state(output_state)

    def get_settings(self) -> DCLevel:
        raise NotImplementedError

    def get_output(self) -> bool:
        raise NotImplementedError

    @proxy.exposed
    def write_settings(self,
                       dc_level: DCLevel = None,
                       output_state: bool = None):
        if dc_level is None and output_state is None:
            raise LambdaPowerSupplyError(
                'must call .write_settings() with at least one arg')

        if dc_level is not None:
            self.set_settings(dc_level)

        if output_state is not None:
            self.set_output(output_state)

        error_strings = []
        _, settings = self.__read_settings()

        if dc_level is not None:
            _margin = self.SET_SUCCESS_MARGIN
            for read, exp in ((settings.V, dc_level.V), (settings.A,
                                                         dc_level.A)):
                if not ((exp - _margin) < read < (exp + _margin)):
                    error_strings.append(dc_level)
                    break
            else:
                self.debug(f'power settings = {settings}')

        if output_state is not None:
            if self.operations_status.IS_OUTPUT ^ output_state:
                error_strings.append(f'output_enable={output_state}')
            else:
                self.debug(f'output state = {output_state}')

        if error_strings:
            raise LambdaPowerSupplyError(f'failed to set to ' +
                                         ', '.join(error_strings))

    @register.after('_instrument_setup')
    def _lambda_setup(self) -> None:
        self._set_address()
        self._set_uvp_level(0.)
        self._set_ovp_level(60.)
        self._lambda_cleanup()
        if self.alarms or self.error_codes:
            self._clear_error_registers()

    @register.before('_instrument_cleanup')
    def _lambda_cleanup(self) -> None:
        self.write_settings(DCLevel(0., 0.), False)

    def _instrument_check(self) -> None:
        self._get_measurement()

    @proxy.exposed
    def test(self):
        for _ in range(10):
            self.write_settings(DCLevel(12, 0.1), True)
            self.write_settings(DCLevel(14, 0.2))
            self.write_settings(DCLevel(16, 0.3))
            self.write_settings(DCLevel(10, 0.4))
            self.write_settings(output_state=False)

    def _instrument_debug(self) -> None:
        self.test()
示例#5
0
class ChromaPowerSupply(Serial):
    # TODO check status register endianness
    # TODO check OPC fidelity
    # TODO add inrush delay from test settings command arg to AC measurements or error maybe
    # TODO measure time to execute exposed methods
    # TODO merge leak tester command group idiom to this
    # TODO documentation at least at module level

    # ? W:\TestStation Data Backup\instruments\data\UM-61601~4-acsource-v1.9-102015.pdf
    # ? pg. 67 -> remote operation

    # ? W:\TestStation Data Backup\instruments\data\QSG-61601~4-acsource-v1.0-022010.pdf
    # ? W:\TestStation Data Backup\instruments\data\UM-615,616XX-SoftPanel-v1.6-082013.pdf

    _config = configuration.from_yml(r'W:\Test Data Backup\instruments\config\chroma_power_supply.yml')
    DEVICE_NAME = _config.field(str)
    BAUDRATE = _config.field(int)
    TIMEOUT = _config.field(float)
    TX_WAIT_S = _config.field(float)
    COMMAND_EXEC_WAIT_S = _config.field(float)

    XON_X_OFF = False
    ENCODING = 'utf-8'
    TERM_CHAR = '\r\n'

    _command_config = CommandReader(r'W:\Test Data Backup\instruments\data\Command Source.csv')
    voltage_range = _command_config.setting(True)
    output_state = _command_config.setting()
    output_coupling = _command_config.setting()
    current_limit = _command_config.setting()
    ocp_delay_s = _command_config.setting()
    inrush_start_time_ms = _command_config.setting()
    inrush_measurement_interval_ms = _command_config.setting()
    output_frequency = _command_config.setting()
    _vac_high_range = _command_config.setting()
    _vdc_high_range = _command_config.setting()
    _vac_low_range = _command_config.setting()
    _vdc_low_range = _command_config.setting()
    vac_limit = _command_config.setting()
    vdc_plus_limit = _command_config.setting()
    vdc_minus_limit = _command_config.setting()
    phase_on = _command_config.setting()
    phase_off = _command_config.setting()
    ac_slew_rate = _command_config.setting()
    dc_slew_rate = _command_config.setting()
    freq_slew_rate = _command_config.setting()
    i_rms = _command_config.measurement()
    ipk = _command_config.measurement()
    cf = _command_config.measurement()
    inrush = _command_config.measurement()
    true_power = _command_config.measurement()
    apparent_power = _command_config.measurement()
    reactive_power = _command_config.measurement()
    pf = _command_config.measurement()
    v_rms = _command_config.measurement()
    vdc = _command_config.measurement()
    idc = _command_config.measurement()
    ntr_register = _command_config.status(NTRRegister)
    event_status_register = _command_config.status(EventStatusRegister)
    status_byte_register = _command_config.status(StatusByteRegister)
    clear_protection_latch = _command_config.command()
    clear_registers = _command_config.command()
    vdc_setting: Setting
    vac_setting: Setting

    @register.after('__init__')
    def _set_state_constants(self) -> None:
        self._voltage_range_callback('LOW')

    @register.after('_instrument_setup')
    def _set_output_constant_values(self) -> None:
        # noinspection PyCallingNonCallable
        self.output_coupling('ACDC')
        self.setting(ChromaGenSettings())

    # noinspection PyTypeChecker
    def _voltage_range_callback(self, v_range: str) -> None:
        _range = _RangeState[v_range]
        self.vdc_setting = {_RangeState.HIGH: self._vdc_high_range,
                            _RangeState.LOW: self._vdc_low_range}[_range]
        self.vac_setting = {_RangeState.HIGH: self._vac_high_range,
                            _RangeState.LOW: self._vac_low_range}[_range]

    @proxy.exposed
    def setting(self, condition: Union[SettingMessage],
                delay_override: float = None) -> None:
        for command in condition.requests_in_series(self):
            self.write(command)
        self._instrument_delay(delay_override or self.COMMAND_EXEC_WAIT_S)
        condition.verify(self)

    # noinspection PyCallingNonCallable
    @proxy.exposed
    def output_enable(self) -> None:
        self.clear_protection_latch()
        self.output_state('ON')

    @proxy.exposed
    @register.before('_instrument_cleanup')
    @register.after('_instrument_setup')
    def output_disable(self) -> None:
        self.setting(OutputOffCondition())

    @proxy.exposed
    def operation_completed_bit(self) -> bool:
        self.write('*OPC?')
        return bool(int(self.read()))

    @proxy.exposed
    def check_status_registers(self) -> 'StatusRegisters':
        return StatusRegisters.fulfill(self)

    def _instrument_check(self) -> None:
        ntr = self.check_status_registers().ntr_register
        if ntr:
            raise ChromaPowerSupplyError(ntr)

    def _instrument_debug(self) -> None:
        from time import perf_counter
        for vdc in range(5, 15):
            ti = perf_counter()
            self.write_settings(float(vdc), 0., 60., 0.5)
            self.info('settings', perf_counter() - ti)
            ti = perf_counter()
            self.output_enable()
            self.info('output enable', perf_counter() - ti)
            ti = perf_counter()
            self.info(self.measure())
            self.info('measure', perf_counter() - ti)
            ti = perf_counter()
            self.output_disable()
            self.info('output disable', perf_counter() - ti)

    @proxy.exposed
    def write_settings(self, vdc: float, vac: float, freq: float,
                       i_limit: float, phase_on: float = 0., phase_off: float = 0.) -> None:
        self.setting(_ChromaTestCondition(ChromaTestCondition(vdc, vac, freq, i_limit, phase_on, phase_off)))

    @proxy.exposed
    def measure(self) -> ChromaMeasurement:
        return ChromaMeasurement.fulfill(self)
示例#6
0
class HipotTester(Serial):
    # ? W:\TestStation Data Backup\instruments\data\GPT-9800-m.pdf
    _config = configuration.from_yml(
        r'W:\Test Data Backup\instruments\config\hipot_tester.yml')
    display_name = _config.field(str)

    TEST_DURATION_MARGIN = _config.field(float)
    DELAY_BETWEEN_MEASUREMENTS_S = _config.field(float)
    HWID = _config.field(str)
    BAUDRATE = _config.field(int)
    TIMEOUT = _config.field(float)
    TX_WAIT_S = _config.field(float)
    ERROR_CHECK_ONLY_AFTER_LOAD = _config.field(bool)

    XON_X_OFF = False
    ENCODING = 'utf-8'
    TERM_CHAR = '\r\n'

    _get_identify = Command('*IDN?', '{}GPT{}')
    _get_errors = Command(
        'SYST:ERR ?',
        '{errors}')  # returns error strings from pg.136 of the docs
    _clear_errors = Command('*CLS')  # clears internal registers
    _get_measurement = Command('MEAS ?', _test_measurement_proto)
    _get_test_settings = Command('MANU%d:EDIT:SHOW ?', _test_settings_proto)
    _start_test = Command('FUNC:TEST ON')
    _stop_test = Command('FUNC:TEST OFF')

    # for DC and AC
    _set_to_manual = Command('MAIN:FUNC MANU')
    _set_test_type = Command('MANU:EDIT:MODE %s')  # {ACW, DCW}
    _set_ramp_t = Command('MANU:RTIM %f')  # 0.1~999.9 seconds
    _set_test_number = Command('MANU:STEP %d')  # 1-100
    _set_arc_mode = Command('MANU:UTIL:ARCM %s')  # {OFF, ON_CONT, ON_STOP}
    _set_ground_mode = Command('MANU:UTIL:GROUNDMODE %s')  # {ON, OFF}

    _get_test_number = Command('MANU:STEP ?', '{test_program:d}')
    _get_arc_mode = Command('MANU:UTIL:ARCM ?', 'ARC {arc_mode}')
    _get_ground_mode = Command('MANU:UTIL:GROUNDMODE ?', '{gound_mode}')

    # for AC
    _set_ac_voltage = Command('MANU:ACW:VOLT %.3f')  # 0.1-5.0kV
    _set_ac_upper = Command('MANU:ACW:CHIS %f')  # 0.001 ~ 042.0mA
    _set_ac_lower = Command('MANU:ACW:CLOS %f')  # 0.000 ~ 041.9mA
    _set_ac_test_t = Command('MANU:ACW:TTIM %f')  # 0.5 ~ 999.9 seconds
    _set_ac_frequency = Command('MANU:ACW:FREQ %d')  # {50, 60} Hz
    _set_ac_ref_current = Command('MANU:ACW:REF %f')  # 0.000 ~ 041.9mA
    _set_ac_arc_current = Command(
        'MANU:ACW:ARCC %f')  # 0.000 ~ 080.0mA (<2x upper)

    _get_ac_frequency = Command('MANU:ACW:FREQ ?', '{frequency:d} Hz')
    _get_ac_ref_current = Command('MANU:ACW:REF ?', '{ref_current:f}mA')
    _get_ac_arc_current = Command('MANU:ACW:ARCC ?', '{arc_current:f}mA')

    # for DC
    _set_dc_voltage = Command('MANU:DCW:VOLT %f')  # 0.100 ~ 6.100kV
    _set_dc_upper = Command('MANU:DCW:CHIS %f')  # 0.001 ~ 11.00mA
    _set_dc_lower = Command('MANU:DCW:CLOS %f')  # 0.000 ~ 010.9mA
    _set_dc_test_t = Command('MANU:DCW:TTIM %f')  # 0.5 ~ 999.9 seconds
    _set_dc_ref_current = Command('MANU:DCW:REF %f')  # 000.0 ~ 010.9mA
    _set_dc_arc_current = Command('MANU:DCW:ARCC %f')  # 000.0 ~ 22.00mA

    _get_dc_ref_current = Command('MANU:DCW:REF ?', '{ref_current:f}mA')
    _get_dc_arc_current = Command('MANU:DCW:ARCC ?', '{arc_current:f}mA')

    _still_testing_steps = {'TEST', 'VIEW'}

    @register.after('_instrument_setup')
    def _hipot_setup(self) -> None:
        self._clear_errors()
        self._set_to_manual()

    def _instrument_check(self) -> None:
        self._get_identify()

    def check_for_error_code(self, packet: str) -> None:
        errors = self._get_errors()
        if errors and (errors != '0,No Error'):
            self._clear_errors()
            raise HipotTesterError(
                f'got error codes "{errors}" after {packet}')

    def set_test_program(self, test_program: int) -> None:
        self._set_test_number(test_program)
        if test_program != self._get_test_number():
            raise HipotTesterError(
                f'failed to set test program to {test_program}')

    def __run_test(self,
                   test_specs: HipotProgram,
                   consumer: HIPOT_CONSUMER = None) -> bool:  # type: ignore
        consumer = consumer if callable(consumer) else self.debug
        consumer(test_specs)  # type: ignore
        self._start_test()
        max_test_len = test_specs.total_t + self.TEST_DURATION_MARGIN  # type: ignore
        tf = max_test_len + time()  # type: ignore
        last_t, do_delay, ramp_te = None, True, 0.

        while 1:
            self._instrument_delay(
                self.DELAY_BETWEEN_MEASUREMENTS_S if do_delay else 0.)
            do_delay = True

            try:
                meas = HipotMeasurement(
                    **self._get_measurement())  # type: ignore

            except HipotTesterError as e:
                do_delay = False
                if time() > tf:
                    raise HipotTesterError(
                        f'failed to receive test result after {max_test_len}'
                    ) from e

            else:
                if meas.time_elapsed != last_t:
                    last_t = meas.time_elapsed
                    if meas.step == 'R':
                        ramp_te = last_t

                    elif meas.step == 'T':
                        meas.total_time += ramp_te

                    consumer(meas)

                    if meas.test_status not in self._still_testing_steps:
                        return meas.test_status == 'PASS'

    @proxy.exposed
    def get_test_program(self, test_number: int) -> HipotProgram:
        test_program = HipotProgram(**self._get_test_settings(
            test_number))  # type: ignore

        test_program.arc_mode = ArcMode[self._get_arc_mode()]
        test_program.ground_mode = GroundMode[self._get_ground_mode()]

        k = 'ac' if test_program.is_ac else 'dc'
        for attr in ('ref_current', 'arc_current'):
            setattr(test_program, attr, getattr(self, f'_get_{k}_{attr}')())

        if test_program.is_ac:
            _freq: int = self._get_ac_frequency()  # type: ignore
            test_program.frequency = Frequency.from_number(_freq)
        else:
            test_program.frequency = Frequency.NONE

        return test_program

    @proxy.exposed
    def run_test_by_number(self,
                           test_number: int,
                           consumer: HIPOT_CONSUMER = None) -> bool:
        self._clear_errors()
        self.set_test_program(test_number)
        test_program = self.get_test_program(test_number)

        return self.__run_test(test_program, consumer)

    @proxy.exposed
    def run_test_by_specification(self,
                                  test_program: HipotProgram,
                                  consumer: HIPOT_CONSUMER = None) -> bool:
        test_number = 21
        self._clear_errors()
        self.set_test_program(test_number)
        self._set_test_type(test_program.test_type.name)
        self._set_arc_mode(test_program.arc_mode.name)
        self._set_ground_mode(test_program.ground_mode.name)
        self._set_ramp_t(test_program.ramp_t)

        k = 'ac' if test_program.is_ac else 'dc'
        for attr in ('test_t', 'voltage', 'upper', 'lower', 'ref_current',
                     'arc_current'):
            getattr(self, f'_set_{k}_{attr}')(getattr(test_program, attr))

        if test_program.is_ac:
            self._set_ac_frequency(test_program.frequency.to_number())

        if not test_program == self.get_test_program(test_number):
            raise HipotTesterError(
                f'failed to confirm test programming from spec {test_program}')

        if self.ERROR_CHECK_ONLY_AFTER_LOAD:
            self.check_for_error_code('checking after load')

        return self.__run_test(test_program, consumer)

    def _instrument_debug(self) -> None:
        # ac_spec = HipotProgram(
        #     ramp_t=3.0, test_t=1.0, voltage=0.6, upper=1.0,
        #     lower=0.0, ref_current=0.0, arc_current=1.0, test_type=TestType.ACW,
        #     frequency=Frequency.SIXTY, arc_mode=ArcMode.ON_STOP,
        #     ground_mode=GroundMode.ON, is_ac=True, total_t=4.0
        # )
        dc_spec = HipotProgram(ramp_t=10.0,
                               test_t=3.3,
                               voltage=1.5,
                               upper=0.1,
                               lower=0.0,
                               ref_current=0.0,
                               arc_current=1.0,
                               test_type=TestType.DCW,
                               frequency=Frequency.NONE,
                               arc_mode=ArcMode.ON_STOP,
                               ground_mode=GroundMode.ON,
                               is_ac=False,
                               total_t=13.3)
        self.run_test_by_number(2)
        self.run_test_by_specification(dc_spec)
示例#7
0
class Cirris(Serial):
    """
    get currently programmed tests as a command_string
    run specific test and return bool P/F
    """

    # ? W:\TestStation Data Backup\instruments\data\Cirris TestStation Language 2019.3.1.pdf

    _config = configuration.from_yml(
        r'W:\Test Data Backup\instruments\config\cirrus.yml')
    DEVICE_NAME = _config.field(str)
    BAUDRATE = _config.field(int)
    TIMEOUT = _config.field(float)
    RX_ACCUMULATION_TIME_S = _config.field(float)

    XON_X_OFF = False
    ENCODING = 'utf-8'
    TERM_CHAR = '\r\n'

    _start_test = NoReturn('CHTE({test_prog} {test_type})')
    _get_status = NoReturn('STAT')
    _remote_mode = NoReturn('')

    _fail_sound = ReturnBool('SOUN(3)')
    _pass_sound = ReturnBool('SOUN(5)')
    # TODO: do we need to turn the sound off or set its duration?
    _is_cable_present = ReturnBool('PRES')
    _calculate_fault_location = ReturnBool('FAUL({do_calculate})')
    _local_mode = ReturnBool('EXIT')
    _self_test = ReturnBool('SELF')
    _delay_test_start = ReturnBool('TDEY({setting})')

    _list_tests = ReturnStatus('M_LI')

    def get_status(self) -> Union[bool, str]:
        self._get_status()
        rx = self.read()
        if rx in {'T', 'F'}:
            return rx == 'T'
        return rx

    @proxy.exposed
    def is_cable_present(self) -> bool:
        return self._is_cable_present()

    @proxy.exposed
    def get_tests(self) -> str:
        result = self._list_tests()
        if not result:
            raise CirrisError('failed to retrieve test list')
        return result

    @proxy.exposed
    def run_test(self, test_program: CirrusTestProgram) -> Union[str, bool]:
        # TODO: parse failing result string
        self._start_test(test_prog=test_program.program_number,
                         test_type=test_program.test_type.value)
        tf = time() + test_program.duration
        while 1:

            try:
                result = self.read()

            except SerialTimeoutException as e:
                if time() > tf:
                    raise CirrisError('failed to get test result') from e

            else:
                if result == 'T':
                    return True

                return result

    @register.after('_instrument_setup')
    def _cirris_setup(self) -> None:
        self._remote_mode()
        if not self._calculate_fault_location(do_calculate=False):
            raise CirrisError('faild to disable fault location calc')
        if not self._delay_test_start(setting=False):
            raise CirrisError('failed to disable test start delay')

    @register.before('_instrument_cleanup')
    def _cirris_cleanup(self) -> None:
        self._local_mode()

    def _instrument_check(self) -> None:
        self.get_tests()

    def _instrument_debug(self) -> None:
        [self.info(line) for line in self.get_tests().splitlines()]
示例#8
0
class LeakTester(TCPIP):
    _config = configuration.from_yml(
        r'W:\Test Data Backup\instruments\config\leak_tester.yml')
    display_name = _config.field(str)

    IP_ADDRESS = _config.field(str)
    PORT = _config.field(int)
    BUFFER_SIZE = _config.field(int)
    TIMEOUT = _config.field(float)
    TX_WAIT_S = _config.field(float)
    PROCESSING_TIME_S = _config.field(float)
    TEST_PROGRAM_MAP = _config.field(dict)

    ENCODING = 'utf-8'
    TERM_CHAR = ']'
    START_TEST_COMMAND = 'SRP'

    _test_program_name = Command('SPN%s')
    _test_program_number = Command('SCP%d')
    _test_pressure = Command('STP%3.5f')
    # noinspection SpellCheckingInspection
    _pressure_max = Command('SPTP%3.5f')
    # noinspection SpellCheckingInspection
    _pressure_min = Command('SPTM%3.5f')
    _fast_fill_timer = Command('ST3%3.1f')
    _fill_timer = Command('ST4%3.1f')
    _settle_timer = Command('ST5%3.1f')
    _test_timer = Command('ST6%3.1f')
    _vent_timer = Command('ST7%3.1f')
    _increase_limit = Command('SML%3.5f')
    _decay_limit = Command('SMD%3.5f')
    _test_volume = Command('STV%3.5f')
    _test_type = Command('STT%d')

    @register.before('__init__')
    def _leak_test_constants(self) -> None:
        self.test_program_map = {
            int(k): v
            for k, v in self.TEST_PROGRAM_MAP.items()
        }

    def _instrument_check(self) -> None:
        self._test_program_number()

    def __set_test_program_number(self, test_program_number: int) -> None:
        self._test_program_number(test_program_number)
        self._instrument_delay(self.PROCESSING_TIME_S)
        if self._test_program_number() != test_program_number:
            raise LeakTesterError(
                f'failed to set test program to number {test_program_number}')

    def __run_test(self,
                   test_program: LeakTesterSettings,
                   consumer: LT_CONSUMER = None) -> bool:  # type: ignore
        consumer = consumer if callable(consumer) else self.debug
        consumer(test_program)  # type: ignore

        self.write(self.START_TEST_COMMAND)

        while 1:
            try:
                line = self.read()

            except socket.timeout:
                raise LeakTesterError(
                    'no message from leak tester during test')

            else:
                if not line:
                    continue
                meas = LeakTesterMeasurement(line)
                if consumer:
                    consumer(meas)  # type: ignore
                if meas.is_result:
                    return meas.is_pass

    def __get_program_number_from_model_number(self, mn: int) -> int:
        if mn not in self.test_program_map:
            raise LeakTesterError(f'no test program for mn {mn}')
        return self.test_program_map[mn]

    @proxy.exposed
    def get_test_by_number(self, test_number: int) -> LeakTesterSettings:
        self.__set_test_program_number(test_number)
        return LeakTesterSettings(**{
            k: getattr(self, f'_{k}')()
            for k in LeakTesterSettings.field_names()
        })

    @proxy.exposed
    def get_test_from_model_number(self, mn: int) -> LeakTesterSettings:
        return self.get_test_by_number(
            self.__get_program_number_from_model_number(mn))

    @proxy.exposed
    def run_test_by_number(self,
                           test_number: int,
                           consumer: LT_CONSUMER = None) -> bool:
        return self.__run_test(self.get_test_by_number(test_number), consumer)

    @proxy.exposed
    def run_test_from_model_number(self,
                                   mn: int,
                                   consumer: LT_CONSUMER = None) -> bool:
        return self.run_test_by_number(
            self.__get_program_number_from_model_number(mn), consumer)

    @proxy.exposed
    def run_test_by_specification(self,
                                  test_program: LeakTesterSettings,
                                  consumer: LT_CONSUMER = None) -> bool:
        test_number = 21
        self.__set_test_program_number(test_number)
        [
            getattr(self, f'_{k}')(getattr(test_program, k))
            for k in test_program.field_names()
        ]

        self._instrument_delay(self.PROCESSING_TIME_S)

        new_program = self.get_test_by_number(test_number)
        if test_program != new_program:
            raise LeakTesterError(
                f'failed to confirm test from {test_program} to {new_program}')

        return self.__run_test(test_program, consumer)

    def _instrument_debug(self) -> None:
        self.info(self.get_test_by_number(15))
示例#9
0
class BKPowerSupply(VISA, _DCPowerSupply):
    def calculate_knee(self, percent_of_max: float, num_steps: int, top: float,
                       bottom: float, consumer: Callable[[DCKneeUpdate],
                                                         None]) -> float:
        raise NotImplementedError

    # ? W:\Test Data Backup\test\doc\9200_Series_manual.pdf
    _config = configuration.from_yml(r'instruments\bk_power_supply.yml')
    display_name = _config.field(str)
    PATTERN = _config.field(str)
    MEASUREMENT_WAIT = _config.field(float)
    COMMAND_EXECUTION_TIMEOUT = _config.field(float)

    RAMP_STEPS: Tuple[DCLevel, ...] = (
        DCLevel(24., 15.),
        DCLevel(26., 13.8),
        DCLevel(28., 12.9),
        DCLevel(30., 12.),
        DCLevel(32., 11.3),
    )

    # noinspection SpellCheckingInspection
    class __Command:
        RESET = '*RST'
        SETUP = '*ESE 60;*SRE 48;*CLS'
        IS_DONE = '*OPC? '
        SET_VALUES = 'APPL %.6f,%.6f'
        GET_VALUES = 'APPL?'
        SET_OUTPUT = 'OUTP %d'
        GET_OUTPUT = 'OUTP?'
        GET_VOLT = ':MEAS:VOLT?'
        GET_CURR = ':MEAS:CURR?'
        GET_POW = ':MEAS:POW?'

    next_meas = 0.

    def _instrument_check(self) -> None:
        self.read(self.__Command.GET_VOLT)

    def __command(self, packet: str) -> None:
        self.write(packet)
        command_timeout = self.COMMAND_EXECUTION_TIMEOUT + time()
        while command_timeout > time():
            if self.read(self.__Command.IS_DONE):
                return
        raise BKPowerSupplyError(f'failed to confirm command {packet}')

    @proxy.exposed
    def send_reset(self):
        return self.__command(self.__Command.RESET)

    @register.after('_instrument_setup')
    def _bk_setup(self):
        self.__command(self.__Command.SETUP)
        self.next_meas = time()

    @register.after('_bk_setup')
    @register.before('_instrument_cleanup')
    def _bk_cleanup(self) -> None:
        self.write_settings(DCLevel(0., 0.), False)

    @proxy.exposed
    def read_settings(self) -> Tuple[DCLevel, bool]:
        return DCLevel(*self.read(self.__Command.GET_VALUES)), \
               bool(self.read(self.__Command.GET_OUTPUT))

    @proxy.exposed
    def set_settings(self, dc_level: DCLevel) -> None:
        self.__command(self.__Command.SET_VALUES % (dc_level.V, dc_level.A))

    @proxy.exposed
    def get_settings(self) -> DCLevel:
        return DCLevel(*self.read(self.__Command.GET_VALUES))

    @proxy.exposed
    def set_output(self, output_state: bool) -> None:
        self.__command(self.__Command.SET_OUTPUT %
                       int(cast(bool, output_state)))

    @proxy.exposed
    def get_output(self) -> bool:
        return bool(self.read(self.__Command.GET_OUTPUT))

    @proxy.exposed
    def write_settings(self,
                       dc_level: DCLevel = None,
                       output_state: bool = None):
        if dc_level is None and output_state is None:
            raise BKPowerSupplyError(
                'must call .write_settings() with at least one arg')

        was_dc_level_correct = (dc_level is None) or (dc_level
                                                      == self.get_settings())
        if was_dc_level_correct:
            if dc_level is not None:
                self.info(f'power settings = {dc_level}')
        else:
            self.set_settings(dc_level)

        was_output_state_correct = (
            output_state is None) or not (output_state ^ self.get_output())
        if was_output_state_correct:
            if output_state is not None:
                self.info(f'output state = {output_state}')
        else:
            self.set_output(output_state)

        error_strings = []

        if not was_dc_level_correct:
            if self.get_settings() != dc_level:
                error_strings.append(dc_level)
            else:
                self.info(f'power settings = {dc_level}')

        if not was_output_state_correct:
            if self.get_output() ^ cast(bool, output_state):
                error_strings.append(f'output_enable={output_state}')
            else:
                self.info(f'output state = {output_state}')

        if error_strings:
            raise BKPowerSupplyError(f'failed to set to ' +
                                     ', '.join(error_strings))
        else:
            self.next_meas = time() + (self.MEASUREMENT_WAIT * 1.5)

    @proxy.exposed
    def measure(self, fresh: bool = True):
        """
        note that, even though we send the measure command, rather than the fetch command,
        the BK appears to be responding with a buffered value updated every 220ms
        this method returns in ~15ms unless @fresh, in which case it waits for the value to have been updated
        """
        if fresh:
            self._instrument_delay(self.next_meas - time())
            self.next_meas = time() + self.MEASUREMENT_WAIT
        meas = DCLevel(*list(
            map(self.read,
                [self.__Command.GET_VOLT, self.__Command.GET_CURR])))
        self.info(meas, f'fresh={fresh}')
        return meas

    @proxy.exposed
    def ramp_up(self):
        [
            self.write_settings(step, output_state=True)
            for step in self.RAMP_STEPS
        ]

    @proxy.exposed
    def calculate_connection_state(self, calc):
        return _DCPowerSupply.calculate_connection_state(self, calc)

    @proxy.exposed
    def off(self):
        return _DCPowerSupply.off(self)

    def _instrument_debug(self) -> None:
        self.log_level(logging.DEBUG)
        self.calculate_connection_state(LightLineV1ConnectionState)
        self.ramp_up()
        self.measure(fresh=True)
        self.write_settings(DCLevel(0., 0.))
        tf = time() + 5
        while time() < tf:
            self.measure(fresh=True)
示例#10
0
class Assure(HackScript):
    bk = TestInstrument(BKPowerSupply(), logging.INFO)
    lm = TestInstrument(LightMeter(), logging.INFO)
    ftdi = TestInstrument(RS485(), logging.DEBUG)
    nfc = TestInstrument(NFC(), logging.INFO)

    _config = configuration.from_yml('pixie_hack/.yml')
    ASSOCIATION_TABLE_PATH = _config.field(str)
    SHIPMENT_ASSOCIATION_TABLE_PATH = _config.field(str)
    AFTER_ERASE_WAIT_S = _config.field(float)

    psu_label_pattern = re.compile(r'(?i)\[PSU#\|WDPB:001-(\d{4})]')
    dut_label_pattern = re.compile(r'(?i)\[PIXIE2PT0:(\d{4})]')

    def __init__(self):
        super().__init__()
        self.AFTER_ERASE_WAIT_S = int(self.AFTER_ERASE_WAIT_S)
        self.num_steps = int(self.AFTER_ERASE_WAIT_S * 10)

    def get_result_from_id(self, dut_id: int) -> bool:
        with open(self.ASSOCIATION_TABLE_PATH, newline='') as rf:
            return {
                int(row['dut_id']): row['test_pf'].upper() == 'TRUE'
                for row in csv.DictReader(rf)
            }.get(dut_id, None)

    def associate_id_with_shipment_label(self, dut_id: int) -> int:
        with open(self.SHIPMENT_ASSOCIATION_TABLE_PATH, newline='') as rf:
            table = {
                int(row['dut_id']): int(row['shipment_id'])
                for row in csv.DictReader(rf)
            }
        shipment_id = table.get(dut_id, None)
        if shipment_id is not None:
            return shipment_id
        if not table:
            table[dut_id] = 1
        else:
            table[dut_id] = max(table.values()) + 1
        with open(self.SHIPMENT_ASSOCIATION_TABLE_PATH, 'w+',
                  newline='') as wf:
            writer = csv.DictWriter(wf, ['dut_id', 'shipment_id'])
            writer.writeheader()
            writer.writerows(
                [dict(dut_id=k, shipment_id=v) for k, v in table.items()])
        return table[dut_id]

    def __call__(self) -> None:
        try:
            while 1:
                user_input = input('scan white DUT label -> ')
                parsed = self.dut_label_pattern.findall(user_input)
                if not parsed:
                    print('scan invalid')
                    continue
                dut_id = int(parsed[0])
                if not self.get_result_from_id(dut_id):
                    print(f'DUT #{dut_id} did not pass the light test.')
                    continue

                shipment_id = self.associate_id_with_shipment_label(dut_id)
                user_input = input(
                    f'apply power to light, then scan black PSU label #{shipment_id} -> '
                )
                if user_input.upper() != f'[PSU#|WDPB:001-{shipment_id:04d}]':
                    print('scan invalid')
                    continue

                print('\nwaiting for startup...\n')
                self.progress = progressbar.ProgressBar(maxval=self.num_steps)
                self.progress.start()
                for i in range(1, self.num_steps + 1):
                    sleep(.1)
                    self.progress.update(i)
                print()

                print('\nerasing test firmware...\n')
                self.progress = progressbar.ProgressBar(maxval=5)
                self.progress.start()
                for i in range(1, 6):
                    self.ftdi.ser.send_ascii('U')
                    sleep(1.)
                    self.progress.update(i)
                print()

                print(f'\nwhite #{dut_id} / black #{shipment_id} done.\n')

        except KeyboardInterrupt:
            pass