# ラズパイピコキーボード (ダイナミックスキャン版)
# SPDX-FileCopyrightText: 2021 Akira Nagai, Kenta Ida
# SPDX-License-Identifier: MIT 
# このソースは以下のAdafruitのサンプルソースを元に作成しています。
#  https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/master/Pico_RP2040_Mech_Keyboard/pico_mech_keyboard.py
# オリジナルのライセンス定義は以下の通りです。
# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# RaspberryPi Pico RP2040 Mechanical Keyboard

import time
import board

from digitalio import DigitalInOut, Direction, Pull
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode

led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = True

kbd = Keyboard(usb_hid.devices)
cc = ConsumerControl(usb_hid.devices)

# ダイナミックスキャン
# 行のピン
row_pins = [
    board.GP6,
    board.GP4,
    board.GP9,
]
# 列のピン
column_pins = [
    board.GP5,
    board.GP7,
    board.GP8,
    board.GP10,
]

# スイッチの状態
class Switch(object):
    def __init__(self):
        self.__counter = 0        # チャタリング除去用カウンタ
        self.__counter_max = 10   # チャタリング除去用カウンタの最大値
        self.__changed = False    # 今回キー状態が変化したか
        self.__is_pressed = False # キーが押されているか？
    
    def update(self, is_key_down: bool):
        """
        スイッチの状態を更新する
        is_key_down: スイッチが押されているならTrueを渡す
        """
        self.__changed = False  # キー状態変化フラグを下げておく
        if is_key_down: # スイッチ押されてる
            # チャタリング除去カウンタを増やす
            if self.__counter < self.__counter_max:
                self.__counter += 1
                if self.__counter == self.__counter_max:
                    # 最大値になったので、押されたことにする
                    self.__changed = True
                    self.__is_pressed = True
        else: # スイッチ押されていない
            if self.__counter > 0:
                self.__counter -= 1
                if self.__counter == 0:
                    # 0になったので、押されていないことにする
                    self.__changed = True
                    self.__is_pressed = False
    
    def has_changed(self) -> bool: return self.__changed
    def is_pressed(self) -> bool: return self.__is_pressed
    def is_pushed(self) -> bool: return self.__changed and self.__is_pressed
    def is_released(self) -> bool: return self.__changed and not self.__is_pressed

# スイッチ状態配列
switches = [Switch() for i in range(len(row_pins)*len(column_pins))]

# 各ピンに対応するIOを初期化
row_pin_ios = [DigitalInOut(pin) for pin in row_pins]   # 行のピンを初期化
for io in row_pin_ios:
    io.direction = Direction.OUTPUT # 行ピンはOUTPUT
    io.value = True                 # Hにして未選択にする
column_pin_ios = [DigitalInOut(pin) for pin in column_pins]
for io in column_pin_ios:
    io.direction = Direction.INPUT # 列ピンはINPUT
    io.pull = Pull.UP              # プルアップしておく

# 対応するキーが普通のキーかメディアキーか
MEDIA = 1
KEY = 2

class KeyMap(object):
    def __init__(self, key_type:int, key_codes: list[int]):
        self.__key_type = key_type
        self.__key_codes = key_codes
    def key_type(self) -> int: return self.__key_type
    def key_codes(self) -> list[int]: return self.__key_codes
# 左上から順に。
keymap = {
    (0):  KeyMap(KEY, [Keycode.G]),
    (1):  KeyMap(KEY, [Keycode.P]),
    (2):  KeyMap(KEY, [Keycode.F]),
    (3):  KeyMap(KEY, [Keycode.A]),
    (4):  KeyMap(KEY, [Keycode.E]),
    (5):  KeyMap(KEY, [Keycode.W]),
    (6):  KeyMap(KEY, [Keycode.Q]),
    (7):  KeyMap(KEY, [Keycode.R]),
    (8):  KeyMap(KEY, [Keycode.C]),
    (9):  KeyMap(KEY, [Keycode.X]),
    (10): KeyMap(KEY, [Keycode.Z]),
    (11): KeyMap(KEY, [Keycode.V]),
}

while True:
    # スイッチの状態を更新
    for row_index in range(len(row_pin_ios)):
        row_pin_io = row_pin_ios[row_index]
        row_pin_io.value = False    # 行ピンにLを出力して行を選択
        for column_index in range(len(column_pin_ios)):
            column_pin_io = column_pin_ios[column_index]
            # スイッチの番号 = 左上から列方向に0,1,2,3,...
            switch_index = row_index*len(column_pin_ios) + column_index
            # スイッチの状態を更新
            switch = switches[switch_index]
            # スイッチが押されていたらcolumn_pin_io.valueはFalse
            # なので、notで反転する
            switch.update(not column_pin_io.value) 
        row_pin_io.value = True     # 行の選択を解除

    # スイッチの状態からキーの状態を更新
    for switch_index in range(len(switches)):
        switch = switches[switch_index]
        key = keymap.get(switch_index)
        if key is None: continue  # このスイッチにはキーがマップされてない
        if switch.is_pushed():  # 今回押された
            led.value = not led.value # LEDの出力を反転しておく
            try:
                if key.key_type() == KEY:
                    kbd.press(*key.key_codes())
                else:
                    cc.send(key.key_codes())
            except ValueError:  # 同時6個以上押されているとエラーになるので無視する
                pass
        elif switch.is_released(): # 今回はなされた
            try:
                if key.key_type() == KEY:
                    kbd.release(*key.key_codes())
                else:
                    cc.release(key.key_codes())
            except ValueError:  # 同時6個以上押されているとエラーになるので無視する
                pass
