Frank Adams
Published © Apache-2.0

USB Laptop Keyboard Controller Solder Party RP2350 Stamp XL

Convert a laptop keyboard into a USB keyboard with the Raspberry Pi microcontroller on the Solder Party RP2350 Stamp XL.

IntermediateFull instructions provided16 hours352
USB Laptop Keyboard Controller Solder Party RP2350 Stamp XL

Things used in this project

Hardware components

Solder Party RP2350 Stamp XL
Sold at lectronz.com and at pimoroni.com
×1
SparkFun USB C Breakout - Horizontal
Can also be wired to USB Type A, Mini-B, or microB breakout connectors
×1
FPC Breakout Board
Sold at Aliexpress, Amazon, EBay, and others
×1
2mm Pitch Single Row Header Pins
×1
2.54mm Pitch Dual Row Header Pins
×1
Carrier Board for Stamp XL, FPC, & USB
Send the zipped Gerber PCB file to JLCPCB or PCBWay or send the KiCad PCB file to OSHPark or Eurocircuits (among others)
×1

Software apps and online services

Thonny Python IDE
Adafruit Circuit Python
KMK

Story

Read more

Schematics

Carrier Board - KiCad PCB File

Send this file to board fabrication companies like OSHPark.com and Eurocircuits.com that accept KiCad files

Carrier Board - Zipped Gerber PCB File

Send this zipped Gerber PCB File to board fabrication companies like JLCPCB.com and PCBWay.com (among others).

Code

Matrix_Decoder_RP2350B.py

Python
This Circuit Python program is used to decode the key matrix of a laptop keyboard.
Load this routine in the Stamp XL drive with the name "code.py"
#   Copyright 2025 Frank Adams
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#       http://www.apache.org/licenses/LICENSE-2.0
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
# This program is used to decode the key matrix of a laptop keyboard. Use an FPC connector to connect all the 
# laptop keyboard pins to the GPIO pins of a RP2350 Stamp XL which uses a RP2350B chip and has 48 GPIO.
# The program cycles thru all the possible pin 
# combinations looking for a connection when a key is pressed. Open an editor with a text file that lists all 
# the keyboard keys. The program will send over USB, the two GPIO pin numbers that are connected when a key is pressed.
# Once all keys have been tested, the editor will contain a complete listing of the keyboard connections. 
# 
import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode

kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)

# List the Pi Pico GP I/O pins in the array that are connected to the keyboard FPC cable.
# If the program finds that 2 pins are always connected (probably grounds or LEDs), remove them from the I_O list.

I_O = [board.GP0, board.GP1, board.GP2, board.GP3, board.GP4, board.GP5, board.GP6, board.GP7, board.GP8,  
board.GP9, board.GP10, board.GP11, board.GP12, board.GP13, board.GP14, board.GP15, board.GP16, board.GP17, 
board.GP18, board.GP19, board.GP20, board.GP21, board.GP22, board.GP23, board.GP24, board.GP25, board.GP26,
board.GP27, board.GP28, board.GP29, board.GP30, board.GP31, board.GP32, board.GP33]


# This function converts the board.GP number from the array into a text number and sends it over USB.
# It will work with up to 48 GPIO pins (for the RP2350B)
def Send_GP(GP):
    if GP == board.GP0:
        layout.write('0')
    elif GP == board.GP1:
        layout.write('1')
    elif GP == board.GP2:
        layout.write('2')
    elif GP == board.GP3:
        layout.write('3')
    elif GP == board.GP4:
        layout.write('4')
    elif GP == board.GP5:
        layout.write('5')
    elif GP == board.GP6:
        layout.write('6')
    elif GP == board.GP7:
        layout.write('7')
    elif GP == board.GP8:
        layout.write('8')
    elif GP == board.GP9:
        layout.write('9')
    elif GP == board.GP10:
        layout.write('10')
    elif GP == board.GP11:
        layout.write('11')
    elif GP == board.GP12:
        layout.write('12')
    elif GP == board.GP13:
        layout.write('13')
    elif GP == board.GP14:
        layout.write('14')
    elif GP == board.GP15:
        layout.write('15')
    elif GP == board.GP16:
        layout.write('16')
    elif GP == board.GP17:
        layout.write('17')
    elif GP == board.GP18:
        layout.write('18')
    elif GP == board.GP19:
        layout.write('19')
    elif GP == board.GP20:
        layout.write('20')
    elif GP == board.GP21:
        layout.write('21')
    elif GP == board.GP22:
        layout.write('22')
    elif GP == board.GP23:
        layout.write('23')
    elif GP == board.GP24:
        layout.write('24')
    elif GP == board.GP25:
        layout.write('25')
    elif GP == board.GP26:
        layout.write('26')
    elif GP == board.GP27:
        layout.write('27')
    elif GP == board.GP28:
        layout.write('28')
    elif GP == board.GP29:
        layout.write('29')
    elif GP == board.GP30:
        layout.write('30')
    elif GP == board.GP31:
        layout.write('31')
    elif GP == board.GP32:
        layout.write('32')
    elif GP == board.GP33:
        layout.write('33')
    elif GP == board.GP34:
        layout.write('34')
    elif GP == board.GP35:
        layout.write('35')
    elif GP == board.GP36:
        layout.write('36')
    elif GP == board.GP37:
        layout.write('37')
    elif GP == board.GP38:
        layout.write('38')
    elif GP == board.GP39:
        layout.write('39')
    elif GP == board.GP40:
        layout.write('40')
    elif GP == board.GP41:
        layout.write('41')
    elif GP == board.GP42:
        layout.write('42')
    elif GP == board.GP43:
        layout.write('43')
    elif GP == board.GP44:
        layout.write('44')
    elif GP == board.GP45:
        layout.write('45')
    elif GP == board.GP46:
        layout.write('46')
    elif GP == board.GP47:
        layout.write('47')
    else: 
        layout.write('not defined')    

while True:
    # outer loop drives a gpio pin low
    for i in range(0, len(I_O)-1): # drive each gpio pin low up to the second from the last
        row = digitalio.DigitalInOut(I_O[i])
        row.direction = digitalio.Direction.OUTPUT
        row.value = False
        # inner loop reads all gpio pins that are greater than the outer loop index number
        for j in range(i+1, len(I_O)): # read up to the last gpio pin in the array
            column = digitalio.DigitalInOut(I_O[j])
            column.direction = digitalio.Direction.INPUT
            column.pull = digitalio.Pull.UP
            if column.value == False: # key is pushed if low
                Send_GP(I_O[j])  # display inner loop gpio pin number 
                kbd.send(Keycode.TAB, Keycode.TAB)  # move over 2 tabs for next number
                Send_GP(I_O[i])  # display outer loop gpio pin number
                kbd.send(Keycode.DOWN_ARROW)  # move down to next line
                while column.value == False:  # loop until key is released to proceed
                    pass # Do nothing
            column.deinit()	 # release the column pin as an input w/ pullup
        # 
        row.deinit() # release the row pin as an output
     
    time.sleep(0.001) # small delay before repeating main loop

code_LenovoE550.py

Python
KMK code for a Lenovo E550 laptop keyboard.
Load this routine in the Stamp XL drive with the name "code.py"
#   Copyright 2025 Frank Adams
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#       http://www.apache.org/licenses/LICENSE-2.0
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
#   The following GPIO connections and key matrix are for a Lenovo E550 laptop keyboard.
#   Use this KMK code as your starting point if using a different keyboard.
#   Change the name to code.py when copying this file to the Stamp XL.
#   See step 16 of my Instructable for more information.
#
import board

from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC
from kmk.scanners import DiodeOrientation
from kmk.extensions.media_keys import MediaKeys
from kmk.modules.layers import Layers

keyboard = KMKKeyboard()

keyboard.col_pins = (board.GP0, board.GP1, board.GP2, board.GP4, board.GP5, board.GP7, board.GP8, board.GP11, board.GP29)
keyboard.row_pins = (board.GP3, board.GP6, board.GP9, board.GP10, board.GP12, board.GP13, board.GP14, board.GP15, 
board.GP16, board.GP17, board.GP18, board.GP19, board.GP20, board.GP21, board.GP22, board.GP23, board.GP28, board.GP30, board.GP31)
keyboard.diode_orientation = DiodeOrientation.COL2ROW # Most laptop keyboards have no diodes so ROW2COL also works

keyboard.modules.append(Layers())
keyboard.extensions.append(MediaKeys())

FN = KC.TG(1)

# Add any other keys that your keyboard has using the KMK keycodes from https://docs.qmk.fm/keycodes_basic
# and the media keys from https://github.com/KMKfw/kmk_firmware/blob/main/docs/en/media_keys.md
# All key names are preceded with KC. except the FN key. KC.NO is used when there is no key at that matrix location.
# This keyboard has a keypad and those keys start with KC.P followed by the name or number from the keycodes_basic list.

keyboard.keymap = [
    [#layer 0: Base Layer
    KC.LSHIFT,		KC.NO,		KC.RSHIFT,	KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,
    KC.TAB,			KC.NO,		KC.Z,		KC.A,		KC.N1,		KC.Q,		KC.GRAVE,	KC.ESC,		KC.NO,
    KC.Y,			KC.N,		KC.M,		KC.J,		KC.N7,		KC.U,		KC.N6,		KC.H,		KC.NO,
    KC.F3,			KC.NO,		KC.C,		KC.D,		KC.N3,		KC.E,		KC.F2,		KC.F4,		KC.NO,
    KC.CAPS,		KC.NO,		KC.X,		KC.S,		KC.N2,		KC.W,		KC.F1,		KC.NO,		KC.NO,
    KC.T,			KC.B,		KC.V,		KC.F,		KC.N4,		KC.R,		KC.N5,		KC.G,		KC.NO,
    KC.F7,			KC.NO,		KC.DOT,		KC.L,		KC.N9,		KC.O,		KC.F8,		KC.NO,		KC.NO,
    KC.LBRC,		KC.SLASH,	KC.NO,		KC.SCOLON,	KC.N0,		KC.P,		KC.MINUS,	KC.QUOTE,	KC.NO,
    KC.RBRC,		KC.NO,		KC.COMMA,	KC.K,		KC.N8,		KC.I,		KC.EQUAL,	KC.F6,		KC.NO,
    KC.NO,			KC.NO,		KC.RCTRL,	KC.NO,		KC.NO,		KC.NO,		KC.LCTRL,	KC.NO,		KC.NO,
    KC.NO,			KC.RALT,	KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.LALT,	KC.NO,
    KC.LGUI,		KC.RIGHT,	KC.NO,		KC.NO,		KC.F12,		KC.NO,		KC.NO,		KC.NO,		KC.NO,
    KC.NO,			KC.LEFT,	KC.PDOT,	KC.NO,		KC.END,		KC.NO,		KC.NO,		KC.UP,		KC.NO,
    KC.NO,			KC.DOWN,	KC.NO,		KC.NO,		KC.F11,		KC.NO,		KC.HOME,	KC.NO,		KC.NO,
    KC.BSPACE,		KC.SPACE,	KC.ENTER,	KC.BSLASH,	KC.F10,		KC.NO,		KC.F9,		KC.F5,		KC.NO,
    KC.NO,			KC.PGDOWN,	KC.PGUP,	KC.PSCREEN,	KC.INSERT,	KC.NO,		KC.DELETE,	KC.NO,		KC.NO,
    KC.NO,			KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		FN,
    KC.PSLS,		KC.PPLS,	KC.P9,		KC.P7,		KC.P8,		KC.PAST,	KC.PMNS,	KC.NUM_LOCK, KC.NO,
    KC.P5,			KC.P0,		KC.PENT,	KC.P2,		KC.P3,		KC.P6,		KC.P1,		KC.P4,		KC.NO,
    ],

# The Fn media layer is a copy of the base layer except where a media key exists (like mute at Fn-F1)  
	[#layer 1: Fn Media Layer
    KC.LSHIFT,		KC.NO,		KC.RSHIFT,	KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,
    KC.TAB,			KC.NO,		KC.Z,		KC.A,		KC.N1,		KC.Q,		KC.GRAVE,	KC.ESC,		KC.NO,
    KC.Y,			KC.N,		KC.M,		KC.J,		KC.N7,		KC.U,		KC.N6,		KC.H,		KC.NO,
    KC.VOLU,		KC.NO,		KC.C,		KC.D,		KC.N3,		KC.E,		KC.VOLD,	KC.F4,		KC.NO,
    KC.CAPS,		KC.NO,		KC.X,		KC.S,		KC.N2,		KC.W,		KC.MUTE,	KC.NO,		KC.NO,
    KC.T,			KC.B,		KC.V,		KC.F,		KC.N4,		KC.R,		KC.N5,		KC.G,		KC.NO,
    KC.F7,			KC.NO,		KC.DOT,		KC.L,		KC.N9,		KC.O,		KC.F8,		KC.NO,		KC.NO,
    KC.LBRC,		KC.SLASH,	KC.NO,		KC.SCOLON,	KC.N0,		KC.P,		KC.MINUS,	KC.QUOTE,	KC.NO,
    KC.RBRC,		KC.NO,		KC.COMMA,	KC.K,		KC.N8,		KC.I,		KC.EQUAL,	KC.BRIU,	KC.NO,
    KC.NO,			KC.NO,		KC.RCTRL,	KC.NO,		KC.NO,		KC.NO,		KC.LCTRL,	KC.NO,		KC.NO,
    KC.NO,			KC.RALT,	KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.LALT,	KC.NO,
    KC.LGUI,		KC.RIGHT,	KC.NO,		KC.NO,		KC.F12,		KC.NO,		KC.NO,		KC.NO,		KC.NO,
    KC.NO,			KC.LEFT,	KC.PDOT,	KC.NO,		KC.END,		KC.NO,		KC.NO,		KC.UP,		KC.NO,
    KC.NO,			KC.DOWN,	KC.NO,		KC.NO,		KC.F11,		KC.NO,		KC.HOME,	KC.NO,		KC.NO,
    KC.BSPACE,		KC.SPACE,	KC.ENTER,	KC.BSLASH,	KC.F10,		KC.NO,		KC.F9,		KC.BRID,	KC.NO,
    KC.NO,			KC.PGDOWN,	KC.PGUP,	KC.PSCREEN,	KC.INSERT,	KC.NO,		KC.DELETE,	KC.NO,		KC.NO,
    KC.NO,			KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		KC.NO,		FN,
    KC.PSLS,		KC.PPLS,	KC.P9,		KC.P7,		KC.P8,		KC.PAST,	KC.PMNS,	KC.NUM_LOCK, KC.NO,
    KC.P5,			KC.P0,		KC.PENT,	KC.P2,		KC.P3,		KC.P6,		KC.P1,		KC.P4,		KC.NO,
	],
	
]

if __name__ == '__main__':
    keyboard.go()

KMK Key List for E550

Plain text
This gives the GPIO connections to the 2350 for every key on the Lenovo E550 keyboard
KC.LCTRL					        17	8
KC.RCTRL					        17	2
KC.LSHIFT					        3	  0
KC.RSHIFT					        3	  2
KC.LALT						        18	11
KC.RALT						        18	1
KC.LGUI						        19	0
KC.RGUI						        
FN							          29	28
KC.A						          6	  4
KC.B						          13	1
KC.C						          10	2
KC.D						          10	4
KC.E						          10	7
KC.F						          13	4
KC.G						          13	11
KC.H						          11	9
KC.I						          16	7
KC.J						          9	  4
KC.K						          16	4
KC.L						          14	4
KC.M						          9	  2
KC.N						          9	  1
KC.O						          14	7
KC.P						          15	7
KC.Q					          	7	  6
KC.R						          13	7
KC.S						          12	4
KC.T						          13	0
KC.U						          9	  7
KC.V						          13	2
KC.W						          12	7
KC.X						          12	2
KC.Y						          9	  0
KC.Z						          6	  2
KC.GRAVE				        	8	  6
KC.N1					          	6 	5
KC.N2						          12	5
KC.N3						          10	5
KC.N4						          13	5
KC.N5						          13	8
KC.N6					          	9	  8
KC.N7					            9	  5
KC.N8					          	16	5
KC.N9						          14	5
KC.N0						          15	5
KC.MINUS				        	15	8
KC.EQUAL				        	16	8
KC.BSPACE					        22	0
KC.ESC					        	11	6
KC.F1						          12	8
KC.F2					          	10	8
KC.F3					          	10	0
KC.F4					          	11	10
KC.F5					          	22	11
KC.F6						          16	11
KC.F7						          14	0
KC.F8					          	14	8
KC.F9						          22	8
KC.F10					        	22	5
KC.F11					        	21	5
KC.F12						        19	5
KC.INSERT					        23	5
KC.DELETE					        23	8
KC.RIGHT				        	19	1
KC.LEFT						        20	1
KC.UP						          20	11
KC.DOWN					        	21	1
KC.SLASH				        	15	1
KC.DOT 					        	14	2
KC.COMMA				        	16	2
KC.SCOLON				        	15	4
KC.QUOTE					        15	11
KC.ENTER				        	22	2
KC.LBRC						        15	0
KC.RBRC						        16	0
KC.BSLASH				        	22	4
KC.CAPS						        12	0
KC.TAB						        6	  0
KC.SPACE					        22	1
KC.HOME						        21	8
KC.END					        	20	5
KC.PGUP						        23	2
KC.PGDOWN					        23	1
KC.PSCREEN					      23	4
KC.SCROLLLOCK				
KC.NUM_LOCK					      30	11
KC.PAUSE					        
KC.PSLS						        30	0
KC.PAST						        30	7
KC.PMNS						        30	8
KC.PPLS						        30	1
KC.PENT						        31	2
KC.PDOT						        20	2
KC.P0					          	31	1
KC.P1						          31	8
KC.P2						          31	4   
KC.P3					          	31	5
KC.P4					          	31	11
KC.P5						          31	0
KC.P6						          31	7
KC.P7						          30	4
KC.P8						          30	5
KC.P9						          30	2
KC.AUDIO_MUTE	Func		    12	8
KC.AUDIO_VOL_UP	Func		  10	0
KC.AUDIO_VOL_DOWN	Func	  10	8
KC.BRIGHTNESS_UP	Func	  16	11
KC.BRIGHTNESS_DOWN	Func	22	11
KC.MEDIA_NEXT_TRACK	Func	
KC.MEDIA_PREV_TRACK	Func	
KC.MEDIA_STOP	Func		
KC.MEDIA_PLAY_PAUSE	Func	
KC.MEDIA_EJECT	Func		
KC.MEDIA_FAST_FORWARD Func
KC.MEDIA_REWIND	Func		

Blank KMK Key List

Plain text
Lists all the keyboard keys using KMK keycodes
KC.LCTRL					          
KC.RCTRL					          
KC.LSHIFT					          
KC.RSHIFT					          
KC.LALT						          
KC.RALT						          
KC.LGUI						          
KC.RGUI						          
FN							            
KC.A						            
KC.B						            
KC.C						            
KC.D						            
KC.E						            
KC.F						            
KC.G						            
KC.H						            
KC.I						            
KC.J						            
KC.K						            
KC.L						            
KC.M						            
KC.N						            
KC.O						            
KC.P						            
KC.Q						            
KC.R						            
KC.S						            
KC.T						            
KC.U						            
KC.V						            
KC.W						            
KC.X						            
KC.Y						            
KC.Z						            
KC.GRAVE					          
KC.N1						            
KC.N2						            
KC.N3						            
KC.N4						            
KC.N5						            
KC.N6						            
KC.N7						            
KC.N8						            
KC.N9						            
KC.N0						            
KC.MINUS					          
KC.EQUAL					          
KC.BSPACE					          
KC.ESC						          
KC.F1						            
KC.F2						            
KC.F3						            
KC.F4						            
KC.F5						            
KC.F6						            
KC.F7						            
KC.F8						            
KC.F9						            
KC.F10						          
KC.F11						          
KC.F12						          
KC.INSERT					          
KC.DELETE					          
KC.RIGHT					          
KC.LEFT						          
KC.UP						            
KC.DOWN						          
KC.SLASH					          
KC.DOT 						          
KC.COMMA					          
KC.SCOLON					          
KC.QUOTE					          
KC.ENTER					          
KC.LBRC						          
KC.RBRC						          
KC.BSLASH					          
KC.CAPS						          
KC.TAB						          
KC.SPACE					          
KC.HOME						          
KC.END						          
KC.PGUP						          
KC.PGDOWN					          
KC.PSCREEN					        
KC.SCROLLLOCK				        
KC.NUM_LOCK					        
KC.PAUSE					          
KC.PSLS						          
KC.PAST						          
KC.PMNS						          
KC.PPLS						          
KC.PENT						          
KC.PDOT						          
KC.P0						            
KC.P1						            
KC.P2						            
KC.P3						            
KC.P4						            
KC.P5						            
KC.P6						            
KC.P7						            
KC.P8						            
KC.P9						            
KC.AUDIO_MUTE	Func		      
KC.AUDIO_VOL_UP	Func		    
KC.AUDIO_VOL_DOWN	Func	    
KC.BRIGHTNESS_UP	Func	    
KC.BRIGHTNESS_DOWN	Func	  
KC.MEDIA_NEXT_TRACK	Func	  
KC.MEDIA_PREV_TRACK	Func	  
KC.MEDIA_STOP	Func		      
KC.MEDIA_PLAY_PAUSE	Func	  
KC.MEDIA_EJECT	Func		    
KC.MEDIA_FAST_FORWARD Func  
KC.MEDIA_REWIND	Func		    

Matrix Generator

Python
This program reads the Key list text file and determines the column and row GPIO's and builds a key matrix table.
from enum import Enum
import os

"""
    Copyright 2019 Marcel Hillesheim
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
"""

"""
author: Marcel Hillesheim with modifications by Frank Adams to work with KMK

input:
Put the key list text file in the same folder as this Python program (so it's easy to find).
output:
Matrix column and row I/O's
and
Main key matrix

Description:
https://www.hackster.io/frank-adams/usb-laptop-keyboard-controller-solder-party-rp2350-stamp-xl-4fa642

Little tool to help creating a key matrix for a usb-keyboards using KMK.

Lenovo keyboards use a single row pin and a single column pin for the FN key which causes this program
to not place the FN key in the matrix. Manually place FN in the matrix (without the KC. in front)
It's not pretty but it's functional :d
"""

# 2350
con_pin_2350 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
              28, 29, 30, 31, 32, 33]
kmk_devices = [('2350', con_pin_2350)]

separator = "-----------------------------------------------------\n"


class KeyType(Enum):
    KEY = 1
    Func = 2
    MODIFIER = 3
    # for all-one matrix
    ONE = 4


class Key:
    is_assigned = False

    def __init__(self, label, modifier_value, pin1, pin2):
        self.label = label
        self.pin1 = pin1
        self.pin2 = pin2
        self.type = KeyType.KEY
        # generate key type from label
        for key_type in KeyType:
            if label.startswith(key_type.name):
                self.type = key_type
        # check if user set the key type
        for key_type in KeyType:
            if modifier_value == key_type.name:
                self.type = key_type

def generate_matrix(path, con_pin):
    file = open(path)
    content = file.readlines()

    keys = []

    # result list for input pins
    input_pins = []
    # result list for output pins
    output_pins = []

    content = [x.strip() for x in content]

    # read in file and store values line by line into key objects
    for line in content:
        line = line.split()
        if len(line) >= 3:
            keys.append(Key(line[0], line[1], int(line[-1]), int(line[-2])))
    # initialize matrix creator by finding common pins of control key
    temporary = [keys[0].pin1, keys[1].pin1, keys[0].pin2, keys[1].pin2]
    # determine if there is a common pin
    for pin in temporary:
        if temporary.count(pin) == 2:
            output_pins.append(pin)

    # if no common pin found ask user for initial input
    if not output_pins:
        output_pins.append(int(input("No common pin found for the Control keys. Please enter an output (row) pin for a Control key: ")))

    print("initial output pin: {}".format(output_pins[0]))
    # iterate until no new output pins or input pins are found
    found = True
    while found:
        found = False
        for key in keys:
            # if not already assigned
            if not key.is_assigned:
                # set partner pin to the opposite array e.g. pin1 output pin -> pin2 input pin
                if key.pin1 in output_pins:
                    input_pins.append(key.pin2)
                    key.is_assigned = True
                    found = True
                elif key.pin1 in input_pins:
                    output_pins.append(key.pin2)
                    key.is_assigned = True
                    found = True
                elif key.pin2 in output_pins:
                    input_pins.append(key.pin1)
                    key.is_assigned = True
                    found = True
                elif key.pin2 in input_pins:
                    output_pins.append(key.pin1)
                    key.is_assigned = True
                    found = True

    input_pins = list(set(input_pins))
    output_pins = list(set(output_pins))
    input_pins.sort()
    output_pins.sort()
    # Output results
    print(separator + "Results:\n" + separator)
    # print GPIO pins
    print("GPIO PINS:")
    print("\n" + str(len(input_pins)) + " input column GPIO's:")
    print(input_pins)
    print("\n" + str(len(output_pins)) + " output row GPIO's:")
    print(output_pins)
    print(separator)

    # create the different matrices for every key type
    for key_type in KeyType:
        matrix = separator + key_type.name + "\n" + separator + "{\n"
        # rows
        for output_pin in output_pins:
            key_row = ""
            # columns
            for input_pin in input_pins:
                # default key value
                key_label = "KC.NO"
                if key_type == KeyType.ONE:
                    key_label = "1"
                # search for key that matches with row and column pin
                for key in keys:
                    if (((key.pin1 == input_pin) | (key.pin2 == input_pin)) & (
                            (key.pin1 == output_pin) | (key.pin2 == output_pin))):
                        if key.type == key_type:
                            key_label = key.label
                key_row = key_row + key_label + ","
            matrix = matrix + "{" + key_row[:-1] + "},\n"
        matrix = matrix[:-1] + "\n}"
        print(matrix)
    print(separator + "Finished")
    
if __name__ == "__main__":
    default_file_names = ["Keyboard_pin_list_KMK.txt"]
    file_suggestions = []

    for file_name in os.listdir():
        if file_name.endswith('.txt'):
            if file_name in default_file_names:
                file_suggestions.insert(0, file_name)
            else:
                file_suggestions.append(file_name)
    max_length = max(len(file_name) for file_name in file_suggestions)
    print('{:^6s} {:^{max_length}s}'.format('index', 'file name', max_length=max_length))
    for i, suggestion in enumerate(file_suggestions):
        print('{:^6s} {:^{max_length}s}'.format(str(i + 1), suggestion, max_length=max_length))
    while True:
        user_input = input(
            separator + 'Enter the index number of the *.txt file you want.\nOR: enter your own filepath:\n')
        if os.path.exists(user_input):
            path = user_input
            break
        elif user_input.isdigit():
            path = file_suggestions[int(user_input) - 1]
            break
        else:
            print('Couldn\'t handle input. Please try again.')

    print(separator)
    print('{:^6s} {:^12s}'.format('index', 'device'))
    for i, device in enumerate(kmk_devices):
        print('{:^6s} {:^12s}'.format(str(i + 1), device[0]))

    while True:
        user_input = input(
            separator + 'Please enter the index number of your device:\n')
        # TODO custom pin layout
        if user_input.isdigit():
            con_pin = kmk_devices[int(user_input) - 1][1]
            break
        else:
            print('Couldn\'t handle input. Please try again.')
    generate_matrix(path, con_pin)

USB_Laptop_Keyboard_Controller

Repository for all project files

Credits

Frank Adams
5 projects • 14 followers
I am a retired Boeing engineer that enjoys experimenting with Pi, Arduino, and Teensy projects.

Comments