import chess
from Hardware import HardwareInterface, SafeDecorator
from typing import List, Any, Callable
import board
import busio
import digitalio
import adafruit_tca9548a
import adafruit_character_lcd.character_lcd_i2c as character_lcd
from adafruit_ht16k33 import matrix
from adafruit_mcp230xx.mcp23017 import MCP23017
[docs]class HardwareImplementation(HardwareInterface.HardwareInterface):
""" Interface to Hardware chessboard"""
_board_reed = [[None] * 8 for _ in range(8)]
_perform_safe: Callable[[Callable[[Any], Any]], Callable[[Any], Any]]
def __init__(self):
""" Set up hardware connection
Sets up connections to the following bus devices over the i2c bus:
- Sets up a TCA9548a to handle the high traffic load on the bus
- Sets up a 8x8 reed_matrix mapped trough 4 MCP23017
- Sets up a 9x9 led_matrix with the interface LedWrapper which uses a HT16k33 and 1 MCP23017
"""
i2c = busio.I2C(board.SCL, board.SDA)
tca = adafruit_tca9548a.TCA9548A(i2c, address=0x71)
self._perform_safe = SafeDecorator.perform_safe_factory(lambda: setattr(tca.i2c, '_locked', False))
self._lcd = self._perform_safe(lambda: character_lcd.Character_LCD_I2C(tca[6], 16, 2, address=0x27))
self._perform_safe(setattr)(self._lcd, "backlight", True)
mcp = [self._perform_safe(lambda i: MCP23017(tca[i], address=0x20))(i) for i in range(4)]
# Initialize reed matrix
for file in range(8):
for rank in range(8):
pin_id = file if rank % 2 == 0 else 15 - file
self._board_reed[file][rank] = self._perform_safe(mcp[rank // 2].get_pin)(pin_id)
self._perform_safe(setattr)(self._board_reed[file][rank], "direction", digitalio.Direction.INPUT)
self._perform_safe(setattr)(self._board_reed[file][rank], "pull", digitalio.Pull.UP)
led_input_mcp = MCP23017(tca[5], address=0x20)
# Map output buttons
self._buttons = []
for pinId in range(8, 13):
self._buttons.append(self._perform_safe(led_input_mcp.get_pin)(pinId))
self._perform_safe(setattr)(self._buttons[-1], "direction", digitalio.Direction.INPUT)
self._perform_safe(setattr)(self._buttons[-1], "pull", digitalio.Pull.UP)
# Initialize LED matrix
self._led_wrapper = LedWrapper(matrix.MatrixBackpack16x8(tca[4]), led_input_mcp, self._perform_safe)
self._led_wrapper.clear()
def __del__(self):
""" Turn of LED before shutting down """
self._led_wrapper.clear()
[docs] def mark_squares(self, squares: List[List[bool]]) -> None:
""" Marks squares on the chessboard where squares is an 8x8 matrix implemented as a 2s list
Note that squares are mapped as squares[file][rank] ie. a1 = squares[0][0], a2 = squares[0][1], \
b1 = squares[1][0] and h8 is squares[7][7]
:param squares: 8x8 matrix of squares to mark on the chessboard where square [file][rank] \
is marked if square[file][rank] == TRUE
"""
self._led_wrapper.set_squares(squares)
[docs] def get_occupancy(self) -> List[List[bool]]:
""" Returns all occupied squares as 8x8 matrix implemented as a 2d list
Note that squares are mapped as squares[file][rank] so if square a2 is occupied then get_occupancy[0][1] is TRUE
:return: 8x8 matrix with all occupied squares on the chessboard
"""
result = []
for file in self._board_reed:
result_file = []
for square in file:
result_file.append(not self._perform_safe(getattr)(square, 'value')) # result_file += [not square.value]
result.append(result_file)
return result
[docs] def game_end_offers(self) -> HardwareInterface.Offer:
""" Returns continue, draw or return offers
If no button pressed returns continue, otherwise wait for confirmation
:return: Always returns continue
"""
if self._perform_safe(getattr)(self._buttons[0], 'value'):
while True:
if self._perform_safe(getattr)(self._buttons[4], 'value'):
return HardwareInterface.Offer.RESIGN
if self._perform_safe(getattr)(self._buttons[3], 'value'):
return HardwareInterface.Offer.CONTINUE
if self._perform_safe(getattr)(self._buttons[1], 'value'):
while True:
if self._perform_safe(getattr)(self._buttons[4], 'value'):
return HardwareInterface.Offer.DRAW
if self._perform_safe(getattr)(self._buttons[3], 'value'):
return HardwareInterface.Offer.DRAW
return HardwareInterface.Offer.CONTINUE
[docs] def display(self, txt: str):
""" Displays text string on hardware
:param txt: text to display on hardware
"""
self._perform_safe(set_attr)(self._lcd, "message", txt)
print(txt)
[docs]class LedWrapper:
"""" Wraps LED hardware """
def __init__(self, ht16k33: matrix.MatrixBackpack16x8, mcp: MCP23017, perform_safe):
""" Initializes the led matrix using the ht16k33 and MCP23017
:param ht16k33: ht16k33 instance controlling a 8x9 led matrix
:param mcp: MCP23017 instance controlling the left most row of LED
"""
self._ht16k33 = ht16k33
self._perform_safe = perform_safe
self._column = [self._perform_safe(lambda i: mcp.get_pin(i))(i) for i in range(0, 9)]
for pin in self._column:
self._perform_safe(setattr)(pin, 'direction', digitalio.Direction.OUTPUT) # pin.direction = OUTPUT
self._perform_safe(setattr)(pin, 'value', False) # pin.value = False
self.clear()
[docs] def clear(self):
""" Clears all square on the chessboard """
self._perform_safe(self._ht16k33.fill)(0) # squares.fill(0)
for led in self._column:
self._perform_safe(setattr)(led, 'value', False) # led.value = False
[docs] def set_squares(self, squares: List[List[bool]]):
""" Marks squares on the chessboard
Improvement on calling clear() then marking all squires since this prevents a flickering of the LED
:param squares: 8x8 matrix with the squares to be marked
"""
def set_led(rank: int, file: int, val: bool):
""" Turn LED at position rank, file on iff val
:param rank: rank of LED to turn
:param file: file of LED to turn
:param val: turns on LED iff val is True
"""
self._ht16k33[rank, file] = val
for file in range(9):
for rank in range(9):
# Check of LED should be turned on
value = False
if file > 0 and rank > 0:
value = squares[file - 1][rank - 1]
if not value and file < 8 and rank > 0:
value = squares[file][rank - 1]
if not value and file > 0 and rank < 8:
value = squares[file - 1][rank]
if not value and file < 8 and rank < 8:
value = squares[file][rank]
# Turn LED on or off
if file == 0:
self._perform_safe(setattr)(self._column[8 - rank], 'value',
value) # _column[8 - rank].value = True
else:
self._perform_safe(set_led)(8 - rank, file - 1, value) # _ht16k33[7 - rank][file - 1]=True