Once you've finished setting up your Feather ESP32-S3 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import microcontroller
import board
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
import adafruit_ble
from adafruit_ble.advertising import Advertisement
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.standard.hid import HIDService
from adafruit_seesaw import seesaw, rotaryio, digitalio
# ios modifier
mod = Keycode.CAPS_LOCK
# keycodes
KBD_CODES = [
[Keycode.SPACE], # center
[mod], # up
[Keycode.LEFT_ARROW], # left
[Keycode.DOWN_ARROW], # down
[Keycode.RIGHT_ARROW], # right
]
i2c = board.STEMMA_I2C()
seesaw = seesaw.Seesaw(i2c, addr=0x49)
seesaw_product = (seesaw.get_version() >> 16) & 0xFFFF
print(f"Found product {seesaw_product}")
if seesaw_product != 5740:
print("Wrong firmware loaded? Expected 5740")
buttons = []
button_names = ["Select", "Up", "Left", "Down", "Right"]
button_states = []
for s in range(1, 6):
seesaw.pin_mode(s, seesaw.INPUT_PULLUP)
pin = digitalio.DigitalIO(seesaw, s)
pin_state = False
buttons.append(pin)
button_states.append(pin_state)
encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = 0
if not buttons[0].value and button_states[0] is False:
button_states[0] = True
try:
import _bleio
time.sleep(2)
_bleio.adapter.erase_bonding()
time.sleep(2)
print("Last BLE bonding deleted, restarting..")
time.sleep(2)
microcontroller.reset()
except Exception: # pylint: disable=broad-except
pass
# BLE HID setup
hid = HIDService()
# keyboard & mouse HID setup
kbd = Keyboard(hid.devices)
advertisement = ProvideServicesAdvertisement(hid)
advertisement.appearance = 961
scan_response = Advertisement()
scan_response.complete_name = "CircuitPython HID"
ble = adafruit_ble.BLERadio()
if not ble.connected:
print("advertising")
ble.start_advertising(advertisement, scan_response)
else:
print("already connected")
print(ble.connections)
time.sleep(2)
while True:
# check for BLE connection
while not ble.connected:
pass
# while BLE connected
while ble.connected:
position = encoder.position
if position != last_position:
# if the encoder is turned to the right
if position > last_position:
kbd.send(Keycode.RIGHT_ARROW)
# if the encoder is turned to the left
if position < last_position:
kbd.send(Keycode.LEFT_ARROW)
# reset encoder position
last_position = position
print(f"Position: {position}")
for b in range(5):
if not buttons[b].value and button_states[b] is False:
button_states[b] = True
if b != 0:
kbd.press(*KBD_CODES[b])
print(*KBD_CODES[b])
else:
kbd.press(mod)
kbd.press(*KBD_CODES[b])
print(f"{button_names[b]} button pressed")
if buttons[b].value and button_states[b] is True:
button_states[b] = False
kbd.release_all()
print(f"{button_names[b]} button released")
# if BLE disconnects, begin advertising again
ble.start_advertising(advertisement)
Upload the Code and Libraries to the Feather ESP32-S3
After downloading the Project Bundle, plug your Feather ESP32-S3 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the Feather ESP32-S3's CIRCUITPY drive.
- lib folder
- code.py
Your Feather ESP32-S3 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.
How the CircuitPython Code Works
At the top of the code are user modifiable keycodes. The defaults match the documented keycodes in the Apple Watch documentation. The code uses the caps lock key as the modifier key.
# ios modifier
mod = Keycode.CAPS_LOCK
# keycodes
KBD_CODES = [
[Keycode.SPACE], # center
[mod], # up
[Keycode.LEFT_ARROW], # left
[Keycode.DOWN_ARROW], # down
[Keycode.RIGHT_ARROW], # right
]
i2c = board.STEMMA_I2C()
seesaw = seesaw.Seesaw(i2c, addr=0x49)
seesaw_product = (seesaw.get_version() >> 16) & 0xFFFF
print(f"Found product {seesaw_product}")
if seesaw_product != 5740:
print("Wrong firmware loaded? Expected 5740")
buttons = []
button_names = ["Select", "Up", "Left", "Down", "Right"]
button_states = []
for s in range(1, 6):
seesaw.pin_mode(s, seesaw.INPUT_PULLUP)
pin = digitalio.DigitalIO(seesaw, s)
pin_state = False
buttons.append(pin)
button_states.append(pin_state)
encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = 0
Erase Bonding
If you find you need to forget BLE pairing on the Feather, you can hold down the center button the encoder and reboot the board. If the board detects that that button is being held down before the loop, it will delete all BLE bonding and reset.
if not buttons[0].value and button_states[0] is False:
button_states[0] = True
try:
import _bleio
time.sleep(2)
_bleio.adapter.erase_bonding()
time.sleep(2)
print("Last BLE bonding deleted, restarting..")
time.sleep(2)
microcontroller.reset()
except Exception: # pylint: disable=broad-except
pass
# BLE HID setup
hid = HIDService()
# keyboard & mouse HID setup
kbd = Keyboard(hid.devices)
advertisement = ProvideServicesAdvertisement(hid)
advertisement.appearance = 961
scan_response = Advertisement()
scan_response.complete_name = "CircuitPython HID"
ble = adafruit_ble.BLERadio()
if not ble.connected:
print("advertising")
ble.start_advertising(advertisement, scan_response)
else:
print("already connected")
print(ble.connections)
time.sleep(2)
The Loop
In the loop, the encoder sends the right arrow command when turned to the right and sends the left arrow command when turned to the left. The up, down, left, right and center buttons send the keycodes listed in the KBD_CODES array.
while True:
# check for BLE connection
while not ble.connected:
pass
# while BLE connected
while ble.connected:
position = encoder.position
if position != last_position:
# if the encoder is turned to the right
if position > last_position:
kbd.send(Keycode.RIGHT_ARROW)
# if the encoder is turned to the left
if position < last_position:
kbd.send(Keycode.LEFT_ARROW)
# reset encoder position
last_position = position
print(f"Position: {position}")
for b in range(5):
if not buttons[b].value and button_states[b] is False:
button_states[b] = True
if b != 0:
kbd.press(*KBD_CODES[b])
print(*KBD_CODES[b])
else:
kbd.press(mod)
kbd.press(*KBD_CODES[b])
print(f"{button_names[b]} button pressed")
if buttons[b].value and button_states[b] is True:
button_states[b] = False
kbd.release_all()
print(f"{button_names[b]} button released")
# if BLE disconnects, begin advertising again
ble.start_advertising(advertisement)
Page last edited January 22, 2025
Text editor powered by tinymce.