diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md new file mode 100644 index 00000000000..b216f358a90 --- /dev/null +++ b/keyboards/tssouthpaw/README.md @@ -0,0 +1,31 @@ +# TS Southpaw + +A southpaw numpad keyboard with RGB matrix and work timer functionality. + +* Keyboard Maintainer: [TS Design Works LLC](https://github.com/tsdesignworks) +* Hardware Supported: TS Southpaw Rev 1.6, RP2040 MCU +* Hardware Availability: [GitHub](https://github.com/tsdesignworks/tssouthpaw) + +Make example for this keyboard (after setting up your build environment): + + make tssouthpaw:default + +Flashing example for this keyboard: + + make tssouthpaw:default:flash + +See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs). + +## Bootloader + +Enter the bootloader in 2 ways: + +* **Physical reset button**: Briefly press the button on the back of the PCB +* **Bootmagic reset**: Hold down the ESC key and plug in the keyboard + +## Features + +* RGB Matrix with 104 LEDs +* Rotary encoder for volume control +* Work timer with visual notifications +* Southpaw numpad layout (numpad on the left side) \ No newline at end of file diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h new file mode 100644 index 00000000000..d1fbde9e0b5 --- /dev/null +++ b/keyboards/tssouthpaw/config.h @@ -0,0 +1,50 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + + #pragma once + + // LED indices for indicator LEDs + #define CAPS_LOCK_LED 62 // LED index for Caps Lock indicator + #define NUM_LOCK_LED 2 // LED index for Num Lock indicator + #define MIC_MUTE_LED 3 // LED index for KC_MICMUTE + + // Work Timer LED indices + #define WORK_TIMER_LED_START 4 // F1 key LED + #define WORK_TIMER_LED_END 15 // F12 key LED + + // Work Timer EEPROM addresses + // Using built-in wear leveling for RP2040 + #define EEPROM_WORK_TIMER_ACTIVE 32 // 1 byte - active state + #define EEPROM_WORK_TIMER_START 33 // 4 bytes - start time + #define EEPROM_WORK_TIMER_ELAPSED 37 // 4 bytes - elapsed work time + // 4 bytes - total break time (37+4) + // 1 byte - timer type (37+8) + // 1 byte - break state (37+9) + // 4 bytes - break start time (37+10) + // Total: 15 bytes + + // Work Timer Notification Configuration + #define WORK_TIMER_NOTIFICATION_BRIGHTNESS 250 // Brightness for work timer notifications + + // Minimum brightness for timer notifications to ensure visibility + #define RGB_MATRIX_MINIMUM_BRIGHTNESS 64 // Ensures timer notifications remain visible + + // Dynamic macro configuration + #define DYNAMIC_MACRO_SIZE 128 // More efficient macro size + + // RP2040 Settings + #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET // Enable double-tap reset + #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 500U // Timeout window in ms diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json new file mode 100644 index 00000000000..e6c2b73135a --- /dev/null +++ b/keyboards/tssouthpaw/keyboard.json @@ -0,0 +1,310 @@ +{ + "manufacturer": "TS Design Works LLC", + "keyboard_name": "TS-Southpaw-Rev-1.6", + "maintainer": "ts_design_works_llc", + "bootloader": "rp2040", + "processor": "RP2040", + "diode_direction": "COL2ROW", + "url": "https://github.com/tsdesignworks/tssouthpaw", + "build": { + "lto": true + }, + "matrix_pins": { + "cols": ["GP26", "GP25", "GP24", "GP23", "GP22", "GP16", "GP17", "GP15", "GP14", "GP13", "GP12", "GP11", "GP10", "GP9", "GP8", "GP7", "GP6", "GP5", "GP4", "GP3", "GP2"], + "rows": ["GP29", "GP28", "GP21", "GP20", "GP19", "GP18"] + }, + "usb": { + "device_version": "0.0.2", + "pid": "0x6163", + "vid": "0x7856", + "suspend_wakeup_delay": 200 + }, + "ws2812": { + "driver": "vendor", + "pin": "GP27" + }, + "encoder": { + "rotary": [ + {"pin_a": "GP1", "pin_b": "GP0", "resolution": 4} + ] + }, + "features": { + "bootmagic": true, + "caps_word": true, + "extrakey": true, + "nkro": true, + "rgb_matrix": true, + "dynamic_macro": true, + + }, + "debounce": 5, + "rgb_matrix": { + "driver": "ws2812", + "sleep": true, + "led_process_limit": 8, + "default_mode": "SOLID_COLOR", + "animations": { + "solid_color": true, + "breathing": true, + "gradient_up_down": true, + "gradient_left_right": true, + "cycle_out_in": true, + "cycle_out_in_dual": true, + "cycle_pinwheel": true, + "cycle_spiral": true, + "rainbow_moving_chevron": true, + "rainbow_beacon": true, + "rainbow": true, + "rainbow_swirl": true, + "rainbow_pinwheels": true, + "solid_reactive_simple": true, + "solid_reactive": true, + "splash": true, + "multisplash": true, + "solid_splash": true, + "raindrops": true, + "jellybean_raindrops": true, + "typing_heatmap": true, + "digital_rain": true, + "pixel_rain": true + }, + "layout": [ + {"matrix": [0, 0], "x": 0, "y": 0, "flags": 4}, + {"matrix": [0, 1], "x": 11, "y": 0, "flags": 4}, + {"matrix": [0, 2], "x": 22, "y": 0, "flags": 4}, + {"matrix": [0, 3], "x": 34, "y": 0, "flags": 4}, + {"matrix": [0, 8], "x": 45, "y": 0, "flags": 4}, + {"matrix": [0, 9], "x": 56, "y": 0, "flags": 4}, + {"matrix": [0, 10], "x": 68, "y": 0, "flags": 4}, + {"matrix": [0, 11], "x": 78, "y": 0, "flags": 4}, + {"matrix": [0, 12], "x": 89, "y": 0, "flags": 4}, + {"matrix": [0, 13], "x": 100, "y": 0, "flags": 4}, + {"matrix": [0, 14], "x": 112, "y": 0, "flags": 4}, + {"matrix": [0, 15], "x": 123, "y": 0, "flags": 4}, + {"matrix": [0, 16], "x": 134, "y": 0, "flags": 4}, + {"matrix": [0, 17], "x": 146, "y": 0, "flags": 4}, + {"matrix": [0, 18], "x": 157, "y": 0, "flags": 4}, + {"matrix": [0, 19], "x": 168, "y": 0, "flags": 4}, + {"matrix": [0, 20], "x": 179, "y": 0, "flags": 4}, + {"matrix": [1, 0], "x": 191, "y": 0, "flags": 4}, + {"matrix": [1, 1], "x": 202, "y": 0, "flags": 4}, + {"matrix": [1, 2], "x": 213, "y": 0, "flags": 4}, + {"matrix": [1, 3], "x": 224, "y": 0, "flags": 4}, + {"matrix": [1, 4], "x": 0, "y": 13, "flags": 4}, + {"matrix": [1, 5], "x": 11, "y": 13, "flags": 4}, + {"matrix": [1, 6], "x": 22, "y": 13, "flags": 4}, + {"matrix": [1, 7], "x": 34, "y": 13, "flags": 4}, + {"matrix": [1, 8], "x": 45, "y": 13, "flags": 4}, + {"matrix": [1, 9], "x": 56, "y": 13, "flags": 4}, + {"matrix": [1, 10], "x": 68, "y": 13, "flags": 4}, + {"matrix": [1, 11], "x": 78, "y": 13, "flags": 4}, + {"matrix": [1, 12], "x": 89, "y": 13, "flags": 4}, + {"matrix": [1, 13], "x": 100, "y": 13, "flags": 4}, + {"matrix": [1, 14], "x": 112, "y": 13, "flags": 4}, + {"matrix": [1, 15], "x": 123, "y": 13, "flags": 4}, + {"matrix": [1, 16], "x": 134, "y": 13, "flags": 4}, + {"matrix": [1, 17], "x": 146, "y": 13, "flags": 4}, + {"matrix": [1, 18], "x": 157, "y": 13, "flags": 4}, + {"matrix": [1, 20], "x": 168, "y": 13, "flags": 4}, + {"matrix": [2, 0], "x": 179, "y": 13, "flags": 4}, + {"matrix": [2, 1], "x": 191, "y": 13, "flags": 4}, + {"matrix": [2, 2], "x": 202, "y": 13, "flags": 4}, + {"matrix": [2, 4], "x": 213, "y": 13, "flags": 4}, + {"matrix": [2, 5], "x": 224, "y": 13, "flags": 4}, + {"matrix": [2, 7], "x": 0, "y": 26, "flags": 4}, + {"matrix": [2, 8], "x": 11, "y": 26, "flags": 4}, + {"matrix": [2, 9], "x": 22, "y": 26, "flags": 4}, + {"matrix": [2, 10], "x": 34, "y": 26, "flags": 4}, + {"matrix": [2, 11], "x": 45, "y": 26, "flags": 4}, + {"matrix": [2, 12], "x": 56, "y": 26, "flags": 4}, + {"matrix": [2, 13], "x": 68, "y": 26, "flags": 4}, + {"matrix": [2, 14], "x": 78, "y": 26, "flags": 4}, + {"matrix": [2, 15], "x": 89, "y": 26, "flags": 4}, + {"matrix": [2, 16], "x": 100, "y": 26, "flags": 4}, + {"matrix": [2, 17], "x": 112, "y": 26, "flags": 4}, + {"matrix": [2, 18], "x": 123, "y": 26, "flags": 4}, + {"matrix": [2, 19], "x": 134, "y": 26, "flags": 4}, + {"matrix": [2, 20], "x": 146, "y": 26, "flags": 4}, + {"matrix": [3, 0], "x": 157, "y": 26, "flags": 4}, + {"matrix": [3, 1], "x": 168, "y": 26, "flags": 4}, + {"matrix": [3, 2], "x": 179, "y": 26, "flags": 4}, + {"matrix": [3, 3], "x": 191, "y": 26, "flags": 4}, + {"matrix": [3, 4], "x": 202, "y": 26, "flags": 4}, + {"matrix": [3, 5], "x": 213, "y": 26, "flags": 4}, + {"matrix": [3, 7], "x": 224, "y": 26, "flags": 4}, + {"matrix": [3, 8], "x": 0, "y": 38, "flags": 4}, + {"matrix": [3, 9], "x": 11, "y": 38, "flags": 4}, + {"matrix": [3, 10], "x": 22, "y": 38, "flags": 4}, + {"matrix": [3, 11], "x": 34, "y": 38, "flags": 4}, + {"matrix": [3, 12], "x": 45, "y": 38, "flags": 4}, + {"matrix": [3, 13], "x": 56, "y": 38, "flags": 4}, + {"matrix": [3, 14], "x": 68, "y": 38, "flags": 4}, + {"matrix": [3, 15], "x": 78, "y": 38, "flags": 4}, + {"matrix": [3, 16], "x": 89, "y": 38, "flags": 4}, + {"matrix": [3, 17], "x": 100, "y": 38, "flags": 4}, + {"matrix": [3, 18], "x": 112, "y": 38, "flags": 4}, + {"matrix": [3, 20], "x": 123, "y": 38, "flags": 4}, + {"matrix": [4, 0], "x": 134, "y": 38, "flags": 4}, + {"matrix": [4, 1], "x": 146, "y": 38, "flags": 4}, + {"matrix": [4, 2], "x": 157, "y": 38, "flags": 4}, + {"matrix": [4, 5], "x": 168, "y": 38, "flags": 4}, + {"matrix": [4, 7], "x": 179, "y": 38, "flags": 4}, + {"matrix": [4, 9], "x": 191, "y": 38, "flags": 4}, + {"matrix": [4, 10], "x": 202, "y": 38, "flags": 4}, + {"matrix": [4, 11], "x": 213, "y": 38, "flags": 4}, + {"matrix": [4, 12], "x": 224, "y": 38, "flags": 4}, + {"matrix": [4, 13], "x": 0, "y": 51, "flags": 4}, + {"matrix": [4, 14], "x": 11, "y": 51, "flags": 4}, + {"matrix": [4, 15], "x": 22, "y": 51, "flags": 4}, + {"matrix": [4, 16], "x": 34, "y": 51, "flags": 4}, + {"matrix": [4, 17], "x": 45, "y": 51, "flags": 4}, + {"matrix": [4, 18], "x": 56, "y": 51, "flags": 4}, + {"matrix": [4, 19], "x": 68, "y": 51, "flags": 4}, + {"matrix": [5, 1], "x": 78, "y": 51, "flags": 4}, + {"matrix": [5, 2], "x": 89, "y": 51, "flags": 4}, + {"matrix": [5, 3], "x": 100, "y": 51, "flags": 4}, + {"matrix": [5, 4], "x": 112, "y": 51, "flags": 4}, + {"matrix": [5, 5], "x": 123, "y": 51, "flags": 4}, + {"matrix": [5, 6], "x": 134, "y": 51, "flags": 4}, + {"matrix": [5, 8], "x": 146, "y": 51, "flags": 4}, + {"matrix": [5, 9], "x": 157, "y": 51, "flags": 4}, + {"matrix": [5, 13], "x": 168, "y": 51, "flags": 4}, + {"matrix": [5, 16], "x": 179, "y": 51, "flags": 4}, + {"matrix": [5, 18], "x": 191, "y": 51, "flags": 4}, + {"matrix": [5, 19], "x": 202, "y": 51, "flags": 4}, + {"matrix": [5, 20], "x": 213, "y": 51, "flags": 4} + ] + }, + "layouts": { + "LAYOUT": { + "layout": [ + {"label": "Esc", "matrix": [0, 0], "x": 0, "y": 0}, + {"label": "Delete", "matrix": [0, 1], "x": 1, "y": 0}, + {"label": "Num Lock", "matrix": [0, 2], "x": 2, "y": 0}, + {"label": "MicMute", "matrix": [0, 3], "x": 3, "y": 0}, + {"label": "No", "matrix": [0, 4], "x": 4, "y": 0}, + {"label": "No", "matrix": [0, 5], "x": 5, "y": 0}, + {"label": "No", "matrix": [0, 6], "x": 6, "y": 0}, + {"label": "Mute", "matrix": [0, 7], "x": 7, "y": 0}, + {"label": "F1", "matrix": [0, 8], "x": 8, "y": 0}, + {"label": "F2", "matrix": [0, 9], "x": 9, "y": 0}, + {"label": "F3", "matrix": [0, 10], "x": 10, "y": 0}, + {"label": "F4", "matrix": [0, 11], "x": 11, "y": 0}, + {"label": "F5", "matrix": [0, 12], "x": 12, "y": 0}, + {"label": "F6", "matrix": [0, 13], "x": 13, "y": 0}, + {"label": "F7", "matrix": [0, 14], "x": 14, "y": 0}, + {"label": "F8", "matrix": [0, 15], "x": 15, "y": 0}, + {"label": "F9", "matrix": [0, 16], "x": 16, "y": 0}, + {"label": "F10", "matrix": [0, 17], "x": 17, "y": 0}, + {"label": "F11", "matrix": [0, 18], "x": 18, "y": 0}, + {"label": "F12", "matrix": [0, 19], "x": 19, "y": 0}, + {"label": "LGUI(KC_L)", "matrix": [0, 20], "x": 20, "y": 0}, + {"label": "Backspace", "matrix": [1, 0], "x": 0, "y": 1}, + {"label": "/", "matrix": [1, 1], "x": 1, "y": 1}, + {"label": "*", "matrix": [1, 2], "x": 2, "y": 1}, + {"label": "-", "matrix": [1, 3], "x": 3, "y": 1}, + {"label": "DM_REC1", "matrix": [1, 4], "x": 4, "y": 1}, + {"label": "DM_REC2", "matrix": [1, 5], "x": 5, "y": 1}, + {"label": "`", "matrix": [1, 6], "x": 6, "y": 1}, + {"label": "1", "matrix": [1, 7], "x": 7, "y": 1}, + {"label": "2", "matrix": [1, 8], "x": 8, "y": 1}, + {"label": "3", "matrix": [1, 9], "x": 9, "y": 1}, + {"label": "4", "matrix": [1, 10], "x": 10, "y": 1}, + {"label": "5", "matrix": [1, 11], "x": 11, "y": 1}, + {"label": "6", "matrix": [1, 12], "x": 12, "y": 1}, + {"label": "7", "matrix": [1, 13], "x": 13, "y": 1}, + {"label": "8", "matrix": [1, 14], "x": 14, "y": 1}, + {"label": "9", "matrix": [1, 15], "x": 15, "y": 1}, + {"label": "0", "matrix": [1, 16], "x": 16, "y": 1}, + {"label": "-", "matrix": [1, 17], "x": 17, "y": 1}, + {"label": "=", "matrix": [1, 18], "x": 18, "y": 1}, + {"label": "No", "matrix": [1, 19], "x": 19, "y": 1}, + {"label": "Backspace", "matrix": [1, 20], "x": 20, "y": 1}, + {"label": "7", "matrix": [2, 0], "x": 0, "y": 2}, + {"label": "8", "matrix": [2, 1], "x": 1, "y": 2}, + {"label": "9", "matrix": [2, 2], "x": 2, "y": 2}, + {"label": "NO", "matrix": [2, 3], "x": 3, "y": 2}, + {"label": "Calculator", "matrix": [2, 4], "x": 4, "y": 2}, + {"label": "Print Screen", "matrix": [2, 5], "x": 5, "y": 2}, + {"label": "No", "matrix": [2, 6], "x": 6, "y": 2}, + {"label": "Tab", "matrix": [2, 7], "x": 7, "y": 2}, + {"label": "Q", "matrix": [2, 8], "x": 8, "y": 2}, + {"label": "W", "matrix": [2, 9], "x": 9, "y": 2}, + {"label": "E", "matrix": [2, 10], "x": 10, "y": 2}, + {"label": "R", "matrix": [2, 11], "x": 11, "y": 2}, + {"label": "T", "matrix": [2, 12], "x": 12, "y": 2}, + {"label": "Y", "matrix": [2, 13], "x": 13, "y": 2}, + {"label": "U", "matrix": [2, 14], "x": 14, "y": 2}, + {"label": "I", "matrix": [2, 15], "x": 15, "y": 2}, + {"label": "O", "matrix": [2, 16], "x": 16, "y": 2}, + {"label": "P", "matrix": [2, 17], "x": 17, "y": 2}, + {"label": "[", "matrix": [2, 18], "x": 18, "y": 2}, + {"label": "]", "matrix": [2, 19], "x": 19, "y": 2}, + {"label": "\\", "matrix": [2, 20], "x": 20, "y": 2}, + {"label": "4", "matrix": [3, 0], "x": 0, "y": 3}, + {"label": "5", "matrix": [3, 1], "x": 1, "y": 3}, + {"label": "6", "matrix": [3, 2], "x": 2, "y": 3}, + {"label": "PSLS", "matrix": [3, 3], "x": 3, "y": 3}, + {"label": "DM_PLY1", "matrix": [3, 4], "x": 4, "y": 3}, + {"label": "DM_PLY2", "matrix": [3, 5], "x": 5, "y": 3}, + {"label": "No", "matrix": [3, 6], "x": 6, "y": 3}, + {"label": "Caps Lock", "matrix": [3, 7], "x": 7, "y": 3}, + {"label": "A", "matrix": [3, 8], "x": 8, "y": 3}, + {"label": "S", "matrix": [3, 9], "x": 9, "y": 3}, + {"label": "D", "matrix": [3, 10], "x": 10, "y": 3}, + {"label": "F", "matrix": [3, 11], "x": 11, "y": 3}, + {"label": "G", "matrix": [3, 12], "x": 12, "y": 3}, + {"label": "H", "matrix": [3, 13], "x": 13, "y": 3}, + {"label": "J", "matrix": [3, 14], "x": 14, "y": 3}, + {"label": "K", "matrix": [3, 15], "x": 15, "y": 3}, + {"label": "L", "matrix": [3, 16], "x": 16, "y": 3}, + {"label": ";", "matrix": [3, 17], "x": 17, "y": 3}, + {"label": "'", "matrix": [3, 18], "x": 18, "y": 3}, + {"label": "No", "matrix": [3, 19], "x": 19, "y": 3}, + {"label": "Enter", "matrix": [3, 20], "x": 20, "y": 3}, + {"label": "1", "matrix": [4, 0], "x": 0, "y": 4}, + {"label": "2", "matrix": [4, 1], "x": 1, "y": 4}, + {"label": "3", "matrix": [4, 2], "x": 2, "y": 4}, + {"label": "NO", "matrix": [4, 3], "x": 3, "y": 4}, + {"label": "No", "matrix": [4, 4], "x": 4, "y": 4}, + {"label": "Up", "matrix": [4, 5], "x": 5, "y": 4}, + {"label": "No", "matrix": [4, 6], "x": 6, "y": 4}, + {"label": "Shift", "matrix": [4, 7], "x": 7, "y": 4}, + {"label": "No", "matrix": [4, 8], "x": 8, "y": 4}, + {"label": "Z", "matrix": [4, 9], "x": 9, "y": 4}, + {"label": "X", "matrix": [4, 10], "x": 10, "y": 4}, + {"label": "C", "matrix": [4, 11], "x": 11, "y": 4}, + {"label": "V", "matrix": [4, 12], "x": 12, "y": 4}, + {"label": "B", "matrix": [4, 13], "x": 13, "y": 4}, + {"label": "N", "matrix": [4, 14], "x": 14, "y": 4}, + {"label": "M", "matrix": [4, 15], "x": 15, "y": 4}, + {"label": ",", "matrix": [4, 16], "x": 16, "y": 4}, + {"label": ".", "matrix": [4, 17], "x": 17, "y": 4}, + {"label": "/", "matrix": [4, 18], "x": 18, "y": 4}, + {"label": "Shift", "matrix": [4, 19], "x": 19, "y": 4}, + {"label": "No", "matrix": [4, 20], "x": 20, "y": 4}, + {"label": "No", "matrix": [5, 0], "x": 0, "y": 5}, + {"label": "0", "matrix": [5, 1], "x": 1, "y": 5}, + {"label": ".", "matrix": [5, 2], "x": 2, "y": 5}, + {"label": "Enter", "matrix": [5, 3], "x": 3, "y": 5}, + {"label": "Left", "matrix": [5, 4], "x": 4, "y": 5}, + {"label": "Down", "matrix": [5, 5], "x": 5, "y": 5}, + {"label": "Right", "matrix": [5, 6], "x": 6, "y": 5}, + {"label": "No", "matrix": [5, 7], "x": 7, "y": 5}, + {"label": "Ctrl", "matrix": [5, 8], "x": 8, "y": 5}, + {"label": "Alt", "matrix": [5, 9], "x": 9, "y": 5}, + {"label": "No", "matrix": [5, 10], "x": 10, "y": 5}, + {"label": "No", "matrix": [5, 11], "x": 11, "y": 5}, + {"label": "No", "matrix": [5, 12], "x": 12, "y": 5}, + {"label": "Space", "matrix": [5, 13], "x": 13, "y": 5}, + {"label": "No", "matrix": [5, 14], "x": 14, "y": 5}, + {"label": "No", "matrix": [5, 15], "x": 15, "y": 5}, + {"label": "Alt", "matrix": [5, 16], "x": 16, "y": 5}, + {"label": "No", "matrix": [5, 17], "x": 17, "y": 5}, + {"label": "GUI", "matrix": [5, 18], "x": 18, "y": 5}, + {"label": "MO(FN)", "matrix": [5, 19], "x": 19, "y": 5}, + {"label": "Ctrl", "matrix": [5, 20], "x": 20, "y": 5} + ] + } + } +} diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c new file mode 100644 index 00000000000..6bf5c995967 --- /dev/null +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -0,0 +1,169 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + + #include QMK_KEYBOARD_H + #include "rgb_effects/rgb_effects.h" + #include "rgb_effects/work_timer.h" + + // Define layers + enum layers { + BASE, // Base layer + FN, // Function layer + }; + + // Define custom keycodes + enum custom_keycodes { + KC_MICMUTE = SAFE_RANGE, // Microphone mute key + KC_MEDIA, // Media layer toggle + KC_WRKTMR, // Work timer toggle + KC_WRKPAU, // Work timer pause/resume + KC_CPSWRD, // Caps word toggle + KC_30MIN, // 30-minute timer + KC_1HR, // 1-hour timer + KC_4HR, // 4-hour timer + KC_8HR, // 8-hour timer + KC_10HR, // 10-hour timer + }; + + #ifdef ENCODER_MAP_ENABLE + const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { + [BASE] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, + [FN] = { ENCODER_CCW_CW(_______, _______) }, + }; + #endif + + /** + * Key processing function + * Handles custom keycodes and effects + */ + bool process_record_user(uint16_t keycode, keyrecord_t *record) { + // Handle ESC key with ripple effect + if (keycode == KC_ESC) { + if (record->event.pressed) { + start_esc_ripple_effect(); + return true; // Let QMK handle the actual keypress + } else { + stop_esc_ripple_effect(); + return true; + } + } + + // Handle other custom keycodes + switch (keycode) { + case KC_MUTE: + if (record->event.pressed) { + tap_code(KC_MUTE); // Trigger mute on key press + tap_code(KC_MPLY); // Play/pause on key press + } + return false; + + case KC_MICMUTE: + if (record->event.pressed) { + tap_code16(LCTL(LSFT(KC_M))); // MS Teams mic mute shortcut + toggle_mic_mute_effect(); // Toggle the red-pulse effect + } + return false; + + // Work timer keycodes + case KC_WRKTMR: + if (record->event.pressed) { + toggle_work_timer(); // Toggle timer on/off + } + return false; + + case KC_30MIN: + if (record->event.pressed) { + start_timer(TIMER_TYPE_30MIN); + } + return false; + + case KC_1HR: + if (record->event.pressed) { + start_timer(TIMER_TYPE_1HR); + } + return false; + + case KC_4HR: + if (record->event.pressed) { + start_timer(TIMER_TYPE_4HR); + } + return false; + + case KC_8HR: + if (record->event.pressed) { + start_timer(TIMER_TYPE_8HR); + } + return false; + + case KC_10HR: + if (record->event.pressed) { + start_timer(TIMER_TYPE_10HR); + } + return false; + + case KC_WRKPAU: + if (record->event.pressed) { + toggle_pause_work_timer(); // Pause/resume the work timer + } + return false; + + case KC_CPSWRD: + if (record->event.pressed) { + caps_word_toggle(); // Toggle caps word mode + } + return false; + + default: + return true; // Process all other keycodes normally + } + } + + /** + * Caps Word Callback + * Determine which keys should maintain caps word mode + */ + bool caps_word_press_user(uint16_t keycode) { + switch (keycode) { + // These keys don't disable Caps Word + case KC_A ... KC_Z: + case KC_MINS: + return true; + } + return false; + } + + // Keymap definitions + const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + /* Base Layer */ + [BASE] = LAYOUT( + KC_ESC, KC_DEL, KC_NUM_LOCK, KC_MICMUTE, KC_NO, KC_NO, KC_NO, KC_MUTE, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, LGUI(KC_L), + KC_BSPC, KC_PSLS, KC_PAST, KC_PMNS, DM_REC1, DM_REC2, KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_NO, KC_BSPC, + KC_P7, KC_P8, KC_P9, KC_NO, KC_CALC, KC_PSCR, KC_NO, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, + KC_P4, KC_P5, KC_P6, KC_PPLS, DM_PLY1, DM_PLY2, KC_NO, KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NO, KC_ENT, + KC_P1, KC_P2, KC_P3, KC_NO, KC_NO, KC_UP, KC_NO, KC_LSFT, KC_NO, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_NO, + KC_NO, KC_P0, KC_PDOT, KC_PENT, KC_LEFT, KC_DOWN, KC_RIGHT, KC_NO, KC_LCTL, KC_LALT, KC_NO, KC_NO, KC_NO, KC_SPC, KC_NO, KC_NO, KC_RALT, KC_NO, KC_RGUI, MO(FN), KC_RCTL + ), + + /* Function Layer */ + [FN] = LAYOUT( + _______, _______, _______, _______, KC_NO , KC_NO , KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, RGB_VAD, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + _______, KC_8HR , KC_10HR, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + KC_4HR , _______, _______, RGB_VAI, _______, _______, KC_NO , KC_CPSWRD, _______, KC_WRKTMR, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + KC_1HR , _______, KC_30MIN, KC_NO , KC_NO , RGB_HUI, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , + KC_NO , _______, _______, _______, RGB_RMOD, RGB_HUD, RGB_MOD, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , KC_WRKPAU, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ + ), + }; diff --git a/keyboards/tssouthpaw/keymaps/default/readme.md b/keyboards/tssouthpaw/keymaps/default/readme.md new file mode 100644 index 00000000000..360dc6c2ef9 --- /dev/null +++ b/keyboards/tssouthpaw/keymaps/default/readme.md @@ -0,0 +1,35 @@ +/** + * This is a readme.md file that should be placed in the keymaps/default/ directory + * to provide information about the keymap. + */ + + ## keymaps/default/readme.md + + ```markdown + # Default Keymap for TS Southpaw + + This is the default keymap for the TS Southpaw keyboard. It includes a base layer with a standard QWERTY layout plus southpaw numpad, and a function layer with work timer controls and RGB lighting adjustments. + + ## Features + + - Southpaw numpad layout (numpad on the left side) + - Work timer system with multiple duration options + - Volume control via rotary encoder + - RGB lighting effects and controls + - Arrow keys highlighted in blue + - ESC key ripple effect + - Caps Lock and Num Lock indicators + - Microphone mute function with indicator + + ## Layers + + ### Base Layer + - Standard QWERTY layout + - Left-side numpad + - Media controls + - Function key row (F1-F12) + + ### Function Layer (Accessed via key to left of right Control) + - RGB lighting controls + - Work timer controls (30MIN, 1HR, 4HR, 8HR, 10HR) + - Caps Word toggle \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/rgb_effects.c b/keyboards/tssouthpaw/rgb_effects/rgb_effects.c new file mode 100644 index 00000000000..c79f4c12842 --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects/rgb_effects.c @@ -0,0 +1,267 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + +/** + * TS Southpaw RGB Effects System + */ + + #include "rgb_effects.h" + #include "quantum.h" + #include "rgb_matrix.h" + #include "work_timer.h" + + /** + * Color definitions for RGB effects + */ + static const rgb_color_t CAPS_LOCK_COLOR = {255, 60, 0}; // Orange for Caps Lock + static const rgb_color_t NUM_LOCK_COLOR = {255, 60, 0}; // Orange for Num Lock + static const rgb_color_t ESC_RIPPLE_COLOR = {255, 0, 0}; // Red for ESC ripple effect + static const rgb_color_t MIC_MUTE_COLOR = {255, 0, 0}; // Red for Mic Mute + static const rgb_color_t ARROW_KEYS_COLOR = {0, 0, 255}; // Blue for arrow keys + + /** + * State tracking structure for LED effects + */ + typedef struct { + bool active; + uint8_t prev_rgb_mode; + } led_state_t; + + /** + * State variables for LED indicators + */ + static led_state_t caps_lock_state = {false, 0}; + static led_state_t num_lock_state = {false, 0}; + static led_state_t mic_mute_state = {false, 0}; + + /** + * ESC ripple effect state variables + */ + static struct { + bool active; + uint8_t step; + uint16_t timer; + bool expanding; + } ripple_state = {false, 0, 0, true}; + + /** + * ESC ripple effect LED configuration + */ + #define MAX_RIPPLE_LAYERS 6 + #define MAX_LEDS_PER_LAYER 10 + + // LED indices for each ripple layer + // 255 is used as a placeholder for unused indices + static const uint8_t esc_splash_ripple[MAX_RIPPLE_LAYERS][MAX_LEDS_PER_LAYER] = { + {0, 255, 255, 255, 255, 255, 255, 255, 255, 255}, // Center LED only + {1, 19, 255, 255, 255, 255, 255, 255, 255, 255}, // First ring + {38, 39, 6, 7, 15, 255, 255, 255, 255, 255}, // Second ring + {42, 43, 25, 48, 49, 255, 255, 255, 255, 255}, // Third ring + {80, 81, 67, 68, 75, 76, 255, 255, 255, 255}, // Fourth ring + {102, 103, 71, 36, 18, 93, 100, 255, 255, 255} // Fifth ring + }; + + // Number of actual LEDs in each ripple layer for more efficient processing + static const uint8_t leds_per_layer[MAX_RIPPLE_LAYERS] = {1, 2, 5, 5, 6, 7}; + + /** + * Arrow key LED configuration + */ + static const uint8_t arrow_key_leds[] = {78, 94, 95, 96}; + #define ARROW_KEY_COUNT (sizeof(arrow_key_leds) / sizeof(arrow_key_leds[0])) + + /** + * Apply a pulsing effect to a specific LED + * + * @param led_index The index of the LED to apply the effect to + * @param color The base RGB color to use + */ + static void apply_pulse_effect(uint8_t led_index, rgb_color_t color) { + // Create a pulsing effect by varying brightness based on timer + uint8_t brightness = abs((timer_read() / 2) % 510 - 255); + rgb_matrix_set_color(led_index, + (color.r * brightness) / 255, + (color.g * brightness) / 255, + (color.b * brightness) / 255); + } + + /** + * Handle the state change for an indicator LED + * + * @param active Whether the indicator should be active + * @param state Pointer to the LED state structure to update + */ + static void update_indicator_state(bool active, led_state_t *state) { + if (active) { + if (!state->active) { + state->prev_rgb_mode = rgb_matrix_get_mode(); + state->active = true; + } + } else if (state->active) { + state->active = false; + rgb_matrix_mode_noeeprom(state->prev_rgb_mode); + } + } + + /** + * Handle Caps Lock indicator lighting + */ + void handle_caps_lock_rgb(void) { + bool caps_active = host_keyboard_led_state().caps_lock; + update_indicator_state(caps_active, &caps_lock_state); + + if (caps_lock_state.active) { + apply_pulse_effect(CAPS_LOCK_LED, CAPS_LOCK_COLOR); + } + } + + /** + * Handle Num Lock indicator lighting + * Note: Active when Num Lock is OFF (inverse logic) + */ + void handle_num_lock_rgb(void) { + bool num_lock_inactive = !host_keyboard_led_state().num_lock; + update_indicator_state(num_lock_inactive, &num_lock_state); + + if (num_lock_state.active) { + apply_pulse_effect(NUM_LOCK_LED, NUM_LOCK_COLOR); + } + } + + /** + * Toggle the Mic Mute effect state + */ + void toggle_mic_mute_effect(void) { + if (!mic_mute_state.active) { + mic_mute_state.prev_rgb_mode = rgb_matrix_get_mode(); + mic_mute_state.active = true; + } else { + mic_mute_state.active = false; + rgb_matrix_mode_noeeprom(mic_mute_state.prev_rgb_mode); + } + } + + /** + * Handle Mic Mute indicator lighting + */ + void handle_mic_mute_rgb(void) { + if (mic_mute_state.active) { + apply_pulse_effect(MIC_MUTE_LED, MIC_MUTE_COLOR); + } + } + + /** + * Start the ESC ripple effect + */ + void start_esc_ripple_effect(void) { + ripple_state.active = true; + ripple_state.step = 0; + ripple_state.timer = timer_read(); + ripple_state.expanding = true; + } + + /** + * Stop the ESC ripple effect + */ + void stop_esc_ripple_effect(void) { + ripple_state.active = false; + } + + /** + * Handle the ESC ripple effect animation + */ + void handle_esc_ripple_effect(void) { + if (!ripple_state.active) return; + + // Update ripple step based on timer (every 15ms) + if (timer_elapsed(ripple_state.timer) > 15) { + ripple_state.timer = timer_read(); + + // Update step based on direction + if (ripple_state.expanding) { + ripple_state.step++; + if (ripple_state.step >= MAX_RIPPLE_LAYERS - 1) { + ripple_state.step = MAX_RIPPLE_LAYERS - 1; + ripple_state.expanding = false; + } + } else { + if (ripple_state.step > 0) { + ripple_state.step--; + } else { + ripple_state.expanding = true; + } + } + } + + // Turn off all ripple LEDs first + for (uint8_t layer = 0; layer < MAX_RIPPLE_LAYERS; layer++) { + for (uint8_t i = 0; i < leds_per_layer[layer]; i++) { + uint8_t led_index = esc_splash_ripple[layer][i]; + if (led_index != 255) { // Skip placeholder values + rgb_matrix_set_color(led_index, 0, 0, 0); + } + } + } + + // Set active layer LEDs + uint8_t current_layer = ripple_state.step; + for (uint8_t i = 0; i < leds_per_layer[current_layer]; i++) { + uint8_t led_index = esc_splash_ripple[current_layer][i]; + if (led_index != 255) { // Skip placeholder values + rgb_matrix_set_color(led_index, + ESC_RIPPLE_COLOR.r, + ESC_RIPPLE_COLOR.g, + ESC_RIPPLE_COLOR.b); + } + } + } + + /** + * Handle arrow key highlighting + */ + static void handle_arrow_keys(void) { + // Only override arrows in SOLID_COLOR mode + if (rgb_matrix_get_mode() == RGB_MATRIX_SOLID_COLOR) { + uint8_t val = rgb_matrix_get_val(); // current brightness (0–255) + + for (uint8_t i = 0; i < ARROW_KEY_COUNT; i++) { + rgb_matrix_set_color(arrow_key_leds[i], + ARROW_KEYS_COLOR.r * val / 255, + ARROW_KEYS_COLOR.g * val / 255, + ARROW_KEYS_COLOR.b * val / 255); + } + } + } + + /** + * Combined overlay for arrow-keys + reactive handlers + */ + bool rgb_matrix_indicators_user(void) { + // Apply arrow key highlighting + handle_arrow_keys(); + + // Run all reactive handlers + handle_caps_lock_rgb(); + handle_num_lock_rgb(); + handle_mic_mute_rgb(); + handle_esc_ripple_effect(); + + // Handle work timer through extern call (implemented in work_timer.c) + update_work_timer(); + handle_work_timer(); + + return true; // continue with the normal effect pipeline + } \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/rgb_effects.h b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h new file mode 100644 index 00000000000..2333833ea68 --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h @@ -0,0 +1,85 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + + #pragma once + + #include "quantum.h" + + // LED indices for indicator LEDs + #define CAPS_LOCK_LED 62 // LED index for Caps Lock indicator + #define NUM_LOCK_LED 2 // LED index for Num Lock indicator + #define MIC_MUTE_LED 3 // LED index for KC_MICMUTE + +/** + * RGB Effect types enumeration + * For potential future extension of effect types + */ + typedef enum { + RGB_EFFECT_NONE, + RGB_EFFECT_BREATHING, + RGB_EFFECT_RAINBOW, + RGB_EFFECT_CUSTOM + } rgb_effect_type_t; + + /** + * RGB Color structure for cleaner color definitions + */ + typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + } rgb_color_t; + + /** + * Function declarations for handling RGB effects + */ + + // Handle LED indicators for keyboard state + void handle_caps_lock_rgb(void); + void handle_num_lock_rgb(void); + void handle_mic_mute_rgb(void); + + // Ripple effect functions + void handle_esc_ripple_effect(void); + void start_esc_ripple_effect(void); + void stop_esc_ripple_effect(void); + + // Microphone mute effect toggle + void toggle_mic_mute_effect(void); + + /** + * Utility functions for RGB effects + */ + + // Gets the keyboard's current RGB mode + static inline uint8_t get_current_rgb_mode(void) { + return rgb_matrix_get_mode(); + } + + // Sets an LED to a specific RGB color + static inline void set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b) { + rgb_matrix_set_color(led_index, r, g, b); + } + + // Calculate color gradient between two colors based on progress (0.0 - 1.0) + static inline rgb_color_t calculate_gradient_color(rgb_color_t start, rgb_color_t end, float progress) { + rgb_color_t result; + result.r = start.r + (int)((float)(end.r - start.r) * progress); + result.g = start.g + (int)((float)(end.g - start.g) * progress); + result.b = start.b + (int)((float)(end.b - start.b) * progress); + return result; + } + \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c new file mode 100644 index 00000000000..7b76452dce5 --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -0,0 +1,393 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + +#include "work_timer.h" +#include "quantum.h" +#include "rgb_effects/rgb_effects.h" +#include "timer.h" + +// Simple work timer structure +typedef struct { + // Basic state + bool active; // Is the timer running? + bool paused; // Is the timer paused? + work_timer_type_t type; // Timer type (30min, 1hr, etc.) + + // Time tracking + uint32_t start_time; // When this timer started + uint32_t end_time; // When this timer should end + uint32_t pause_time; // When timer was paused (if paused) + uint32_t break_time; // Break duration for this timer + + // Midpoint break tracking + uint32_t mid_point; // Time value at midpoint (when break starts) + uint32_t mid_point_end; // Time value when break ends +} work_timer_t; + +// Global timer state +static work_timer_t timer = {0}; + +// Timer colors +static const rgb_color_t TIMER_START_COLOR = {WORK_TIMER_START_R, WORK_TIMER_START_G, WORK_TIMER_START_B}; +static const rgb_color_t TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MID_G, WORK_TIMER_MID_B}; +static const rgb_color_t TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B}; +static const rgb_color_t TIMER_BREAK_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B}; + +// Function declarations +static void save_timer_state(void); +static void load_timer_state(void); +static void display_progress_bar(uint8_t num_leds, float progress, float brightness); + +/** + * Configure a timer with the specified type + */ +static void configure_timer(work_timer_type_t type) { + timer.type = type; + + // Set break duration based on timer type + switch (type) { + case TIMER_TYPE_30MIN: + timer.break_time = MID_BREAK_30MIN_DURATION; + break; + + case TIMER_TYPE_1HR: + timer.break_time = MID_BREAK_1HR_DURATION; + break; + + case TIMER_TYPE_4HR: + timer.break_time = MID_BREAK_4HR_DURATION; + break; + + case TIMER_TYPE_8HR: + timer.break_time = LUNCH_BREAK_DURATION; + break; + + case TIMER_TYPE_10HR: + timer.break_time = LUNCH_BREAK_DURATION; + break; + + default: + timer.break_time = LUNCH_BREAK_DURATION; + break; + } + + // Calculate timer duration (excluding break) + uint32_t work_duration = 0; + switch (type) { + case TIMER_TYPE_30MIN: work_duration = TIMER_30MIN_DURATION; break; + case TIMER_TYPE_1HR: work_duration = TIMER_1HR_DURATION; break; + case TIMER_TYPE_4HR: work_duration = TIMER_4HR_DURATION; break; + case TIMER_TYPE_8HR: work_duration = TIMER_8HR_DURATION; break; + case TIMER_TYPE_10HR: work_duration = TIMER_10HR_DURATION; break; + default: work_duration = TIMER_8HR_DURATION; break; + } + + // Calculate end time and midpoint + timer.end_time = timer.start_time + work_duration; + timer.mid_point = timer.start_time + (work_duration / 2); + timer.mid_point_end = timer.mid_point + timer.break_time; +} + +/** + * Save timer state to EEPROM + */ +static void save_timer_state(void) { + uint8_t buffer[24] = {0}; + + // Basic state + buffer[0] = timer.active ? 1 : 0; + buffer[1] = timer.paused ? 1 : 0; + buffer[2] = (uint8_t)timer.type; + + // Time values + memcpy(&buffer[3], &timer.start_time, sizeof(uint32_t)); + memcpy(&buffer[7], &timer.end_time, sizeof(uint32_t)); + memcpy(&buffer[11], &timer.pause_time, sizeof(uint32_t)); + memcpy(&buffer[15], &timer.break_time, sizeof(uint32_t)); + memcpy(&buffer[19], &timer.mid_point, sizeof(uint32_t)); + + // Write to EEPROM + eeprom_update_block(buffer, (void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); +} + +/** + * Load timer state from EEPROM + */ +static void load_timer_state(void) { + uint8_t buffer[24] = {0}; + + // Read from EEPROM + eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); + + // Basic state + timer.active = buffer[0] ? true : false; + timer.paused = buffer[1] ? true : false; + timer.type = (work_timer_type_t)buffer[2]; + + // Time values + memcpy(&timer.start_time, &buffer[3], sizeof(uint32_t)); + memcpy(&timer.end_time, &buffer[7], sizeof(uint32_t)); + memcpy(&timer.pause_time, &buffer[11], sizeof(uint32_t)); + memcpy(&timer.break_time, &buffer[15], sizeof(uint32_t)); + memcpy(&timer.mid_point, &buffer[19], sizeof(uint32_t)); + + // Calculate mid_point_end from loaded values + if (timer.active) { + timer.mid_point_end = timer.mid_point + timer.break_time; + + // Verify timer is still valid + if (!timer.paused) { + uint32_t current_time = timer_read32(); + if (current_time > timer.end_time) { + timer.active = false; + save_timer_state(); + } + } + } +} + +/** + * Display progress bar with gradient colors + */ +static void display_progress_bar(uint8_t num_leds, float progress, float brightness) { + // Progress is a value from 0.0 to 1.0 + float hours_per_led = 1.0f / (float)num_leds; + + // Determine how many LEDs should be lit + uint8_t leds_lit = (uint8_t)(progress / hours_per_led); + if (leds_lit > num_leds) leds_lit = num_leds; + + // Calculate progress for the partially-lit LED + float current_led_progress = (progress - (leds_lit * hours_per_led)) / hours_per_led; + + // Set colors for each LED in the progress bar + for (uint8_t i = 0; i < num_leds; i++) { + rgb_color_t color = {0, 0, 0}; // Default to off + + if (i < leds_lit) { + // Fully lit LED - calculate gradient color based on position + float position = (float)i / (float)(num_leds - 1); + + // Use green to orange gradient for most of bar, orange to red at end + if (position < 0.7f) { + // First 70% is green to orange + float adj_progress = position / 0.7f; + color = calculate_gradient_color(TIMER_START_COLOR, TIMER_MID_COLOR, adj_progress); + } else { + // Last 30% is orange to red + float adj_progress = (position - 0.7f) / 0.3f; + color = calculate_gradient_color(TIMER_MID_COLOR, TIMER_END_COLOR, adj_progress); + } + + // Apply brightness + color.r = (uint8_t)((float)color.r * brightness); + color.g = (uint8_t)((float)color.g * brightness); + color.b = (uint8_t)((float)color.b * brightness); + } + else if (i == leds_lit && current_led_progress > 0.0f) { + // Partially lit LED + float position = (float)i / (float)(num_leds - 1); + rgb_color_t full_color; + + if (position < 0.7f) { + float adj_progress = position / 0.7f; + full_color = calculate_gradient_color(TIMER_START_COLOR, TIMER_MID_COLOR, adj_progress); + } else { + float adj_progress = (position - 0.7f) / 0.3f; + full_color = calculate_gradient_color(TIMER_MID_COLOR, TIMER_END_COLOR, adj_progress); + } + + // Dim by progress and brightness + color.r = (uint8_t)((float)full_color.r * current_led_progress * brightness); + color.g = (uint8_t)((float)full_color.g * current_led_progress * brightness); + color.b = (uint8_t)((float)full_color.b * current_led_progress * brightness); + } + + // Set the LED color + rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, color.b); + } +} + +/** + * Initialize the work timer + */ +void work_timer_init(void) { + load_timer_state(); +} + +/** + * Toggle the work timer on/off + */ +void toggle_work_timer(void) { + if (timer.active) { + // Turn off timer + timer.active = false; + timer.paused = false; + + save_timer_state(); + + // Force RGB refresh + rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); + } else { + // Start a new timer + timer.active = true; + timer.paused = false; + timer.start_time = timer_read32(); + timer.pause_time = 0; + + // Configure for current timer type + configure_timer(timer.type); + save_timer_state(); + } +} + +/** + * Start a specific timer type + */ +void start_timer(work_timer_type_t type) { + // Start fresh + timer.active = true; + timer.paused = false; + timer.start_time = timer_read32(); + timer.pause_time = 0; + + // Configure based on type + configure_timer(type); + save_timer_state(); +} + +/** + * Pause or resume the timer + */ +void toggle_pause_work_timer(void) { + if (!timer.active) return; + + if (timer.paused) { + // Resume - calculate time paused and adjust timers + uint32_t current_time = timer_read32(); + uint32_t pause_duration = current_time - timer.pause_time; + + // Extend all time points by pause duration + timer.end_time += pause_duration; + timer.mid_point += pause_duration; + timer.mid_point_end += pause_duration; + + timer.paused = false; + } else { + // Pause + timer.pause_time = timer_read32(); + timer.paused = true; + } + + save_timer_state(); +} + +/** + * Check if the blue pulse effect should be active + */ +bool is_timer_pulse_active(void) { + if (!timer.active || timer.paused) return false; + + uint32_t current_time = timer_read32(); + + // Only return true when we're in the mid-point break window + return (current_time >= timer.mid_point && current_time < timer.mid_point_end); +} + +/** + * Update timer state + */ +void update_work_timer(void) { + if (!timer.active || timer.paused) return; + + uint32_t current_time = timer_read32(); + + // Check if timer has ended + if (current_time >= timer.end_time) { + timer.active = false; + save_timer_state(); + rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); + } +} + +/** + * Handle work timer visualization + */ +void handle_work_timer(void) { + static bool was_active = false; + + if (!timer.active) { + if (was_active) { + rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); + was_active = false; + } + return; + } + + was_active = true; + + // Get current time and brightness + uint32_t current_time = timer_read32(); + uint8_t rgb_brightness = rgb_matrix_get_val(); + float brightness_factor = (float)rgb_brightness / 255.0f; + + // Number of LEDs in the progress bar + const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; + + // Calculate progress based on active timer duration + float progress; + if (timer.paused) { + // When paused, use pause time for progress calculation + progress = (float)(timer.pause_time - timer.start_time) / + (float)(timer.end_time - timer.start_time); + } else { + // When active, use current time for progress calculation + progress = (float)(current_time - timer.start_time) / + (float)(timer.end_time - timer.start_time); + } + + // Clamp progress to valid range + if (progress > 1.0f) progress = 1.0f; + if (progress < 0.0f) progress = 0.0f; + + // Check if we're in the break window (midpoint of the timer) + if (!timer.paused && current_time >= timer.mid_point && current_time < timer.mid_point_end) { + // In break time - show blue pulse + rgb_color_t color = TIMER_BREAK_COLOR; + + // Slow pulse for break time + uint8_t break_pulse = timer_read() / 10; + float break_intensity = (float)(abs(break_pulse % 510 - 255)) / 255.0f; + + // Apply blue pulse to all LEDs in progress bar + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)color.r * break_intensity * brightness_factor), + (uint8_t)((float)color.g * break_intensity * brightness_factor), + (uint8_t)((float)color.b * break_intensity * brightness_factor)); + } + } else { + // Normal progress display + display_progress_bar(num_leds, progress, brightness_factor); + } +} + +/** + * Periodic timer task + */ +void work_timer_task(void) { + update_work_timer(); +} diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.h b/keyboards/tssouthpaw/rgb_effects/work_timer.h new file mode 100644 index 00000000000..ea35f40a8dd --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.h @@ -0,0 +1,89 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + +#pragma once + +#include "quantum.h" + +// Work Timer type enum +typedef enum { + TIMER_TYPE_30MIN, + TIMER_TYPE_1HR, + TIMER_TYPE_4HR, + TIMER_TYPE_8HR, + TIMER_TYPE_10HR +} work_timer_type_t; + +// Work Timer state enum +typedef enum { + TIMER_STATE_INACTIVE, + TIMER_STATE_ACTIVE, + TIMER_STATE_PAUSED, + TIMER_STATE_LUNCH, + TIMER_STATE_MID_BREAK, + TIMER_STATE_COMPLETED +} work_timer_state_t; + +// Work Timer Color definitions - modified for more yellow/orange focus +// Start point color (green) +#define WORK_TIMER_START_R 0 +#define WORK_TIMER_START_G 255 +#define WORK_TIMER_START_B 0 + +// Mid point color (orange) +#define WORK_TIMER_MID_R 255 +#define WORK_TIMER_MID_G 112 +#define WORK_TIMER_MID_B 0 + +// End point color (red) +#define WORK_TIMER_END_R 255 +#define WORK_TIMER_END_G 0 +#define WORK_TIMER_END_B 0 + +// Lunch break color (blue) +#define WORK_TIMER_LUNCH_R 0 +#define WORK_TIMER_LUNCH_G 0 +#define WORK_TIMER_LUNCH_B 255 + +// Standard timer durations (in milliseconds) +// IMPORTANT: These represent actual working time (excluding breaks) +#define TIMER_30MIN_DURATION 1800000 // 30 minutes +#define TIMER_1HR_DURATION 3600000 // 1 hour +#define TIMER_4HR_DURATION 14400000 // 4 hours +#define TIMER_8HR_DURATION 28800000 // 8 hours (real world 9 hours with lunch) +#define TIMER_10HR_DURATION 36000000 // 10 hours (real world 11 hours with lunch) + +// Break durations +#define BREAK_WARNING_TIME 60000 // 60 seconds (kept for calculations but not used for warnings) +#define MID_BREAK_30MIN_DURATION 30000 // 30 seconds +#define MID_BREAK_1HR_DURATION 45000 // 45 seconds +#define MID_BREAK_4HR_DURATION 60000 // 60 seconds +#define LUNCH_BREAK_DURATION 3600000 // 1 hour default + +// Define maximum valid duration for sanity checking +#define TIMER_MAX_DURATION 50400000 // 14 hours - sanity check + +// Function declarations for work timer +void work_timer_init(void); +void start_timer(work_timer_type_t timer_type); +void toggle_pause_work_timer(void); +void update_work_timer(void); +void handle_work_timer(void); +void toggle_work_timer(void); +void work_timer_task(void); + +// Functions for checking timer states +bool is_timer_pulse_active(void); diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk new file mode 100644 index 00000000000..716da52f309 --- /dev/null +++ b/keyboards/tssouthpaw/rules.mk @@ -0,0 +1,5 @@ +# This is the rules.mk file for the RGB Effects firmware. +SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c + +# Enable encoder functionality +ENCODER_ENABLE = yes diff --git a/keyboards/tssouthpaw/tssouthpaw.c b/keyboards/tssouthpaw/tssouthpaw.c new file mode 100644 index 00000000000..54cab161306 --- /dev/null +++ b/keyboards/tssouthpaw/tssouthpaw.c @@ -0,0 +1,88 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + + #include "tssouthpaw.h" + #include "rgb_effects/rgb_effects.h" + #include "rgb_effects/work_timer.h" + + /** + * Keyboard initialization + * Called once at startup + */ + void keyboard_post_init_kb(void) { + // Initialize RGB lighting effects + rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR); + rgb_matrix_sethsv_noeeprom(HSV_GREEN); // Set default color to green + + // Enable N-Key Rollover + keymap_config.nkro = true; + + // Load saved work timer state from EEPROM if applicable + work_timer_init(); + + // Continue with any user-level initialization + keyboard_post_init_user(); + } + + /** + * Power management function - Called when system is going to sleep + */ + void suspend_power_down_kb(void) { + // If timer pulse is active, ensure RGB matrix remains active + if (is_timer_pulse_active()) { + // Ensure RGB matrix has reasonable brightness for notifications + uint8_t val = rgb_matrix_get_val(); + if (val < RGB_MATRIX_MINIMUM_BRIGHTNESS) { + rgb_matrix_sethsv_noeeprom(rgb_matrix_get_hue(), rgb_matrix_get_sat(), RGB_MATRIX_MINIMUM_BRIGHTNESS); + } + + // Return early to skip deep sleep routines + return; + } + + // Only disable RGB effects when no timer pulse is active + rgb_matrix_set_suspend_state(true); + suspend_power_down_user(); + } + + /** + * Power management function - Called when system is waking from sleep + */ + void suspend_wakeup_init_kb(void) { + // Make sure the timer pulse state is properly reflected + // This ensures the timer visuals are immediately visible + if (is_timer_pulse_active()) { + // Ensure RGB matrix effect is enabled with proper brightness + uint8_t val = rgb_matrix_get_val(); + if (val < RGB_MATRIX_MINIMUM_BRIGHTNESS) { + rgb_matrix_sethsv_noeeprom(rgb_matrix_get_hue(), rgb_matrix_get_sat(), RGB_MATRIX_MINIMUM_BRIGHTNESS); + } + } + + suspend_wakeup_init_user(); + } + + /** + * Regular task hook to ensure timer updates even during suspend + */ + void housekeeping_task_kb(void) { + // Update work timer even during suspend + if (is_timer_pulse_active()) { + update_work_timer(); + handle_work_timer(); + } + } + \ No newline at end of file diff --git a/keyboards/tssouthpaw/tssouthpaw.h b/keyboards/tssouthpaw/tssouthpaw.h new file mode 100644 index 00000000000..42947807a9e --- /dev/null +++ b/keyboards/tssouthpaw/tssouthpaw.h @@ -0,0 +1,21 @@ +/* Copyright 2025 TS Design Works LLC + * + * 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 2 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 . + */ + + #pragma once + + #include "quantum.h" + + // Include to ensure backward compatibility with existing user keymaps