Ejemplo n.º 1
0
def run(args):
    sdl2.ext.init()
    window = sdl2.ext.Window(
        "Live scan",
        (800, 600),
        flags=(sdl2.SDL_WINDOW_SHOWN | sdl2.SDL_WINDOW_RESIZABLE),
    )

    scanner_event_type = sdl2.SDL_RegisterEvents(1)
    if scanner_event_type == -1:
        raise RuntimeError()
    scanner_event = sdl2.SDL_Event()
    scanner_event.type = scanner_event_type

    factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
    scanner = Scanner(args, scanner_event, factory)
    scanner.start()

    renderer = sdl2.ext.Renderer(window, logical_size=scanner.sprite.size)

    window.size = renderer.logical_size[0] * 20, renderer.logical_size[1] * 20

    running = True
    tex = None
    while running:
        for event in sdl2.ext.get_events():
            if event.type == sdl2.SDL_QUIT:
                running = False
                break
            if event.type == scanner_event_type:
                tex = sdl2.SDL_CreateTextureFromSurface(
                    renderer.sdlrenderer, scanner.sprite.surface).contents
                renderer.copy(tex)
                renderer.present()
            if event.type == sdl2.SDL_WINDOWEVENT:
                win_ev = event.window
                if win_ev.event == sdl2.SDL_WINDOWEVENT_SIZE_CHANGED:
                    renderer.clear()
                    if tex is not None:
                        renderer.copy(tex)
                    renderer.present()
            if event.type == sdl2.SDL_KEYDOWN:
                if event.key.keysym.sym == sdl2.SDLK_s:
                    im = contrast_enhance(
                        surface_to_pil(scanner.completed_part.surface),
                        args.contrast)
                    im.save(args.image_out)
            window.refresh()
    scanner.join()
Ejemplo n.º 2
0
Archivo: 3dwf.py Proyecto: mfkiwl/pysdr
    def append(self, stuff):
        stufflen = len(stuff)

        if self.fill_edge + stufflen + self.headlen > len(self.buf):
            self.buf[0:self.headlen] = self.buf[self.fill_edge:self.fill_edge + self.headlen]
            self.fill_edge = 0

        self.slice(self.fill_edge, self.fill_edge + stufflen)[:] = stuff
        self.fill_edge += stufflen

    def slice(self, a, b):
        return self.buf[a + self.headlen: b + self.headlen]


UPDATE_EVENT_TYPE = sdl2.SDL_RegisterEvents(1)
UPDATE_EVENT = sdl2.SDL_Event()
UPDATE_EVENT.type = UPDATE_EVENT_TYPE


def input_thread(readfunc, ringbuf, nbins, overlap, viewer):
    window = 0.5 * (1.0 - np.cos((2 * math.pi * np.arange(nbins)) / nbins))

    #ringbuf.append(np.fromfile(fil, count=ringbuf.headlen, dtype=np.complex64))
    ringbuf.append(np.frombuffer(readfunc(ringbuf.headlen * 8), dtype=np.complex64))

    while True:
        #ringbuf.append(np.fromfile(fil, count=nbins - overlap, dtype=np.complex64))
        ringbuf.append(np.frombuffer(readfunc((nbins - overlap) * 8), dtype=np.complex64))

        frame = ringbuf.slice(ringbuf.fill_edge - nbins, ringbuf.fill_edge)
Ejemplo n.º 3
0
class GameControllerInput:
    # Class of thread to use for event loop (useful to swap out for a Qt thread if
    # this is to be run in a GUI context)
    THREAD_TYPE = threading.Thread

    # Limit the rate at which commands are sent to the scope in response to the controller
    # such that the scope is busy processing those commands no more than this fraction of the time.
    MAX_AXIS_COMMAND_WALLCLOCK_TIME_PORTION = 0.3

    # The maximum number of milliseconds to defer issuance of scope commands in
    # response to controller axis motion (in order to enforce MAX_AXIS_COMMAND_WALLCLOCK_TIME_PORTION).
    MAX_AXIS_COMMAND_COOL_OFF_MS = 500

    # AXES_THROTTLE_DELAY_EXPIRED_EVENT: Sent by the timer thread to wake up the main
    # SDL thread and cause it to update the scope state in response to any axis position changes
    # that have occurred since last updating the scope for an axis position change.
    AXES_THROTTLE_DELAY_EXPIRED_EVENT = sdl2.SDL_RegisterEvents(1)
    COARSE_DEMAND_FACTOR = 20
    FINE_DEMAND_FACTOR = 1
    Z_DEMAND_FACTOR = 0.5
    # These dead zones are appropriate for the Logitech f310 gamepad in Xinput mode.  The f310, in Xinput mode,
    # does not reliably report axis displacements less than ~2000 units in magnitude.  In legacy mode, the f310
    # does not exhibit this issue.  Unfortunately, legacy mode does not offer trigger Z-axis, making it much less
    # useful for our purposes.  So, we use the f310 in Xinput mode with an adequate dead zone.
    AXIS_DEAD_ZONES = {
        sdl2.SDL_CONTROLLER_AXIS_LEFTX: (-2000, 2000),
        sdl2.SDL_CONTROLLER_AXIS_LEFTY: (-2000, 2000),
        sdl2.SDL_CONTROLLER_AXIS_RIGHTX: (-2000, 2000),
        sdl2.SDL_CONTROLLER_AXIS_RIGHTY: (-2000, 2000)
    }

    def __init__(self, scope, device=0, warnings_enabled=False):
        """
        Parameters:
            scope: microscope client, instance of scope_client.ScopeClient.
            device: the numerical index or string name of the input device (from output of enumerate_devices()).
            warnings_enabled: if True, print debug information to stderr.
        """
        init_sdl()
        self.device, self.device_name, index, self.is_game_controller = open_device(device)
        if self.is_game_controller:
            self.jdevice = sdl2.SDL_GameControllerGetJoystick(self.device)
        else:
            self.jdevice = self.device
        self.device_id = sdl2.SDL_JoystickInstanceID(self.jdevice)
        self.num_axes = sdl2.SDL_JoystickNumAxes(self.jdevice)
        self.num_buttons = sdl2.SDL_JoystickNumButtons(self.jdevice)
        self.num_hats = sdl2.SDL_JoystickNumHats(self.jdevice)
        self.warnings_enabled = warnings_enabled
        self.scope = scope._clone()
        self.event_loop_is_running = False
        self.quit_event_posted = False
        self.throttle_delay_command_time_ratio = 1 - self.MAX_AXIS_COMMAND_WALLCLOCK_TIME_PORTION
        self.throttle_delay_command_time_ratio /= self.MAX_AXIS_COMMAND_WALLCLOCK_TIME_PORTION
        self._axes_throttle_delay_lock = threading.Lock()
        self._c_on_axes_throttle_delay_expired_timer_callback = SDL_TIMER_CALLBACK_TYPE(self._on_axes_throttle_delay_expired_timer_callback)
        self.handle_button_callback = None

    def init_handlers(self):
        self._event_handlers = {
            sdl2.SDL_QUIT: self._on_quit_event,
            self.AXES_THROTTLE_DELAY_EXPIRED_EVENT: self._on_axes_throttle_delay_expired_event,
            sdl2.SDL_JOYHATMOTION: self._on_joyhatmotion_event
        }
        if self.is_game_controller:
            self._event_handlers.update({
                sdl2.SDL_CONTROLLERDEVICEREMOVED: self._on_device_removed_event,
                sdl2.SDL_CONTROLLERAXISMOTION: self._on_axis_motion_event,
                sdl2.SDL_CONTROLLERBUTTONDOWN: self._on_button_event,
                sdl2.SDL_CONTROLLERBUTTONUP: self._on_button_event
            })
        else:
            self._event_handlers.update({
                sdl2.SDL_JOYDEVICEREMOVED: self._on_device_removed_event,
                sdl2.SDL_JOYAXISMOTION: self._on_axis_motion_event,
                sdl2.SDL_JOYBUTTONDOWN: self._on_button_event,
                sdl2.SDL_JOYBUTTONUP: self._on_button_event
            })
        self._axis_movement_handlers = {
            sdl2.SDL_CONTROLLER_AXIS_LEFTX: functools.partial(
                self._handle_axis_motion,
                demand_factor=self.COARSE_DEMAND_FACTOR,
                invert=True,
                set_stage_velocity_method=self.scope.stage.move_along_x
            ),
            sdl2.SDL_CONTROLLER_AXIS_LEFTY: functools.partial(
                self._handle_axis_motion,
                demand_factor=self.COARSE_DEMAND_FACTOR,
                invert=False,
                set_stage_velocity_method=self.scope.stage.move_along_y
            ),
            sdl2.SDL_CONTROLLER_AXIS_RIGHTX: functools.partial(
                self._handle_axis_motion,
                demand_factor=self.FINE_DEMAND_FACTOR,
                invert=True,
                set_stage_velocity_method=self.scope.stage.move_along_x
            ),
            sdl2.SDL_CONTROLLER_AXIS_RIGHTY: functools.partial(
                self._handle_axis_motion,
                demand_factor=self.FINE_DEMAND_FACTOR,
                invert=False,
                set_stage_velocity_method=self.scope.stage.move_along_y
            ),
            sdl2.SDL_CONTROLLER_AXIS_TRIGGERLEFT: functools.partial(
                self._handle_axis_motion,
                demand_factor=self.Z_DEMAND_FACTOR,
                invert=True,
                set_stage_velocity_method=self.scope.stage.move_along_z
            ),
            sdl2.SDL_CONTROLLER_AXIS_TRIGGERRIGHT: functools.partial(
                self._handle_axis_motion,
                demand_factor=self.Z_DEMAND_FACTOR,
                invert=False,
                set_stage_velocity_method=self.scope.stage.move_along_z
            )
        }

    def event_loop(self):
        assert not self.event_loop_is_running
        self._next_axes_tick = 0
        self._axes_throttle_delay_timer_set = False
        self._get_axis_pos = sdl2.SDL_GameControllerGetAxis if self.is_game_controller else sdl2.SDL_JoystickGetAxis
        self.event_loop_is_running = True
        try:
            assert SDL_INITED
            assert self.device
            self.init_handlers()
            self._last_axes_positions = {axis_idx: None for axis_idx in self._axis_movement_handlers.keys()}
            try:
                while not self.quit_event_posted:
                    event = sdl2.SDL_Event()
                    # If there is no event for an entire second, we iterate, giving CPython an opportunity to
                    # raise KeyboardInterrupt.
                    if sdl2.SDL_WaitEventTimeout(ctypes.byref(event), 1000):
                        self._event_handlers.get(event.type, self._on_unhandled_event)(event)
            except KeyboardInterrupt:
                pass
        finally:
            self.event_loop_is_running = False
            self._halt_stage(only_axes_with_nonzero_last_command_velocity=True)

    def make_and_start_event_loop_thread(self):
        assert not self.event_loop_is_running
        self.thread = self.THREAD_TYPE(target=self.event_loop)
        self.thread.start()

    def stop_and_destroy_event_loop_thread(self):
        assert self.event_loop_is_running
        self.exit_event_loop()
        self.thread.join()
        del self.thread

    def exit_event_loop(self):
        '''The exit_event_loop method is thread safe and is safe to call even if the event loop is not running.
        Calling exit_event_loop pushes a quit request onto the SDL event queue, causing self.event_loop() to
        exit gracefully (IE, return) if it is running.'''
        event = sdl2.SDL_Event()
        event.type = sdl2.SDL_QUIT
        sdl2.SDL_PushEvent(ctypes.byref(event))

    def _on_quit_event(self, event):
        self.quit_event_posted = True

    def _on_unhandled_event(self, event):
        if self.warnings_enabled:
            print('Received unhandled SDL event: ' + SDL_EVENT_NAMES.get(event.type, "UNKNOWN"), file=sys.stderr)

    @only_for_our_device
    def _on_device_removed_event(self, event):
        self.handle_device_removed()

    def handle_device_removed(self):
        print('Our SDL input device has been disconnected.  Exiting event loop...', sys.stderr)
        self.exit_event_loop()

    @only_for_our_device
    def _on_button_event(self, event):
        if self.is_game_controller:
            idx = event.cbutton.button
            state = bool(event.cbutton.state)
        else:
            idx = event.jbutton.button
            state = bool(event.jbutton.state)
        self.handle_button(idx, state)

    def _halt_stage(self, only_axes_with_nonzero_last_command_velocity=False):
        if only_axes_with_nonzero_last_command_velocity:
            for axis, pos in self._last_axes_positions.items():
                if pos != 0:
                    self._axis_movement_handlers[axis](demand=0)
        else:
            self.scope.stage.stop_x()
            self.scope.stage.stop_y()
            self.scope.stage.stop_z()
        for axis in self._last_axes_positions.keys():
            self._last_axes_positions[axis] = 0

    def handle_button(self, button_idx, pressed):
        if button_idx == sdl2.SDL_CONTROLLER_BUTTON_A:
            # Stop all stage movement when what is typically the gamepad X button is pressed or released
            self._halt_stage()
        if self.handle_button_callback is not None:
            self.handle_button_callback(button_idx, pressed)

    @only_for_our_device
    def _on_joyhatmotion_event(self, event):
        self.handle_joyhatmotion(event.jhat.hat, event.jhat.value)

    def handle_joyhatmotion(self, hat_idx, pos):
        pass

    @only_for_our_device
    def _on_axis_motion_event(self, event):
        # A subtle point: we need to set the axes throttle delay timer only when cooldown has not expired and
        # no timer is set.  That is, if the joystick moves, SDL tells us about it.  When SDL tells us, if we
        # have too recently handled an axis move event, we defer handling the event by setting a timer that wakes
        # us up when the cooldown has expired.  There is never a need to set a new axes throttle delay timer
        # in response to timer expiration.
        curr_ticks = sdl2.SDL_GetTicks()
        if curr_ticks >= self._next_axes_tick:
            self._on_axes_motion()
        else:
            with self._axes_throttle_delay_lock:
                if not self._axes_throttle_delay_timer_set:
                    defer_ticks = round(max(1, self._next_axes_tick - curr_ticks))
                    if not sdl2.SDL_AddTimer(defer_ticks, self._c_on_axes_throttle_delay_expired_timer_callback, ctypes.c_void_p(0)):
                        sdl_e = sdl2.SDL_GetError()
                        sdl_e = sdl_e.decode('utf-8') if sdl_e else 'UNKNOWN ERROR'
                        raise RuntimeError('Failed to set timer: {}'.format(sdl_e))
                    self._axes_throttle_delay_timer_set = True

    def _on_axes_throttle_delay_expired_timer_callback(self, interval, _):
        # NB: SDL timer callbacks execute on a special thread that is not the main thread
        if not self.event_loop_is_running:
            return
        with self._axes_throttle_delay_lock:
            self._axes_throttle_delay_timer_set = False
            if sdl2.SDL_GetTicks() < self._next_axes_tick:
                if self.warnings_enabled:
                    print('Axes throttling delay expiration callback pre-empted.', sys.stderr)
                return 0
        event = sdl2.SDL_Event()
        event.type = self.AXES_THROTTLE_DELAY_EXPIRED_EVENT
        sdl2.SDL_PushEvent(event)
        # Returning 0 tells SDL to not recycle this timer.  _handle_axes_motion, in the main SDL thread, will
        # ultimately be caused to set a new timer by the event we just pushed.
        return 0

    def _on_axes_throttle_delay_expired_event(self, event):
        if sdl2.SDL_GetTicks() < self._next_axes_tick:
            if self.warnings_enabled:
                print('Axes throttling delay expiration event pre-empted.', file=sys.stderr)
            return
        self._on_axes_motion()

    def _on_axes_motion(self):
        command_ticks = 0
        for axis, cmd in self._axis_movement_handlers.items():
            pos = self._get_axis_pos(self.device, axis)
            dead_zone = self.AXIS_DEAD_ZONES.get(axis)
            if dead_zone is not None:
                if dead_zone[0] <= pos <= dead_zone[1]:
                    pos = 0
                elif pos < dead_zone[0]:
                    pos -= dead_zone[0]
                elif pos > dead_zone[1]:
                    pos -= dead_zone[1]
            if pos != self._last_axes_positions[axis]:
                demand = pos / (32768 if pos <= 0 else 32767)
                t0 = sdl2.SDL_GetTicks()
                cmd(demand=demand)
                t1 = sdl2.SDL_GetTicks()
                self._last_axes_positions[axis] = pos
                command_ticks += t1 - t0
        self._next_axes_tick = sdl2.SDL_GetTicks() + int(min(
            command_ticks * self.throttle_delay_command_time_ratio,
            self.MAX_AXIS_COMMAND_COOL_OFF_MS
        ))

    def _handle_axis_motion(self, demand, demand_factor, invert, set_stage_velocity_method):
        v = demand * demand_factor
        if invert:
            v *= -1
        try:
            set_stage_velocity_method(v)
        except RPCError as e:
            if self.warnings_enabled:
                print(e, file=sys.stderr)
Ejemplo n.º 4
0
class JoypadInput:
    DEFAULT_MAX_AXIS_COMMAND_WALLCLOCK_TIME_PORTION = 0.3333
    DEFAULT_MAX_AXIS_COMMAND_COOL_OFF = 500
    # AXES_THROTTLE_DELAY_EXPIRED_EVENT: Sent by the timer thread to wake up the main
    # SDL thread and cause it to update the scope state in response to any axis position changes
    # that have occurred since last updating the scope for an axis position change.
    AXES_THROTTLE_DELAY_EXPIRED_EVENT = sdl2.SDL_RegisterEvents(1)
    COARSE_DEMAND_FACTOR = 20
    FINE_DEMAND_FACTOR = 1
    Z_DEMAND_FACTOR = 0.5
    # These dead zones are appropriate for the Logitech f310 gamepad in Xinput mode.  The f310, in Xinput mode,
    # does not reliably report axis displacements less than ~2000 units in magnitude.  In legacy mode, the f310
    # does not exhibit this issue.  Unfortunately, legacy mode does not offer trigger Z-axis, making it much less
    # useful for our purposes.  So, we use the f310 in Xinput mode with an adequate dead zone.
    AXIS_DEAD_ZONES = {
        sdl2.SDL_CONTROLLER_AXIS_LEFTX: (-2000, 2000),
        sdl2.SDL_CONTROLLER_AXIS_LEFTY: (-2000, 2000),
        sdl2.SDL_CONTROLLER_AXIS_RIGHTX: (-2000, 2000),
        sdl2.SDL_CONTROLLER_AXIS_RIGHTY: (-2000, 2000)
    }

    def __init__(
            self,
            input_device_index=0,
            input_device_name=None,
            scope_server_host='127.0.0.1',
            zmq_context=None,
            maximum_portion_of_wallclock_time_allowed_for_axis_commands=DEFAULT_MAX_AXIS_COMMAND_WALLCLOCK_TIME_PORTION,
            maximum_axis_command_cool_off=DEFAULT_MAX_AXIS_COMMAND_COOL_OFF,
            warnings_enabled=False):
        """* input_device_index: The argument passed to SDL_JoystickOpen(index) or SDL_GameControllerOpen(index).
        Ignored if the value of input_device_name is not None.
        * input_device_name: If specified, input_device_name should be the exact string or UTF8-encoded bytearray by
        which SDL identifies the controller you wish to use, as reported by SDL_JoystickName(..).  For USB devices,
        this is USB iManufacturer + ' ' + iProduct.  (See example below.)
        * scope_server_host: IP address or hostname of scope server.
        * zmq_context: If None, one is created.
        * maximum_portion_of_wallclock_time_allowed_for_axis_commands: Limit the rate at which commands are sent to
        the scope in response to controller axis motion such that the scope such that the scope is busy processing
        those commands no more
        than this fraction of the time.
        * maximum_axis_command_cool_off: The maximum number of milliseconds to defer issuance of scope commands in
        response to controller axis motion (in order to enforce
        maximum_portion_of_wallclock_time_allowed_for_axis_commands).

        For example, a Sony PS4 controller with the following lsusb -v output would be known to SDL as 'Sony Computer
        Entertainment Wireless Controller':

        Bus 003 Device 041: ID 054c:05c4 Sony Corp.
        Device Descriptor:
          bLength                18
          bDescriptorType         1
          bcdUSB               2.00
          bDeviceClass            0
          bDeviceSubClass         0
          bDeviceProtocol         0
          bMaxPacketSize0        64
          idVendor           0x054c Sony Corp.
          idProduct          0x05c4
          bcdDevice            1.00
          iManufacturer           1 Sony Computer Entertainment
          iProduct                2 Wireless Controller
          iSerial                 0
          bNumConfigurations      1
        ...

        Additionally, sdl_control.enumerate_devices(), a module function, returns a list of the currently available
        SDL joystick and game controller input devices, in the order by which SDL knows them.  So, if you know that
        your input device is a Logilech something-or-other, and sdl_control.enumerate_devices() returns the following:
        [
            'Nintenbo Olympic Sport Mat v3.5',
            'MANUFACTURER NAME HERE. DONT FORGET TO SET THIS!!     Many Product Ltd. 1132 Guangzhou    $  !*llSN9_Q   ',
            'Duckhunt Defender Scanline-Detecting Plastic Gun That Sadly Does Not Work With LCDs',
            'Macrosoft ZBox-720 Controller Colossal-Hands Mondo Edition',
            'Logilech SixThousandAxis KiloButtonPad With Haptic Feedback Explosion',
            'Gametech Gameseries MegaGamer Excel Spreadsheet 3D-Orb For Executives, Doom3D Edition',
            'Gametech Gameseries MegaGamer Excel Spreadsheet 3D-Orb For Light Rail Transport, Doom3D Edition'
        ]
        You will therefore want to specify input_device_index=4 or
        input_device_name='Logilech SixThousandAxis KiloButtonPad With Haptic Feedback Explosion'
        """
        assert 0 < maximum_portion_of_wallclock_time_allowed_for_axis_commands <= 1
        init_sdl()
        self.device, self.device_is_game_controller = open_device(
            input_device_index, input_device_name)
        if self.device_is_game_controller:
            self.jdevice = sdl2.SDL_GameControllerGetJoystick(self.device)
        else:
            self.jdevice = self.device
        self.device_id = sdl2.SDL_JoystickInstanceID(self.jdevice)
        self.num_axes = sdl2.SDL_JoystickNumAxes(self.jdevice)
        self.num_buttons = sdl2.SDL_JoystickNumButtons(self.jdevice)
        self.num_hats = sdl2.SDL_JoystickNumHats(self.jdevice)
        self.warnings_enabled = warnings_enabled
        if warnings_enabled:
            print('JoypadInput is connecting to scope server...',
                  file=sys.stderr)
        self.scope, self.scope_properties = scope_client.client_main(
            scope_server_host, zmq_context)
        if warnings_enabled:
            print('JoypadInput successfully connected to scope server.',
                  file=sys.stderr)
        self.event_loop_is_running = False
        self.quit_event_posted = False
        self.throttle_delay_command_time_ratio = 1 - maximum_portion_of_wallclock_time_allowed_for_axis_commands
        self.throttle_delay_command_time_ratio /= maximum_portion_of_wallclock_time_allowed_for_axis_commands
        self.maximum_axis_command_cool_off = maximum_axis_command_cool_off
        self._axes_throttle_delay_lock = threading.Lock()
        self._c_on_axes_throttle_delay_expired_timer_callback = SDL_TIMER_CALLBACK_TYPE(
            self._on_axes_throttle_delay_expired_timer_callback)
        self.handle_button_callback = self.default_handle_button_callback

    def init_handlers(self):
        self._event_handlers = {
            sdl2.SDL_QUIT: self._on_quit_event,
            self.AXES_THROTTLE_DELAY_EXPIRED_EVENT:
            self._on_axes_throttle_delay_expired_event,
            sdl2.SDL_JOYHATMOTION: self._on_joyhatmotion_event
        }
        if self.device_is_game_controller:
            self._event_handlers.update({
                sdl2.SDL_CONTROLLERDEVICEREMOVED:
                self._on_device_removed_event,
                sdl2.SDL_CONTROLLERAXISMOTION:
                self._on_axis_motion_event,
                sdl2.SDL_CONTROLLERBUTTONDOWN:
                self._on_button_event,
                sdl2.SDL_CONTROLLERBUTTONUP:
                self._on_button_event
            })
        else:
            self._event_handlers.update({
                sdl2.SDL_JOYDEVICEREMOVED:
                self._on_device_removed_event,
                sdl2.SDL_JOYAXISMOTION:
                self._on_axis_motion_event,
                sdl2.SDL_JOYBUTTONDOWN:
                self._on_button_event,
                sdl2.SDL_JOYBUTTONUP:
                self._on_button_event
            })
        self._axis_movement_handlers = {
            sdl2.SDL_CONTROLLER_AXIS_LEFTX:
            functools.partial(
                self._handle_axis_motion,
                demand_factor=self.COARSE_DEMAND_FACTOR,
                invert=True,
                set_stage_velocity_method=self.scope.stage.move_along_x),
            sdl2.SDL_CONTROLLER_AXIS_LEFTY:
            functools.partial(
                self._handle_axis_motion,
                demand_factor=self.COARSE_DEMAND_FACTOR,
                invert=False,
                set_stage_velocity_method=self.scope.stage.move_along_y),
            sdl2.SDL_CONTROLLER_AXIS_RIGHTX:
            functools.partial(
                self._handle_axis_motion,
                demand_factor=self.FINE_DEMAND_FACTOR,
                invert=True,
                set_stage_velocity_method=self.scope.stage.move_along_x),
            sdl2.SDL_CONTROLLER_AXIS_RIGHTY:
            functools.partial(
                self._handle_axis_motion,
                demand_factor=self.FINE_DEMAND_FACTOR,
                invert=False,
                set_stage_velocity_method=self.scope.stage.move_along_y),
            sdl2.SDL_CONTROLLER_AXIS_TRIGGERLEFT:
            functools.partial(
                self._handle_axis_motion,
                demand_factor=self.Z_DEMAND_FACTOR,
                invert=True,
                set_stage_velocity_method=self.scope.stage.move_along_z),
            sdl2.SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
            functools.partial(
                self._handle_axis_motion,
                demand_factor=self.Z_DEMAND_FACTOR,
                invert=False,
                set_stage_velocity_method=self.scope.stage.move_along_z)
        }

    def event_loop(self):
        assert not self.event_loop_is_running
        self._next_axes_tick = 0
        self._axes_throttle_delay_timer_set = False
        self._get_axis_pos = sdl2.SDL_GameControllerGetAxis if self.device_is_game_controller else sdl2.SDL_JoystickGetAxis
        self.event_loop_is_running = True
        try:
            assert SDL_INITED
            assert self.device
            self.init_handlers()
            self._last_axes_positions = {
                axis_idx: None
                for axis_idx in self._axis_movement_handlers.keys()
            }
            try:
                while not self.quit_event_posted:
                    event = sdl2.SDL_Event()
                    # If there is no event for an entire second, we iterate, giving CPython an opportunity to
                    # raise KeyboardInterrupt.
                    if sdl2.SDL_WaitEventTimeout(ctypes.byref(event), 1000):
                        self._event_handlers.get(
                            event.type, self._on_unhandled_event)(event)
            except KeyboardInterrupt:
                pass
        finally:
            self.event_loop_is_running = False
            self._halt_stage(only_axes_with_nonzero_last_command_velocity=True)

    def make_and_start_event_loop_thread(self):
        assert not self.event_loop_is_running
        self.thread = threading.Thread(target=self.event_loop)
        self.thread.start()

    def stop_and_destroy_event_loop_thread(self):
        assert self.event_loop_is_running
        self.exit_event_loop()
        self.thread.join()
        del self.thread

    def exit_event_loop(self):
        '''The exit_event_loop method is thread safe and is safe to call even if the event loop is not running.
        Calling exit_event_loop pushes a quit request onto the SDL event queue, causing self.event_loop() to
        exit gracefully (IE, return) if it is running.'''
        event = sdl2.SDL_Event()
        event.type = sdl2.SDL_QUIT
        sdl2.SDL_PushEvent(ctypes.byref(event))

    def _on_quit_event(self, event):
        self.quit_event_posted = True

    def _on_unhandled_event(self, event):
        if self.warnings_enabled:
            print('Received unhandled SDL event: ' +
                  SDL_EVENT_NAMES.get(event.type, "UNKNOWN"),
                  file=sys.stderr)

    @only_for_our_device
    def _on_device_removed_event(self, event):
        self.handle_device_removed()

    def handle_device_removed(self):
        print(
            'Our SDL input device has been disconnected.  Exiting event loop...',
            sys.stderr)
        self.exit_event_loop()

    @only_for_our_device
    def _on_button_event(self, event):
        if self.device_is_game_controller:
            idx = event.cbutton.button
            state = bool(event.cbutton.state)
        else:
            idx = event.jbutton.button
            state = bool(event.jbutton.state)
        self.handle_button(idx, state)

    @staticmethod
    def default_handle_button_callback(self, button_idx, pressed):
        if button_idx == sdl2.SDL_CONTROLLER_BUTTON_A:
            # Stop all stage movement when what is typically the gamepad X button is pressed or released
            self._halt_stage()

    def _halt_stage(self, only_axes_with_nonzero_last_command_velocity=False):
        if only_axes_with_nonzero_last_command_velocity:
            for axis, pos in self._last_axes_positions.items():
                if pos != 0:
                    self._axis_movement_handlers[axis](demand=0)
        else:
            self.scope.stage.stop_x()
            self.scope.stage.stop_y()
            self.scope.stage.stop_z()
        for axis in self._last_axes_positions.keys():
            self._last_axes_positions[axis] = 0

    def handle_button(self, button_idx, pressed):
        if self.handle_button_callback is not None:
            self.handle_button_callback(self, button_idx, pressed)

    @only_for_our_device
    def _on_joyhatmotion_event(self, event):
        self.handle_joyhatmotion(event.jhat.hat, event.jhat.value)

    def handle_joyhatmotion(self, hat_idx, pos):
        pass

    @only_for_our_device
    def _on_axis_motion_event(self, event):
        # A subtle point: we need to set the axes throttle delay timer only when cooldown has not expired and
        # no timer is set.  That is, if the joystick moves, SDL tells us about it.  When SDL tells us, if we
        # have too recently handled an axis move event, we defer handling the event by setting a timer that wakes
        # us up when the cooldown has expired.  There is never a need to set a new axes throttle delay timer
        # in response to timer expiration.
        curr_ticks = sdl2.SDL_GetTicks()
        if curr_ticks >= self._next_axes_tick:
            self._on_axes_motion()
        else:
            with self._axes_throttle_delay_lock:
                if not self._axes_throttle_delay_timer_set:
                    defer_ticks = round(
                        max(1, self._next_axes_tick - curr_ticks))
                    if not sdl2.SDL_AddTimer(
                            defer_ticks, self.
                            _c_on_axes_throttle_delay_expired_timer_callback,
                            ctypes.c_void_p(0)):
                        sdl_e = sdl2.SDL_GetError()
                        sdl_e = sdl_e.decode(
                            'utf-8') if sdl_e else 'UNKNOWN ERROR'
                        raise RuntimeError(
                            'Failed to set timer: {}'.format(sdl_e))
                    self._axes_throttle_delay_timer_set = True

    def _on_axes_throttle_delay_expired_timer_callback(self, interval, _):
        # NB: SDL timer callbacks execute on a special thread that is not the main thread
        if not self.event_loop_is_running:
            return
        with self._axes_throttle_delay_lock:
            self._axes_throttle_delay_timer_set = False
            if sdl2.SDL_GetTicks() < self._next_axes_tick:
                if self.warnings_enabled:
                    print(
                        'Axes throttling delay expiration callback pre-empted.',
                        sys.stderr)
                return 0
        event = sdl2.SDL_Event()
        event.type = self.AXES_THROTTLE_DELAY_EXPIRED_EVENT
        sdl2.SDL_PushEvent(event)
        # Returning 0 tells SDL to not recycle this timer.  _handle_axes_motion, in the main SDL thread, will
        # ultimately be caused to set a new timer by the event we just pushed.
        return 0

    def _on_axes_throttle_delay_expired_event(self, event):
        if sdl2.SDL_GetTicks() < self._next_axes_tick:
            if self.warnings_enabled:
                print('Axes throttling delay expiration event pre-empted.',
                      file=sys.stderr)
            return
        self._on_axes_motion()

    def _on_axes_motion(self):
        command_ticks = 0
        for axis, cmd in self._axis_movement_handlers.items():
            pos = self._get_axis_pos(self.device, axis)
            dead_zone = self.AXIS_DEAD_ZONES.get(axis)
            if dead_zone is not None:
                if dead_zone[0] <= pos <= dead_zone[1]:
                    pos = 0
                elif pos < dead_zone[0]:
                    pos -= dead_zone[0]
                elif pos > dead_zone[1]:
                    pos -= dead_zone[1]
            if pos != self._last_axes_positions[axis]:
                demand = pos / (32768 if pos <= 0 else 32767)
                t0 = sdl2.SDL_GetTicks()
                cmd(demand=demand)
                t1 = sdl2.SDL_GetTicks()
                self._last_axes_positions[axis] = pos
                command_ticks += t1 - t0
        self._next_axes_tick = sdl2.SDL_GetTicks() + int(
            min(command_ticks * self.throttle_delay_command_time_ratio,
                self.maximum_axis_command_cool_off))

    def _handle_axis_motion(self, demand, demand_factor, invert,
                            set_stage_velocity_method):
        try:
            v = demand * demand_factor
            if invert:
                v *= -1
            set_stage_velocity_method(v)
        except RPCError as e:
            if self.warnings_enabled:
                print(e, file=sys.stderr)
Ejemplo n.º 5
0
 def __init__(self):
     self.type: int = sdl2.SDL_RegisterEvents(1)
     logger.debug(f"New event type {self!r}")
Ejemplo n.º 6
0
def registerPesEventType():
    global EVENT_TYPE
    EVENT_TYPE = sdl2.SDL_RegisterEvents(1)
    if EVENT_TYPE == -1:
        return False
    return True