Merge pull request #433 from DinosaurPotato534/main

Fire Hazard
This commit is contained in:
Alex Ren 2025-03-21 16:44:24 -04:00 committed by GitHub
commit bf56ceba84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 166224 additions and 0 deletions

View file

@ -0,0 +1,20 @@
I was originally inspired to make this because I typically need to mute/deafen on calls a lot.
The hardest challenge I faced was attempting to CAD the case. I am very unfamiliar with CAD modeling.
BOM:
- 4x Cherry MX Switches
- 1x EC11 Encoder
- 6x SK6812 MINI Leds
- 1x 0.96in 128x32px OLED (SSD1306)
- 4x M3x20mm screws
- 1x Bottom case (3D Printed)
- 1x Top plate (3D Printed)
- 1x Seeed XIAO RP2040
- 4x Blank DSA Keycaps
![Render](./render.png)
![Schematic](./fire-hazard-schematic.png)
![PCB](./fire-hazard-pcb.png)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,202 @@
0
SECTION
2
HEADER
9
$INSUNITS
70
4
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
LTYPE
0
LTYPE
72
65
70
64
2
CONTINUOUS
3
______
73
0
40
0
0
ENDTAB
0
TABLE
2
LAYER
0
ENDTAB
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
8
0
10
3.025
20
-59.675
11
16.025
21
-59.675
0
LINE
8
0
10
3.025
20
-73.675
11
16.025
21
-73.675
0
LINE
8
0
10
2.525
20
-60.175
11
2.525
21
-73.175
0
LINE
8
0
10
16.525
20
-60.175
11
16.525
21
-73.175
0
ARC
8
0
10
3.025
20
-60.175
40
0.5
50
90
51
180
0
ARC
8
0
10
16.025
20
-60.175
40
0.5
50
0
51
90
0
ARC
8
0
10
3.025
20
-73.175
40
0.5
50
180
51
270
0
ARC
8
0
10
16.025
20
-73.175
40
0.5
50
270
51
0
0
LINE
8
0
10
0
20
-76.2
11
19.05
21
-76.2
0
LINE
8
0
10
0
20
-57.15
11
19.05
21
-57.15
0
LINE
8
0
10
0
20
-76.2
11
0
21
-57.15
0
LINE
8
0
10
19.05
20
-76.2
11
19.05
21
-57.15
0
ENDSEC
0
EOF

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View file

@ -0,0 +1,37 @@
import time
import random
from displayio import Group
class DisplayManager:
def __init__(self, display, splash_group, media_info):
self.display = display
self.splash_group = splash_group
self.media_info = media_info
self.last_activity = time.monotonic()
self.last_shift = time.monotonic()
self.shift_interval = 30
self.timeout = 600
self.original_position = (splash_group.x, splash_group.y)
def update(self):
current_time = time.monotonic()
if current_time - self.last_shift > self.shift_interval:
self._shift_content()
self.last_shift = current_time
if current_time - self.last_activity > self.timeout:
if "No track playing" in self.media_info.get_media_info():
self.display.brightness = 0.1
else:
self.display.brightness = 1.0
else:
self.display.brightness = 1.0
def register_activity(self):
self.last_activity = time.monotonic()
self.display.brightness = 1.0
def _shift_content(self):
self.splash_group.x = self.original_position[0] + random.randint(-2, 2)
self.splash_group.y = self.original_position[1] + random.randint(-2, 2)

View file

@ -0,0 +1,23 @@
import board
from kmk.kmk_keyboard import KMKKeyboard
from kmk.scanners import DiodeOrientation
from kmk.scanners.keypad import KeysScanner
class MacroPad(KMKKeyboard):
def __init__(self):
# Key matrix pins
self.direct_pins = [
board.D5,
board.D6,
board.D7,
board.D8,
]
self.led1 = board.D9
self.led2 = board.D10
self.scanner = KeysScanner(
pins=self.direct_pins,
value_when_pressed=False,
pull=True,
)

View file

@ -0,0 +1,40 @@
import time
import board
import digitalio
from rainbowio import colorwheel
class LEDManager:
def __init__(self, rgb_pixels):
self.pixels = rgb_pixels
self.color_cycle = 0
self.last_update = time.monotonic()
self.update_interval = 0.05
self.mute_led = digitalio.DigitalInOut(board.D9)
self.mute_led.direction = digitalio.Direction.OUTPUT
self.deafen_led = digitalio.DigitalInOut(board.D10)
self.deafen_led.direction = digitalio.Direction.OUTPUT
self.is_muted = False
self.is_deafened = False
def update_chain(self):
current = time.monotonic()
if current - self.last_update > self.update_interval:
for i in range(4):
color_index = (self.color_cycle + (i * 64)) % 255
self.pixels[i] = colorwheel(color_index)
self.color_cycle = (self.color_cycle + 1) % 255
self.pixels.show()
self.last_update = current
def toggle_mute(self):
self.is_muted = not self.is_muted
self.mute_led.value = self.is_muted
def toggle_deafen(self):
self.is_deafened = not self.is_deafened
self.deafen_led.value = self.is_deafened
if self.is_deafened:
self.is_muted = True
self.mute_led.value = True

View file

@ -0,0 +1,107 @@
import board
import displayio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1306
from kmk.keys import KC
from kmk.modules.layers import Layers
from kmk.modules.encoder import EncoderHandler
from kmk.modules.pixelmap import PixelMap
from kmk.extensions.RGB import RGB
from kmk.extensions.media_keys import MediaKeys
from kb import MacroPad
import time
from media_info import MediaInfo
from display_manager import DisplayManager
from led_manager import LEDManager
keyboard = MacroPad()
layers = Layers()
encoder_handler = EncoderHandler()
keyboard.modules = [layers, encoder_handler]
encoder_handler.pins = ((board.A2, board.A3, board.A1),)
encoder_handler.map = [(
((KC.VOLD, KC.VOLU, KC.MUTE),),
((KC.LEFT, KC.RIGHT, KC.ENTER),),
)]
rgb = RGB(
pixel_pin=board.NEOPIXEL,
num_pixels=4,
num_strands=1,
)
keyboard.extensions.append(rgb)
led_manager = LEDManager(rgb.pixels)
displayio.release_displays()
i2c = board.I2C()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=64)
media = MediaInfo()
splash = displayio.Group()
album_group = displayio.Group(x=2, y=16)
album_bitmap = media.get_album_art()
album_palette = displayio.Palette(1)
album_palette[0] = 0xFFFFFF
album_tile = displayio.TileGrid(album_bitmap, pixel_shader=album_palette)
album_group.append(album_tile)
splash.append(album_group)
text_area = label.Label(
terminalio.FONT,
text="Waiting...",
color=0xFFFFFF,
x=36,
y=24,
scale=1
)
splash.append(text_area)
display.show(splash)
display_manager = DisplayManager(display, splash, media)
def update_display():
track_info = media.get_media_info()
text_area.text = track_info
album_bitmap = media.get_album_art()
album_tile.bitmap = album_bitmap
keyboard.keymap = [
[
KC.MT(KC.M, KC.LCTL(KC.LSFT)),
KC.MT(KC.D, KC.LCTL(KC.LSFT)),
KC.LGUI(KC.D),
KC.LCTL(KC.LSFT(KC.M)) + KC.LCTL(KC.LSFT(KC.D)) + KC.LGUI(KC.D),
],
[KC.LSFT, KC.LCTL, KC.LALT, KC.MO(1)]
]
def handle_key_event(key_event):
if key_event.pressed:
if key_event.key_number == 0:
led_manager.toggle_mute()
elif key_event.key_number == 1:
led_manager.toggle_deafen()
if __name__ == "__main__":
last_update = 0
while True:
keyboard.go()
current_time = time.monotonic()
key_event = keyboard.events.get()
if key_event:
display_manager.register_activity()
handle_key_event(key_event)
if current_time - last_update >= 5:
update_display()
last_update = current_time
display_manager.update()
led_manager.update_chain()

View file

@ -0,0 +1,29 @@
import usb_hid
from PIL import Image
import displayio
import adafruit_imageload
class MediaInfo:
def __init__(self):
self.current_track = "No track info"
self.default_image = self.create_default_image()
def create_default_image(self):
# Create a blank 32x32 bitmap
bitmap = displayio.Bitmap(32, 32, 1)
return bitmap
def get_media_info(self):
try:
consumer = usb_hid.devices[1]
if consumer:
return self.current_track
return "No track playing"
except:
return "Media Error"
def get_album_art(self):
try:
return self.default_image
except:
return self.default_image

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
(fp_lib_table
(version 7)
(lib (name "OPL")(type "KiCad")(uri "${KIPRJMOD}/OPL_Kicad_Library-master/Seeed Studio XIAO Series Library")(options "")(descr ""))
(lib (name "XIAO")(type "KiCad")(uri "${KIPRJMOD}/Seeeduino-xiao-rp2040-KiCAD-Library/XIAO_PCB.pretty")(options "")(descr ""))
(lib (name "OLED")(type "KiCad")(uri "${KIPRJMOD}/OLED")(options "")(descr ""))
(lib (name "models")(type "KiCad")(uri "${KIPRJMOD}/models.pretty")(options "")(descr ""))
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,83 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36,
39,
40
],
"visible_layers": "ffffc3c_ffffffff",
"zone_display_mode": 0
},
"git": {
"repo_password": "",
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "macropad.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

View file

@ -0,0 +1,607 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [
"silk_edge_clearance|143086368|156000000|5aaa8654-dd56-48cf-8c68-17cb85f3ba11|dbfd85e1-9a6c-4ffb-a5a7-44afef483712",
"silk_edge_clearance|143460000|156037000|5aaa8654-dd56-48cf-8c68-17cb85f3ba11|2e42044d-7a8a-493e-8cd8-d2eabd01b893",
"silk_edge_clearance|145941000|156037000|5aaa8654-dd56-48cf-8c68-17cb85f3ba11|41a887ed-f290-48f2-bbc0-cca011ef5596",
"silk_edge_clearance|154940000|156037000|5aaa8654-dd56-48cf-8c68-17cb85f3ba11|87edde6d-46ba-4bed-8fe8-1f9c54a37eb2",
"silk_edge_clearance|157823101|156000000|5aaa8654-dd56-48cf-8c68-17cb85f3ba11|878a02db-fb14-48c8-bad3-83813a72f576",
"silk_edge_clearance|159545000|155875000|5aaa8654-dd56-48cf-8c68-17cb85f3ba11|22456291-6f2f-497f-8126-2565ba2592ab",
"silk_over_copper|141555227|154161423|dbfd85e1-9a6c-4ffb-a5a7-44afef483712|00000000-0000-0000-0000-000000000000",
"silk_over_copper|141555575|154154311|a46485f0-0aa8-47c8-8def-771e8bb2a51f|00000000-0000-0000-0000-000000000000",
"silk_over_copper|141561547|136829200|21d4eba4-49f5-4ff8-9ae8-e42243312554|00000000-0000-0000-0000-000000000000",
"silk_over_copper|159328453|136829200|0da15fe0-815c-43fd-ba8d-518f10a003ed|00000000-0000-0000-0000-000000000000",
"silk_over_copper|159334682|154170800|878a02db-fb14-48c8-bad3-83813a72f576|00000000-0000-0000-0000-000000000000",
"silk_over_copper|159334788|154170340|9127932b-2f21-4a23-9337-640f091c7f83|00000000-0000-0000-0000-000000000000",
"silk_overlap|124204819|104205000|4af3a606-7148-4392-bfac-af85c0a78ced|7d16912f-acd1-4055-bf10-7e2ff9259a6c",
"silk_overlap|124500000|104205000|7c60083c-79c1-4eee-9d8c-d9b98a03f095|7d16912f-acd1-4055-bf10-7e2ff9259a6c",
"silk_overlap|124500000|107300000|6db5470d-8702-446c-b043-b1c8ec151368|93c5d661-1899-42e6-a00d-fa827dcaf6da",
"silk_overlap|124500000|107300000|7c60083c-79c1-4eee-9d8c-d9b98a03f095|93c5d661-1899-42e6-a00d-fa827dcaf6da",
"silk_overlap|149435000|104205000|e9ea621a-27b8-4500-aa9a-96b177b69573|f7b6131f-9fb1-45bc-84ea-cf7d1e8cd94f",
"silk_overlap|149500000|107050000|f7b6131f-9fb1-45bc-84ea-cf7d1e8cd94f|c1428697-5c1d-47ab-b4e6-b85e2418d274",
"silk_overlap|149500000|107050000|f9f74350-8870-441d-bb97-1793b68888b1|c1428697-5c1d-47ab-b4e6-b85e2418d274",
"silk_overlap|199752438|104205000|14417b95-cddb-4c79-bbf1-b67ce52a6133|8a26decd-652b-4c7a-b2e3-7dc76f975038",
"silk_overlap|199985000|104714285|fabd3209-7bc1-4495-946c-843f6a593a4d|8a26decd-652b-4c7a-b2e3-7dc76f975038",
"text_height|159835000|157000000|3703d34d-ddb9-4e25-b3e0-f7a964685881|00000000-0000-0000-0000-000000000000"
],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_near_hole": "error",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"simulation_model_issue": "ignore",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "macropad.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"name": "Grouped By Value",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"a2876b9e-1cdb-4ad0-a2b0-3cd14c585726",
"Root"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
(sym_lib_table
(version 7)
(lib (name "OPL")(type "KiCad")(uri "${KIPRJMOD}/OPL_Kicad_Library-master/Seeed Studio XIAO Series Library/Seeed_Studio_XIAO_Series.kicad_sym")(options "")(descr ""))
(lib (name "XIAO")(type "KiCad")(uri "${KIPRJMOD}/Seeeduino-xiao-rp2040-KiCAD-Library/MOUDLE-SEEEDUINO-XIAO.kicad_sym")(options "")(descr ""))
)

View file

@ -0,0 +1,37 @@
import time
import random
from displayio import Group
class DisplayManager:
def __init__(self, display, splash_group, media_info):
self.display = display
self.splash_group = splash_group
self.media_info = media_info
self.last_activity = time.monotonic()
self.last_shift = time.monotonic()
self.shift_interval = 30
self.timeout = 600
self.original_position = (splash_group.x, splash_group.y)
def update(self):
current_time = time.monotonic()
if current_time - self.last_shift > self.shift_interval:
self._shift_content()
self.last_shift = current_time
if current_time - self.last_activity > self.timeout:
if "No track playing" in self.media_info.get_media_info():
self.display.brightness = 0.1
else:
self.display.brightness = 1.0
else:
self.display.brightness = 1.0
def register_activity(self):
self.last_activity = time.monotonic()
self.display.brightness = 1.0
def _shift_content(self):
self.splash_group.x = self.original_position[0] + random.randint(-2, 2)
self.splash_group.y = self.original_position[1] + random.randint(-2, 2)

View file

@ -0,0 +1,23 @@
import board
from kmk.kmk_keyboard import KMKKeyboard
from kmk.scanners import DiodeOrientation
from kmk.scanners.keypad import KeysScanner
class MacroPad(KMKKeyboard):
def __init__(self):
# Key matrix pins
self.direct_pins = [
board.D5,
board.D6,
board.D7,
board.D8,
]
self.led1 = board.D9
self.led2 = board.D10
self.scanner = KeysScanner(
pins=self.direct_pins,
value_when_pressed=False,
pull=True,
)

View file

@ -0,0 +1,40 @@
import time
import board
import digitalio
from rainbowio import colorwheel
class LEDManager:
def __init__(self, rgb_pixels):
self.pixels = rgb_pixels
self.color_cycle = 0
self.last_update = time.monotonic()
self.update_interval = 0.05
self.mute_led = digitalio.DigitalInOut(board.D9)
self.mute_led.direction = digitalio.Direction.OUTPUT
self.deafen_led = digitalio.DigitalInOut(board.D10)
self.deafen_led.direction = digitalio.Direction.OUTPUT
self.is_muted = False
self.is_deafened = False
def update_chain(self):
current = time.monotonic()
if current - self.last_update > self.update_interval:
for i in range(4):
color_index = (self.color_cycle + (i * 64)) % 255
self.pixels[i] = colorwheel(color_index)
self.color_cycle = (self.color_cycle + 1) % 255
self.pixels.show()
self.last_update = current
def toggle_mute(self):
self.is_muted = not self.is_muted
self.mute_led.value = self.is_muted
def toggle_deafen(self):
self.is_deafened = not self.is_deafened
self.deafen_led.value = self.is_deafened
if self.is_deafened:
self.is_muted = True
self.mute_led.value = True

View file

@ -0,0 +1,107 @@
import board
import displayio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1306
from kmk.keys import KC
from kmk.modules.layers import Layers
from kmk.modules.encoder import EncoderHandler
from kmk.modules.pixelmap import PixelMap
from kmk.extensions.RGB import RGB
from kmk.extensions.media_keys import MediaKeys
from kb import MacroPad
import time
from media_info import MediaInfo
from display_manager import DisplayManager
from led_manager import LEDManager
keyboard = MacroPad()
layers = Layers()
encoder_handler = EncoderHandler()
keyboard.modules = [layers, encoder_handler]
encoder_handler.pins = ((board.A2, board.A3, board.A1),)
encoder_handler.map = [(
((KC.VOLD, KC.VOLU, KC.MUTE),),
((KC.LEFT, KC.RIGHT, KC.ENTER),),
)]
rgb = RGB(
pixel_pin=board.NEOPIXEL,
num_pixels=4,
num_strands=1,
)
keyboard.extensions.append(rgb)
led_manager = LEDManager(rgb.pixels)
displayio.release_displays()
i2c = board.I2C()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=64)
media = MediaInfo()
splash = displayio.Group()
album_group = displayio.Group(x=2, y=16)
album_bitmap = media.get_album_art()
album_palette = displayio.Palette(1)
album_palette[0] = 0xFFFFFF
album_tile = displayio.TileGrid(album_bitmap, pixel_shader=album_palette)
album_group.append(album_tile)
splash.append(album_group)
text_area = label.Label(
terminalio.FONT,
text="Waiting...",
color=0xFFFFFF,
x=36,
y=24,
scale=1
)
splash.append(text_area)
display.show(splash)
display_manager = DisplayManager(display, splash, media)
def update_display():
track_info = media.get_media_info()
text_area.text = track_info
album_bitmap = media.get_album_art()
album_tile.bitmap = album_bitmap
keyboard.keymap = [
[
KC.MT(KC.M, KC.LCTL(KC.LSFT)),
KC.MT(KC.D, KC.LCTL(KC.LSFT)),
KC.LGUI(KC.D),
KC.LCTL(KC.LSFT(KC.M)) + KC.LCTL(KC.LSFT(KC.D)) + KC.LGUI(KC.D),
],
[KC.LSFT, KC.LCTL, KC.LALT, KC.MO(1)]
]
def handle_key_event(key_event):
if key_event.pressed:
if key_event.key_number == 0:
led_manager.toggle_mute()
elif key_event.key_number == 1:
led_manager.toggle_deafen()
if __name__ == "__main__":
last_update = 0
while True:
keyboard.go()
current_time = time.monotonic()
key_event = keyboard.events.get()
if key_event:
display_manager.register_activity()
handle_key_event(key_event)
if current_time - last_update >= 5:
update_display()
last_update = current_time
display_manager.update()
led_manager.update_chain()

View file

@ -0,0 +1,29 @@
import usb_hid
from PIL import Image
import displayio
import adafruit_imageload
class MediaInfo:
def __init__(self):
self.current_track = "No track info"
self.default_image = self.create_default_image()
def create_default_image(self):
# Create a blank 32x32 bitmap
bitmap = displayio.Bitmap(32, 32, 1)
return bitmap
def get_media_info(self):
try:
consumer = usb_hid.devices[1]
if consumer:
return self.current_track
return "No track playing"
except:
return "Media Error"
def get_album_art(self):
try:
return self.default_image
except:
return self.default_image

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB