143 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import digitalio
 | 
						|
 | 
						|
from keypad import Event as KeyEvent
 | 
						|
 | 
						|
from kmk.scanners import DiodeOrientation, Scanner
 | 
						|
 | 
						|
 | 
						|
class MatrixScanner(Scanner):
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        cols,
 | 
						|
        rows,
 | 
						|
        diode_orientation=DiodeOrientation.COLUMNS,
 | 
						|
        rollover_cols_every_rows=None,
 | 
						|
        offset=0,
 | 
						|
    ):
 | 
						|
        self.len_cols = len(cols)
 | 
						|
        self.len_rows = len(rows)
 | 
						|
        self.offset = offset
 | 
						|
 | 
						|
        # A pin cannot be both a row and column, detect this by combining the
 | 
						|
        # two tuples into a set and validating that the length did not drop
 | 
						|
        #
 | 
						|
        # repr() hackery is because CircuitPython Pin objects are not hashable
 | 
						|
        unique_pins = {repr(c) for c in cols} | {repr(r) for r in rows}
 | 
						|
        assert (
 | 
						|
            len(unique_pins) == self.len_cols + self.len_rows
 | 
						|
        ), 'Cannot use a pin as both a column and row'
 | 
						|
        del unique_pins
 | 
						|
 | 
						|
        self.diode_orientation = diode_orientation
 | 
						|
 | 
						|
        # __class__.__name__ is used instead of isinstance as the MCP230xx lib
 | 
						|
        # does not use the digitalio.DigitalInOut, but rather a self defined one:
 | 
						|
        # https://github.com/adafruit/Adafruit_CircuitPython_MCP230xx/blob/3f04abbd65ba5fa938fcb04b99e92ae48a8c9406/adafruit_mcp230xx/digital_inout.py#L33
 | 
						|
 | 
						|
        if self.diode_orientation == DiodeOrientation.COLUMNS:
 | 
						|
            self.outputs = [
 | 
						|
                x
 | 
						|
                if x.__class__.__name__ == 'DigitalInOut'
 | 
						|
                else digitalio.DigitalInOut(x)
 | 
						|
                for x in cols
 | 
						|
            ]
 | 
						|
            self.inputs = [
 | 
						|
                x
 | 
						|
                if x.__class__.__name__ == 'DigitalInOut'
 | 
						|
                else digitalio.DigitalInOut(x)
 | 
						|
                for x in rows
 | 
						|
            ]
 | 
						|
            self.translate_coords = True
 | 
						|
        elif self.diode_orientation == DiodeOrientation.ROWS:
 | 
						|
            self.outputs = [
 | 
						|
                x
 | 
						|
                if x.__class__.__name__ == 'DigitalInOut'
 | 
						|
                else digitalio.DigitalInOut(x)
 | 
						|
                for x in rows
 | 
						|
            ]
 | 
						|
            self.inputs = [
 | 
						|
                x
 | 
						|
                if x.__class__.__name__ == 'DigitalInOut'
 | 
						|
                else digitalio.DigitalInOut(x)
 | 
						|
                for x in cols
 | 
						|
            ]
 | 
						|
            self.translate_coords = False
 | 
						|
        else:
 | 
						|
            raise ValueError(f'Invalid DiodeOrientation: {self.diode_orienttaion}')
 | 
						|
 | 
						|
        for pin in self.outputs:
 | 
						|
            pin.switch_to_output()
 | 
						|
 | 
						|
        for pin in self.inputs:
 | 
						|
            pin.switch_to_input(pull=digitalio.Pull.DOWN)
 | 
						|
 | 
						|
        self.rollover_cols_every_rows = rollover_cols_every_rows
 | 
						|
        if self.rollover_cols_every_rows is None:
 | 
						|
            self.rollover_cols_every_rows = self.len_rows
 | 
						|
 | 
						|
        self._key_count = self.len_cols * self.len_rows
 | 
						|
        self.state = bytearray(self.key_count)
 | 
						|
 | 
						|
    @property
 | 
						|
    def key_count(self):
 | 
						|
        return self._key_count
 | 
						|
 | 
						|
    def scan_for_changes(self):
 | 
						|
        '''
 | 
						|
        Poll the matrix for changes and return either None (if nothing updated)
 | 
						|
        or a bytearray (reused in later runs so copy this if you need the raw
 | 
						|
        array itself for some crazy reason) consisting of (row, col, pressed)
 | 
						|
        which are (int, int, bool)
 | 
						|
        '''
 | 
						|
        ba_idx = 0
 | 
						|
        any_changed = False
 | 
						|
 | 
						|
        for oidx, opin in enumerate(self.outputs):
 | 
						|
            opin.value = True
 | 
						|
 | 
						|
            for iidx, ipin in enumerate(self.inputs):
 | 
						|
                # cast to int to avoid
 | 
						|
                #
 | 
						|
                # >>> xyz = bytearray(3)
 | 
						|
                # >>> xyz[2] = True
 | 
						|
                # Traceback (most recent call last):
 | 
						|
                #   File "<stdin>", line 1, in <module>
 | 
						|
                # OverflowError: value would overflow a 1 byte buffer
 | 
						|
                #
 | 
						|
                # I haven't dived too far into what causes this, but it's
 | 
						|
                # almost certainly because bool types in Python aren't just
 | 
						|
                # aliases to int values, but are proper pseudo-types
 | 
						|
                new_val = int(ipin.value)
 | 
						|
                old_val = self.state[ba_idx]
 | 
						|
 | 
						|
                if old_val != new_val:
 | 
						|
                    if self.translate_coords:
 | 
						|
                        new_oidx = oidx + self.len_cols * (
 | 
						|
                            iidx // self.rollover_cols_every_rows
 | 
						|
                        )
 | 
						|
                        new_iidx = iidx - self.rollover_cols_every_rows * (
 | 
						|
                            iidx // self.rollover_cols_every_rows
 | 
						|
                        )
 | 
						|
 | 
						|
                        row = new_iidx
 | 
						|
                        col = new_oidx
 | 
						|
                    else:
 | 
						|
                        row = oidx
 | 
						|
                        col = iidx
 | 
						|
 | 
						|
                    pressed = new_val
 | 
						|
                    self.state[ba_idx] = new_val
 | 
						|
 | 
						|
                    any_changed = True
 | 
						|
                    break
 | 
						|
 | 
						|
                ba_idx += 1
 | 
						|
 | 
						|
            opin.value = False
 | 
						|
            if any_changed:
 | 
						|
                break
 | 
						|
 | 
						|
        if any_changed:
 | 
						|
            key_number = self.len_cols * row + col + self.offset
 | 
						|
            return KeyEvent(key_number, pressed)
 |