|
| 1 | +# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | + |
| 5 | +import board |
| 6 | +import keypad |
| 7 | +import rotaryio |
| 8 | +import neopixel |
| 9 | +import usb_hid |
| 10 | +from adafruit_hid.keyboard import Keyboard |
| 11 | +from adafruit_hid.keycode import Keycode |
| 12 | +import adafruit_ble |
| 13 | +from adafruit_ble.advertising import Advertisement |
| 14 | +from adafruit_ble.advertising.standard import ProvideServicesAdvertisement |
| 15 | +from adafruit_ble.services.standard.hid import HIDService |
| 16 | +from adafruit_ble.services.standard.device_info import DeviceInfoService |
| 17 | + |
| 18 | +# BLE advertisement names |
| 19 | +ble_maker = "Adafruit Industries" |
| 20 | +ble_name = "BLE ESP32-S3 MacroPad" |
| 21 | +# neopixel colors |
| 22 | +RED = (255, 0, 0) |
| 23 | +ORANGE = (255, 127, 0) |
| 24 | +YELLOW = (255, 255, 0) |
| 25 | +GREEN = (0, 255, 0) |
| 26 | +AQUA = (0, 255, 255) |
| 27 | +BLUE = (0, 0, 255) |
| 28 | +PURPLE = (127, 0, 255) |
| 29 | +PINK = (255, 0, 255) |
| 30 | +OFF = (0, 0, 0) |
| 31 | +# axis states selected with keys 9-11 |
| 32 | +axis_states = [0, "x", "y", "z"] |
| 33 | +state = axis_states[0] |
| 34 | +# keymap for key matrix |
| 35 | +keymap = { |
| 36 | + (0): (axis_states[0], [Keycode.HOME], RED), # HOME X/Y |
| 37 | + (1): (axis_states[0], [Keycode.END], ORANGE), # HOME Z |
| 38 | + (2): (axis_states[0], (Keycode.HOME, Keycode.END), YELLOW), # HOME ALL |
| 39 | + |
| 40 | + (3): (axis_states[0], (Keycode.SHIFT, Keycode.A), GREEN), # SHORTCUT A |
| 41 | + (4): (axis_states[0], (Keycode.SHIFT, Keycode.B), AQUA), # SHORTCUT B |
| 42 | + (5): (axis_states[0], (Keycode.SHIFT, Keycode.C), BLUE), # SHORTCUT C |
| 43 | + |
| 44 | + (6): (axis_states[0], [Keycode.TWO], AQUA), # SET STEPS 1MM |
| 45 | + (7): (axis_states[0], [Keycode.THREE], PURPLE), # SET STEPS 10MM |
| 46 | + (8): (axis_states[0], [Keycode.FOUR], PINK), # SET STEPS 100MM |
| 47 | + |
| 48 | + (9): (axis_states[1], None, RED), # SET X-AXIS STATE |
| 49 | + (10): (axis_states[2], None, GREEN), # SET Y-AXIS STATE |
| 50 | + (11): (axis_states[3], None, BLUE), # SET Z-AXIS STATE |
| 51 | +} |
| 52 | +# keymap for encoder based on state; pos = [0], neg = [1] |
| 53 | +encoder_map = { |
| 54 | + ("x"): ([Keycode.RIGHT_ARROW], [Keycode.LEFT_ARROW]), |
| 55 | + ("y"): ([Keycode.UP_ARROW], [Keycode.DOWN_ARROW]), |
| 56 | + ("z"): ([Keycode.W], [Keycode.S]), |
| 57 | +} |
| 58 | +# make a keyboard |
| 59 | +kbd = Keyboard(usb_hid.devices) |
| 60 | +# key matrix |
| 61 | +COLUMNS = 3 |
| 62 | +ROWS = 4 |
| 63 | +keys = keypad.KeyMatrix( |
| 64 | + row_pins=(board.D12, board.D11, board.D10, board.D9), |
| 65 | + column_pins=(board.A0, board.A1, board.A2), |
| 66 | + columns_to_anodes=False, |
| 67 | +) |
| 68 | +# neopixels and key num to pixel function |
| 69 | +pixels = neopixel.NeoPixel(board.D5, 12, brightness=0.3) |
| 70 | +def key_to_pixel_map(key_number): |
| 71 | + row = key_number // COLUMNS |
| 72 | + column = key_number % COLUMNS |
| 73 | + if row % 2 == 1: |
| 74 | + column = COLUMNS - column - 1 |
| 75 | + return row * COLUMNS + column |
| 76 | +pixels.fill(OFF) # Begin with pixels off. |
| 77 | + |
| 78 | +# make an encoder |
| 79 | +encoder = rotaryio.IncrementalEncoder(board.A3, board.A4) |
| 80 | +last_position = 0 |
| 81 | + |
| 82 | +# connect to BLE |
| 83 | +hid = HIDService() |
| 84 | +device_info = DeviceInfoService(software_revision=adafruit_ble.__version__, |
| 85 | + manufacturer=ble_maker) |
| 86 | +advertisement = ProvideServicesAdvertisement(hid) |
| 87 | +# Advertise as "Keyboard" (0x03C1) icon when pairing |
| 88 | +# https://www.bluetooth.com/specifications/assigned-numbers/ |
| 89 | +advertisement.appearance = 961 |
| 90 | +scan_response = Advertisement() |
| 91 | +scan_response.complete_name = ble_name |
| 92 | + |
| 93 | +ble = adafruit_ble.BLERadio() |
| 94 | +if not ble.connected: |
| 95 | + print("advertising") |
| 96 | + ble.start_advertising(advertisement, scan_response) |
| 97 | +else: |
| 98 | + print("already connected") |
| 99 | + print(ble.connections) |
| 100 | + |
| 101 | +while True: |
| 102 | + while not ble.connected: |
| 103 | + pass |
| 104 | + while ble.connected: |
| 105 | + # poll for key event |
| 106 | + key_event = keys.events.get() |
| 107 | + # get position of encoder |
| 108 | + position = encoder.position |
| 109 | + # if position changes.. |
| 110 | + if position != last_position: |
| 111 | + # ..and it increases.. |
| 112 | + if position > last_position: |
| 113 | + # ..and state is x: |
| 114 | + if state is axis_states[1]: |
| 115 | + kbd.press(*encoder_map[state][0]) |
| 116 | + # ..and state is y: |
| 117 | + if state is axis_states[2]: |
| 118 | + kbd.press(*encoder_map[state][0]) |
| 119 | + # ..and state is z: |
| 120 | + if state is axis_states[3]: |
| 121 | + kbd.press(*encoder_map[state][0]) |
| 122 | + # ..and it decreases.. |
| 123 | + if position < last_position: |
| 124 | + # ..and state is x: |
| 125 | + if state is axis_states[1]: |
| 126 | + kbd.press(*encoder_map[state][1]) |
| 127 | + # ..and state is y: |
| 128 | + if state is axis_states[2]: |
| 129 | + kbd.press(*encoder_map[state][1]) |
| 130 | + # ..and state is z: |
| 131 | + if state is axis_states[3]: |
| 132 | + kbd.press(*encoder_map[state][1]) |
| 133 | + # print(position) |
| 134 | + # release all keys |
| 135 | + kbd.release_all() |
| 136 | + # update last_position |
| 137 | + last_position = position |
| 138 | + # if a key event.. |
| 139 | + if key_event: |
| 140 | + # print(key_event) |
| 141 | + # ..and it's pressed.. |
| 142 | + if key_event.pressed: |
| 143 | + # ..and it's keys 0-8, send key presses from keymap: |
| 144 | + if keymap[key_event.key_number][0] is axis_states[0]: |
| 145 | + state = axis_states[0] |
| 146 | + kbd.press(*keymap[key_event.key_number][1]) |
| 147 | + # ..and it's key 9, set state to x |
| 148 | + if keymap[key_event.key_number][0] is axis_states[1]: |
| 149 | + state = axis_states[1] |
| 150 | + pixels[key_to_pixel_map(10)] = OFF |
| 151 | + pixels[key_to_pixel_map(11)] = OFF |
| 152 | + # ..and it's key 10, set state to y |
| 153 | + if keymap[key_event.key_number][0] is axis_states[2]: |
| 154 | + state = axis_states[2] |
| 155 | + pixels[key_to_pixel_map(9)] = OFF |
| 156 | + pixels[key_to_pixel_map(11)] = OFF |
| 157 | + # ..and it's key 11, set state to z |
| 158 | + if keymap[key_event.key_number][0] is axis_states[3]: |
| 159 | + state = axis_states[3] |
| 160 | + pixels[key_to_pixel_map(9)] = OFF |
| 161 | + pixels[key_to_pixel_map(10)] = OFF |
| 162 | + # turn on neopixel for key with color from keymap |
| 163 | + pixels[key_to_pixel_map(key_event.key_number)] = keymap[key_event.key_number][2] |
| 164 | + # ..and it's released.. |
| 165 | + if key_event.released: |
| 166 | + # if it's key 0-8, release the key press and turn off neopixel |
| 167 | + if keymap[key_event.key_number][0] is axis_states[0]: |
| 168 | + kbd.release(*keymap[key_event.key_number][1]) |
| 169 | + pixels.fill(OFF) |
| 170 | + ble.start_advertising(advertisement) |
0 commit comments