#
#        CircuitPython HID Macro Keyboary
#
#      Copyright 2021 github.com/Skyfighter64


import board
import digitalio
import usb_hid

import time
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

import neopixel


# define the available key types
class KeyType():
    # no action for this key
    NONE = 0
    # standard letters, numbers, and keyboard keys
    KEY = 1
    # media keys like volume or play/pause
    MEDIA = 2
    # switch makro layers
    LAYER = 3
    # change lighting
    LIGHTING = 4
# define available layer switch actions
class Layer():
    # switch to the layer while the key is pressed
    PEEK = 0
    # switch layers when key was pressed
    SWITCH = 1

# define available lighting modification actions
class Lighting():
    # turn the LEDs on/off by setting their brightness to  0.5 / 0
    TOGGLE = 0
    # increase the brightness of all leds in small steps
    INCREASE_BRIGHTNESS = 1
    # decrease the brightness of all leds
    DECREASE_BRIGHTNESS = 2


# keymap formats:
# (KeyType.NONE,)
# or
# (KeyType.KEY, (Keycode.A, Keycode.B, ...))
# or
# (KeyType.MEDIA, ConsumerControlCode.VOLUME_DECREMENT)
# or
# (KeyType.LAYER, (Layer.SWITCH, 1))
# or
# (KeyType.LIGHTING, Lighting.INCREASE_BRIGHTNESS))



# note: for one-element Keycodes, an extra comma is needed
#   Example: (KeyType.KEY, (Keycode.A,))
#                                    ^
#       See python docs of touple for more details
# For a list of all available Keycodes and ConsumerControlCodes, see:
# https://circuitpython.readthedocs.io/projects/hid/en/latest/api.html#adafruit-hid-keycode-keycode
# and
# https://circuitpython.readthedocs.io/projects/hid/en/latest/api.html#adafruit-hid-consumer-control-code-consumercontrolcode
#

# keymap containing all key layers with all key presses
# every i'th entry corresponds to the i'th button in the "pins" list
keymap = [
    # layer 0 (default):
    [
        (KeyType.MEDIA, ConsumerControlCode.VOLUME_INCREMENT), #Volume UP
        (KeyType.MEDIA, ConsumerControlCode.VOLUME_DECREMENT), # Volume Down
        (KeyType.MEDIA, ConsumerControlCode.MUTE),             # Volume Mute
        (KeyType.KEY, (Keycode.ALT, Keycode.F10)), # shadowplay
        (KeyType.MEDIA, ConsumerControlCode.SCAN_NEXT_TRACK),
        (KeyType.MEDIA, ConsumerControlCode.PLAY_PAUSE),
        (KeyType.MEDIA, ConsumerControlCode.SCAN_PREVIOUS_TRACK),
        (KeyType.KEY, (Keycode.WINDOWS, Keycode.PRINT_SCREEN)),
        (KeyType.KEY, (Keycode.F20,)),      # Extra F Keys for program Hotkeys
        (KeyType.KEY, (Keycode.F21,)),
        (KeyType.LAYER, (Layer.SWITCH, 1)), # switch to makro layer 1
        (KeyType.KEY, (Keycode.F23,))
    ],

    # layer 1:
    [
        (KeyType.LIGHTING, Lighting.INCREASE_BRIGHTNESS), # adjust lighting brightness
        (KeyType.LIGHTING, Lighting.DECREASE_BRIGHTNESS),
        (KeyType.LIGHTING, Lighting.TOGGLE),
        (KeyType.KEY, (Keycode.F16,)),  # more extra F keys for custom program hotkeys
        (KeyType.KEY, (Keycode.F17,)),
        (KeyType.KEY, (Keycode.F18,)),
        (KeyType.KEY, (Keycode.F19,)),
        (KeyType.KEY, (Keycode.CONTROL, Keycode.F13)),
        (KeyType.KEY, (Keycode.CONTROL,Keycode.F14,)),
        (KeyType.KEY, (Keycode.CONTROL,Keycode.F15,)),
        (KeyType.LAYER, (Layer.SWITCH, 0)), # switch back to makro layer 0 when pressed]
        (KeyType.KEY, (Keycode.CONTROL,Keycode.F16,))
    ]
]


# initialize the internal LED
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT

keyboard = Keyboard(usb_hid.devices)
consumer_control = ConsumerControl(usb_hid.devices)

# the gpio pins at which each button is connected
pins = [board.GP10, board.GP11, board.GP12]
buttons = []

# initialize neopixel leds
leds = neopixel.NeoPixel(board.GP28, 1, brightness=0.5, auto_write=False)

# variable for the currently active layer
activeLayer = 0
previousLayer = 0
# variable containing the previous layer if peeking or 'None' if not peeking

# initialize buttons as input with an internal Pull UP resistor
for pin in pins:
    button = digitalio.DigitalInOut(pin)
    button.switch_to_input(pull=digitalio.Pull.UP)
    buttons.append(button)

# initialize the last states with False
last_states = [False] * len(buttons)


# function sending the pressed key event for the given key touple defined in the keymap
# @param key: a touple of the structure (KeyType.KEY, (Keycode[, Keycode, ...]))
#                       or       (KeyType.MEDIA, ConsumerControlCode)
#                       or       (KeyType.LAYER, (Layer.SWITCH | Layer.PEEK, layer index : int))
#                       or       (KeyType.LIGHTING, [Lighting Action])
#                       or       (KeyType.NONE, [...])
def PressKey(key):
    # reference global variables
    global activeLayer
    global previousLayer
    global leds

    # check for standard key actions
    if (key[0] == KeyType.KEY):
        try:
            keyboard.press(*key[1])
        except ValueError:
            # more than 6 keys were pressed
            # this is forbidden by the library
            pass
    # check for media key actions
    elif (key[0] == KeyType.MEDIA):
        consumer_control.press(key[1])
    # check for layer actions
    elif (key[0] == KeyType.LAYER):
        # switch to the layer
        previousLayer = activeLayer
        activeLayer = key[1][1]
    # check for lighting actions
    elif (key[0] == KeyType.LIGHTING):
        # check for lighting off action
        if(key[1] == Lighting.TOGGLE):
            if (leds.brightness == 0):
                leds.brightness = 0.5
            else:
                leds.brightness = 0
        elif(key[1] == Lighting.INCREASE_BRIGHTNESS):
            # increase led brightness if possible
            newBrightness = leds.brightness + 0.1
            if (newBrightness > 1.0):
                newBrightness = 1.0
            leds.brightness = newBrightness
        elif(key[1] == Lighting.DECREASE_BRIGHTNESS):
            # decrease led brightness if possible
            newBrightness = leds.brightness - 0.1
            if (newBrightness < 0):
                newBrightness = 0.0
            leds.brightness = newBrightness
    print("Pressed:  " + str(key[1]))


# function sending the released key event for the given key touple
# @param key: a touple of the structure (KeyType.KEY, (Keycode[, Keycode, ...]))
#                       or       (KeyType.MEDIA, ConsumerControlCode)
#                       or       (KeyType.LAYER, (Layer.SWITCH | Layer.PEEK, layer index : int))
def ReleaseKey(key):
    # reference global variables
    global activeLayer
    global previousLayer

    if (key[0] == KeyType.KEY):
        keyboard.release(*key[1])
    elif (key[0] == KeyType.MEDIA):
        consumer_control.release()
    elif (key[0] == KeyType.LAYER):
        # check if layer mode is "PEEK"
        if(key[1][0] == Layer.PEEK):
            # reset to old layer
            activeLayer = previousLayer
    print("Released: " + str(key[1]))


# function checking and executing all key presses
def CheckKeys():
    # reference global variables
    global activeLayer
    # loop through all buttons
    for i in range(len(buttons)):
        # check if the index exceeds the keymap layer's size
        if (i >= len(keymap[activeLayer])):
            # layer size exceeded, stop checking
            break
        # check if a button was pressed
        if (not buttons[i].value and last_states[i]) :
            PressKey(keymap[activeLayer][i])
        # check if button was released
        elif ( buttons[i].value and not last_states[i]):
            ReleaseKey(keymap[activeLayer][i])
        # update the last state
        last_states[i] = buttons[i].value

# function defining the lighting of each layer
def UpdateLEDs():
    if(activeLayer == 0):
        leds[0] = (255, 20, 32)
        leds[1] = (125, 255, 32)
        leds[2] = (50, 32, 255)
        leds[3] = (255, 10, 255)

    else:
        leds[0] = (200, 20, 180)
        leds[1] = (40, 110, 195)
        leds[2] = (20, 120, 200)
        leds[3] = (220, 20, 160)
    leds.show()

# endless main loop
while True:
    # set the internal led on when default layer is active
    led.value = (activeLayer == 0)
    # check for key presses
    CheckKeys()
    # set the neopixels
    UpdateLEDs()
    # sleep for debouncing
    time.sleep(0.005)
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT