Skip to content

Commit 9dcc83b

Browse files
authored
Merge pull request #882 from shorepine/claude/encoder-fail-silently
amyboard: rotary encoder helpers fail silently on missing hardware
2 parents 76105d8 + e2791c3 commit 9dcc83b

File tree

1 file changed

+74
-39
lines changed

1 file changed

+74
-39
lines changed

tulip/shared/amyboard-py/amyboard.py

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -716,57 +716,92 @@ def _web_encoder_press(state):
716716
# read_buttons() # returns a boolean list of the state of the 4 buttons.
717717
# Code based on abstracting
718718
# https://github.com/adafruit/Adafruit_CircuitPython_seesaw/blob/main/adafruit_seesaw/seesaw.py.
719+
#
720+
# Missing-hardware behaviour: if the Seesaw encoder breakout isn't on the
721+
# I2C bus (user built an AMYboard with no rotary encoder attached), the
722+
# helpers below catch the resulting OSError on the first try, cache the
723+
# offending device address in _seesaw_missing, and silently return safe
724+
# defaults from then on. This lets sketches like preset_selector.py still
725+
# import and run cleanly — they just don't see encoder motion or button
726+
# presses instead of crashing the board at sketch import time.
727+
728+
_seesaw_missing = set() # addresses we've already probed and confirmed absent
719729

720730
def read_encoder(encoder=0, seesaw_dev=0x49, delay=0.008):
721-
"""Read the cumulated value of encoder 0..3."""
731+
"""Read the cumulated value of encoder 0..3.
732+
733+
Returns 0 if the seesaw device isn't present on the I2C bus (see
734+
module-level note on missing-hardware behaviour)."""
722735
if web():
723736
return _web_encoder_pos
724-
i2c = get_i2c()
725-
result = bytearray(4)
726-
ENCODER_BASE = 0x11
727-
ENCODER_POSITION = 0x30
728-
i2c.writeto(seesaw_dev, bytes([ENCODER_BASE, ENCODER_POSITION + encoder]))
729-
time.sleep(delay)
730-
i2c.readfrom_into(seesaw_dev, result)
731-
return struct.unpack(">i", result)[0]
737+
if seesaw_dev in _seesaw_missing:
738+
return 0
739+
try:
740+
i2c = get_i2c()
741+
result = bytearray(4)
742+
ENCODER_BASE = 0x11
743+
ENCODER_POSITION = 0x30
744+
i2c.writeto(seesaw_dev, bytes([ENCODER_BASE, ENCODER_POSITION + encoder]))
745+
time.sleep(delay)
746+
i2c.readfrom_into(seesaw_dev, result)
747+
return struct.unpack(">i", result)[0]
748+
except OSError:
749+
_seesaw_missing.add(seesaw_dev)
750+
return 0
732751

733752
def init_buttons(pins=(12, 14, 17, 9), seesaw_dev=0x49):
734-
"""Setup the seesaw quad encoder button pins to input_pullup."""
753+
"""Setup the seesaw quad encoder button pins to input_pullup.
754+
755+
Silently no-ops if the seesaw device isn't on the I2C bus."""
735756
if web():
736757
return
737-
mask = 0
738-
for p in pins:
739-
mask |= (1 << p)
740-
mask_bytes = struct.pack('>I', mask)
741-
i2c = get_i2c()
742-
GPIO_BASE = 0x01
743-
GPIO_DIRCLR_BULK = 0x03
744-
GPIO_PULLENSET = 0x0B
745-
GPIO_BULK_SET = 0x05
746-
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_DIRCLR_BULK]) + mask_bytes)
747-
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_PULLENSET]) + mask_bytes)
748-
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_BULK_SET]) + mask_bytes)
758+
if seesaw_dev in _seesaw_missing:
759+
return
760+
try:
761+
mask = 0
762+
for p in pins:
763+
mask |= (1 << p)
764+
mask_bytes = struct.pack('>I', mask)
765+
i2c = get_i2c()
766+
GPIO_BASE = 0x01
767+
GPIO_DIRCLR_BULK = 0x03
768+
GPIO_PULLENSET = 0x0B
769+
GPIO_BULK_SET = 0x05
770+
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_DIRCLR_BULK]) + mask_bytes)
771+
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_PULLENSET]) + mask_bytes)
772+
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_BULK_SET]) + mask_bytes)
773+
except OSError:
774+
_seesaw_missing.add(seesaw_dev)
749775

750776
def read_buttons(pins=(12, 14, 17, 9), seesaw_dev=0x49, delay=0.008):
751-
"""Read the 4 seesaw encoder push buttons."""
777+
"""Read the 4 seesaw encoder push buttons.
778+
779+
Returns [False, False, ...] (one entry per pin) if the seesaw device
780+
isn't on the I2C bus."""
752781
if web():
753782
return [_web_encoder_button] * len(pins)
754-
i2c = get_i2c()
755-
GPIO_BASE = 0x01
756-
GPIO_BULK = 0x04
757-
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_BULK]))
758-
time.sleep(delay)
759-
buffer = bytearray(4)
760-
i2c.readfrom_into(seesaw_dev, buffer)
761-
mask = struct.unpack('>I', buffer)[0]
762-
result = []
763-
for p in pins:
764-
state = True
765-
if (mask & (1 << p)):
766-
# bit set means button not pressed.
767-
state = False
768-
result.append(state)
769-
return result
783+
if seesaw_dev in _seesaw_missing:
784+
return [False] * len(pins)
785+
try:
786+
i2c = get_i2c()
787+
GPIO_BASE = 0x01
788+
GPIO_BULK = 0x04
789+
i2c.writeto(seesaw_dev, bytes([GPIO_BASE, GPIO_BULK]))
790+
time.sleep(delay)
791+
buffer = bytearray(4)
792+
i2c.readfrom_into(seesaw_dev, buffer)
793+
mask = struct.unpack('>I', buffer)[0]
794+
result = []
795+
for p in pins:
796+
state = True
797+
if (mask & (1 << p)):
798+
# bit set means button not pressed.
799+
state = False
800+
result.append(state)
801+
return result
802+
except OSError:
803+
_seesaw_missing.add(seesaw_dev)
804+
return [False] * len(pins)
770805

771806
def monitor_encoders():
772807
"""Show status of encoders on display."""

0 commit comments

Comments
 (0)