Add layers to pimoroni trackball
This commit is contained in:
parent
90c4ce99f3
commit
4b0e459d62
@ -19,6 +19,24 @@ keyboard.modules.append(trackball)
|
|||||||
|
|
||||||
Module will also work when you cannot use `busio` and do `import bitbangio as io` instead.
|
Module will also work when you cannot use `busio` and do `import bitbangio as io` instead.
|
||||||
|
|
||||||
|
### Key inputs
|
||||||
|
|
||||||
|
If you have used this thing on a mobile device, you will know it excels at cursor movement. You can get the same for any of your Layer:
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
trackball = Trackball(i2c, mode=TrackballMode.MOUSE_MODE, layers=[
|
||||||
|
# act like an encoder, input arrow keys
|
||||||
|
KeyLayer(KC.UP, KC.RIGHT, KC.DOWN, KC.LEFT, KC.ENTER),
|
||||||
|
# on layer 1 and above use the default pointing behavior
|
||||||
|
PointingLayer()
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Backlight
|
||||||
|
|
||||||
Setup backlight color using below commands:
|
Setup backlight color using below commands:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -55,16 +55,95 @@ class TrackballMode:
|
|||||||
SCROLL_MODE = const(1)
|
SCROLL_MODE = const(1)
|
||||||
|
|
||||||
|
|
||||||
|
class TrackballLayer():
|
||||||
|
'''Just to show an interface for layers'''
|
||||||
|
def handle(self, keyboard, trackball, x, y, switch, state):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class PointingLayer(TrackballLayer):
|
||||||
|
'''the default behavior: act as trackball'''
|
||||||
|
def handle(self, keyboard, trackball, x, y, switch, state):
|
||||||
|
if trackball.mode == TrackballMode.MOUSE_MODE:
|
||||||
|
if x >= 0:
|
||||||
|
trackball.pointing_device.report_x[0] = x
|
||||||
|
else:
|
||||||
|
trackball.pointing_device.report_x[0] = 0xFF & x
|
||||||
|
if y >= 0:
|
||||||
|
trackball.pointing_device.report_y[0] = y
|
||||||
|
else:
|
||||||
|
trackball.pointing_device.report_y[0] = 0xFF & y
|
||||||
|
trackball.pointing_device.hid_pending = x != 0 or y != 0
|
||||||
|
else: # SCROLL_MODE
|
||||||
|
trackball.pointing_device.report_w[0] = y
|
||||||
|
trackball.pointing_device.hid_pending = y != 0
|
||||||
|
|
||||||
|
if switch == 1: # Button pressed
|
||||||
|
trackball.pointing_device.button_status[0] |= trackball.pointing_device.MB_LMB
|
||||||
|
trackball.pointing_device.hid_pending = True
|
||||||
|
|
||||||
|
if not state and trackball.previous_state is True: # Button released
|
||||||
|
trackball.pointing_device.button_status[0] &= ~trackball.pointing_device.MB_LMB
|
||||||
|
trackball.pointing_device.hid_pending = True
|
||||||
|
|
||||||
|
trackball.previous_state = state
|
||||||
|
|
||||||
|
|
||||||
|
class KeyLayer(TrackballLayer):
|
||||||
|
'''Act like an encoder. '''
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
|
||||||
|
def __init__(self, up, right, down, left, press, axis_snap=.25, steps=8):
|
||||||
|
self.up = up
|
||||||
|
self.right = right
|
||||||
|
self.down = down
|
||||||
|
self.left = left
|
||||||
|
self.press = press
|
||||||
|
'''snap to axis. the higher the value, the more we snap to the dominant direction'''
|
||||||
|
self.axis_snap = axis_snap
|
||||||
|
'''how many px we move to trigger a key tap, less means more key taps'''
|
||||||
|
self.steps = steps
|
||||||
|
|
||||||
|
def handle(self, keyboard, trackball, x, y, switch, state):
|
||||||
|
if y and abs(x / y) < self.axis_snap:
|
||||||
|
x = 0
|
||||||
|
if x and abs(y / x) < self.axis_snap:
|
||||||
|
y = 0
|
||||||
|
|
||||||
|
self.x += x
|
||||||
|
self.y += y
|
||||||
|
x_taps = self.x // self.steps
|
||||||
|
y_taps = self.y // self.steps
|
||||||
|
self.x %= self.steps
|
||||||
|
self.y %= self.steps
|
||||||
|
for i in range(x_taps, 0, 1):
|
||||||
|
keyboard.tap_key(self.left)
|
||||||
|
for i in range(x_taps, 0, -1):
|
||||||
|
keyboard.tap_key(self.right)
|
||||||
|
for i in range(y_taps, 0, 1):
|
||||||
|
keyboard.tap_key(self.up)
|
||||||
|
for i in range(y_taps, 0, -1):
|
||||||
|
keyboard.tap_key(self.down)
|
||||||
|
if switch and state:
|
||||||
|
keyboard.tap_key(self.press)
|
||||||
|
|
||||||
|
|
||||||
class Trackball(Module):
|
class Trackball(Module):
|
||||||
'''Module handles usage of Trackball Breakout by Pimoroni'''
|
'''Module handles usage of Trackball Breakout by Pimoroni'''
|
||||||
|
|
||||||
def __init__(self, i2c, mode=TrackballMode.MOUSE_MODE, address=I2C_ADDRESS):
|
def __init__(self, i2c, mode=TrackballMode.MOUSE_MODE, address=I2C_ADDRESS, angle_offset=ANGLE_OFFSET, layers=None):
|
||||||
|
self.angle_offset = angle_offset
|
||||||
|
if layers is None:
|
||||||
|
layers = [PointingLayer()]
|
||||||
self._i2c_address = address
|
self._i2c_address = address
|
||||||
self._i2c_bus = i2c
|
self._i2c_bus = i2c
|
||||||
|
|
||||||
self.pointing_device = PointingDevice()
|
self.pointing_device = PointingDevice()
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.previous_state = False # click state
|
self.previous_state = False # click state
|
||||||
|
self.layers = layers
|
||||||
self.polling_interval = 20
|
self.polling_interval = 20
|
||||||
|
|
||||||
chip_id = struct.unpack('<H', bytearray(self._i2c_rdwr([REG_CHIP_ID_L], 2)))[0]
|
chip_id = struct.unpack('<H', bytearray(self._i2c_rdwr([REG_CHIP_ID_L], 2)))[0]
|
||||||
@ -91,33 +170,14 @@ class Trackball(Module):
|
|||||||
|
|
||||||
up, down, left, right, switch, state = self._read_raw_state()
|
up, down, left, right, switch, state = self._read_raw_state()
|
||||||
|
|
||||||
if self.mode == TrackballMode.MOUSE_MODE:
|
x, y = self._calculate_movement(right - left, down - up)
|
||||||
x_axis, y_axis = self._calculate_movement(right - left, down - up)
|
|
||||||
if x_axis >= 0:
|
|
||||||
self.pointing_device.report_x[0] = x_axis
|
|
||||||
else:
|
|
||||||
self.pointing_device.report_x[0] = 0xFF & x_axis
|
|
||||||
if y_axis >= 0:
|
|
||||||
self.pointing_device.report_y[0] = y_axis
|
|
||||||
else:
|
|
||||||
self.pointing_device.report_y[0] = 0xFF & y_axis
|
|
||||||
self.pointing_device.hid_pending = x_axis != 0 or y_axis != 0
|
|
||||||
else: # SCROLL_MODE
|
|
||||||
if up >= 0:
|
|
||||||
self.pointing_device.report_w[0] = up
|
|
||||||
if down > 0:
|
|
||||||
self.pointing_device.report_w[0] = 0xFF & (0 - down)
|
|
||||||
self.pointing_device.hid_pending = up != 0 or down != 0
|
|
||||||
|
|
||||||
if switch == 1: # Button pressed
|
layer = self.layers[-1]
|
||||||
self.pointing_device.button_status[0] |= self.pointing_device.MB_LMB
|
active_layer = keyboard.active_layers[0]
|
||||||
self.pointing_device.hid_pending = True
|
if len(self.layers) > active_layer:
|
||||||
|
layer = self.layers[active_layer]
|
||||||
|
layer.handle(keyboard, self, x, y, switch, state)
|
||||||
|
|
||||||
if not state and self.previous_state is True: # Button released
|
|
||||||
self.pointing_device.button_status[0] &= ~self.pointing_device.MB_LMB
|
|
||||||
self.pointing_device.hid_pending = True
|
|
||||||
|
|
||||||
self.previous_state = state
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def after_matrix_scan(self, keyboard):
|
def after_matrix_scan(self, keyboard):
|
||||||
@ -174,26 +234,6 @@ class Trackball(Module):
|
|||||||
)
|
)
|
||||||
return up, down, left, right, switch, switch_state
|
return up, down, left, right, switch, switch_state
|
||||||
|
|
||||||
def _calculate_movement(self, raw_x, raw_y):
|
|
||||||
'''Calculate accelerated movement vector from raw data'''
|
|
||||||
if raw_x == 0 and raw_y == 0:
|
|
||||||
return 0, 0
|
|
||||||
|
|
||||||
var_accel = 1
|
|
||||||
power = 2.5
|
|
||||||
|
|
||||||
angle_rad = math.atan2(raw_y, raw_x) + ANGLE_OFFSET
|
|
||||||
vector_length = math.sqrt(pow(raw_x, 2) + pow(raw_y, 2))
|
|
||||||
vector_length = pow(vector_length * var_accel, power)
|
|
||||||
x = math.floor(vector_length * math.cos(angle_rad))
|
|
||||||
y = math.floor(vector_length * math.sin(angle_rad))
|
|
||||||
|
|
||||||
limit = 127 # hid size limit
|
|
||||||
x_clamped = max(min(limit, x), -limit)
|
|
||||||
y_clamped = max(min(limit, y), -limit)
|
|
||||||
|
|
||||||
return x_clamped, y_clamped
|
|
||||||
|
|
||||||
def _i2c_rdwr(self, data, length=0):
|
def _i2c_rdwr(self, data, length=0):
|
||||||
'''Write and optionally read I2C data.'''
|
'''Write and optionally read I2C data.'''
|
||||||
while not self._i2c_bus.try_lock():
|
while not self._i2c_bus.try_lock():
|
||||||
@ -216,3 +256,23 @@ class Trackball(Module):
|
|||||||
|
|
||||||
def _tb_mode_press(self, key, keyboard, *args, **kwargs):
|
def _tb_mode_press(self, key, keyboard, *args, **kwargs):
|
||||||
self.mode = not self.mode
|
self.mode = not self.mode
|
||||||
|
|
||||||
|
def _calculate_movement(self, raw_x, raw_y):
|
||||||
|
'''Calculate accelerated movement vector from raw data'''
|
||||||
|
if raw_x == 0 and raw_y == 0:
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
var_accel = 1
|
||||||
|
power = 2.5
|
||||||
|
|
||||||
|
angle_rad = math.atan2(raw_y, raw_x) + self.angle_offset
|
||||||
|
vector_length = math.sqrt(pow(raw_x, 2) + pow(raw_y, 2))
|
||||||
|
vector_length = pow(vector_length * var_accel, power)
|
||||||
|
x = math.floor(vector_length * math.cos(angle_rad))
|
||||||
|
y = math.floor(vector_length * math.sin(angle_rad))
|
||||||
|
|
||||||
|
limit = 127 # hid size limit
|
||||||
|
x_clamped = max(min(limit, x), -limit)
|
||||||
|
y_clamped = max(min(limit, y), -limit)
|
||||||
|
|
||||||
|
return x_clamped, y_clamped
|
||||||
|
Loading…
x
Reference in New Issue
Block a user