From 5b5e0f3c0d409ccfd6f02ed3db8b182ba36e83be Mon Sep 17 00:00:00 2001 From: kthorpe88 <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 20 Aug 2022 07:26:37 -0600 Subject: [PATCH 01/54] Add files via upload --- keyboards/tdcleft104/Makefile | 3 + keyboards/tdcleft104/config.h | 70 +++++++++++ keyboards/tdcleft104/info.json | 119 ++++++++++++++++++ keyboards/tdcleft104/keymaps/default/keymap.c | 28 +++++ keyboards/tdcleft104/rules.mk | 15 +++ keyboards/tdcleft104/tdcleft104.c | 1 + keyboards/tdcleft104/tdcleft104.h | 37 ++++++ 7 files changed, 273 insertions(+) create mode 100644 keyboards/tdcleft104/Makefile create mode 100644 keyboards/tdcleft104/config.h create mode 100644 keyboards/tdcleft104/info.json create mode 100644 keyboards/tdcleft104/keymaps/default/keymap.c create mode 100644 keyboards/tdcleft104/rules.mk create mode 100644 keyboards/tdcleft104/tdcleft104.c create mode 100644 keyboards/tdcleft104/tdcleft104.h diff --git a/keyboards/tdcleft104/Makefile b/keyboards/tdcleft104/Makefile new file mode 100644 index 00000000000..57b2ef62e5f --- /dev/null +++ b/keyboards/tdcleft104/Makefile @@ -0,0 +1,3 @@ +ifndef MAKEFILE_INCLUDED + include ../../Makefile +endif diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h new file mode 100644 index 00000000000..be7de3e8b6d --- /dev/null +++ b/keyboards/tdcleft104/config.h @@ -0,0 +1,70 @@ +// Copyright 2022 TDC + +// 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. + +// T his 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 "config.h" + +/* key matrix size */ +#define MATRIX_ROWS 6 +#define MATRIX_COLS 20 + +/* Keyboard Matrix Assignments */ +#define MATRIX_ROW_PINS {B3,B0,A7,A6,A5,A4} +#define MATRIX_COL_PINS {A9,A8,B15,B14,B13,B12,B11,B10,B2,B1,A3,A2,A1,A0,B9,B8,B7,B4,B6,B5} +#define UNUSED_PINS {A10,A11} + +/* COL2ROW or ROW2COL */ +#define DIODE_DIRECTION COL2ROW + +#define RGB_DI_PIN A15 +#ifdef RGB_DI_PIN +#define RGBLED_NUM 104 +#define RGBLIGHT_HUE_STEP 6 +#define RGBLIGHT_SAT_STEP 6 +#define RGBLIGHT_VAL_STEP 6 +#define RGBLIGHT_DEFAULT_HUE 4 +#define RGBLIGHT_LIMIT_VAL 15 /* The maximum brightness level */ +#define RGBLIGHT_SLEEP /* If defined, the RGB lighting will be switched off when the host goes to sleep */ +/*== all animations enable ==*/ +// #define RGBLIGHT_ANIMATIONS +// /*== or choose animations ==*/ +#define RGBLIGHT_EFFECT_BREATHING +#define RGBLIGHT_EFFECT_RAINBOW_MOOD +#define RGBLIGHT_EFFECT_RAINBOW_SWIRL +#define RGBLIGHT_EFFECT_SNAKE +#define RGBLIGHT_EFFECT_KNIGHT +//#define RGBLIGHT_EFFECT_CHRISTMAS +#define RGBLIGHT_EFFECT_STATIC_GRADIENT +// #define RGBLIGHT_EFFECT_RGB_TEST +#define RGBLIGHT_EFFECT_TWINKLE +#define RGBLIGHT_EFFECT_ALTERNATING +#endif + +/* define if matrix has ghost */ +//#define MATRIX_HAS_GHOST + +/* Set 0 if debouncing isn't needed */ +#define DEBOUNCE 5 + +#define LOCKING_SUPPORT_ENABLE +/* Locking resynchronize hack */ +#define LOCKING_RESYNC_ENABLE + + + + + + diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json new file mode 100644 index 00000000000..9825d33f205 --- /dev/null +++ b/keyboards/tdcleft104/info.json @@ -0,0 +1,119 @@ +{ + "keyboard_name": "tdcleft104", + "url": "", + "maintainer": "tdc", + "usb": { + "vid": "0x7856", + "pid": "0x6163", + "device_version": "0.0.2" + }, + "layouts": { + "LAYOUT_tdcleft104": { + "layout": [ + {"label":"Esc", "x":0, "y":0}, + {"label":"", "x":1, "y":0}, + {"label":"", "x":2, "y":0}, + {"label":"Num Lock", "x":3, "y":0}, + {"label":"", "x":4.25, "y":0, "w":1.25}, + {"label":"F1", "x":5.5, "y":0}, + {"label":"F2", "x":6.5, "y":0}, + {"label":"F3", "x":7.5, "y":0}, + {"label":"F4", "x":8.5, "y":0}, + {"label":"F5", "x":9.5, "y":0}, + {"label":"F6", "x":10.5, "y":0}, + {"label":"F7", "x":11.5, "y":0}, + {"label":"F8", "x":12.5, "y":0}, + {"label":"F9", "x":13.5, "y":0}, + {"label":"F10", "x":14.5, "y":0}, + {"label":"F11", "x":15.5, "y":0}, + {"label":"F12", "x":16.5, "y":0}, + {"label":"Scroll Lock", "x":17.5, "y":0}, + {"label":"", "x":18.5, "y":0, "w":1.25}, + {"label":"Delete", "x":0, "y":1.5}, + {"label":"/", "x":1, "y":1.5}, + {"label":"*", "x":2, "y":1.5}, + {"label":"-", "x":3, "y":1.5}, + {"label":"~", "x":4.25, "y":1.5}, + {"label":"!", "x":5.25, "y":1.5}, + {"label":"@", "x":6.25, "y":1.5}, + {"label":"#", "x":7.25, "y":1.5}, + {"label":"$", "x":8.25, "y":1.5}, + {"label":"%", "x":9.25, "y":1.5}, + {"label":"^", "x":10.25, "y":1.5}, + {"label":"&", "x":11.25, "y":1.5}, + {"label":"*", "x":12.25, "y":1.5}, + {"label":"(", "x":13.25, "y":1.5}, + {"label":")", "x":14.25, "y":1.5}, + {"label":"_", "x":15.25, "y":1.5}, + {"label":"+", "x":16.25, "y":1.5}, + {"label":"Backspace", "x":17.25, "y":1.5, "w":1.5}, + {"label":"F13", "x":18.75, "y":1.5}, + {"label":"7", "x":0, "y":2.5}, + {"label":"8", "x":1, "y":2.5}, + {"label":"9", "x":2, "y":2.5}, + {"label":"+", "x":3, "y":2.5, "h":2}, + {"label":"Tab", "x":4.25, "y":2.5, "w":1.25}, + {"label":"Q", "x":5.5, "y":2.5}, + {"label":"W", "x":6.5, "y":2.5}, + {"label":"E", "x":7.5, "y":2.5}, + {"label":"R", "x":8.5, "y":2.5}, + {"label":"T", "x":9.5, "y":2.5}, + {"label":"Y", "x":10.5, "y":2.5}, + {"label":"U", "x":11.5, "y":2.5}, + {"label":"I", "x":12.5, "y":2.5}, + {"label":"O", "x":13.5, "y":2.5}, + {"label":"P", "x":14.5, "y":2.5}, + {"label":"{", "x":15.5, "y":2.5}, + {"label":"}", "x":16.5, "y":2.5}, + {"label":"|", "x":17.5, "y":2.5, "w":1.25}, + {"label":"F16", "x":18.75, "y":2.5}, + {"label":"4", "x":0, "y":3.5}, + {"label":"5", "x":1, "y":3.5}, + {"label":"6", "x":2, "y":3.5}, + {"label":"Caps Lock", "x":4.25, "y":3.5, "w":1.25}, + {"label":"A", "x":5.75, "y":3.5}, + {"label":"S", "x":6.75, "y":3.5}, + {"label":"D", "x":7.75, "y":3.5}, + {"label":"F", "x":8.75, "y":3.5}, + {"label":"G", "x":9.75, "y":3.5}, + {"label":"H", "x":10.75, "y":3.5}, + {"label":"J", "x":11.75, "y":3.5}, + {"label":"K", "x":12.75, "y":3.5}, + {"label":"L", "x":13.75, "y":3.5}, + {"label":":", "x":14.75, "y":3.5}, + {"label":"|", "x":15.75, "y":3.5}, + {"label":"Enter", "x":16.75, "y":3.5, "w":2}, + {"label":"F14", "x":18.75, "y":3.5}, + {"label":"1", "x":0, "y":4.5}, + {"label":"2", "x":1, "y":4.5}, + {"label":"3", "x":2, "y":4.5}, + {"label":"Enter", "x":3, "y":4.5, "h":2}, + {"label":"Shift", "x":4.25, "y":4.5, "w":1.75}, + {"label":"Z", "x":6, "y":4.5}, + {"label":"X", "x":7, "y":4.5}, + {"label":"C", "x":8, "y":4.5}, + {"label":"V", "x":9, "y":4.5}, + {"label":"B", "x":10, "y":4.5}, + {"label":"N", "x":11, "y":4.5}, + {"label":"M", "x":12, "y":4.5}, + {"label":"<", "x":13, "y":4.5}, + {"label":">", "x":14, "y":4.5}, + {"label":"?", "x":15, "y":4.5}, + {"label":"Shift", "x":16, "y":4.5, "w":1.5}, + {"label":"u2191", "x":17.5, "y":4.5, "w":1.25}, + {"label":"F15", "x":18.75, "y":4.5}, + {"label":"0", "x":0, "y":5.5, "w":2}, + {"label":".", "x":2, "y":5.5}, + {"label":"Ctrl", "x":4.25, "y":5.5}, + {"label":"Fn", "x":5.25, "y":5.5}, + {"label":"", "x":6.25, "y":5.5}, + {"label":"Alt", "x":7.25, "y":5.5}, + {"x":8.25, "y":5.5, "w":6.25}, + {"label":"Alt", "x":14.5, "y":5.5}, + {"label":"Ctrl", "x":15.5, "y":5.5}, + {"label":"u2190", "x":16.5, "y":5.5}, + {"label":"u2193", "x":17.5, "y":5.5, "w":1.25}, + {"label":"u2192", "x":18.75, "y":5.5}] + } + } +} \ No newline at end of file diff --git a/keyboards/tdcleft104/keymaps/default/keymap.c b/keyboards/tdcleft104/keymaps/default/keymap.c new file mode 100644 index 00000000000..d432fc49a1d --- /dev/null +++ b/keyboards/tdcleft104/keymaps/default/keymap.c @@ -0,0 +1,28 @@ +// Copyright 2022 TDC + +// 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. + +// T his 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 + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + //default + [0] = LAYOUT_tdcleft104( + KC_ESC, KC_DEL, KC_CALC, KC_NLCK, KC_PSCR, 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_SLCK, RGB_M_P, + KC_BSPC, KC_PSLS, KC_PAST, KC_PMNS, 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_BSPC, RGB_TOG, + KC_P7, KC_P8, KC_P9, KC_PPLS, 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, RGB_MOD, + KC_P4, KC_P5, KC_P6, 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_ENT, RGB_HUD, + KC_P1, KC_P2, KC_P3, KC_PENT, KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, RGB_SAD, + KC_P0, KC_PDOT, KC_LCTL, KC_TRNS, KC_LGUI, KC_LGUI, KC_SPC, KC_RALT, KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT), +}; diff --git a/keyboards/tdcleft104/rules.mk b/keyboards/tdcleft104/rules.mk new file mode 100644 index 00000000000..af751a32685 --- /dev/null +++ b/keyboards/tdcleft104/rules.mk @@ -0,0 +1,15 @@ +# MCU name +MCU = STM32F072 + +# Bootloader selection +BOOTLOADER = stm32-dfu + +# Build Options +# comment out to disable the options. +# +BOOTMAGIC_ENABLE = yes # Enable Bootmagic Lite +EXTRAKEY_ENABLE = yes # Audio control and System control +NKRO_ENABLE = no # Enable N-Key Rollover +AUDIO_ENABLE = no # Audio output +BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality +RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow diff --git a/keyboards/tdcleft104/tdcleft104.c b/keyboards/tdcleft104/tdcleft104.c new file mode 100644 index 00000000000..10fd12d3549 --- /dev/null +++ b/keyboards/tdcleft104/tdcleft104.c @@ -0,0 +1 @@ +#include "tdcleft104.h" diff --git a/keyboards/tdcleft104/tdcleft104.h b/keyboards/tdcleft104/tdcleft104.h new file mode 100644 index 00000000000..dcfa4f9ebbd --- /dev/null +++ b/keyboards/tdcleft104/tdcleft104.h @@ -0,0 +1,37 @@ +// Copyright 2022 TDC + +// 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. + +// T his 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" + +#define ____ KC_NO + +#define LAYOUT_tdcleft104( \ + K000, K001, K002, K003, K004, K006, K007, K008, K009, K010, K011, K012, K013, K014, K015, K016, K017, K018, K019, \ + K100, K101, K102, K103, K104, K105, K106, K107, K108, K109, K110, K111, K112, K113, K114, K115, K116, K118, K119, \ + K200, K201, K202, K203, K204, K206, K207, K208, K209, K210, K211, K212, K213, K214, K215, K216, K217, K218, K219, \ + K300, K301, K302, K304, K306, K307, K308, K309, K310, K311, K312, K313, K314, K315, K316, K317, K319, \ + K400, K401, K402, K403, K405, K406, K407, K408, K409, K410, K411, K412, K413, K414, K415, K416, K418, K419, \ + K501, K502, K504, K505, K506, K507, K511, K515, K516, K517, K518, K519 \ +) { \ + { K000, K001, K002, K003, K004, ____, K006, K007, K008, K009, K010, K011, K012, K013, K014, K015, K016, K017, K018, K019 }, \ + { K100, K101, K102, K103, K104, K105, K106, K107, K108, K109, K110, K111, K112, K113, K114, K115, K116, ____, K118, K119 }, \ + { K200, K201, K202, K203, K204, ____, K206, K207, K208, K209, K210, K211, K212, K213, K214, K215, K216, K217, K218, K219 }, \ + { K300, K301, K302, ____, K304, ____, K306, K307, K308, K309, K310, K311, K312, K313, K314, K315, K316, K317, ____, K319 }, \ + { K400, K401, K402, K403, ____, K405, K406, K407, K408, K409, K410, K411, K412, K413, K414, K415, K416, ____, K418, K419 }, \ + { ____, K501, K502, ____, K504, K505, K506, K507, ____, ____, ____, K511, ____, ____, ____, K515, K516, K517, K518, K519 }, \ +} From b6c503903f8c8f222c74aaf503c863c9b3781194 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 20 Aug 2022 10:22:50 -0600 Subject: [PATCH 02/54] Delete Makefile --- keyboards/tdcleft104/Makefile | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 keyboards/tdcleft104/Makefile diff --git a/keyboards/tdcleft104/Makefile b/keyboards/tdcleft104/Makefile deleted file mode 100644 index 57b2ef62e5f..00000000000 --- a/keyboards/tdcleft104/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -ifndef MAKEFILE_INCLUDED - include ../../Makefile -endif From 425930682ce0ff48b4f8d3c93973c1b9925956a8 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 20 Aug 2022 10:23:09 -0600 Subject: [PATCH 03/54] Update tdcleft104.c --- keyboards/tdcleft104/tdcleft104.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/keyboards/tdcleft104/tdcleft104.c b/keyboards/tdcleft104/tdcleft104.c index 10fd12d3549..f08b7c2e2e2 100644 --- a/keyboards/tdcleft104/tdcleft104.c +++ b/keyboards/tdcleft104/tdcleft104.c @@ -1 +1,16 @@ +// Copyright 2022 TDC + +// 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. + +// T his 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 "tdcleft104.h" From cca6b08a7d833b4280e32ca612ede4ffd3d75b94 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 20 Aug 2022 22:18:23 -0600 Subject: [PATCH 04/54] Update keymap.c --- keyboards/tdcleft104/keymaps/default/keymap.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/keyboards/tdcleft104/keymaps/default/keymap.c b/keyboards/tdcleft104/keymaps/default/keymap.c index d432fc49a1d..1d949000e1b 100644 --- a/keyboards/tdcleft104/keymaps/default/keymap.c +++ b/keyboards/tdcleft104/keymaps/default/keymap.c @@ -17,6 +17,23 @@ #include QMK_KEYBOARD_H const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + /* LAYOUT_tdcleft104 0: Default LAYOUT_tdcleft104 + * |--------------------------------------------------------------------------------------------------------- + * |Esc |Del |Calc|Nlck|----|Pscr|----|F1 |F2 |F3 |F4 |F5 |F6 |F7 |F8 |F9 |F10 |F11 |F12 |Slck|_M_P| + * |--------------------------------------------------------------------------------------------------------- + * |----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----| + * |--------------------------------------------------------------------------------------------------------- + * |Bspc|Psls|Past|Pwns|----|Grv |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | |Bsp |Tog | + * |--------------------------------------------------------------------------------------------------------- + * |P7 |P8 |P9 |Ppls|----|Tab | |Q |W |E |R |T |Y |U |I |O |P |[ |] |\ |Mod | + * |--------------------------------------------------------------------------------------------------------- + * |P4 |P5 |P6 | | |Caps| |A |S |D |F |G |H |J |K |L |; |' |Entr| |Hud | + * |--------------------------------------------------------------------------------------------------------- + * |1 |2 |3 |Entr| | |Lshi|Z |X |C |V |B |N |M |, |. |/ |Rshi| |Up |Sad | + * |--------------------------------------------------------------------------------------------------------- + * | |0 |Pdot| | |Lctr|Lfn |LGui|Lalt| | | |Spac| | | |Ralt|Rctr|left|Down|Rght| + * `--------------------------------------------------------------------------------------------------------- + */ //default [0] = LAYOUT_tdcleft104( KC_ESC, KC_DEL, KC_CALC, KC_NLCK, KC_PSCR, 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_SLCK, RGB_M_P, From 1b0fc02e686ae4aadae4bbe06ac0f867cf0743cd Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 21 Aug 2022 12:40:25 -0600 Subject: [PATCH 05/54] Update config.h --- keyboards/tdcleft104/config.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index be7de3e8b6d..db39c199852 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -15,7 +15,7 @@ #pragma once -#include "config.h" +#include "config_common.h" /* key matrix size */ #define MATRIX_ROWS 6 @@ -23,8 +23,7 @@ /* Keyboard Matrix Assignments */ #define MATRIX_ROW_PINS {B3,B0,A7,A6,A5,A4} -#define MATRIX_COL_PINS {A9,A8,B15,B14,B13,B12,B11,B10,B2,B1,A3,A2,A1,A0,B9,B8,B7,B4,B6,B5} -#define UNUSED_PINS {A10,A11} +#define MATRIX_COL_PINS {A9,A8,B15,B14,B13,B12,B11,B10,B2,B1,A3,A2,A1,A0,B9,B8,B7,B4,B6,B5 /* COL2ROW or ROW2COL */ #define DIODE_DIRECTION COL2ROW From 225903449ca9fdb854db996f380355da676a0eff Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 21 Aug 2022 13:06:08 -0600 Subject: [PATCH 06/54] Update tdcleft104.h --- keyboards/tdcleft104/tdcleft104.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/tdcleft104.h b/keyboards/tdcleft104/tdcleft104.h index dcfa4f9ebbd..796cbeb3a3c 100644 --- a/keyboards/tdcleft104/tdcleft104.h +++ b/keyboards/tdcleft104/tdcleft104.h @@ -20,7 +20,7 @@ #define ____ KC_NO -#define LAYOUT_tdcleft104( \ +#define LAYOUT( \ K000, K001, K002, K003, K004, K006, K007, K008, K009, K010, K011, K012, K013, K014, K015, K016, K017, K018, K019, \ K100, K101, K102, K103, K104, K105, K106, K107, K108, K109, K110, K111, K112, K113, K114, K115, K116, K118, K119, \ K200, K201, K202, K203, K204, K206, K207, K208, K209, K210, K211, K212, K213, K214, K215, K216, K217, K218, K219, \ From b77b5225d238f261a1b6490ec59a25b0e0bdb226 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 21 Aug 2022 13:07:10 -0600 Subject: [PATCH 07/54] Update keymap.c --- keyboards/tdcleft104/keymaps/default/keymap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keyboards/tdcleft104/keymaps/default/keymap.c b/keyboards/tdcleft104/keymaps/default/keymap.c index 1d949000e1b..9613b339440 100644 --- a/keyboards/tdcleft104/keymaps/default/keymap.c +++ b/keyboards/tdcleft104/keymaps/default/keymap.c @@ -17,7 +17,7 @@ #include QMK_KEYBOARD_H const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { - /* LAYOUT_tdcleft104 0: Default LAYOUT_tdcleft104 + /* LAYOUT0: Default LAYOUT * |--------------------------------------------------------------------------------------------------------- * |Esc |Del |Calc|Nlck|----|Pscr|----|F1 |F2 |F3 |F4 |F5 |F6 |F7 |F8 |F9 |F10 |F11 |F12 |Slck|_M_P| * |--------------------------------------------------------------------------------------------------------- @@ -35,7 +35,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { * `--------------------------------------------------------------------------------------------------------- */ //default - [0] = LAYOUT_tdcleft104( + [0] = LAYOUT( KC_ESC, KC_DEL, KC_CALC, KC_NLCK, KC_PSCR, 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_SLCK, RGB_M_P, KC_BSPC, KC_PSLS, KC_PAST, KC_PMNS, 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_BSPC, RGB_TOG, KC_P7, KC_P8, KC_P9, KC_PPLS, 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, RGB_MOD, From 1851d33eac2c99c1cf30ba75667ab7b61180dd47 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 21 Aug 2022 13:07:38 -0600 Subject: [PATCH 08/54] Update info.json --- keyboards/tdcleft104/info.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index 9825d33f205..fe25ed1eb62 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -8,7 +8,7 @@ "device_version": "0.0.2" }, "layouts": { - "LAYOUT_tdcleft104": { + "LAYOUT": { "layout": [ {"label":"Esc", "x":0, "y":0}, {"label":"", "x":1, "y":0}, @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -} \ No newline at end of file +} From 26f7338a5ead5be89a6466390ef60fa941d8f82b Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 21 Aug 2022 13:10:33 -0600 Subject: [PATCH 09/54] Add files via upload --- keyboards/tdcleft104/readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 keyboards/tdcleft104/readme.md diff --git a/keyboards/tdcleft104/readme.md b/keyboards/tdcleft104/readme.md new file mode 100644 index 00000000000..729b6fd4813 --- /dev/null +++ b/keyboards/tdcleft104/readme.md @@ -0,0 +1,14 @@ +#[tdcleft104] + +A fullsize keyboard with 104 hot swap switches +RGB key switch lighting + +* Keyboard Maintainer: (tdc) +* Hardware Supported: TDC PCB +* Hardware Availability: US + +Qmk example for this keyboard (after setting up your build environment): + + qmk compile -kb tdcleft104 -km default + +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). From 33b77f90a5152bfaaf53865f8d888e4fe1bf3bfd Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 21 Aug 2022 14:08:10 -0600 Subject: [PATCH 10/54] Update readme.md --- keyboards/tdcleft104/readme.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/keyboards/tdcleft104/readme.md b/keyboards/tdcleft104/readme.md index 729b6fd4813..8dba21c9ddd 100644 --- a/keyboards/tdcleft104/readme.md +++ b/keyboards/tdcleft104/readme.md @@ -7,8 +7,21 @@ RGB key switch lighting * Hardware Supported: TDC PCB * Hardware Availability: US -Qmk example for this keyboard (after setting up your build environment): +* Qmk example for this keyboard (after setting up your build environment): + + qmk compile -kb tdcleft104 -km default + +* Must enter Bootloader + + ## Bootloader + +* Enter the bootloader + + **Physical reset button**: Briefly press the button on the back of the PCB. + +* Qmk example for flashing: + + qmk flash -kb tdcleft104 -km default - qmk compile -kb tdcleft104 -km default 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). From 9d85589da64665af7c9a08274b7a35bd9c866376 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Mon, 22 Aug 2022 19:00:25 -0700 Subject: [PATCH 11/54] remove empty space --- keyboards/tdcleft104/config.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index db39c199852..56d466adc1f 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -61,9 +61,3 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE - - - - - - From f56eb68c9015c3d4b1786690febddb0a0dc8cac0 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 20:28:24 -0600 Subject: [PATCH 12/54] Update info.json --- keyboards/tdcleft104/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index fe25ed1eb62..21a392f74bf 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -} ++} From 131c426e6fb637b7719ee8742efd73fb54584956 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 20:44:26 -0600 Subject: [PATCH 14/54] Update info.json --- keyboards/tdcleft104/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index 21a392f74bf..fe25ed1eb62 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -+} +} From 7ac93aaaa94833bba2c58cb7b441c01d96816066 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 21:43:25 -0600 Subject: [PATCH 15/54] Update config.h --- keyboards/tdcleft104/config.h | 1 + 1 file changed, 1 insertion(+) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 56d466adc1f..a325e5c448e 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -61,3 +61,4 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE ++ { From c7fbc0de0d76dde6f6e67b768a05f8edd581ee5a Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 21:43:50 -0600 Subject: [PATCH 16/54] Update info.json --- keyboards/tdcleft104/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index fe25ed1eb62..7e2da0da5cb 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -} ++ { From ec8fa9d7ab5035809af038572be7b0d81086190f Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 21:50:26 -0600 Subject: [PATCH 17/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index a325e5c448e..28e28df3670 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -61,4 +61,4 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE -+ { +{ From 3ecce2a94e83cfd312c8ad84494705478b9232b4 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 21:50:54 -0600 Subject: [PATCH 18/54] Update info.json --- keyboards/tdcleft104/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index 7e2da0da5cb..95f79285704 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -+ { +{ From 191c043155c660380636f30b0ee0c86a1602d9b3 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 21:52:32 -0600 Subject: [PATCH 19/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 28e28df3670..affee291be4 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -61,4 +61,4 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE -{ +-{ From 3abd4ffb3766a14b043dcc9b6f9e2332c21155e3 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 21:53:15 -0600 Subject: [PATCH 20/54] Update config.h --- keyboards/tdcleft104/config.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index affee291be4..177d7021647 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -1,3 +1,4 @@ +{ // Copyright 2022 TDC // This program is free software: you can redistribute it and/or modify @@ -61,4 +62,4 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE --{ +{ From e6fb4a3c42b63f84368b326ba2a37654eb13b6f1 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Mon, 22 Aug 2022 22:10:59 -0600 Subject: [PATCH 21/54] Update info.json --- keyboards/tdcleft104/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index 95f79285704..fe25ed1eb62 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -{ +} From b855300bad9f78a9e9b1ce15a187eb814bec11a8 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 07:34:37 -0600 Subject: [PATCH 22/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 177d7021647..7c3aea35c93 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -62,4 +62,4 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE -{ + { From 32cd2dc1fcfa723e5c8963d32007742d2fe25313 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 07:34:48 -0600 Subject: [PATCH 23/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 7c3aea35c93..c8dcbbe7ec8 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -62,4 +62,4 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE - { + { From 04a16bc1ca7bbbab501d0a7313e543907bc0797c Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 23 Aug 2022 23:37:52 +1000 Subject: [PATCH 24/54] Update keyboards/tdcleft104/config.h --- keyboards/tdcleft104/config.h | 1 - 1 file changed, 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index c8dcbbe7ec8..8a7101548e3 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -1,4 +1,3 @@ -{ // Copyright 2022 TDC // This program is free software: you can redistribute it and/or modify From c4cb35b9e9a8db04a1fe78eea25b15e4affd3822 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 23 Aug 2022 23:38:00 +1000 Subject: [PATCH 25/54] Update keyboards/tdcleft104/config.h --- keyboards/tdcleft104/config.h | 1 - 1 file changed, 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 8a7101548e3..56d466adc1f 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -61,4 +61,3 @@ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ #define LOCKING_RESYNC_ENABLE - { From 847bbce8c013e857e00c953f3b57a47aaa4f1be3 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 07:43:30 -0600 Subject: [PATCH 26/54] Update rules.mk --- keyboards/tdcleft104/rules.mk | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/keyboards/tdcleft104/rules.mk b/keyboards/tdcleft104/rules.mk index af751a32685..a1e028f2805 100644 --- a/keyboards/tdcleft104/rules.mk +++ b/keyboards/tdcleft104/rules.mk @@ -5,11 +5,14 @@ MCU = STM32F072 BOOTLOADER = stm32-dfu # Build Options -# comment out to disable the options. +# change yes to no to disable # -BOOTMAGIC_ENABLE = yes # Enable Bootmagic Lite +BOOTMAGIC_ENABLE = no # Enable Bootmagic Lite +MOUSEKEY_ENABLE = no # Mouse keys EXTRAKEY_ENABLE = yes # Audio control and System control +CONSOLE_ENABLE = no # Console for debug +COMMAND_ENABLE = no # Commands for debug and configuration NKRO_ENABLE = no # Enable N-Key Rollover -AUDIO_ENABLE = no # Audio output BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow +AUDIO_ENABLE = no # Audio output From 3f22b7cdb535adfac279839d586f736c8df24ec3 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:07:56 -0600 Subject: [PATCH 27/54] Update info.json --- keyboards/tdcleft104/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index fe25ed1eb62..d798cca356d 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -6,7 +6,7 @@ "vid": "0x7856", "pid": "0x6163", "device_version": "0.0.2" - }, + }, "layouts": { "LAYOUT": { "layout": [ From 7412f5d2c915a1f38752a52be42fb2281a080a75 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:09:27 -0600 Subject: [PATCH 28/54] Update rules.mk --- keyboards/tdcleft104/rules.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keyboards/tdcleft104/rules.mk b/keyboards/tdcleft104/rules.mk index a1e028f2805..88bb6a4ee02 100644 --- a/keyboards/tdcleft104/rules.mk +++ b/keyboards/tdcleft104/rules.mk @@ -1,3 +1,4 @@ +{ # MCU name MCU = STM32F072 @@ -16,3 +17,4 @@ NKRO_ENABLE = no # Enable N-Key Rollover BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output +} From c2f230b9eed8237983766dba0d490b56cfb2d94b Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:26:34 -0600 Subject: [PATCH 29/54] Add files via upload --- keyboards/tdcleft104/rules.mk | 38 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/keyboards/tdcleft104/rules.mk b/keyboards/tdcleft104/rules.mk index 88bb6a4ee02..b4f9de0f27d 100644 --- a/keyboards/tdcleft104/rules.mk +++ b/keyboards/tdcleft104/rules.mk @@ -1,20 +1,18 @@ -{ -# MCU name -MCU = STM32F072 - -# Bootloader selection -BOOTLOADER = stm32-dfu - -# Build Options -# change yes to no to disable -# -BOOTMAGIC_ENABLE = no # Enable Bootmagic Lite -MOUSEKEY_ENABLE = no # Mouse keys -EXTRAKEY_ENABLE = yes # Audio control and System control -CONSOLE_ENABLE = no # Console for debug -COMMAND_ENABLE = no # Commands for debug and configuration -NKRO_ENABLE = no # Enable N-Key Rollover -BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality -RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow -AUDIO_ENABLE = no # Audio output -} +# MCU name +MCU = STM32F072 + +# Bootloader selection +BOOTLOADER = stm32-dfu + +# Build Options +# change yes to no to disable +# +BOOTMAGIC_ENABLE = no # Enable Bootmagic Lite +MOUSEKEY_ENABLE = no # Mouse keys +EXTRAKEY_ENABLE = yes # Audio control and System control +CONSOLE_ENABLE = no # Console for debug +COMMAND_ENABLE = no # Commands for debug and configuration +NKRO_ENABLE = no # Enable N-Key Rollover +BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality +RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow +AUDIO_ENABLE = no # Audio output \ No newline at end of file From 78906ac755882319520e58b13f2408de69220bb0 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:29:09 -0600 Subject: [PATCH 30/54] Add files via upload --- keyboards/tdcleft104/info.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json index d798cca356d..95eeb6bb938 100644 --- a/keyboards/tdcleft104/info.json +++ b/keyboards/tdcleft104/info.json @@ -6,7 +6,7 @@ "vid": "0x7856", "pid": "0x6163", "device_version": "0.0.2" - }, +}, "layouts": { "LAYOUT": { "layout": [ @@ -116,4 +116,4 @@ {"label":"u2192", "x":18.75, "y":5.5}] } } -} +} \ No newline at end of file From 92c88da74ee9894fa498a4a39f3ff89ea398f1d3 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:36:53 -0700 Subject: [PATCH 31/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 56d466adc1f..3e579ba38b7 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -23,7 +23,7 @@ /* Keyboard Matrix Assignments */ #define MATRIX_ROW_PINS {B3,B0,A7,A6,A5,A4} -#define MATRIX_COL_PINS {A9,A8,B15,B14,B13,B12,B11,B10,B2,B1,A3,A2,A1,A0,B9,B8,B7,B4,B6,B5 +#define MATRIX_COL_PINS {A9,A8,B15,B14,B13,B12,B11,B10,B2,B1,A3,A2,A1,A0,B9,B8,B7,B4,B6,B5} /* COL2ROW or ROW2COL */ #define DIODE_DIRECTION COL2ROW From d2418eec520381146fa09c409a8ab2d1e3cb8701 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:38:46 -0700 Subject: [PATCH 32/54] Update tdcleft104.h --- keyboards/tdcleft104/tdcleft104.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/tdcleft104.h b/keyboards/tdcleft104/tdcleft104.h index 796cbeb3a3c..36370a0c771 100644 --- a/keyboards/tdcleft104/tdcleft104.h +++ b/keyboards/tdcleft104/tdcleft104.h @@ -5,7 +5,7 @@ // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. -// T his program is distributed in the hope that it will be useful, +// 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. From 52474aa53e8278721f208e8fe66278c2c2317d09 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:42:34 -0500 Subject: [PATCH 33/54] Update tdcleft104.c --- keyboards/tdcleft104/tdcleft104.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/tdcleft104.c b/keyboards/tdcleft104/tdcleft104.c index f08b7c2e2e2..6b4e8d1bb23 100644 --- a/keyboards/tdcleft104/tdcleft104.c +++ b/keyboards/tdcleft104/tdcleft104.c @@ -1,4 +1,4 @@ -// Copyright 2022 TDC +// Copyright 2024 TDC // 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 From f00cbe60582e2cce5853d17edd494034e57ac463 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:45:58 -0500 Subject: [PATCH 34/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 3e579ba38b7..05939bfe73c 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -56,7 +56,7 @@ //#define MATRIX_HAS_GHOST /* Set 0 if debouncing isn't needed */ -#define DEBOUNCE 5 +#define DEBOUNCE 3 #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ From f675ab4d7892269eb4b9f1489447fc7d24eae8f8 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:46:14 -0500 Subject: [PATCH 35/54] Update config.h --- keyboards/tdcleft104/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h index 05939bfe73c..ec315e04bd6 100644 --- a/keyboards/tdcleft104/config.h +++ b/keyboards/tdcleft104/config.h @@ -1,4 +1,4 @@ -// Copyright 2022 TDC +// Copyright 2024 TDC // 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 From 1a361834f56c6081df1a239026bca6ad94910ac5 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:46:44 -0500 Subject: [PATCH 36/54] Update keymap.c --- keyboards/tdcleft104/keymaps/default/keymap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/keymaps/default/keymap.c b/keyboards/tdcleft104/keymaps/default/keymap.c index 9613b339440..4904cfbf16d 100644 --- a/keyboards/tdcleft104/keymaps/default/keymap.c +++ b/keyboards/tdcleft104/keymaps/default/keymap.c @@ -1,4 +1,4 @@ -// Copyright 2022 TDC +// Copyright 2024 TDC // 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 From ce87d1ce9f8c7a39ef09d3cedc09fcf16fb26d83 Mon Sep 17 00:00:00 2001 From: TDC <64564678+kthorpe88@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:50:43 -0500 Subject: [PATCH 37/54] Update tdcleft104.h --- keyboards/tdcleft104/tdcleft104.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tdcleft104/tdcleft104.h b/keyboards/tdcleft104/tdcleft104.h index 36370a0c771..2843931e943 100644 --- a/keyboards/tdcleft104/tdcleft104.h +++ b/keyboards/tdcleft104/tdcleft104.h @@ -1,4 +1,4 @@ -// Copyright 2022 TDC +// Copyright 2024 TDC // 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 From 1e625edd2d7f840746c54139288d577163dac5ac Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 3 May 2025 08:45:08 -0400 Subject: [PATCH 38/54] Add TS-Southpaw keyboard support with RGB effects and custom keymap - Created keyboard.json for TS-Southpaw-Rev-1.6 with specifications including USB VID/PID and features. - Implemented default keymap in keymap.c with multiple layers: Base, Function, and Media control. - Added RGB effects handling in rgb_effects.c, including ESC ripple effect and indicator LEDs for Caps Lock, Num Lock, and Mic Mute. - Defined RGB effect types and utility functions in rgb_effects.h for better organization. - Configured rules.mk for RP2040 platform, enabling RGB matrix and custom effects while optimizing for size. --- keyboards/tssouthpaw/README.md | 75 +++++ keyboards/tssouthpaw/config.h | 106 +++++++ keyboards/tssouthpaw/keyboard.json | 169 +++++++++++ keyboards/tssouthpaw/keymaps/default/keymap.c | 262 +++++++++++++++++ .../tssouthpaw/keymaps/default/rgb_effects.c | 266 ++++++++++++++++++ keyboards/tssouthpaw/rgb_effects.h | 67 +++++ keyboards/tssouthpaw/rules.mk | 39 +++ 7 files changed, 984 insertions(+) create mode 100644 keyboards/tssouthpaw/README.md create mode 100644 keyboards/tssouthpaw/config.h create mode 100644 keyboards/tssouthpaw/keyboard.json create mode 100644 keyboards/tssouthpaw/keymaps/default/keymap.c create mode 100644 keyboards/tssouthpaw/keymaps/default/rgb_effects.c create mode 100644 keyboards/tssouthpaw/rgb_effects.h create mode 100644 keyboards/tssouthpaw/rules.mk diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md new file mode 100644 index 00000000000..4aaa33d99c1 --- /dev/null +++ b/keyboards/tssouthpaw/README.md @@ -0,0 +1,75 @@ +# TS-Southpaw-Rev-1.6 + +![TS-Southpaw-Rev-1.6](https://i.imgur.com/placeholder.jpg) + +*A full-sized southpaw mechanical keyboard with RGB lighting and rotary encoder* + +* Keyboard Maintainer: [TS Design Works LLC](https://github.com/tsdesignworks) +* Hardware Supported: TS-Southpaw-Rev-1.6 PCB +* Hardware Availability: [TS Design Works LLC](https://github.com/tsdesignworks) + +## Features + +- Full-sized southpaw layout (numpad on left side) +- 104 hot-swappable switch positions +- Per-key RGB lighting (104 LEDs) +- Rotary encoder with dual-mode functionality +- RP2040 microcontroller (USB-C connectivity) +- QMK firmware with dynamic macros +- Custom RGB indicators for keyboard state +- Visual ESC key ripple effect + +## Keyboard Layout + +### Base Layer +Standard QWERTY layout with numpad on the left side. Function key for accessing additional layers. + +### Function Layer (Fn) +RGB controls, keyboard configuration, and additional functions. + +### Media Layer +Media control keys and alternative encoder mode. + +## Firmware Building + +Make example for this keyboard (after setting up your build environment): + +```shell +qmk compile -kb tssouthpaw -km default +``` + +Flashing example for this keyboard: + +```shell +qmk flash -kb tssouthpaw -km default +``` + +## Bootloader + +Enter the bootloader in 1 of 2 ways: + +* **Physical reset button**: Briefly press the button on the back of the PCB +* **Bootmagic reset**: Hold down the Escape key and plug in the keyboard + +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). + +## RGB Lighting + +Default startup color is green. Special indicators include: +- Caps Lock: Orange pulsing when active +- Num Lock: Orange pulsing when inactive +- Mic Mute: Red pulsing when active +- ESC key: Red ripple effect on press +- Arrow keys: Blue highlighting in solid color mode + +## Rotary Encoder + +The rotary encoder has two modes: +- Default mode: Volume control (rotate for volume up/down) +- Media mode: Track control (rotate for next/previous track) + +## Dynamic Macros + +To record and play macros: +- Record: Press `Fn + DM_REC1` or `Fn + DM_REC2`, input sequence, press `Fn + DM_REC1/2` again +- Playback: Press `Fn + DM_PLY1` or `Fn + DM_PLY2` \ No newline at end of file diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h new file mode 100644 index 00000000000..376232df517 --- /dev/null +++ b/keyboards/tssouthpaw/config.h @@ -0,0 +1,106 @@ +/* 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 + + /******************************************************************************* + * KEYBOARD CORE SETTINGS + ******************************************************************************/ + // Debounce configuration + #define DEBOUNCE 5 // 5ms for better key chatter prevention + + // Dynamic macro configuration + #define DYNAMIC_MACRO_EEPROM_STORAGE // Enable dynamic macro storage in EEPROM + #define DYNAMIC_MACRO_SIZE 256 // Larger macro size + + // Buffer Sizes + #define SERIAL_BUFFER_SIZE 64 // Serial buffer size for communication + + /******************************************************************************* + * POWER MANAGEMENT + ******************************************************************************/ + #define USB_SUSPEND_WAKEUP_DELAY 200 // Delay for waking from suspend + + /******************************************************************************* + * RGB MATRIX CONFIGURATION + ******************************************************************************/ + // Basic configuration + #define RGB_MATRIX_LED_COUNT 104 // Total number of LEDs in the RGB matrix + #define RGB_MATRIX_TIMEOUT 300000 // Auto turn-off RGB after 5 minutes of inactivity + #define RGB_MATRIX_SLEEP // Disable RGB effects when the keyboard is suspended + + // Performance optimization + #define RGB_MATRIX_LED_PROCESS_LIMIT ((RGB_MATRIX_LED_COUNT + 7) / 8) // Optimized processing limit + #define RGB_MATRIX_LED_FLUSH_LIMIT 16 // Moderate flush limit to reduce CPU usage + #define RGB_MATRIX_KEYRELEASES // Reactive effects respond to key releases + + // Brightness and default settings + #define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200 // Cap maximum brightness to 200/255 + #define RGB_MATRIX_DEFAULT_SPD 100 // Default animation speed + #define RGB_MATRIX_DEFAULT_VAL 128 // Default brightness value (50%) + #define RGB_MATRIX_DEFAULT_HUE 0 // Default hue (red) + + // Startup configuration + #define RGB_MATRIX_STARTUP_MODE RGB_MATRIX_SOLID_COLOR // Default startup mode + #define RGB_MATRIX_STARTUP_HUE 85 // Green color on startup + #define RGB_MATRIX_STARTUP_SAT 255 // Full saturation + #define RGB_MATRIX_STARTUP_VAL 128 // Medium brightness on startup + + /******************************************************************************* + * TYPING HEATMAP CONFIGURATION + ******************************************************************************/ + #define RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 50 // Delay before heatmap cools down + #define RGB_MATRIX_TYPING_HEATMAP_SPREAD 40 // Spread of heatmap effect to surrounding keys + #define RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16 // Limit heat intensity for surrounding keys + #define RGB_MATRIX_TYPING_HEATMAP_INCREASE_STEP 32 // Keystroke count for fully heating a key + + /******************************************************************************* + * ENABLED RGB MATRIX EFFECTS + ******************************************************************************/ + // Basic effects + #define ENABLE_RGB_MATRIX_SOLID_COLOR + #define ENABLE_RGB_MATRIX_BREATHING + + // Gradient effects + #define ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN + #define ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT + + // Cycling effects + #define ENABLE_RGB_MATRIX_CYCLE_OUT_IN + #define ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL + #define ENABLE_RGB_MATRIX_CYCLE_PINWHEEL + #define ENABLE_RGB_MATRIX_CYCLE_SPIRAL + + // Moving effects + #define ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON + #define ENABLE_RGB_MATRIX_RAINBOW_BEACON + #define ENABLE_RGB_MATRIX_RAINBOW + #define ENABLE_RGB_MATRIX_RAINBOW_SWIRL + #define ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS + + // Reactive effects + #define ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE + #define ENABLE_RGB_MATRIX_SOLID_REACTIVE + #define ENABLE_RGB_MATRIX_SPLASH + #define ENABLE_RGB_MATRIX_MULTISPLASH + #define ENABLE_RGB_MATRIX_SOLID_SPLASH + + // Special effects + #define ENABLE_RGB_MATRIX_RAINDROPS + #define ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS + #define ENABLE_RGB_MATRIX_TYPING_HEATMAP + #define ENABLE_RGB_MATRIX_DIGITAL_RAIN + #define ENABLE_RGB_MATRIX_EFFECT_PIXEL_RAIN \ No newline at end of file diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json new file mode 100644 index 00000000000..05abb624f27 --- /dev/null +++ b/keyboards/tssouthpaw/keyboard.json @@ -0,0 +1,169 @@ +{ + "keyboard_name": "TS-Southpaw-Rev-1.6", + "url": "https://github.com/tsdesignworks/tssouthpaw", + "maintainer": "ts_design_works_llc", + "manufacturer": "TS Design Works LLC", + "usb": { + "vid": "0x7856", + "pid": "0x6163", + "device_version": "0.0.2" + }, + "features": { + "backlight": false, + "bootmagic": true, + "command": false, + "console": false, + "extrakey": true, + "mousekey": false, + "nkro": true + }, + "qmk": { + "locking": { + "enabled": true, + "resync": 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"] + }, + "ws2812": { + "pin": "GP27" + }, + "diode_direction": "COL2ROW", + "processor": "RP2040", + "bootloader": "rp2040", + "platform": "rp2040", + "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":19, "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} + ] + } + } +} \ No newline at end of file diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c new file mode 100644 index 00000000000..d4c653375c5 --- /dev/null +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -0,0 +1,262 @@ +/* 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.h" + + // Define layers + enum layers { + BASE, // Base layer + FN, // Function layer + MEDIA // Media control layer + }; + + // Define custom keycodes + enum custom_keycodes { + KC_MICMUTE = SAFE_RANGE, // Microphone mute key + KC_MEDIA, // Media layer toggle + }; + + /** + * Encoder mapping + * Defines how the encoder behaves in different layers + */ +#if defined(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(RGB_HUD, RGB_HUI) }, + [MEDIA] = { ENCODER_CCW_CW(KC_MPRV, KC_MNXT) } +}; +#endif // ENCODER_MAP_ENABLE + + // Define combo actions for special keys + #define KC_MUTE_PLAY (KC_MUTE | KC_MPLY << 8) // Mute + Play/Pause combo + + // Variable to track encoder functionality mode + static bool encoder_media_mode = false; // false = volume, true = media control + + /** + * Rotary encoder handling + */ + bool encoder_update_user(uint8_t index, bool clockwise) { + // Different behavior based on encoder mode + if (encoder_media_mode) { + // Media control mode + if (clockwise) { + tap_code(KC_MNXT); // Next track + } else { + tap_code(KC_MPRV); // Previous track + } + } else { + // Volume control mode (default) + if (clockwise) { + tap_code(KC_VOLU); // Volume up + } else { + tap_code(KC_VOLD); // Volume down + } + } + return true; + } + + /** + * 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; + + case KC_MEDIA: + if (record->event.pressed) { + encoder_media_mode = !encoder_media_mode; // Toggle encoder mode + } + return false; + + default: + return true; // Process all other keycodes normally + } + } + + /** + * Layer state change handler + * Can be used for layer-dependent lighting + */ + layer_state_t layer_state_set_user(layer_state_t state) { + uint8_t layer = get_highest_layer(state); + + // Layer-specific lighting could be implemented here + switch (layer) { + case BASE: + // Default RGB settings for base layer + break; + case FN: + // Highlight function keys on FN layer + break; + case MEDIA: + // Highlight media controls on MEDIA layer + break; + } + + return state; + } + + /** + * 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 , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, RM_VALD, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + _______, _______, _______, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, RM_VALU, _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + _______, _______, _______, KC_NO , KC_NO , RM_HUEU, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, KC_NO , + KC_NO, _______, _______, _______, RM_PREV, RM_HUED, RM_NEXT, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ + ), + /* Media Control Layer */ + [MEDIA] = LAYOUT( + _______, _______, _______, _______, KC_NO , KC_NO , KC_NO , KC_MUTE, KC_MPRV, KC_MPLY, KC_MNXT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + _______, _______, _______, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + _______, _______, _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, KC_NO , + KC_NO, _______, _______, _______, _______, _______, _______, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ + ), + }; + + /** + * LED matrix configuration + * Maps physical key layout to LED array + */ + #ifdef RGB_MATRIX_ENABLE + led_config_t g_led_config = { + /* key_matrix to LED index (6 rows × 21 cols) */ + { + { 0, 1, 2, 3, NO_LED, NO_LED, NO_LED, NO_LED, 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, 34, 35, NO_LED, 36 }, + { 37, 38, 39, NO_LED, 40, 41, NO_LED, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55 }, + { 56, 57, 58, 59, 60, 61, NO_LED, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, NO_LED, 74 }, + { 75, 76, 77, NO_LED, NO_LED, 78, NO_LED, 79, NO_LED, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, NO_LED }, + { NO_LED, 91, 92, 93, 94, 95, 96, NO_LED, 97, 98, NO_LED, NO_LED, NO_LED, 99, NO_LED, NO_LED, 100, NO_LED, 101, 102, 103 } + }, + + /* LED index to physical position - scaled 0-224 × 0-64 */ + { + { 0, 0 }, { 11, 0 }, { 22, 0 }, { 34, 0 }, { 45, 0 }, + { 56, 0 }, { 68, 0 }, { 78, 0 }, { 89, 0 }, { 100, 0 }, + { 112, 0 }, { 123, 0 }, { 134, 0 }, { 146, 0 }, { 157, 0 }, + { 168, 0 }, { 179, 0 }, { 191, 0 }, { 202, 0 }, { 213, 0 }, + { 224, 0 }, + { 0, 13 }, { 11, 13 }, { 22, 13 }, { 34, 13 }, { 45, 13 }, + { 56, 13 }, { 68, 13 }, { 78, 13 }, { 89, 13 }, { 100, 13 }, + { 112, 13 }, { 123, 13 }, { 134, 13 }, { 146, 13 }, { 157, 13 }, + { 168, 13 }, { 179, 13 }, { 191, 13 }, { 202, 13 }, { 213, 13 }, + { 224, 13 }, + { 0, 26 }, { 11, 26 }, { 22, 26 }, { NO_LED, NO_LED }, { 45, 26 }, + { 56, 26 }, { NO_LED, NO_LED }, { 78, 26 }, { 89, 26 }, { 100, 26 }, + { 112, 26 }, { 123, 26 }, { 134, 26 }, { 146, 26 }, { 157, 26 }, + { 168, 26 }, { 179, 26 }, { 191, 26 }, { 202, 26 }, { 213, 26 }, + { 224, 26 }, + { 0, 38 }, { 11, 38 }, { 22, 38 }, { 34, 38 }, { 45, 38 }, + { 56, 38 }, { NO_LED, NO_LED }, { 78, 38 }, { 89, 38 }, { 100, 38 }, + { 112, 38 }, { 123, 38 }, { 134, 38 }, { 146, 38 }, { 157, 38 }, + { 168, 38 }, { 179, 38 }, { 191, 38 }, { 202, 38 }, { NO_LED, NO_LED }, { 224, 38 }, + { 0, 51 }, { 11, 51 }, { 22, 51 }, { NO_LED, NO_LED }, { NO_LED, NO_LED }, { 56, 51 }, + { NO_LED, NO_LED }, { 78, 51 }, { NO_LED, NO_LED }, { 100, 51 }, { 112, 51 }, { 123, 51 }, + { 134, 51 }, { 146, 51 }, { 157, 51 }, { 168, 51 }, { 179, 51 }, { 191, 51 }, + { 202, 51 }, { 213, 51 } + }, + + /* LED flags - all keys use the keylight flag */ + { + [0 ... 103] = LED_FLAG_KEYLIGHT + } + }; + #endif /* RGB_MATRIX_ENABLE */ + + /** + * Custom tapping term configuration + */ + uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case MO(FN): + return TAPPING_TERM - 50; // Slightly faster tapping term for layer switch + default: + return TAPPING_TERM; + } + } + + /** + * Keyboard initialization + * Called once at startup + */ + void keyboard_post_init_user(void) { + // Initialize RGB lighting effects + rgb_matrix_mode(RGB_MATRIX_SOLID_COLOR); + rgb_matrix_sethsv(HSV_GREEN); // Set default color to green + + // Set default behavior for NKRO + keymap_config.nkro = true; + + // Set encoder initial mode + encoder_media_mode = false; + } + + /** + * Power management functions + */ + void suspend_power_down_user(void) { + // Disable RGB effects when computer is suspended + rgb_matrix_set_suspend_state(true); + } + + void suspend_wakeup_init_user(void) { + // Re-enable RGB effects when computer wakes up + rgb_matrix_set_suspend_state(false); + } \ No newline at end of file diff --git a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c new file mode 100644 index 00000000000..009b5bbd663 --- /dev/null +++ b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c @@ -0,0 +1,266 @@ +/* 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.h" + + /** + * RGB Color structure for cleaner color definitions + */ + typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + } rgb_color_t; + + /** + * 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(); + + return true; // continue with the normal effect pipeline + } \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects.h b/keyboards/tssouthpaw/rgb_effects.h new file mode 100644 index 00000000000..e294f7c14e9 --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects.h @@ -0,0 +1,67 @@ +/* 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 QMK_KEYBOARD_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 { + EFFECT_NONE = 0, + EFFECT_PULSE, + EFFECT_RIPPLE, + EFFECT_FLASH, + EFFECT_SOLID + } rgb_effect_type_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); + } \ No newline at end of file diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk new file mode 100644 index 00000000000..2fddbe861d2 --- /dev/null +++ b/keyboards/tssouthpaw/rules.mk @@ -0,0 +1,39 @@ +# MCU name and parameters +MCU = RP2040 +F_CPU = 125000000 +BOOTLOADER = rp2040 +PLATFORM = rp2040 + +# RGB Matrix configuration +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = ws2812 +WS2812_DRIVER = vendor +RGB_MATRIX_FRAMEBUFFER_EFFECTS = yes +RGB_MATRIX_KEYPRESSES = yes +RGB_MATRIX_TYPING_HEATMAP = yes +RGB_MATRIX_CUSTOM_KB = no +RGB_MATRIX_CUSTOM_USER = no +RGBLIGHT_SLEEP = yes + +# Include custom RGB effects +SRC += rgb_effects.c + +# Keyboard features +DYNAMIC_MACRO_ENABLE = yes +ENCODER_ENABLE = yes +ENCODER_MAP_ENABLE = yes +KEY_LOCK_ENABLE = yes +LTO_ENABLE = yes + +# Disabled features (to save space and optimize) +DISABLE_ADC = yes +MIDI_ENABLE = no +BLUETOOTH_ENABLE = no +AUDIO_ENABLE = no +LEADER_ENABLE = no +SPACE_CADET_ENABLE = no +GRAVE_ESC_ENABLE = no +MAGIC_ENABLE = no + +# Include paths for QMK firmware headers (required for RP2040) +EXTRAFLAGS += -I"$(QUANTUM_DIR)" -I"$(PLATFORM_DIR)" -I"$(CHIBIOS_DIR)/os/license" -I"$(CHIBIOS_DIR)/os/hal/include" -I"$(CHIBIOS_DIR)/os/hal/ports/$(MCU)" -I"$(CHIBIOS_DIR)/os/hal/ports/$(MCU)/LLD" -I"$(CHIBIOS_DIR)/os/hal/lib/streams" -I"$(CHIBIOS_DIR)/os/kernel/include" -I"$(CHIBIOS_DIR)/os/various" \ No newline at end of file From e1f178ca954df1e4aba3b9f3edfca73c4b6c95db Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 3 May 2025 09:08:14 -0400 Subject: [PATCH 39/54] Update image link in README for TS-Southpaw-Rev-1.6 --- keyboards/tssouthpaw/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md index 4aaa33d99c1..039ab78cae7 100644 --- a/keyboards/tssouthpaw/README.md +++ b/keyboards/tssouthpaw/README.md @@ -1,6 +1,6 @@ # TS-Southpaw-Rev-1.6 -![TS-Southpaw-Rev-1.6](https://i.imgur.com/placeholder.jpg) +![TS-Southpaw-Rev-1.6](https://drive.google.com/file/d/1GbzhuI_gff_ezSdlNdn-enrzYQLmFRU3/view?usp=drive_link) *A full-sized southpaw mechanical keyboard with RGB lighting and rotary encoder* From b2f38bdc20d8362e1a413a9a7d50070d5e7d0780 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 3 May 2025 10:57:15 -0400 Subject: [PATCH 40/54] Add work timer feature with visual indicators and RGB effects --- keyboards/tssouthpaw/README.md | 18 +- keyboards/tssouthpaw/keymaps/default/keymap.c | 101 +++-- .../tssouthpaw/keymaps/default/rgb_effects.c | 378 ++++++++++++++---- keyboards/tssouthpaw/rgb_effects.h | 14 + 4 files changed, 383 insertions(+), 128 deletions(-) diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md index 039ab78cae7..055f62bb093 100644 --- a/keyboards/tssouthpaw/README.md +++ b/keyboards/tssouthpaw/README.md @@ -1,6 +1,6 @@ # TS-Southpaw-Rev-1.6 -![TS-Southpaw-Rev-1.6](https://drive.google.com/file/d/1GbzhuI_gff_ezSdlNdn-enrzYQLmFRU3/view?usp=drive_link) +![TS-Southpaw-Rev-1.6](https://imgur.com/a/pUxQAhL) *A full-sized southpaw mechanical keyboard with RGB lighting and rotary encoder* @@ -18,6 +18,7 @@ - QMK firmware with dynamic macros - Custom RGB indicators for keyboard state - Visual ESC key ripple effect +- 8-hour work timer with progress bar ## Keyboard Layout @@ -62,6 +63,21 @@ Default startup color is green. Special indicators include: - ESC key: Red ripple effect on press - Arrow keys: Blue highlighting in solid color mode +## Work Timer + +The keyboard features an 8-hour work timer with visual indicators: +- F1-F12 keys serve as a progress bar, showing elapsed work time +- Colors fade from green (start) through yellow (middle) to red (end) +- Blue flash at 4 hours for lunch break +- Blue warning flash 5 minutes before lunch break ends +- Red warning flash for the last 5 minutes of the workday + +To use the work timer: +- Start/Stop: Press `Fn + DM_PLY1` +- Pause/Resume: Press `Fn + DM_PLY2` + +The timer automatically shuts off after 8 hours have elapsed. + ## Rotary Encoder The rotary encoder has two modes: diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c index d4c653375c5..d044590380a 100644 --- a/keyboards/tssouthpaw/keymaps/default/keymap.c +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -25,10 +25,12 @@ }; // Define custom keycodes - enum custom_keycodes { - KC_MICMUTE = SAFE_RANGE, // Microphone mute key - KC_MEDIA, // Media layer toggle - }; +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 +}; /** * Encoder mapping @@ -76,44 +78,57 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { * 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; - - case KC_MEDIA: - if (record->event.pressed) { - encoder_media_mode = !encoder_media_mode; // Toggle encoder mode - } - return false; - - default: - return true; // Process all other keycodes normally - } - } - + // 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; + + case KC_MEDIA: + if (record->event.pressed) { + encoder_media_mode = !encoder_media_mode; // Toggle encoder mode + } + return false; + + // New keycodes for work timer + case KC_WRKTMR: + if (record->event.pressed) { + toggle_work_timer(); // Toggle the work timer on/off + } + return false; + + case KC_WRKPAU: + if (record->event.pressed) { + toggle_pause_work_timer(); // Pause/resume the work timer + } + return false; + + default: + return true; // Process all other keycodes normally + } +} + /** * Layer state change handler * Can be used for layer-dependent lighting @@ -155,7 +170,7 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { _______, _______, _______, _______, KC_NO , KC_NO , KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RM_VALD, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, _______, _______, _______, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, RM_VALU, _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, + _______, _______, _______, RM_VALU, KC_WRKTMR, KC_WRKPAU, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, _______, _______, _______, KC_NO , KC_NO , RM_HUEU, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, KC_NO , KC_NO, _______, _______, _______, RM_PREV, RM_HUED, RM_NEXT, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ ), diff --git a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c index 009b5bbd663..ef5b27cd8d9 100644 --- a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c +++ b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c @@ -35,6 +35,15 @@ 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 + /** + * Work Timer color gradient from green to red + */ + static const rgb_color_t WORK_TIMER_START_COLOR = {0, 255, 0}; // Green + static const rgb_color_t WORK_TIMER_MID_COLOR = {255, 255, 0}; // Yellow + static const rgb_color_t WORK_TIMER_END_COLOR = {255, 0, 0}; // Red + static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {0, 0, 255}; // Blue + static const rgb_color_t WORK_TIMER_WARNING_COLOR = {255, 0, 0}; // Red + /** * State tracking structure for LED effects */ @@ -50,6 +59,20 @@ static led_state_t num_lock_state = {false, 0}; static led_state_t mic_mute_state = {false, 0}; + /** + * Work Timer state variables + */ + static struct { + bool active; + uint32_t start_time; + uint32_t elapsed_time; + bool lunch_break; + bool lunch_warning_shown; + bool end_warning_shown; + bool paused; + uint32_t pause_time; + } work_timer_state = {false, 0, 0, false, false, false, false, 0}; + /** * ESC ripple effect state variables */ @@ -180,87 +203,274 @@ * 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(); - - return true; // continue with the normal effect pipeline - } \ No newline at end of file + 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); + } + } +} + +/** + * Calculate color gradient between two colors based on progress (0.0 - 1.0) + */ +static 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; +} + +/** + * Toggle the work timer on/off + */ +void toggle_work_timer(void) { + if (!work_timer_state.active) { + // Start the timer + work_timer_state.active = true; + work_timer_state.start_time = timer_read32(); + work_timer_state.elapsed_time = 0; + work_timer_state.lunch_break = false; + work_timer_state.lunch_warning_shown = false; + work_timer_state.end_warning_shown = false; + work_timer_state.paused = false; + work_timer_state.pause_time = 0; + } else { + // Stop the timer + work_timer_state.active = false; + } +} + +/** + * Pause or resume the work timer + */ +void toggle_pause_work_timer(void) { + if (!work_timer_state.active) return; + + if (!work_timer_state.paused) { + // Pause the timer + work_timer_state.paused = true; + work_timer_state.pause_time = timer_read32(); + } else { + // Resume the timer, adjust start time to account for pause duration + uint32_t pause_duration = timer_read32() - work_timer_state.pause_time; + work_timer_state.start_time += pause_duration; + work_timer_state.paused = false; + } +} + +/** + * Update the work timer state + */ +void update_work_timer(void) { + if (!work_timer_state.active || work_timer_state.paused) return; + + // Calculate elapsed time + work_timer_state.elapsed_time = timer_read32() - work_timer_state.start_time; + + // Check for lunch break + if (work_timer_state.elapsed_time >= LUNCH_BREAK_START && + work_timer_state.elapsed_time < (LUNCH_BREAK_START + LUNCH_BREAK_DURATION)) { + work_timer_state.lunch_break = true; + + // Check for lunch break warning (5 min before end) + if (!work_timer_state.lunch_warning_shown && + work_timer_state.elapsed_time >= (LUNCH_BREAK_START + LUNCH_BREAK_DURATION - BREAK_WARNING_TIME)) { + work_timer_state.lunch_warning_shown = true; + } + } else { + work_timer_state.lunch_break = false; + } + + // Check for end of day warning (5 min before end) + if (!work_timer_state.end_warning_shown && + work_timer_state.elapsed_time >= (WORK_TIMER_DURATION - BREAK_WARNING_TIME)) { + work_timer_state.end_warning_shown = true; + } + + // Auto-stop after 8 hours + if (work_timer_state.elapsed_time >= WORK_TIMER_DURATION) { + work_timer_state.active = false; + } +} + +/** + * Handle the work timer visualization on LEDs + */ +void handle_work_timer(void) { + if (!work_timer_state.active) return; + + // Number of LEDs in the progress bar + const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; + + // Calculate overall progress (0.0 - 1.0) + float overall_progress = (float)work_timer_state.elapsed_time / (float)WORK_TIMER_DURATION; + if (overall_progress > 1.0f) overall_progress = 1.0f; + + // Lunch break or end warning - flash all LEDs + if ((work_timer_state.lunch_break && (timer_read() % 500) < 250) || + (work_timer_state.end_warning_shown && (timer_read() % 500) < 250)) { + + rgb_color_t flash_color; + if (work_timer_state.lunch_break) { + // Flash blue during lunch break + flash_color = WORK_TIMER_LUNCH_COLOR; + } else { + // Flash red for end warning + flash_color = WORK_TIMER_WARNING_COLOR; + } + + // Apply flash color to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + flash_color.r, + flash_color.g, + flash_color.b); + } + } else { + // Normal progress bar display + // Calculate hour segments and LED positions + float hours_per_led = 8.0f / (float)num_leds; + float hours_elapsed = overall_progress * 8.0f; + + // Determine how many LEDs should be fully lit + uint8_t leds_lit = (uint8_t)(hours_elapsed / hours_per_led); + if (leds_lit > num_leds) leds_lit = num_leds; + + // Calculate progress within the current LED + float current_led_progress = (hours_elapsed - (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; + + if (i < leds_lit) { + // Fully lit LED - calculate gradient color based on position + float led_position = (float)i / (float)(num_leds - 1); + + // First half uses green to yellow gradient + if (led_position < 0.5f) { + float half_progress = led_position * 2.0f; // Scale to 0.0 - 1.0 for first half + color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, half_progress); + } + // Second half uses yellow to red gradient + else { + float half_progress = (led_position - 0.5f) * 2.0f; // Scale to 0.0 - 1.0 for second half + color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, half_progress); + } + } + else if (i == leds_lit) { + // Current LED - partially lit based on progress + float led_position = (float)i / (float)(num_leds - 1); + + rgb_color_t full_color; + // First half uses green to yellow gradient + if (led_position < 0.5f) { + float half_progress = led_position * 2.0f; // Scale to 0.0 - 1.0 for first half + full_color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, half_progress); + } + // Second half uses yellow to red gradient + else { + float half_progress = (led_position - 0.5f) * 2.0f; // Scale to 0.0 - 1.0 for second half + full_color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, half_progress); + } + + // Dim the color based on progress within this LED + color.r = full_color.r * current_led_progress; + color.g = full_color.g * current_led_progress; + color.b = full_color.b * current_led_progress; + } + else { + // Unlit LED + color.r = 0; + color.g = 0; + color.b = 0; + } + + // Set the LED color + rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, 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 (new addition) + 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.h b/keyboards/tssouthpaw/rgb_effects.h index e294f7c14e9..a733dd568ac 100644 --- a/keyboards/tssouthpaw/rgb_effects.h +++ b/keyboards/tssouthpaw/rgb_effects.h @@ -23,6 +23,14 @@ #define NUM_LOCK_LED 2 // LED index for Num Lock indicator #define MIC_MUTE_LED 3 // LED index for KC_MICMUTE + /* Work Timer Definitions */ + #define WORK_TIMER_LED_START 4 // F1 key LED + #define WORK_TIMER_LED_END 15 // F12 key LED + #define WORK_TIMER_DURATION 28800000 // 8 hours in milliseconds + #define LUNCH_BREAK_START 14400000 // 4 hours in milliseconds + #define LUNCH_BREAK_DURATION 3600000 // 1 hour in milliseconds + #define BREAK_WARNING_TIME 300000 // 5 minutes in milliseconds + /** * RGB Effect types enumeration * For potential future extension of effect types @@ -52,6 +60,12 @@ // Microphone mute effect toggle void toggle_mic_mute_effect(void); + // Work timer effect functions + void toggle_work_timer(void); + void toggle_pause_work_timer(void); + void update_work_timer(void); + void handle_work_timer(void); + /** * Utility functions for RGB effects */ From 7ed767d83aa9975a789e434074a5d67c033ed301 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 3 May 2025 13:08:40 -0400 Subject: [PATCH 41/54] Add rotary encoder configuration and update rules for encoder support --- keyboards/tssouthpaw/config.h | 10 +++++++++- keyboards/tssouthpaw/rules.mk | 6 +++--- keyboards/tssouthpaw/tssouthpaw.h | 0 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 keyboards/tssouthpaw/tssouthpaw.h diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h index 376232df517..3c0065efc69 100644 --- a/keyboards/tssouthpaw/config.h +++ b/keyboards/tssouthpaw/config.h @@ -25,9 +25,17 @@ // Dynamic macro configuration #define DYNAMIC_MACRO_EEPROM_STORAGE // Enable dynamic macro storage in EEPROM #define DYNAMIC_MACRO_SIZE 256 // Larger macro size - + // Buffer Sizes #define SERIAL_BUFFER_SIZE 64 // Serial buffer size for communication + + #define MATRIX_ROWS 6 +#define MATRIX_COLS 21 + + // Rotary encoder configuration +#define ENCODER_A_PINS { GP0 } // Pin for encoder A +#define ENCODER_B_PINS { GP1 } // Pin for encoder B +#define ENCODER_RESOLUTION 4 // Steps per detent for the rotary encoder /******************************************************************************* * POWER MANAGEMENT diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk index 2fddbe861d2..6d3e072230d 100644 --- a/keyboards/tssouthpaw/rules.mk +++ b/keyboards/tssouthpaw/rules.mk @@ -20,10 +20,10 @@ SRC += rgb_effects.c # Keyboard features DYNAMIC_MACRO_ENABLE = yes -ENCODER_ENABLE = yes -ENCODER_MAP_ENABLE = yes +ENCODER_ENABLE = yes # Enable encoder support +ENCODER_MAP_ENABLE = no # Disable encoder map for simpler volume control KEY_LOCK_ENABLE = yes -LTO_ENABLE = yes +LTO_ENABLE = yes # Link Time Optimization for smaller firmware size # Disabled features (to save space and optimize) DISABLE_ADC = yes diff --git a/keyboards/tssouthpaw/tssouthpaw.h b/keyboards/tssouthpaw/tssouthpaw.h new file mode 100644 index 00000000000..e69de29bb2d From 49926f049ae8d4e17124aade7df4e54df7aa79ba Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 6 May 2025 20:57:03 -0400 Subject: [PATCH 42/54] Remove work timer feature and associated documentation; enable encoder map for improved functionality --- keyboards/tssouthpaw/README.md | 18 +- keyboards/tssouthpaw/config.h | 10 +- keyboards/tssouthpaw/keymaps/default/keymap.c | 91 ++++----- .../tssouthpaw/keymaps/default/rgb_effects.c | 191 +++++++++--------- keyboards/tssouthpaw/rgb_effects.h | 27 ++- keyboards/tssouthpaw/rules.mk | 6 +- keyboards/tssouthpaw/tssouthpaw.h | 0 7 files changed, 152 insertions(+), 191 deletions(-) delete mode 100644 keyboards/tssouthpaw/tssouthpaw.h diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md index 055f62bb093..4aaa33d99c1 100644 --- a/keyboards/tssouthpaw/README.md +++ b/keyboards/tssouthpaw/README.md @@ -1,6 +1,6 @@ # TS-Southpaw-Rev-1.6 -![TS-Southpaw-Rev-1.6](https://imgur.com/a/pUxQAhL) +![TS-Southpaw-Rev-1.6](https://i.imgur.com/placeholder.jpg) *A full-sized southpaw mechanical keyboard with RGB lighting and rotary encoder* @@ -18,7 +18,6 @@ - QMK firmware with dynamic macros - Custom RGB indicators for keyboard state - Visual ESC key ripple effect -- 8-hour work timer with progress bar ## Keyboard Layout @@ -63,21 +62,6 @@ Default startup color is green. Special indicators include: - ESC key: Red ripple effect on press - Arrow keys: Blue highlighting in solid color mode -## Work Timer - -The keyboard features an 8-hour work timer with visual indicators: -- F1-F12 keys serve as a progress bar, showing elapsed work time -- Colors fade from green (start) through yellow (middle) to red (end) -- Blue flash at 4 hours for lunch break -- Blue warning flash 5 minutes before lunch break ends -- Red warning flash for the last 5 minutes of the workday - -To use the work timer: -- Start/Stop: Press `Fn + DM_PLY1` -- Pause/Resume: Press `Fn + DM_PLY2` - -The timer automatically shuts off after 8 hours have elapsed. - ## Rotary Encoder The rotary encoder has two modes: diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h index 3c0065efc69..376232df517 100644 --- a/keyboards/tssouthpaw/config.h +++ b/keyboards/tssouthpaw/config.h @@ -25,17 +25,9 @@ // Dynamic macro configuration #define DYNAMIC_MACRO_EEPROM_STORAGE // Enable dynamic macro storage in EEPROM #define DYNAMIC_MACRO_SIZE 256 // Larger macro size - + // Buffer Sizes #define SERIAL_BUFFER_SIZE 64 // Serial buffer size for communication - - #define MATRIX_ROWS 6 -#define MATRIX_COLS 21 - - // Rotary encoder configuration -#define ENCODER_A_PINS { GP0 } // Pin for encoder A -#define ENCODER_B_PINS { GP1 } // Pin for encoder B -#define ENCODER_RESOLUTION 4 // Steps per detent for the rotary encoder /******************************************************************************* * POWER MANAGEMENT diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c index d044590380a..647d0a7c8fb 100644 --- a/keyboards/tssouthpaw/keymaps/default/keymap.c +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -24,7 +24,7 @@ MEDIA // Media control layer }; - // Define custom keycodes +// Define custom keycodes enum custom_keycodes { KC_MICMUTE = SAFE_RANGE, // Microphone mute key KC_MEDIA, // Media layer toggle @@ -78,57 +78,44 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { * 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; - - case KC_MEDIA: - if (record->event.pressed) { - encoder_media_mode = !encoder_media_mode; // Toggle encoder mode - } - return false; - - // New keycodes for work timer - case KC_WRKTMR: - if (record->event.pressed) { - toggle_work_timer(); // Toggle the work timer on/off - } - return false; - - case KC_WRKPAU: - if (record->event.pressed) { - toggle_pause_work_timer(); // Pause/resume the work timer - } - return false; - - default: - return true; // Process all other keycodes normally - } -} - + // 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; + + case KC_MEDIA: + if (record->event.pressed) { + encoder_media_mode = !encoder_media_mode; // Toggle encoder mode + } + return false; + + default: + return true; // Process all other keycodes normally + } + } + /** * Layer state change handler * Can be used for layer-dependent lighting diff --git a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c index ef5b27cd8d9..cf260e20c23 100644 --- a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c +++ b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c @@ -35,15 +35,6 @@ 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 - /** - * Work Timer color gradient from green to red - */ - static const rgb_color_t WORK_TIMER_START_COLOR = {0, 255, 0}; // Green - static const rgb_color_t WORK_TIMER_MID_COLOR = {255, 255, 0}; // Yellow - static const rgb_color_t WORK_TIMER_END_COLOR = {255, 0, 0}; // Red - static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {0, 0, 255}; // Blue - static const rgb_color_t WORK_TIMER_WARNING_COLOR = {255, 0, 0}; // Red - /** * State tracking structure for LED effects */ @@ -59,20 +50,6 @@ static led_state_t num_lock_state = {false, 0}; static led_state_t mic_mute_state = {false, 0}; - /** - * Work Timer state variables - */ - static struct { - bool active; - uint32_t start_time; - uint32_t elapsed_time; - bool lunch_break; - bool lunch_warning_shown; - bool end_warning_shown; - bool paused; - uint32_t pause_time; - } work_timer_state = {false, 0, 0, false, false, false, false, 0}; - /** * ESC ripple effect state variables */ @@ -203,57 +180,97 @@ * Stop the ESC ripple effect */ void stop_esc_ripple_effect(void) { - ripple_state.active = false; -} + 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); + } + } + } + + /** + * Work Timer state variables + */ +static struct { + bool active; + uint32_t start_time; + uint32_t elapsed_time; + bool lunch_break; + bool lunch_warning_shown; + bool end_warning_shown; + bool paused; + uint32_t pause_time; +} work_timer_state = {false, 0, 0, false, false, false, false, 0}; /** - * Handle the ESC ripple effect animation + * Work Timer color gradient from green to red */ -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); - } - } -} +static const rgb_color_t WORK_TIMER_START_COLOR = {0, 255, 0}; // Green +static const rgb_color_t WORK_TIMER_MID_COLOR = {255, 255, 0}; // Yellow +static const rgb_color_t WORK_TIMER_END_COLOR = {255, 0, 0}; // Red +static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {0, 0, 255}; // Blue +static const rgb_color_t WORK_TIMER_WARNING_COLOR = {255, 0, 0}; // Red /** * Calculate color gradient between two colors based on progress (0.0 - 1.0) @@ -368,9 +385,9 @@ void handle_work_timer(void) { // Apply flash color to all progress bar LEDs for (uint8_t i = 0; i < num_leds; i++) { rgb_matrix_set_color(WORK_TIMER_LED_START + i, - flash_color.r, - flash_color.g, - flash_color.b); + flash_color.r, + flash_color.g, + flash_color.b); } } else { // Normal progress bar display @@ -437,28 +454,10 @@ void handle_work_timer(void) { } } } - -/** - * 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) { + /** + * Combined overlay for arrow-keys + reactive handlers + */ + bool rgb_matrix_indicators_user(void) { // Apply arrow key highlighting handle_arrow_keys(); diff --git a/keyboards/tssouthpaw/rgb_effects.h b/keyboards/tssouthpaw/rgb_effects.h index a733dd568ac..2d56dac9ebd 100644 --- a/keyboards/tssouthpaw/rgb_effects.h +++ b/keyboards/tssouthpaw/rgb_effects.h @@ -23,14 +23,6 @@ #define NUM_LOCK_LED 2 // LED index for Num Lock indicator #define MIC_MUTE_LED 3 // LED index for KC_MICMUTE - /* Work Timer Definitions */ - #define WORK_TIMER_LED_START 4 // F1 key LED - #define WORK_TIMER_LED_END 15 // F12 key LED - #define WORK_TIMER_DURATION 28800000 // 8 hours in milliseconds - #define LUNCH_BREAK_START 14400000 // 4 hours in milliseconds - #define LUNCH_BREAK_DURATION 3600000 // 1 hour in milliseconds - #define BREAK_WARNING_TIME 300000 // 5 minutes in milliseconds - /** * RGB Effect types enumeration * For potential future extension of effect types @@ -42,6 +34,19 @@ EFFECT_FLASH, EFFECT_SOLID } rgb_effect_type_t; + + /* Work Timer Definitions */ +#define WORK_TIMER_LED_START 4 // F1 key LED +#define WORK_TIMER_LED_END 15 // F12 key LED +#define WORK_TIMER_DURATION 28800000 // 8 hours in milliseconds +#define LUNCH_BREAK_START 14400000 // 4 hours in milliseconds +#define LUNCH_BREAK_DURATION 3600000 // 1 hour in milliseconds +#define BREAK_WARNING_TIME 300000 // 5 minutes in milliseconds + +// Work timer effect functions +void toggle_work_timer(void); +void update_work_timer(void); +void handle_work_timer(void); /** * Function declarations for handling RGB effects @@ -60,12 +65,6 @@ // Microphone mute effect toggle void toggle_mic_mute_effect(void); - // Work timer effect functions - void toggle_work_timer(void); - void toggle_pause_work_timer(void); - void update_work_timer(void); - void handle_work_timer(void); - /** * Utility functions for RGB effects */ diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk index 6d3e072230d..2fddbe861d2 100644 --- a/keyboards/tssouthpaw/rules.mk +++ b/keyboards/tssouthpaw/rules.mk @@ -20,10 +20,10 @@ SRC += rgb_effects.c # Keyboard features DYNAMIC_MACRO_ENABLE = yes -ENCODER_ENABLE = yes # Enable encoder support -ENCODER_MAP_ENABLE = no # Disable encoder map for simpler volume control +ENCODER_ENABLE = yes +ENCODER_MAP_ENABLE = yes KEY_LOCK_ENABLE = yes -LTO_ENABLE = yes # Link Time Optimization for smaller firmware size +LTO_ENABLE = yes # Disabled features (to save space and optimize) DISABLE_ADC = yes diff --git a/keyboards/tssouthpaw/tssouthpaw.h b/keyboards/tssouthpaw/tssouthpaw.h deleted file mode 100644 index e69de29bb2d..00000000000 From a0f59dfc1122a44a976d73785b5e019d276d9b7b Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 6 May 2025 20:57:41 -0400 Subject: [PATCH 43/54] Add work timer functionality with RGB effects - Implemented a work timer feature in `work_timer.c` and `work_timer.h` to track work and break durations. - Added support for multiple timer types (30min, 1hr, 4hr, 8hr, 10hr) with configurable durations and break settings. - Integrated RGB color feedback for different timer states (working, lunch break, mid-break, warnings). - Enhanced power management to keep RGB effects active during timer notifications and ensure proper wake/sleep behavior. - Created a new `tssouthpaw.c` and `tssouthpaw.h` to manage keyboard initialization and housekeeping tasks related to the work timer. - Updated `rules.mk` to include new source files and optimize keyboard features. --- keyboards/tssouthpaw/README.md | 216 +++-- keyboards/tssouthpaw/config.h | 117 +-- keyboards/tssouthpaw/keyboard.json | 476 ++++++---- keyboards/tssouthpaw/keymaps/default/keymap.c | 268 ++---- .../tssouthpaw/keymaps/default/readme.md | 35 + .../tssouthpaw/keymaps/default/rgb_effects.c | 475 ---------- .../tssouthpaw/rgb_effects/rgb_effects.c | 267 ++++++ .../{ => rgb_effects}/rgb_effects.h | 68 +- keyboards/tssouthpaw/rgb_effects/work_timer.c | 815 ++++++++++++++++++ keyboards/tssouthpaw/rgb_effects/work_timer.h | 91 ++ keyboards/tssouthpaw/rules.mk | 45 +- keyboards/tssouthpaw/tssouthpaw.c | 132 +++ keyboards/tssouthpaw/tssouthpaw.h | 21 + 13 files changed, 2016 insertions(+), 1010 deletions(-) create mode 100644 keyboards/tssouthpaw/keymaps/default/readme.md delete mode 100644 keyboards/tssouthpaw/keymaps/default/rgb_effects.c create mode 100644 keyboards/tssouthpaw/rgb_effects/rgb_effects.c rename keyboards/tssouthpaw/{ => rgb_effects}/rgb_effects.h (64%) create mode 100644 keyboards/tssouthpaw/rgb_effects/work_timer.c create mode 100644 keyboards/tssouthpaw/rgb_effects/work_timer.h create mode 100644 keyboards/tssouthpaw/tssouthpaw.c create mode 100644 keyboards/tssouthpaw/tssouthpaw.h diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md index 4aaa33d99c1..d164dc4a013 100644 --- a/keyboards/tssouthpaw/README.md +++ b/keyboards/tssouthpaw/README.md @@ -1,75 +1,187 @@ -# TS-Southpaw-Rev-1.6 +# TS Southpaw Keyboard -![TS-Southpaw-Rev-1.6](https://i.imgur.com/placeholder.jpg) +## Overview -*A full-sized southpaw mechanical keyboard with RGB lighting and rotary encoder* - -* Keyboard Maintainer: [TS Design Works LLC](https://github.com/tsdesignworks) -* Hardware Supported: TS-Southpaw-Rev-1.6 PCB -* Hardware Availability: [TS Design Works LLC](https://github.com/tsdesignworks) +The TS Southpaw is a custom mechanical keyboard with a unique southpaw numpad layout (numpad on the left side) optimized for both efficiency and comfort. Built with the QMK firmware on an RP2040 controller, it features full RGB matrix lighting, a rotary encoder for volume control, and specialized features like a work timer system. ## Features -- Full-sized southpaw layout (numpad on left side) -- 104 hot-swappable switch positions -- Per-key RGB lighting (104 LEDs) -- Rotary encoder with dual-mode functionality -- RP2040 microcontroller (USB-C connectivity) -- QMK firmware with dynamic macros -- Custom RGB indicators for keyboard state -- Visual ESC key ripple effect +- **RP2040 Controller** with 16MB of flash storage +- **Full RGB Matrix** with 104 individually addressable LEDs +- **Rotary Encoder** for volume control (4 steps per detent) +- **Customizable Work Timer** with visual notifications +- **Dynamic Macros** for recording and playing custom key sequences +- **Caps Word** functionality for typing in UPPERCASE +- **Comprehensive RGB Effects** including reactive typing and heatmap -## Keyboard Layout +### Keyboard Layout Diagram -### Base Layer -Standard QWERTY layout with numpad on the left side. Function key for accessing additional layers. - -### Function Layer (Fn) -RGB controls, keyboard configuration, and additional functions. - -### Media Layer -Media control keys and alternative encoder mode. - -## Firmware Building - -Make example for this keyboard (after setting up your build environment): - -```shell -qmk compile -kb tssouthpaw -km default +``` +┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ +│Esc│Del│Num│Mic│ │ │ │Mut│ F1│ F2│ F3│ F4│ F5│ F6│ F7│ F8│ F9│F10│F11│F12│Lck│ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│Bsp│ / │ * │ - │Rc1│Rc2│ ` │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ - │ = │ │Bsp│ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ 7 │ 8 │ 9 │ │Cal│PtS│ │Tab│ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ [ │ ] │ \ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ 4 │ 5 │ 6 │ + │Pl1│Pl2│ │Cap│ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ' │ │Ent│ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ 1 │ 2 │ 3 │ │ │ ↑ │ │Sft│ │ Z │ X │ C │ V │ B │ N │ M │ , │ . │ / │Sft│ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ │ 0 │ . │Ent│ ← │ ↓ │ → │ │Ctl│Alt│ │ │ │Spc│ │ │Alt│ │Win│Fn │Ctl│ +└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ``` -Flashing example for this keyboard: +### FN Layout Diagram -```shell -qmk flash -kb tssouthpaw -km default +``` +┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ │ │ │V- │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ │8hr│10h│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│4hr│ │ │V+ │ │ │ │CWd│ │Tmr│ │ │ │ │ │ │ │ │ │ │ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│1hr│ │30m│ │ │H+ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ │ │ │ │RM-│H- │RM+│ │ │ │ │ │ │P/R│ │ │ │ │ │ │ │ +└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ``` -## Bootloader +## Work Timer System -Enter the bootloader in 1 of 2 ways: +The TS Southpaw includes a sophisticated work timer system that helps manage work/break cycles and promotes productive habits. -* **Physical reset button**: Briefly press the button on the back of the PCB -* **Bootmagic reset**: Hold down the Escape key and plug in the keyboard +### Work Timer Types -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). +The keyboard supports multiple timer durations with customized behavior: -## RGB Lighting +| Timer | Duration | Mid-Point | End Warning | +|-------|----------|-----------|-------------| +| 30MIN | 30 minutes | 30-second blue pulse at 15 minutes | 5-minute red pulse before end | +| 1HR | 1 hour | 45-second blue pulse at 30 minutes | 5-minute red pulse before end | +| 4HR | 4 hours | 60-second blue pulse at 2 hours | 5-minute red pulse before end | +| 8HR | 8 hours | 1-hour lunch break at 4 hours with blue indicator | 5-minute red pulse before end | +| 10HR | 10 hours | 1-hour lunch break at 5 hours with blue indicator | 5-minute red pulse before end | -Default startup color is green. Special indicators include: -- Caps Lock: Orange pulsing when active -- Num Lock: Orange pulsing when inactive -- Mic Mute: Red pulsing when active -- ESC key: Red ripple effect on press -- Arrow keys: Blue highlighting in solid color mode +### Visual Feedback + +The work timer provides visual feedback through the function row LEDs (F1-F12): +- Green-to-yellow-to-orange-to-red gradient shows overall progress +- Blue pulses indicate breaks or lunch periods +- Red pulses warn when a timer is about to end + +### Wake-on-Pulse Feature + +The keyboard will automatically wake from sleep mode when important timer notifications are active, ensuring you don't miss break reminders or end-of-timer warnings even when the keyboard's RGB lighting is normally turned off due to inactivity. This feature works by checking if any timer pulse is active before entering full power-down mode, and keeps the RGB system partially active to display notifications. + +### Work Timer Controls + +The following keycodes control the work timer system: + +| Keycode | Function | Location on FN Layer | +|---------|----------|----------------------| +| KC_WRKTMR | Start default 8-hour timer | S key | +| KC_30MIN | Start 30-minute timer | Numpad 3 | +| KC_1HR | Start 1-hour timer | Numpad 1 | +| KC_4HR | Start 4-hour timer | Numpad 4 | +| KC_8HR | Start 8-hour timer | Numpad 8 | +| KC_10HR | Start 10-hour timer | Numpad 9 | +| KC_WRKPAU | Pause/resume current timer | Space Bar key | + +### Timer State Persistence + +The work timer state is saved to EEPROM, allowing it to persist across power cycles. When the keyboard powers back on, it will restore the active timer and adjust the elapsed time accordingly. + +## RGB Effects & Indicators + +The TS Southpaw includes several special RGB effects: + +- **ESC Ripple Effect**: Visual ripple emanating from the ESC key when pressed +- **Arrow Key Highlighting**: Blue highlighting for arrow keys +- **Caps Lock Indicator**: Orange pulse when Caps Lock is active +- **Num Lock Indicator**: Orange pulse when Num Lock is OFF +- **Microphone Mute**: Red pulse when microphone is muted +- **Work Timer Indicators**: Color gradient on function row to show progress +- **Typing Heatmap**: Dynamic color effect showing recently pressed keys +- **RGB Matrix Effects**: Multiple built-in QMK RGB effects including: + - Solid Color + - Breathing + - Gradient + - Rainbow modes + - Reactive typing + - Digital rain + - Raindrop effects ## Rotary Encoder -The rotary encoder has two modes: -- Default mode: Volume control (rotate for volume up/down) -- Media mode: Track control (rotate for next/previous track) +The keyboard features a rotary encoder with 4 steps per detent for precise control: + +- **Default Function**: Volume control (clockwise for volume up, counter-clockwise for volume down) +- **Position**: Located near the arrow keys for easy access +- **Implementation**: Uses hardware interrupts for reliable operation + +## Layers + +The keyboard has three main layers: + +1. **Base Layer**: Standard QWERTY layout with numpad +2. **Function Layer (FN)**: Accessed by holding the key to the left of right Control + - Contains RGB controls, work timer controls, and special functions ## Dynamic Macros -To record and play macros: -- Record: Press `Fn + DM_REC1` or `Fn + DM_REC2`, input sequence, press `Fn + DM_REC1/2` again -- Playback: Press `Fn + DM_PLY1` or `Fn + DM_PLY2` \ No newline at end of file +Record dynamic macros using: +- DM_REC1, DM_REC2: Start recording to slot 1 or 2 +- DM_RSTP: Stop recording +- DM_PLY1, DM_PLY2: Play back macro in slot 1 or 2 + +## Getting Started + +1. Clone the QMK firmware repository +2. Navigate to `keyboards/tssouthpaw` +3. Compile with `qmk compile -kb tssouthpaw -km default` +4. Flash using QMK Toolbox or command line + +## RGB Matrix Control + +Use RGB keycodes on the FN layer: +- RM+, RGB_MOD: next RGB mode +- RM-, RGB_RMOD: previous RGB mode +- V+, RGB_VAI: RGB value up (brightness ↑) +- V-, RGB_VAD: RGB value down (brightness ↓) +- H+, RGB_HUI: RGB hue up +- H-, RGB_HUD: RGB hue down + +## Technical Specifications + +- **MCU**: RP2040 (Raspberry Pi) +- **USB**: Type-C connector +- **RGB**: WS2812B LEDs (104 individually addressable) +- **Matrix**: 6×21 (126 keys) +- **Rotary Encoder**: Resolution 4 steps per detent +- **Memory**: 16MB flash storage + +## Troubleshooting + +### Common Issues + +- **RGB Not Working**: Check if RGB matrix is enabled in your QMK build +- **Timer Not Persisting**: Ensure EEPROM is properly initialized +- **Rotary Encoder Issues**: Verify pin assignments in config.h + +### Reset Options + +- **Bootloader Mode**: To enter bootloader mode, press the reset button while plugging in the keyboard +- **EEPROM Reset**: Hold ESC while plugging in the keyboard to reset all settings to default + +## License + +This keyboard firmware is released under the GPL v2 license. + +## Credits + +Created by TS Design Works LLC +Work Timer inspiration from https://unnecessaryinventions.com/ = https://youtu.be/UTSBCMNLqcw?si=LyVwGeiBSx44G1sk \ No newline at end of file diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h index 376232df517..94de51a3248 100644 --- a/keyboards/tssouthpaw/config.h +++ b/keyboards/tssouthpaw/config.h @@ -16,91 +16,36 @@ #pragma once - /******************************************************************************* - * KEYBOARD CORE SETTINGS - ******************************************************************************/ - // Debounce configuration - #define DEBOUNCE 5 // 5ms for better key chatter prevention - + // 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_EEPROM_STORAGE // Enable dynamic macro storage in EEPROM - #define DYNAMIC_MACRO_SIZE 256 // Larger macro size + #define DYNAMIC_MACRO_SIZE 128 // More efficient macro size - // Buffer Sizes - #define SERIAL_BUFFER_SIZE 64 // Serial buffer size for communication - - /******************************************************************************* - * POWER MANAGEMENT - ******************************************************************************/ - #define USB_SUSPEND_WAKEUP_DELAY 200 // Delay for waking from suspend - - /******************************************************************************* - * RGB MATRIX CONFIGURATION - ******************************************************************************/ - // Basic configuration - #define RGB_MATRIX_LED_COUNT 104 // Total number of LEDs in the RGB matrix - #define RGB_MATRIX_TIMEOUT 300000 // Auto turn-off RGB after 5 minutes of inactivity - #define RGB_MATRIX_SLEEP // Disable RGB effects when the keyboard is suspended - - // Performance optimization - #define RGB_MATRIX_LED_PROCESS_LIMIT ((RGB_MATRIX_LED_COUNT + 7) / 8) // Optimized processing limit - #define RGB_MATRIX_LED_FLUSH_LIMIT 16 // Moderate flush limit to reduce CPU usage - #define RGB_MATRIX_KEYRELEASES // Reactive effects respond to key releases - - // Brightness and default settings - #define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200 // Cap maximum brightness to 200/255 - #define RGB_MATRIX_DEFAULT_SPD 100 // Default animation speed - #define RGB_MATRIX_DEFAULT_VAL 128 // Default brightness value (50%) - #define RGB_MATRIX_DEFAULT_HUE 0 // Default hue (red) - - // Startup configuration - #define RGB_MATRIX_STARTUP_MODE RGB_MATRIX_SOLID_COLOR // Default startup mode - #define RGB_MATRIX_STARTUP_HUE 85 // Green color on startup - #define RGB_MATRIX_STARTUP_SAT 255 // Full saturation - #define RGB_MATRIX_STARTUP_VAL 128 // Medium brightness on startup - - /******************************************************************************* - * TYPING HEATMAP CONFIGURATION - ******************************************************************************/ - #define RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 50 // Delay before heatmap cools down - #define RGB_MATRIX_TYPING_HEATMAP_SPREAD 40 // Spread of heatmap effect to surrounding keys - #define RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16 // Limit heat intensity for surrounding keys - #define RGB_MATRIX_TYPING_HEATMAP_INCREASE_STEP 32 // Keystroke count for fully heating a key - - /******************************************************************************* - * ENABLED RGB MATRIX EFFECTS - ******************************************************************************/ - // Basic effects - #define ENABLE_RGB_MATRIX_SOLID_COLOR - #define ENABLE_RGB_MATRIX_BREATHING - - // Gradient effects - #define ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN - #define ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT - - // Cycling effects - #define ENABLE_RGB_MATRIX_CYCLE_OUT_IN - #define ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL - #define ENABLE_RGB_MATRIX_CYCLE_PINWHEEL - #define ENABLE_RGB_MATRIX_CYCLE_SPIRAL - - // Moving effects - #define ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON - #define ENABLE_RGB_MATRIX_RAINBOW_BEACON - #define ENABLE_RGB_MATRIX_RAINBOW - #define ENABLE_RGB_MATRIX_RAINBOW_SWIRL - #define ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS - - // Reactive effects - #define ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE - #define ENABLE_RGB_MATRIX_SOLID_REACTIVE - #define ENABLE_RGB_MATRIX_SPLASH - #define ENABLE_RGB_MATRIX_MULTISPLASH - #define ENABLE_RGB_MATRIX_SOLID_SPLASH - - // Special effects - #define ENABLE_RGB_MATRIX_RAINDROPS - #define ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS - #define ENABLE_RGB_MATRIX_TYPING_HEATMAP - #define ENABLE_RGB_MATRIX_DIGITAL_RAIN - #define ENABLE_RGB_MATRIX_EFFECT_PIXEL_RAIN \ No newline at end of file + // RP2040 Settings + #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET // Enable double-tap reset + #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 500U // Timeout window in ms + #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_LED GP25 // LED for reset indication \ No newline at end of file diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 05abb624f27..4674909d93d 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -1,168 +1,334 @@ { - "keyboard_name": "TS-Southpaw-Rev-1.6", - "url": "https://github.com/tsdesignworks/tssouthpaw", - "maintainer": "ts_design_works_llc", "manufacturer": "TS Design Works LLC", - "usb": { - "vid": "0x7856", - "pid": "0x6163", - "device_version": "0.0.2" - }, - "features": { - "backlight": false, - "bootmagic": true, - "command": false, - "console": false, - "extrakey": true, - "mousekey": false, - "nkro": true - }, - "qmk": { - "locking": { - "enabled": true, - "resync": true - } + "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": { - "pin": "GP27" - }, - "diode_direction": "COL2ROW", - "processor": "RP2040", - "bootloader": "rp2040", - "platform": "rp2040", + "driver": "vendor", + "pin": "GP27" + }, + "encoder": { + "enabled": true, + "rotary": [ + {"pin_a": "GP1", "pin_b": "GP0", "resolution": 4} + ] + }, + "features": { + "bootmagic": true, + "caps_word": true, + "extrakey": true, + "nkro": true, + "rgb_matrix": true + }, + "caps_word": { + "enabled": true + }, + "debounce": 5, + "rgb_matrix": { + "driver": "ws2812", + "led_count": 104, + "timeout": 600000, + "sleep": true, + "led_process_limit": 8, + "led_flush_limit": 16, + "max_brightness": 250, + "default_mode": "SOLID_COLOR", + "typing_heatmap": { + "decrease_delay_ms": 50, + "spread": 40, + "area_limit": 16, + "increase_step": 32 + }, + "keyreleases": true, + "react_on_keyup": true, + "default": { + "hue": 0, + "sat": 255, + "val": 128, + "speed": 100 + }, + "startup": { + "hue": 85, + "sat": 255, + "val": 128 + }, + "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":19, "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} + "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 index 647d0a7c8fb..747b21b73bd 100644 --- a/keyboards/tssouthpaw/keymaps/default/keymap.c +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -15,64 +15,39 @@ */ #include QMK_KEYBOARD_H - #include "rgb_effects.h" - + #include "rgb_effects/rgb_effects.h" + #include "rgb_effects/work_timer.h" + // Define layers enum layers { BASE, // Base layer FN, // Function layer - MEDIA // Media control 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 }; -// 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 -}; - - /** - * Encoder mapping - * Defines how the encoder behaves in different layers - */ -#if defined(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(RGB_HUD, RGB_HUI) }, - [MEDIA] = { ENCODER_CCW_CW(KC_MPRV, KC_MNXT) } -}; -#endif // ENCODER_MAP_ENABLE - - // Define combo actions for special keys - #define KC_MUTE_PLAY (KC_MUTE | KC_MPLY << 8) // Mute + Play/Pause combo - - // Variable to track encoder functionality mode - static bool encoder_media_mode = false; // false = volume, true = media control - - /** - * Rotary encoder handling - */ + // Function to handle rotary encoder updates bool encoder_update_user(uint8_t index, bool clockwise) { - // Different behavior based on encoder mode - if (encoder_media_mode) { - // Media control mode - if (clockwise) { - tap_code(KC_MNXT); // Next track - } else { - tap_code(KC_MPRV); // Previous track - } + if (clockwise) { + tap_code(KC_VOLU); // Rotate right: Volume up } else { - // Volume control mode (default) - if (clockwise) { - tap_code(KC_VOLU); // Volume up - } else { - tap_code(KC_VOLD); // Volume down - } + tap_code(KC_VOLD); // Rotate left: Volume down } return true; } - + /** * Key processing function * Handles custom keycodes and effects @@ -105,9 +80,52 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { } return false; - case KC_MEDIA: + // 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) { - encoder_media_mode = !encoder_media_mode; // Toggle encoder mode + 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; @@ -115,33 +133,22 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { return true; // Process all other keycodes normally } } - - /** - * Layer state change handler - * Can be used for layer-dependent lighting - */ - layer_state_t layer_state_set_user(layer_state_t state) { - uint8_t layer = get_highest_layer(state); - // Layer-specific lighting could be implemented here - switch (layer) { - case BASE: - // Default RGB settings for base layer - break; - case FN: - // Highlight function keys on FN layer - break; - case MEDIA: - // Highlight media controls on MEDIA layer - break; + /** + * 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 state; + return false; } - - /** - * Keymap definitions - */ + + // Keymap definitions const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { /* Base Layer */ [BASE] = LAYOUT( @@ -152,113 +159,14 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { 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 , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, RM_VALD, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, - _______, _______, _______, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, RM_VALU, KC_WRKTMR, KC_WRKPAU, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, - _______, _______, _______, KC_NO , KC_NO , RM_HUEU, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, KC_NO , - KC_NO, _______, _______, _______, RM_PREV, RM_HUED, RM_NEXT, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ + _______, _______, _______, _______, 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 , _______, _______, _______ ), - /* Media Control Layer */ - [MEDIA] = LAYOUT( - _______, _______, _______, _______, KC_NO , KC_NO , KC_NO , KC_MUTE, KC_MPRV, KC_MPLY, KC_MNXT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, - _______, _______, _______, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, _______, _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, - _______, _______, _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______, KC_NO , - KC_NO, _______, _______, _______, _______, _______, _______, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , _______, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ - ), - }; - - /** - * LED matrix configuration - * Maps physical key layout to LED array - */ - #ifdef RGB_MATRIX_ENABLE - led_config_t g_led_config = { - /* key_matrix to LED index (6 rows × 21 cols) */ - { - { 0, 1, 2, 3, NO_LED, NO_LED, NO_LED, NO_LED, 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, 34, 35, NO_LED, 36 }, - { 37, 38, 39, NO_LED, 40, 41, NO_LED, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55 }, - { 56, 57, 58, 59, 60, 61, NO_LED, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, NO_LED, 74 }, - { 75, 76, 77, NO_LED, NO_LED, 78, NO_LED, 79, NO_LED, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, NO_LED }, - { NO_LED, 91, 92, 93, 94, 95, 96, NO_LED, 97, 98, NO_LED, NO_LED, NO_LED, 99, NO_LED, NO_LED, 100, NO_LED, 101, 102, 103 } - }, - - /* LED index to physical position - scaled 0-224 × 0-64 */ - { - { 0, 0 }, { 11, 0 }, { 22, 0 }, { 34, 0 }, { 45, 0 }, - { 56, 0 }, { 68, 0 }, { 78, 0 }, { 89, 0 }, { 100, 0 }, - { 112, 0 }, { 123, 0 }, { 134, 0 }, { 146, 0 }, { 157, 0 }, - { 168, 0 }, { 179, 0 }, { 191, 0 }, { 202, 0 }, { 213, 0 }, - { 224, 0 }, - { 0, 13 }, { 11, 13 }, { 22, 13 }, { 34, 13 }, { 45, 13 }, - { 56, 13 }, { 68, 13 }, { 78, 13 }, { 89, 13 }, { 100, 13 }, - { 112, 13 }, { 123, 13 }, { 134, 13 }, { 146, 13 }, { 157, 13 }, - { 168, 13 }, { 179, 13 }, { 191, 13 }, { 202, 13 }, { 213, 13 }, - { 224, 13 }, - { 0, 26 }, { 11, 26 }, { 22, 26 }, { NO_LED, NO_LED }, { 45, 26 }, - { 56, 26 }, { NO_LED, NO_LED }, { 78, 26 }, { 89, 26 }, { 100, 26 }, - { 112, 26 }, { 123, 26 }, { 134, 26 }, { 146, 26 }, { 157, 26 }, - { 168, 26 }, { 179, 26 }, { 191, 26 }, { 202, 26 }, { 213, 26 }, - { 224, 26 }, - { 0, 38 }, { 11, 38 }, { 22, 38 }, { 34, 38 }, { 45, 38 }, - { 56, 38 }, { NO_LED, NO_LED }, { 78, 38 }, { 89, 38 }, { 100, 38 }, - { 112, 38 }, { 123, 38 }, { 134, 38 }, { 146, 38 }, { 157, 38 }, - { 168, 38 }, { 179, 38 }, { 191, 38 }, { 202, 38 }, { NO_LED, NO_LED }, { 224, 38 }, - { 0, 51 }, { 11, 51 }, { 22, 51 }, { NO_LED, NO_LED }, { NO_LED, NO_LED }, { 56, 51 }, - { NO_LED, NO_LED }, { 78, 51 }, { NO_LED, NO_LED }, { 100, 51 }, { 112, 51 }, { 123, 51 }, - { 134, 51 }, { 146, 51 }, { 157, 51 }, { 168, 51 }, { 179, 51 }, { 191, 51 }, - { 202, 51 }, { 213, 51 } - }, - - /* LED flags - all keys use the keylight flag */ - { - [0 ... 103] = LED_FLAG_KEYLIGHT - } - }; - #endif /* RGB_MATRIX_ENABLE */ - - /** - * Custom tapping term configuration - */ - uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { - switch (keycode) { - case MO(FN): - return TAPPING_TERM - 50; // Slightly faster tapping term for layer switch - default: - return TAPPING_TERM; - } - } - - /** - * Keyboard initialization - * Called once at startup - */ - void keyboard_post_init_user(void) { - // Initialize RGB lighting effects - rgb_matrix_mode(RGB_MATRIX_SOLID_COLOR); - rgb_matrix_sethsv(HSV_GREEN); // Set default color to green - - // Set default behavior for NKRO - keymap_config.nkro = true; - - // Set encoder initial mode - encoder_media_mode = false; - } - - /** - * Power management functions - */ - void suspend_power_down_user(void) { - // Disable RGB effects when computer is suspended - rgb_matrix_set_suspend_state(true); - } - - void suspend_wakeup_init_user(void) { - // Re-enable RGB effects when computer wakes up - rgb_matrix_set_suspend_state(false); - } \ No newline at end of file + }; \ No newline at end of file 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/keymaps/default/rgb_effects.c b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c deleted file mode 100644 index cf260e20c23..00000000000 --- a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c +++ /dev/null @@ -1,475 +0,0 @@ -/* 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.h" - - /** - * RGB Color structure for cleaner color definitions - */ - typedef struct { - uint8_t r; - uint8_t g; - uint8_t b; - } rgb_color_t; - - /** - * 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); - } - } - } - - /** - * Work Timer state variables - */ -static struct { - bool active; - uint32_t start_time; - uint32_t elapsed_time; - bool lunch_break; - bool lunch_warning_shown; - bool end_warning_shown; - bool paused; - uint32_t pause_time; -} work_timer_state = {false, 0, 0, false, false, false, false, 0}; - -/** - * Work Timer color gradient from green to red - */ -static const rgb_color_t WORK_TIMER_START_COLOR = {0, 255, 0}; // Green -static const rgb_color_t WORK_TIMER_MID_COLOR = {255, 255, 0}; // Yellow -static const rgb_color_t WORK_TIMER_END_COLOR = {255, 0, 0}; // Red -static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {0, 0, 255}; // Blue -static const rgb_color_t WORK_TIMER_WARNING_COLOR = {255, 0, 0}; // Red - -/** - * Calculate color gradient between two colors based on progress (0.0 - 1.0) - */ -static 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; -} - -/** - * Toggle the work timer on/off - */ -void toggle_work_timer(void) { - if (!work_timer_state.active) { - // Start the timer - work_timer_state.active = true; - work_timer_state.start_time = timer_read32(); - work_timer_state.elapsed_time = 0; - work_timer_state.lunch_break = false; - work_timer_state.lunch_warning_shown = false; - work_timer_state.end_warning_shown = false; - work_timer_state.paused = false; - work_timer_state.pause_time = 0; - } else { - // Stop the timer - work_timer_state.active = false; - } -} - -/** - * Pause or resume the work timer - */ -void toggle_pause_work_timer(void) { - if (!work_timer_state.active) return; - - if (!work_timer_state.paused) { - // Pause the timer - work_timer_state.paused = true; - work_timer_state.pause_time = timer_read32(); - } else { - // Resume the timer, adjust start time to account for pause duration - uint32_t pause_duration = timer_read32() - work_timer_state.pause_time; - work_timer_state.start_time += pause_duration; - work_timer_state.paused = false; - } -} - -/** - * Update the work timer state - */ -void update_work_timer(void) { - if (!work_timer_state.active || work_timer_state.paused) return; - - // Calculate elapsed time - work_timer_state.elapsed_time = timer_read32() - work_timer_state.start_time; - - // Check for lunch break - if (work_timer_state.elapsed_time >= LUNCH_BREAK_START && - work_timer_state.elapsed_time < (LUNCH_BREAK_START + LUNCH_BREAK_DURATION)) { - work_timer_state.lunch_break = true; - - // Check for lunch break warning (5 min before end) - if (!work_timer_state.lunch_warning_shown && - work_timer_state.elapsed_time >= (LUNCH_BREAK_START + LUNCH_BREAK_DURATION - BREAK_WARNING_TIME)) { - work_timer_state.lunch_warning_shown = true; - } - } else { - work_timer_state.lunch_break = false; - } - - // Check for end of day warning (5 min before end) - if (!work_timer_state.end_warning_shown && - work_timer_state.elapsed_time >= (WORK_TIMER_DURATION - BREAK_WARNING_TIME)) { - work_timer_state.end_warning_shown = true; - } - - // Auto-stop after 8 hours - if (work_timer_state.elapsed_time >= WORK_TIMER_DURATION) { - work_timer_state.active = false; - } -} - -/** - * Handle the work timer visualization on LEDs - */ -void handle_work_timer(void) { - if (!work_timer_state.active) return; - - // Number of LEDs in the progress bar - const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; - - // Calculate overall progress (0.0 - 1.0) - float overall_progress = (float)work_timer_state.elapsed_time / (float)WORK_TIMER_DURATION; - if (overall_progress > 1.0f) overall_progress = 1.0f; - - // Lunch break or end warning - flash all LEDs - if ((work_timer_state.lunch_break && (timer_read() % 500) < 250) || - (work_timer_state.end_warning_shown && (timer_read() % 500) < 250)) { - - rgb_color_t flash_color; - if (work_timer_state.lunch_break) { - // Flash blue during lunch break - flash_color = WORK_TIMER_LUNCH_COLOR; - } else { - // Flash red for end warning - flash_color = WORK_TIMER_WARNING_COLOR; - } - - // Apply flash color to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - flash_color.r, - flash_color.g, - flash_color.b); - } - } else { - // Normal progress bar display - // Calculate hour segments and LED positions - float hours_per_led = 8.0f / (float)num_leds; - float hours_elapsed = overall_progress * 8.0f; - - // Determine how many LEDs should be fully lit - uint8_t leds_lit = (uint8_t)(hours_elapsed / hours_per_led); - if (leds_lit > num_leds) leds_lit = num_leds; - - // Calculate progress within the current LED - float current_led_progress = (hours_elapsed - (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; - - if (i < leds_lit) { - // Fully lit LED - calculate gradient color based on position - float led_position = (float)i / (float)(num_leds - 1); - - // First half uses green to yellow gradient - if (led_position < 0.5f) { - float half_progress = led_position * 2.0f; // Scale to 0.0 - 1.0 for first half - color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, half_progress); - } - // Second half uses yellow to red gradient - else { - float half_progress = (led_position - 0.5f) * 2.0f; // Scale to 0.0 - 1.0 for second half - color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, half_progress); - } - } - else if (i == leds_lit) { - // Current LED - partially lit based on progress - float led_position = (float)i / (float)(num_leds - 1); - - rgb_color_t full_color; - // First half uses green to yellow gradient - if (led_position < 0.5f) { - float half_progress = led_position * 2.0f; // Scale to 0.0 - 1.0 for first half - full_color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, half_progress); - } - // Second half uses yellow to red gradient - else { - float half_progress = (led_position - 0.5f) * 2.0f; // Scale to 0.0 - 1.0 for second half - full_color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, half_progress); - } - - // Dim the color based on progress within this LED - color.r = full_color.r * current_led_progress; - color.g = full_color.g * current_led_progress; - color.b = full_color.b * current_led_progress; - } - else { - // Unlit LED - color.r = 0; - color.g = 0; - color.b = 0; - } - - // Set the LED color - rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, color.b); - } - } -} - /** - * 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 (new addition) - 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.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.h b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h similarity index 64% rename from keyboards/tssouthpaw/rgb_effects.h rename to keyboards/tssouthpaw/rgb_effects/rgb_effects.h index 2d56dac9ebd..2d09c980ecf 100644 --- a/keyboards/tssouthpaw/rgb_effects.h +++ b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h @@ -16,65 +16,69 @@ #pragma once - #include QMK_KEYBOARD_H - + #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 - - /** + #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 { - EFFECT_NONE = 0, - EFFECT_PULSE, - EFFECT_RIPPLE, - EFFECT_FLASH, - EFFECT_SOLID + RGB_EFFECT_NONE, + RGB_EFFECT_BREATHING, + RGB_EFFECT_RAINBOW, + RGB_EFFECT_CUSTOM } rgb_effect_type_t; - - /* Work Timer Definitions */ -#define WORK_TIMER_LED_START 4 // F1 key LED -#define WORK_TIMER_LED_END 15 // F12 key LED -#define WORK_TIMER_DURATION 28800000 // 8 hours in milliseconds -#define LUNCH_BREAK_START 14400000 // 4 hours in milliseconds -#define LUNCH_BREAK_DURATION 3600000 // 1 hour in milliseconds -#define BREAK_WARNING_TIME 300000 // 5 minutes in milliseconds - -// Work timer effect functions -void toggle_work_timer(void); -void update_work_timer(void); -void handle_work_timer(void); - + + /** + * 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..efcd901e0ba --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -0,0 +1,815 @@ +/* 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" + + // Bitpacked flags to save memory + typedef struct { + uint8_t active: 1; + uint8_t paused: 1; + uint8_t lunch_break: 1; + uint8_t mid_break: 1; + uint8_t lunch_warning_shown: 1; + uint8_t mid_break_warning_shown: 1; + uint8_t end_warning_shown: 1; + uint8_t pulse_active: 1; // Flag to track if any pulse is currently active + } work_timer_flags_t; + + // Work timer state structure + typedef struct { + work_timer_flags_t flags; + work_timer_type_t timer_type; + uint32_t start_time; + uint32_t elapsed_time; // Work time (excluding breaks) + uint32_t pause_time; + uint32_t break_start_time; // When current break started + uint32_t total_break_time; // Total accumulated break time + uint32_t timer_duration; // Total work time duration (excludes breaks) + uint32_t mid_break_start; // When in workday the break occurs + uint32_t mid_break_duration; + bool has_lunch_break; + } work_timer_t; + + // Global work timer state + static work_timer_t work_timer = { + .flags = {0}, + .timer_type = TIMER_TYPE_8HR, + .start_time = 0, + .elapsed_time = 0, + .pause_time = 0, + .break_start_time = 0, + .total_break_time = 0, + .timer_duration = TIMER_8HR_DURATION, + .mid_break_start = TIMER_8HR_DURATION / 2, // Default halfway point + .mid_break_duration = LUNCH_BREAK_DURATION, + .has_lunch_break = true + }; + + // Predefined RGB colors for timer states + static const rgb_color_t WORK_TIMER_START_COLOR = {WORK_TIMER_START_R, WORK_TIMER_START_G, WORK_TIMER_START_B}; + static const rgb_color_t WORK_TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MID_G, WORK_TIMER_MID_B}; + static const rgb_color_t WORK_TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B}; + static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B}; + static const rgb_color_t WORK_TIMER_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; + static const rgb_color_t WORK_TIMER_BREAK_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; + + // Function prototypes for internal functions + static void configure_timer_for_type(work_timer_type_t timer_type); + static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor); + static void save_work_timer_state(void); + static void load_work_timer_state(void); + static void update_pulse_active_state(void); + + /** + * Configure timer parameters based on timer type + */ + static void configure_timer_for_type(work_timer_type_t timer_type) { + work_timer.timer_type = timer_type; + + switch (timer_type) { + case TIMER_TYPE_30MIN: + work_timer.timer_duration = TIMER_30MIN_DURATION; + work_timer.mid_break_start = TIMER_30MIN_DURATION / 2; + work_timer.mid_break_duration = MID_BREAK_30MIN_DURATION; + work_timer.has_lunch_break = false; + break; + + case TIMER_TYPE_1HR: + work_timer.timer_duration = TIMER_1HR_DURATION; + work_timer.mid_break_start = TIMER_1HR_DURATION / 2; + work_timer.mid_break_duration = MID_BREAK_1HR_DURATION; + work_timer.has_lunch_break = false; + break; + + case TIMER_TYPE_4HR: + work_timer.timer_duration = TIMER_4HR_DURATION; + work_timer.mid_break_start = TIMER_4HR_DURATION / 2; + work_timer.mid_break_duration = MID_BREAK_4HR_DURATION; + work_timer.has_lunch_break = false; + break; + + case TIMER_TYPE_8HR: + work_timer.timer_duration = TIMER_8HR_DURATION; + work_timer.mid_break_start = TIMER_8HR_DURATION / 2; + work_timer.mid_break_duration = LUNCH_BREAK_DURATION; + work_timer.has_lunch_break = true; + break; + + case TIMER_TYPE_10HR: + work_timer.timer_duration = TIMER_10HR_DURATION; + work_timer.mid_break_start = TIMER_10HR_DURATION / 2; + work_timer.mid_break_duration = LUNCH_BREAK_DURATION; + work_timer.has_lunch_break = true; + break; + + default: + // Default to 8HR if something goes wrong + work_timer.timer_duration = TIMER_8HR_DURATION; + work_timer.mid_break_start = TIMER_8HR_DURATION / 2; + work_timer.mid_break_duration = LUNCH_BREAK_DURATION; + work_timer.has_lunch_break = true; + break; + } + } + + /** + * Save the work timer state to EEPROM + * Modern QMK using block-based EEPROM operations for RP2040 + */ + static void save_work_timer_state(void) { + // Create a buffer to store all our data + uint8_t buffer[19] = {0}; // 19 bytes total + + // Set the active flag + buffer[0] = work_timer.flags.active; + + // Only save time info if timer is active + if (work_timer.flags.active) { + // Save start time (4 bytes) + memcpy(&buffer[1], &work_timer.start_time, sizeof(uint32_t)); + + // Save elapsed time (4 bytes) + memcpy(&buffer[5], &work_timer.elapsed_time, sizeof(uint32_t)); + + // Save total break time (4 bytes) + memcpy(&buffer[9], &work_timer.total_break_time, sizeof(uint32_t)); + + // Save timer type (1 byte) + buffer[13] = (uint8_t)work_timer.timer_type; + + // Save break state (1 byte) + buffer[14] = (work_timer.flags.lunch_break ? 1 : 0) | + (work_timer.flags.mid_break ? 2 : 0); + + // Save break start time (4 bytes) + memcpy(&buffer[15], &work_timer.break_start_time, sizeof(uint32_t)); + } + + // Write all data at once to EEPROM + eeprom_update_block(buffer, (void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); + } + + /** + * Load the work timer state from EEPROM + * Modern QMK using block-based EEPROM operations for RP2040 + */ + static void load_work_timer_state(void) { + // Create a buffer to read all our data + uint8_t buffer[19] = {0}; // 19 bytes total + + // Read all data at once from EEPROM + eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); + + // Get the active flag + work_timer.flags.active = buffer[0]; + + // Only process the rest if timer was active + if (work_timer.flags.active) { + // Load timer type + work_timer_type_t saved_type = (work_timer_type_t)buffer[13]; + + // Apply configuration for this timer type + configure_timer_for_type(saved_type); + + // Get start time (4 bytes) + memcpy(&work_timer.start_time, &buffer[1], sizeof(uint32_t)); + + // Get the elapsed work time (4 bytes) + memcpy(&work_timer.elapsed_time, &buffer[5], sizeof(uint32_t)); + + // Get the total break time (4 bytes) + memcpy(&work_timer.total_break_time, &buffer[9], sizeof(uint32_t)); + + // Get break state (1 byte) + uint8_t break_state = buffer[14]; + work_timer.flags.lunch_break = (break_state & 1) > 0; + work_timer.flags.mid_break = (break_state & 2) > 0; + + // Get break start time (4 bytes) + memcpy(&work_timer.break_start_time, &buffer[15], sizeof(uint32_t)); + + // If in a break, adjust break_start_time to account for time powered off + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + uint32_t current_time = timer_read32(); + uint32_t time_off = current_time - work_timer.break_start_time; + + // If we've been off for less than the break duration, continue the break + if (work_timer.flags.lunch_break && + time_off < work_timer.mid_break_duration) { + // Stay in lunch break, adjust break_start_time + work_timer.break_start_time = current_time - time_off; + } + else if (work_timer.flags.mid_break && + time_off < work_timer.mid_break_duration) { + // Stay in mid-break, adjust break_start_time + work_timer.break_start_time = current_time - time_off; + } + else { + // Break would have ended while powered off + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + + // Add the remaining break time to total_break_time + // This is approximate but better than nothing + if (time_off < work_timer.mid_break_duration) { + work_timer.total_break_time += work_timer.mid_break_duration; + } + } + } + + // Calculate elapsed wall time (including time powered off) + uint32_t current_time = timer_read32(); + uint32_t wall_time_elapsed = current_time - work_timer.start_time; + + // Adjust start time to account for time powered off, preserving elapsed_time + work_timer.start_time = current_time - wall_time_elapsed; + + // Validate time values - if unreasonable, reset + if (work_timer.elapsed_time > work_timer.timer_duration) { + work_timer.flags.active = 0; + save_work_timer_state(); + } + + // Check for end warning state + if (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + work_timer.flags.end_warning_shown = 1; + } + + // Update pulse active state + update_pulse_active_state(); + } + } + + /** + * Display progress bar with gradient colors + */ + static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor) { + // Calculate hour segments and LED positions + float hours_per_led = 1.0f / (float)num_leds; + + // Determine how many LEDs should be fully lit + uint8_t leds_lit = (uint8_t)(overall_progress / hours_per_led); + if (leds_lit > num_leds) leds_lit = num_leds; + + // Calculate progress within the current LED + float current_led_progress = (overall_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 led_position = (float)i / (float)(num_leds - 1); + + // Adjust transition points - more green-to-orange (first 80% of bar), + // and less orange-to-red (last 20% of bar) + if (led_position < 0.3f) { + // Scale to 0.0 - 1.0 for green-to-orange part (now 80% of the bar) + float adjusted_progress = led_position / 0.3f; + color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); + } + else { + float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% + color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); + } + + // Apply overall RGB brightness factor + color.r = (uint8_t)((float)color.r * brightness_factor); + color.g = (uint8_t)((float)color.g * brightness_factor); + color.b = (uint8_t)((float)color.b * brightness_factor); + } + else if (i == leds_lit && current_led_progress > 0.0f) { + // Current LED - partially lit based on progress + float led_position = (float)i / (float)(num_leds - 1); + + rgb_color_t full_color; + // Adjust transition points - more green-to-orange, less orange-to-red + if (led_position < 0.3f) { + // Scale to 0.0 - 1.0 for green-to-orange part + float adjusted_progress = led_position / 0.3f; + full_color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); + } + // Last 30% is orange-to-red gradient + else { + float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% + full_color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); + } + + // Dim the color based on progress within this LED and overall brightness + color.r = (uint8_t)((float)full_color.r * current_led_progress * brightness_factor); + color.g = (uint8_t)((float)full_color.g * current_led_progress * brightness_factor); + color.b = (uint8_t)((float)full_color.b * current_led_progress * brightness_factor); + } + + // Set the LED color + rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, color.b); + } + } + + /** + * Update the pulse active state + * This determines if the keyboard should wake from sleep for timer notification + */ + static void update_pulse_active_state(void) { + if (!work_timer.flags.active || work_timer.flags.paused) { + work_timer.flags.pulse_active = false; + return; + } + + // Check if any pulse condition is active + bool lunch_warning = false; + bool lunch_end_warning = false; + bool mid_point_warning = false; + bool end_warning = false; + + // For timers with lunch breaks + if (work_timer.has_lunch_break) { + // Lunch break warning (before lunch) + lunch_warning = !work_timer.flags.lunch_break && + (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start); + + // Lunch end warning (before end of lunch) + lunch_end_warning = work_timer.flags.lunch_break && + (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + } else { + // Mid-point break warning for shorter timers + mid_point_warning = !work_timer.flags.mid_break && + (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start); + } + + // End timer warning (5 minutes before end) + end_warning = (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.timer_duration); + + // Set pulse active if any of these conditions are true + bool new_pulse_active = ( + work_timer.flags.mid_break || // Mid-point break pulse + work_timer.flags.lunch_break || // Lunch break + work_timer.flags.end_warning_shown || // End warning + lunch_warning || // Pre-lunch warning + lunch_end_warning || // End of lunch warning + mid_point_warning || // Mid-point warning for shorter timers + end_warning // End warning for all timers + ); + + // Force a wakeup signal if transitioning from inactive to active pulse + if (!work_timer.flags.pulse_active && new_pulse_active) { + // Explicitly wake up keyboard by toggling suspend state + rgb_matrix_set_suspend_state(false); + + // Force appropriate 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); + } + } + + work_timer.flags.pulse_active = new_pulse_active; + } + + /** + * Toggle the work timer on/off + */ + void toggle_work_timer(void) { + if (work_timer.flags.active) { + // If timer is active, stop it + work_timer.flags.active = 0; + work_timer.flags.pulse_active = 0; // Clear pulse active flag + save_work_timer_state(); + } else { + // If timer is inactive, just activate it with current settings + // This assumes the timer type and duration are already set + work_timer.flags.active = 1; + work_timer.flags.paused = 0; + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.flags.end_warning_shown = 0; + work_timer.flags.pulse_active = 0; + + work_timer.start_time = timer_read32(); + work_timer.elapsed_time = 0; + work_timer.pause_time = 0; + work_timer.break_start_time = 0; + work_timer.total_break_time = 0; + + save_work_timer_state(); + } + + // Ensure timer visual updates immediately after toggle + update_pulse_active_state(); + } + + /** + * Initialize the work timer - load state from EEPROM + */ + void work_timer_init(void) { + load_work_timer_state(); + } + + /** + * Start a specific timer type + */ + void start_timer(work_timer_type_t timer_type) { + // Configure timer parameters based on type + configure_timer_for_type(timer_type); + + // Reset timer state + work_timer.flags.active = 1; + work_timer.flags.paused = 0; + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.flags.end_warning_shown = 0; + work_timer.flags.pulse_active = 0; + + work_timer.start_time = timer_read32(); + work_timer.elapsed_time = 0; + work_timer.pause_time = 0; + work_timer.break_start_time = 0; + work_timer.total_break_time = 0; + + save_work_timer_state(); + + // Update pulse state immediately after starting timer + update_pulse_active_state(); + } + + /** + * Pause or resume the work timer + */ + void toggle_pause_work_timer(void) { + if (!work_timer.flags.active) return; + + if (!work_timer.flags.paused) { + // Pause the timer + work_timer.flags.paused = 1; + work_timer.pause_time = timer_read32(); + save_work_timer_state(); + } else { + // Resume the timer, adjust start time to account for pause duration + uint32_t pause_duration = timer_read32() - work_timer.pause_time; + work_timer.start_time += pause_duration; + + // If in a break, adjust break start time too + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + work_timer.break_start_time += pause_duration; + } + + work_timer.flags.paused = 0; + save_work_timer_state(); + } + + // Update pulse state after changing pause status + update_pulse_active_state(); + } + + /** + * Update the work timer state + */ + void update_work_timer(void) { + if (!work_timer.flags.active || work_timer.flags.paused) return; + + // Calculate wall time (real-world time) elapsed since start + uint32_t wall_time_elapsed = timer_read32() - work_timer.start_time; + + // Current wall time + uint32_t current_time = timer_read32(); + + // Process different timer states based on timer type + if (work_timer.has_lunch_break) { + // For timers with lunch breaks (8HR and 10HR) + + // Handle lunch break state transitions + if (!work_timer.flags.lunch_break) { + // Check if it's time to start lunch + if (!work_timer.flags.lunch_warning_shown && + work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start) { + // Pre-lunch warning (red pulse before lunch) + work_timer.flags.lunch_warning_shown = 1; + } + else if (work_timer.elapsed_time >= work_timer.mid_break_start) { + // Start lunch break + work_timer.flags.lunch_break = 1; + work_timer.break_start_time = current_time; + work_timer.flags.lunch_warning_shown = 0; // Reset for lunch end warning + + // Save state when entering lunch break + save_work_timer_state(); + } + } + else { + // Currently in lunch break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // Check for lunch end warning + if (!work_timer.flags.lunch_warning_shown && + break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { + // Pre-end warning (red pulse before end of lunch) + work_timer.flags.lunch_warning_shown = 1; + } + + // FIX: Emergency safety - force end lunch if it's been active way too long + // This addresses the stuck lunch mode issue + if (break_elapsed >= (work_timer.mid_break_duration * 2)) { + // Break has been active for twice as long as it should be - force exit + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.total_break_time += work_timer.mid_break_duration; // Use exact duration instead + save_work_timer_state(); + } + // Normal lunch break end check + else if (break_elapsed >= work_timer.mid_break_duration) { + // End lunch break + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; // FIX: Reset the warning flag + + // Add the break time to total_break_time - use the exact duration + // FIX: Use the exact lunch break duration instead of elapsed time + work_timer.total_break_time += work_timer.mid_break_duration; + + // Save state when exiting lunch break + save_work_timer_state(); + } + } + } + else { + // For shorter timers without lunch breaks (30MIN, 1HR, 4HR) + + // Handle mid-break state transitions + if (!work_timer.flags.mid_break) { + // Check if it's time to start mid-break + if (!work_timer.flags.mid_break_warning_shown && + work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start) { + // Mid-break warning + work_timer.flags.mid_break_warning_shown = 1; + } + else if (work_timer.elapsed_time >= work_timer.mid_break_start) { + // Start mid-break + work_timer.flags.mid_break = 1; + work_timer.break_start_time = current_time; + save_work_timer_state(); + } + } + else { + // Currently in mid-break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // FIX: Emergency safety - force end mid-break if it's been active way too long + if (break_elapsed >= (work_timer.mid_break_duration * 2)) { + // Break has been active for twice as long as it should be - force exit + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.total_break_time += work_timer.mid_break_duration; + save_work_timer_state(); + } + // Normal mid-break end check + else if (break_elapsed >= work_timer.mid_break_duration) { + // End mid-break + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; // FIX: Reset warning flag + + // Add the break time to total_break_time + work_timer.total_break_time += work_timer.mid_break_duration; + save_work_timer_state(); + } + } + } + + // Calculate true work time (excluding breaks) + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + // Currently in a break, so work time is wall time minus total breaks + // minus current break elapsed time + uint32_t current_break_elapsed = timer_elapsed32(work_timer.break_start_time); + work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time - current_break_elapsed; + } else { + // Not in a break, so work time is wall time minus total breaks + work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time; + } + + // Check for end of day warning (5 min before end) + if (!work_timer.flags.end_warning_shown && + work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + work_timer.flags.end_warning_shown = 1; + } + + // Auto-stop after timer duration + if (work_timer.elapsed_time >= work_timer.timer_duration) { + work_timer.flags.active = 0; + save_work_timer_state(); + } + + // Update the pulse active state for wakeup + update_pulse_active_state(); + } + +/** + * Check if any timer pulse is currently active + * Used for wake-from-sleep functionality + * + * @return true if any timer pulse effect is active, false otherwise + */ + bool is_timer_pulse_active(void) { + // If timer is not active or is paused, no pulse is active + if (!work_timer.flags.active || work_timer.flags.paused) { + return false; + } + + // Return the pulse_active flag directly from the work timer state + return work_timer.flags.pulse_active; +} + +/** + * Handle the work timer visualization on LEDs + */ +void handle_work_timer(void) { + if (!work_timer.flags.active) return; + + // Get current RGB matrix brightness value (0-255) + uint8_t rgb_brightness = rgb_matrix_get_val(); + float brightness_factor = (float)rgb_brightness / 255.0f; + + // Ensure minimum brightness during notifications + // (RGB matrix must be awake for this to have an effect) + if (work_timer.flags.pulse_active && rgb_brightness < RGB_MATRIX_MINIMUM_BRIGHTNESS) { + rgb_brightness = RGB_MATRIX_MINIMUM_BRIGHTNESS; + brightness_factor = (float)RGB_MATRIX_MINIMUM_BRIGHTNESS / 255.0f; + // Don't permanently change the RGB settings, just adjust for calculations + } + + // Number of LEDs in the progress bar + const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; + + // Calculate overall progress (0.0 - 1.0) + float overall_progress = (float)work_timer.elapsed_time / (float)work_timer.timer_duration; + if (overall_progress > 1.0f) overall_progress = 1.0f; + + // Create a pulsing effect by varying brightness based on timer + uint8_t pulse_brightness = abs((timer_read() / 4) % 510 - 255); + float pulse_ratio = (float)pulse_brightness / 255.0f; + + // Check for various timer states + bool lunch_warning = false; + bool lunch_end_warning = false; + + // For timers with lunch breaks (8HR and 10HR) + if (work_timer.has_lunch_break) { + // Pre-lunch warning (red pulse) + lunch_warning = !work_timer.flags.lunch_break && + (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start); + + // Lunch-end warning (red pulse) + lunch_end_warning = work_timer.flags.lunch_break && + (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + + // Choose appropriate display based on current state + if (lunch_warning || lunch_end_warning) { + // Pre/Post lunch red warning pulse + rgb_color_t pulse_color = WORK_TIMER_BREAK_WARNING_COLOR; + + // Apply pulsing effect to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); + } + } + else if (work_timer.flags.lunch_break) { + // During lunch break - blue pulse + rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; + + // Use a slower pulse for regular lunch break + uint8_t lunch_pulse = abs((timer_read() / 10) % 510 - 255); + float lunch_pulse_ratio = (float)lunch_pulse / 255.0f; + + // FIXED: Track how long the lunch break has been active + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // FIXED: Emergency safety - if lunch is stuck for more than 2x the duration, force exit + if (break_elapsed > (work_timer.mid_break_duration * 2)) { + work_timer.flags.lunch_break = 0; + work_timer.total_break_time += work_timer.mid_break_duration; + return; // Exit and let the next update handle normal display + } + + // Apply lunch break color to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)pulse_color.r * lunch_pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.g * lunch_pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.b * lunch_pulse_ratio * brightness_factor)); + } + } + else if (work_timer.flags.end_warning_shown) { + // End of day warning - red pulse + rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; + + // Apply pulsing effect to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); + } + } + else { + // Normal progress bar display + display_progress_bar(num_leds, overall_progress, brightness_factor); + } + } + // For timers without lunch breaks (30MIN, 1HR, 4HR) + else { + bool mid_break_warning = !work_timer.flags.mid_break && + (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start); + + // Choose appropriate display based on current state + if (mid_break_warning) { + // Mid-break warning - red pulse + rgb_color_t pulse_color = WORK_TIMER_BREAK_WARNING_COLOR; + + // Apply pulsing effect to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); + } + } + else if (work_timer.flags.mid_break) { + // Mid-break active - blue pulse + rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; + + // Use a faster pulse for mid-break reminder + uint8_t mid_pulse = abs((timer_read() / 3) % 510 - 255); + float mid_pulse_ratio = (float)mid_pulse / 255.0f; + + // FIXED: Track how long the mid-break has been active + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // FIXED: Emergency safety - if mid-break is stuck for more than 2x the duration, force exit + if (break_elapsed > (work_timer.mid_break_duration * 2)) { + work_timer.flags.mid_break = 0; + work_timer.total_break_time += work_timer.mid_break_duration; + return; // Exit and let the next update handle normal display + } + + // Apply pulsing effect to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)pulse_color.r * mid_pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.g * mid_pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.b * mid_pulse_ratio * brightness_factor)); + } + } + else if (work_timer.flags.end_warning_shown) { + // End of timer warning - red pulse + rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; + + // Apply pulsing effect to all progress bar LEDs + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, + (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), + (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); + } + } + else { + // Normal progress bar display + display_progress_bar(num_leds, overall_progress, brightness_factor); + } + } +} + +/** + * Work timer task - can be called periodically + */ +void work_timer_task(void) { + // Update work timer state + update_work_timer(); +} \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.h b/keyboards/tssouthpaw/rgb_effects/work_timer.h new file mode 100644 index 00000000000..0b23fa80cbb --- /dev/null +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.h @@ -0,0 +1,91 @@ +/* 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 + + // Warning color (red) + #define WORK_TIMER_WARNING_R 255 + #define WORK_TIMER_WARNING_G 0 + #define WORK_TIMER_WARNING_B 0 + + // 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 + #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 + + // 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 pulse states + bool is_timer_pulse_active(void); \ No newline at end of file diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk index 2fddbe861d2..c5f47ea24a8 100644 --- a/keyboards/tssouthpaw/rules.mk +++ b/keyboards/tssouthpaw/rules.mk @@ -1,39 +1,24 @@ -# MCU name and parameters +# Include custom features +SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c + +# MCU name MCU = RP2040 -F_CPU = 125000000 -BOOTLOADER = rp2040 -PLATFORM = rp2040 -# RGB Matrix configuration -RGB_MATRIX_ENABLE = yes -RGB_MATRIX_DRIVER = ws2812 -WS2812_DRIVER = vendor -RGB_MATRIX_FRAMEBUFFER_EFFECTS = yes -RGB_MATRIX_KEYPRESSES = yes -RGB_MATRIX_TYPING_HEATMAP = yes -RGB_MATRIX_CUSTOM_KB = no -RGB_MATRIX_CUSTOM_USER = no -RGBLIGHT_SLEEP = yes +# Platform specification +PLATFORM = CHIBIOS -# Include custom RGB effects -SRC += rgb_effects.c - -# Keyboard features +# Features not in info.json DYNAMIC_MACRO_ENABLE = yes -ENCODER_ENABLE = yes -ENCODER_MAP_ENABLE = yes KEY_LOCK_ENABLE = yes -LTO_ENABLE = yes -# Disabled features (to save space and optimize) -DISABLE_ADC = yes -MIDI_ENABLE = no -BLUETOOTH_ENABLE = no -AUDIO_ENABLE = no -LEADER_ENABLE = no +# Disabled features to save space +CONSOLE_ENABLE = no +COMMAND_ENABLE = no SPACE_CADET_ENABLE = no GRAVE_ESC_ENABLE = no MAGIC_ENABLE = no - -# Include paths for QMK firmware headers (required for RP2040) -EXTRAFLAGS += -I"$(QUANTUM_DIR)" -I"$(PLATFORM_DIR)" -I"$(CHIBIOS_DIR)/os/license" -I"$(CHIBIOS_DIR)/os/hal/include" -I"$(CHIBIOS_DIR)/os/hal/ports/$(MCU)" -I"$(CHIBIOS_DIR)/os/hal/ports/$(MCU)/LLD" -I"$(CHIBIOS_DIR)/os/hal/lib/streams" -I"$(CHIBIOS_DIR)/os/kernel/include" -I"$(CHIBIOS_DIR)/os/various" \ No newline at end of file +MUSIC_ENABLE = no +LEADER_ENABLE = no +MIDI_ENABLE = no +BLUETOOTH_ENABLE = no +AUDIO_ENABLE = no \ No newline at end of file diff --git a/keyboards/tssouthpaw/tssouthpaw.c b/keyboards/tssouthpaw/tssouthpaw.c new file mode 100644 index 00000000000..a5f9aece424 --- /dev/null +++ b/keyboards/tssouthpaw/tssouthpaw.c @@ -0,0 +1,132 @@ +/* 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" + + // Forward declaration for the weak function + bool suspend_wakeup_condition_user(void); + + /** + * 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(); + + // Ensure RGB minimum brightness setting is applied + rgb_matrix_set_suspend_state(false); + + // Continue with any user-level initialization + keyboard_post_init_user(); + } + + /** + * Check if the keyboard should wake from sleep + * This function is called by the QMK core during suspend + */ + bool suspend_wakeup_condition_kb(void) { + // Wake the keyboard if a timer pulse is active + if (is_timer_pulse_active()) { + // Explicitly wake the keyboard by returning true AND + // setting suspend state to false for RGB matrix + rgb_matrix_set_suspend_state(false); + + // Force appropriate 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 true; + } + + // Otherwise defer to the user function + return suspend_wakeup_condition_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()) { + // Keep RGB matrix enabled for timer notifications + rgb_matrix_set_suspend_state(false); + + // 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) { + // Re-enable RGB effects when computer wakes up + rgb_matrix_set_suspend_state(false); + + // 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_user(void) { + // Update work timer even during suspend + if (is_timer_pulse_active()) { + update_work_timer(); + handle_work_timer(); + } + } + + // Default implementations for weak functions + __attribute__((weak)) bool suspend_wakeup_condition_user(void) { + return false; + } + + __attribute__((weak)) void keyboard_post_init_user(void) {} + __attribute__((weak)) void suspend_power_down_user(void) {} + __attribute__((weak)) void suspend_wakeup_init_user(void) {} \ 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 From bb66d45cdacfe1449e0a3bf7adf22e37220b0713 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Wed, 7 May 2025 08:07:03 -0400 Subject: [PATCH 44/54] Delete keyboards/tdcleft104 directory Outdated and don't need it here --- keyboards/tdcleft104/config.h | 63 ---------- keyboards/tdcleft104/info.json | 119 ------------------ keyboards/tdcleft104/keymaps/default/keymap.c | 45 ------- keyboards/tdcleft104/readme.md | 27 ---- keyboards/tdcleft104/rules.mk | 18 --- keyboards/tdcleft104/tdcleft104.c | 16 --- keyboards/tdcleft104/tdcleft104.h | 37 ------ 7 files changed, 325 deletions(-) delete mode 100644 keyboards/tdcleft104/config.h delete mode 100644 keyboards/tdcleft104/info.json delete mode 100644 keyboards/tdcleft104/keymaps/default/keymap.c delete mode 100644 keyboards/tdcleft104/readme.md delete mode 100644 keyboards/tdcleft104/rules.mk delete mode 100644 keyboards/tdcleft104/tdcleft104.c delete mode 100644 keyboards/tdcleft104/tdcleft104.h diff --git a/keyboards/tdcleft104/config.h b/keyboards/tdcleft104/config.h deleted file mode 100644 index ec315e04bd6..00000000000 --- a/keyboards/tdcleft104/config.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2024 TDC - -// 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. - -// T his 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 "config_common.h" - -/* key matrix size */ -#define MATRIX_ROWS 6 -#define MATRIX_COLS 20 - -/* Keyboard Matrix Assignments */ -#define MATRIX_ROW_PINS {B3,B0,A7,A6,A5,A4} -#define MATRIX_COL_PINS {A9,A8,B15,B14,B13,B12,B11,B10,B2,B1,A3,A2,A1,A0,B9,B8,B7,B4,B6,B5} - -/* COL2ROW or ROW2COL */ -#define DIODE_DIRECTION COL2ROW - -#define RGB_DI_PIN A15 -#ifdef RGB_DI_PIN -#define RGBLED_NUM 104 -#define RGBLIGHT_HUE_STEP 6 -#define RGBLIGHT_SAT_STEP 6 -#define RGBLIGHT_VAL_STEP 6 -#define RGBLIGHT_DEFAULT_HUE 4 -#define RGBLIGHT_LIMIT_VAL 15 /* The maximum brightness level */ -#define RGBLIGHT_SLEEP /* If defined, the RGB lighting will be switched off when the host goes to sleep */ -/*== all animations enable ==*/ -// #define RGBLIGHT_ANIMATIONS -// /*== or choose animations ==*/ -#define RGBLIGHT_EFFECT_BREATHING -#define RGBLIGHT_EFFECT_RAINBOW_MOOD -#define RGBLIGHT_EFFECT_RAINBOW_SWIRL -#define RGBLIGHT_EFFECT_SNAKE -#define RGBLIGHT_EFFECT_KNIGHT -//#define RGBLIGHT_EFFECT_CHRISTMAS -#define RGBLIGHT_EFFECT_STATIC_GRADIENT -// #define RGBLIGHT_EFFECT_RGB_TEST -#define RGBLIGHT_EFFECT_TWINKLE -#define RGBLIGHT_EFFECT_ALTERNATING -#endif - -/* define if matrix has ghost */ -//#define MATRIX_HAS_GHOST - -/* Set 0 if debouncing isn't needed */ -#define DEBOUNCE 3 - -#define LOCKING_SUPPORT_ENABLE -/* Locking resynchronize hack */ -#define LOCKING_RESYNC_ENABLE diff --git a/keyboards/tdcleft104/info.json b/keyboards/tdcleft104/info.json deleted file mode 100644 index 95eeb6bb938..00000000000 --- a/keyboards/tdcleft104/info.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "keyboard_name": "tdcleft104", - "url": "", - "maintainer": "tdc", - "usb": { - "vid": "0x7856", - "pid": "0x6163", - "device_version": "0.0.2" -}, - "layouts": { - "LAYOUT": { - "layout": [ - {"label":"Esc", "x":0, "y":0}, - {"label":"", "x":1, "y":0}, - {"label":"", "x":2, "y":0}, - {"label":"Num Lock", "x":3, "y":0}, - {"label":"", "x":4.25, "y":0, "w":1.25}, - {"label":"F1", "x":5.5, "y":0}, - {"label":"F2", "x":6.5, "y":0}, - {"label":"F3", "x":7.5, "y":0}, - {"label":"F4", "x":8.5, "y":0}, - {"label":"F5", "x":9.5, "y":0}, - {"label":"F6", "x":10.5, "y":0}, - {"label":"F7", "x":11.5, "y":0}, - {"label":"F8", "x":12.5, "y":0}, - {"label":"F9", "x":13.5, "y":0}, - {"label":"F10", "x":14.5, "y":0}, - {"label":"F11", "x":15.5, "y":0}, - {"label":"F12", "x":16.5, "y":0}, - {"label":"Scroll Lock", "x":17.5, "y":0}, - {"label":"", "x":18.5, "y":0, "w":1.25}, - {"label":"Delete", "x":0, "y":1.5}, - {"label":"/", "x":1, "y":1.5}, - {"label":"*", "x":2, "y":1.5}, - {"label":"-", "x":3, "y":1.5}, - {"label":"~", "x":4.25, "y":1.5}, - {"label":"!", "x":5.25, "y":1.5}, - {"label":"@", "x":6.25, "y":1.5}, - {"label":"#", "x":7.25, "y":1.5}, - {"label":"$", "x":8.25, "y":1.5}, - {"label":"%", "x":9.25, "y":1.5}, - {"label":"^", "x":10.25, "y":1.5}, - {"label":"&", "x":11.25, "y":1.5}, - {"label":"*", "x":12.25, "y":1.5}, - {"label":"(", "x":13.25, "y":1.5}, - {"label":")", "x":14.25, "y":1.5}, - {"label":"_", "x":15.25, "y":1.5}, - {"label":"+", "x":16.25, "y":1.5}, - {"label":"Backspace", "x":17.25, "y":1.5, "w":1.5}, - {"label":"F13", "x":18.75, "y":1.5}, - {"label":"7", "x":0, "y":2.5}, - {"label":"8", "x":1, "y":2.5}, - {"label":"9", "x":2, "y":2.5}, - {"label":"+", "x":3, "y":2.5, "h":2}, - {"label":"Tab", "x":4.25, "y":2.5, "w":1.25}, - {"label":"Q", "x":5.5, "y":2.5}, - {"label":"W", "x":6.5, "y":2.5}, - {"label":"E", "x":7.5, "y":2.5}, - {"label":"R", "x":8.5, "y":2.5}, - {"label":"T", "x":9.5, "y":2.5}, - {"label":"Y", "x":10.5, "y":2.5}, - {"label":"U", "x":11.5, "y":2.5}, - {"label":"I", "x":12.5, "y":2.5}, - {"label":"O", "x":13.5, "y":2.5}, - {"label":"P", "x":14.5, "y":2.5}, - {"label":"{", "x":15.5, "y":2.5}, - {"label":"}", "x":16.5, "y":2.5}, - {"label":"|", "x":17.5, "y":2.5, "w":1.25}, - {"label":"F16", "x":18.75, "y":2.5}, - {"label":"4", "x":0, "y":3.5}, - {"label":"5", "x":1, "y":3.5}, - {"label":"6", "x":2, "y":3.5}, - {"label":"Caps Lock", "x":4.25, "y":3.5, "w":1.25}, - {"label":"A", "x":5.75, "y":3.5}, - {"label":"S", "x":6.75, "y":3.5}, - {"label":"D", "x":7.75, "y":3.5}, - {"label":"F", "x":8.75, "y":3.5}, - {"label":"G", "x":9.75, "y":3.5}, - {"label":"H", "x":10.75, "y":3.5}, - {"label":"J", "x":11.75, "y":3.5}, - {"label":"K", "x":12.75, "y":3.5}, - {"label":"L", "x":13.75, "y":3.5}, - {"label":":", "x":14.75, "y":3.5}, - {"label":"|", "x":15.75, "y":3.5}, - {"label":"Enter", "x":16.75, "y":3.5, "w":2}, - {"label":"F14", "x":18.75, "y":3.5}, - {"label":"1", "x":0, "y":4.5}, - {"label":"2", "x":1, "y":4.5}, - {"label":"3", "x":2, "y":4.5}, - {"label":"Enter", "x":3, "y":4.5, "h":2}, - {"label":"Shift", "x":4.25, "y":4.5, "w":1.75}, - {"label":"Z", "x":6, "y":4.5}, - {"label":"X", "x":7, "y":4.5}, - {"label":"C", "x":8, "y":4.5}, - {"label":"V", "x":9, "y":4.5}, - {"label":"B", "x":10, "y":4.5}, - {"label":"N", "x":11, "y":4.5}, - {"label":"M", "x":12, "y":4.5}, - {"label":"<", "x":13, "y":4.5}, - {"label":">", "x":14, "y":4.5}, - {"label":"?", "x":15, "y":4.5}, - {"label":"Shift", "x":16, "y":4.5, "w":1.5}, - {"label":"u2191", "x":17.5, "y":4.5, "w":1.25}, - {"label":"F15", "x":18.75, "y":4.5}, - {"label":"0", "x":0, "y":5.5, "w":2}, - {"label":".", "x":2, "y":5.5}, - {"label":"Ctrl", "x":4.25, "y":5.5}, - {"label":"Fn", "x":5.25, "y":5.5}, - {"label":"", "x":6.25, "y":5.5}, - {"label":"Alt", "x":7.25, "y":5.5}, - {"x":8.25, "y":5.5, "w":6.25}, - {"label":"Alt", "x":14.5, "y":5.5}, - {"label":"Ctrl", "x":15.5, "y":5.5}, - {"label":"u2190", "x":16.5, "y":5.5}, - {"label":"u2193", "x":17.5, "y":5.5, "w":1.25}, - {"label":"u2192", "x":18.75, "y":5.5}] - } - } -} \ No newline at end of file diff --git a/keyboards/tdcleft104/keymaps/default/keymap.c b/keyboards/tdcleft104/keymaps/default/keymap.c deleted file mode 100644 index 4904cfbf16d..00000000000 --- a/keyboards/tdcleft104/keymaps/default/keymap.c +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2024 TDC - -// 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. - -// T his 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 - -const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { - /* LAYOUT0: Default LAYOUT - * |--------------------------------------------------------------------------------------------------------- - * |Esc |Del |Calc|Nlck|----|Pscr|----|F1 |F2 |F3 |F4 |F5 |F6 |F7 |F8 |F9 |F10 |F11 |F12 |Slck|_M_P| - * |--------------------------------------------------------------------------------------------------------- - * |----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----| - * |--------------------------------------------------------------------------------------------------------- - * |Bspc|Psls|Past|Pwns|----|Grv |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | |Bsp |Tog | - * |--------------------------------------------------------------------------------------------------------- - * |P7 |P8 |P9 |Ppls|----|Tab | |Q |W |E |R |T |Y |U |I |O |P |[ |] |\ |Mod | - * |--------------------------------------------------------------------------------------------------------- - * |P4 |P5 |P6 | | |Caps| |A |S |D |F |G |H |J |K |L |; |' |Entr| |Hud | - * |--------------------------------------------------------------------------------------------------------- - * |1 |2 |3 |Entr| | |Lshi|Z |X |C |V |B |N |M |, |. |/ |Rshi| |Up |Sad | - * |--------------------------------------------------------------------------------------------------------- - * | |0 |Pdot| | |Lctr|Lfn |LGui|Lalt| | | |Spac| | | |Ralt|Rctr|left|Down|Rght| - * `--------------------------------------------------------------------------------------------------------- - */ - //default - [0] = LAYOUT( - KC_ESC, KC_DEL, KC_CALC, KC_NLCK, KC_PSCR, 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_SLCK, RGB_M_P, - KC_BSPC, KC_PSLS, KC_PAST, KC_PMNS, 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_BSPC, RGB_TOG, - KC_P7, KC_P8, KC_P9, KC_PPLS, 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, RGB_MOD, - KC_P4, KC_P5, KC_P6, 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_ENT, RGB_HUD, - KC_P1, KC_P2, KC_P3, KC_PENT, KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, RGB_SAD, - KC_P0, KC_PDOT, KC_LCTL, KC_TRNS, KC_LGUI, KC_LGUI, KC_SPC, KC_RALT, KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT), -}; diff --git a/keyboards/tdcleft104/readme.md b/keyboards/tdcleft104/readme.md deleted file mode 100644 index 8dba21c9ddd..00000000000 --- a/keyboards/tdcleft104/readme.md +++ /dev/null @@ -1,27 +0,0 @@ -#[tdcleft104] - -A fullsize keyboard with 104 hot swap switches -RGB key switch lighting - -* Keyboard Maintainer: (tdc) -* Hardware Supported: TDC PCB -* Hardware Availability: US - -* Qmk example for this keyboard (after setting up your build environment): - - qmk compile -kb tdcleft104 -km default - -* Must enter Bootloader - - ## Bootloader - -* Enter the bootloader - - **Physical reset button**: Briefly press the button on the back of the PCB. - -* Qmk example for flashing: - - qmk flash -kb tdcleft104 -km default - - -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). diff --git a/keyboards/tdcleft104/rules.mk b/keyboards/tdcleft104/rules.mk deleted file mode 100644 index b4f9de0f27d..00000000000 --- a/keyboards/tdcleft104/rules.mk +++ /dev/null @@ -1,18 +0,0 @@ -# MCU name -MCU = STM32F072 - -# Bootloader selection -BOOTLOADER = stm32-dfu - -# Build Options -# change yes to no to disable -# -BOOTMAGIC_ENABLE = no # Enable Bootmagic Lite -MOUSEKEY_ENABLE = no # Mouse keys -EXTRAKEY_ENABLE = yes # Audio control and System control -CONSOLE_ENABLE = no # Console for debug -COMMAND_ENABLE = no # Commands for debug and configuration -NKRO_ENABLE = no # Enable N-Key Rollover -BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality -RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow -AUDIO_ENABLE = no # Audio output \ No newline at end of file diff --git a/keyboards/tdcleft104/tdcleft104.c b/keyboards/tdcleft104/tdcleft104.c deleted file mode 100644 index 6b4e8d1bb23..00000000000 --- a/keyboards/tdcleft104/tdcleft104.c +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 TDC - -// 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. - -// T his 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 "tdcleft104.h" diff --git a/keyboards/tdcleft104/tdcleft104.h b/keyboards/tdcleft104/tdcleft104.h deleted file mode 100644 index 2843931e943..00000000000 --- a/keyboards/tdcleft104/tdcleft104.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2024 TDC - -// 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" - -#define ____ KC_NO - -#define LAYOUT( \ - K000, K001, K002, K003, K004, K006, K007, K008, K009, K010, K011, K012, K013, K014, K015, K016, K017, K018, K019, \ - K100, K101, K102, K103, K104, K105, K106, K107, K108, K109, K110, K111, K112, K113, K114, K115, K116, K118, K119, \ - K200, K201, K202, K203, K204, K206, K207, K208, K209, K210, K211, K212, K213, K214, K215, K216, K217, K218, K219, \ - K300, K301, K302, K304, K306, K307, K308, K309, K310, K311, K312, K313, K314, K315, K316, K317, K319, \ - K400, K401, K402, K403, K405, K406, K407, K408, K409, K410, K411, K412, K413, K414, K415, K416, K418, K419, \ - K501, K502, K504, K505, K506, K507, K511, K515, K516, K517, K518, K519 \ -) { \ - { K000, K001, K002, K003, K004, ____, K006, K007, K008, K009, K010, K011, K012, K013, K014, K015, K016, K017, K018, K019 }, \ - { K100, K101, K102, K103, K104, K105, K106, K107, K108, K109, K110, K111, K112, K113, K114, K115, K116, ____, K118, K119 }, \ - { K200, K201, K202, K203, K204, ____, K206, K207, K208, K209, K210, K211, K212, K213, K214, K215, K216, K217, K218, K219 }, \ - { K300, K301, K302, ____, K304, ____, K306, K307, K308, K309, K310, K311, K312, K313, K314, K315, K316, K317, ____, K319 }, \ - { K400, K401, K402, K403, ____, K405, K406, K407, K408, K409, K410, K411, K412, K413, K414, K415, K416, ____, K418, K419 }, \ - { ____, K501, K502, ____, K504, K505, K506, K507, ____, ____, ____, K511, ____, ____, ____, K515, K516, K517, K518, K519 }, \ -} From ae9814ec613bb4ad66f7514dbd4e94e13b35b7ed Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Wed, 7 May 2025 19:28:19 -0400 Subject: [PATCH 45/54] Refactor README and configuration files for TS Southpaw keyboard: streamline documentation, update RGB settings, and enhance work timer functionality. --- keyboards/tssouthpaw/README.md | 208 ++++------------------------- keyboards/tssouthpaw/keyboard.json | 26 +--- keyboards/tssouthpaw/rules.mk | 25 +--- keyboards/tssouthpaw/tssouthpaw.c | 39 +----- 4 files changed, 30 insertions(+), 268 deletions(-) diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md index d164dc4a013..b216f358a90 100644 --- a/keyboards/tssouthpaw/README.md +++ b/keyboards/tssouthpaw/README.md @@ -1,187 +1,31 @@ -# TS Southpaw Keyboard +# TS Southpaw -## Overview +A southpaw numpad keyboard with RGB matrix and work timer functionality. -The TS Southpaw is a custom mechanical keyboard with a unique southpaw numpad layout (numpad on the left side) optimized for both efficiency and comfort. Built with the QMK firmware on an RP2040 controller, it features full RGB matrix lighting, a rotary encoder for volume control, and specialized features like a work timer system. +* 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 -- **RP2040 Controller** with 16MB of flash storage -- **Full RGB Matrix** with 104 individually addressable LEDs -- **Rotary Encoder** for volume control (4 steps per detent) -- **Customizable Work Timer** with visual notifications -- **Dynamic Macros** for recording and playing custom key sequences -- **Caps Word** functionality for typing in UPPERCASE -- **Comprehensive RGB Effects** including reactive typing and heatmap - -### Keyboard Layout Diagram - -``` -┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ -│Esc│Del│Num│Mic│ │ │ │Mut│ F1│ F2│ F3│ F4│ F5│ F6│ F7│ F8│ F9│F10│F11│F12│Lck│ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│Bsp│ / │ * │ - │Rc1│Rc2│ ` │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ - │ = │ │Bsp│ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ 7 │ 8 │ 9 │ │Cal│PtS│ │Tab│ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ [ │ ] │ \ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ 4 │ 5 │ 6 │ + │Pl1│Pl2│ │Cap│ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ' │ │Ent│ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ 1 │ 2 │ 3 │ │ │ ↑ │ │Sft│ │ Z │ X │ C │ V │ B │ N │ M │ , │ . │ / │Sft│ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ │ 0 │ . │Ent│ ← │ ↓ │ → │ │Ctl│Alt│ │ │ │Spc│ │ │Alt│ │Win│Fn │Ctl│ -└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ -``` - -### FN Layout Diagram - -``` -┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ -│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ │ │ │V- │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ │8hr│10h│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│4hr│ │ │V+ │ │ │ │CWd│ │Tmr│ │ │ │ │ │ │ │ │ │ │ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│1hr│ │30m│ │ │H+ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ -│ │ │ │ │RM-│H- │RM+│ │ │ │ │ │ │P/R│ │ │ │ │ │ │ │ -└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ -``` - -## Work Timer System - -The TS Southpaw includes a sophisticated work timer system that helps manage work/break cycles and promotes productive habits. - -### Work Timer Types - -The keyboard supports multiple timer durations with customized behavior: - -| Timer | Duration | Mid-Point | End Warning | -|-------|----------|-----------|-------------| -| 30MIN | 30 minutes | 30-second blue pulse at 15 minutes | 5-minute red pulse before end | -| 1HR | 1 hour | 45-second blue pulse at 30 minutes | 5-minute red pulse before end | -| 4HR | 4 hours | 60-second blue pulse at 2 hours | 5-minute red pulse before end | -| 8HR | 8 hours | 1-hour lunch break at 4 hours with blue indicator | 5-minute red pulse before end | -| 10HR | 10 hours | 1-hour lunch break at 5 hours with blue indicator | 5-minute red pulse before end | - -### Visual Feedback - -The work timer provides visual feedback through the function row LEDs (F1-F12): -- Green-to-yellow-to-orange-to-red gradient shows overall progress -- Blue pulses indicate breaks or lunch periods -- Red pulses warn when a timer is about to end - -### Wake-on-Pulse Feature - -The keyboard will automatically wake from sleep mode when important timer notifications are active, ensuring you don't miss break reminders or end-of-timer warnings even when the keyboard's RGB lighting is normally turned off due to inactivity. This feature works by checking if any timer pulse is active before entering full power-down mode, and keeps the RGB system partially active to display notifications. - -### Work Timer Controls - -The following keycodes control the work timer system: - -| Keycode | Function | Location on FN Layer | -|---------|----------|----------------------| -| KC_WRKTMR | Start default 8-hour timer | S key | -| KC_30MIN | Start 30-minute timer | Numpad 3 | -| KC_1HR | Start 1-hour timer | Numpad 1 | -| KC_4HR | Start 4-hour timer | Numpad 4 | -| KC_8HR | Start 8-hour timer | Numpad 8 | -| KC_10HR | Start 10-hour timer | Numpad 9 | -| KC_WRKPAU | Pause/resume current timer | Space Bar key | - -### Timer State Persistence - -The work timer state is saved to EEPROM, allowing it to persist across power cycles. When the keyboard powers back on, it will restore the active timer and adjust the elapsed time accordingly. - -## RGB Effects & Indicators - -The TS Southpaw includes several special RGB effects: - -- **ESC Ripple Effect**: Visual ripple emanating from the ESC key when pressed -- **Arrow Key Highlighting**: Blue highlighting for arrow keys -- **Caps Lock Indicator**: Orange pulse when Caps Lock is active -- **Num Lock Indicator**: Orange pulse when Num Lock is OFF -- **Microphone Mute**: Red pulse when microphone is muted -- **Work Timer Indicators**: Color gradient on function row to show progress -- **Typing Heatmap**: Dynamic color effect showing recently pressed keys -- **RGB Matrix Effects**: Multiple built-in QMK RGB effects including: - - Solid Color - - Breathing - - Gradient - - Rainbow modes - - Reactive typing - - Digital rain - - Raindrop effects - -## Rotary Encoder - -The keyboard features a rotary encoder with 4 steps per detent for precise control: - -- **Default Function**: Volume control (clockwise for volume up, counter-clockwise for volume down) -- **Position**: Located near the arrow keys for easy access -- **Implementation**: Uses hardware interrupts for reliable operation - -## Layers - -The keyboard has three main layers: - -1. **Base Layer**: Standard QWERTY layout with numpad -2. **Function Layer (FN)**: Accessed by holding the key to the left of right Control - - Contains RGB controls, work timer controls, and special functions - -## Dynamic Macros - -Record dynamic macros using: -- DM_REC1, DM_REC2: Start recording to slot 1 or 2 -- DM_RSTP: Stop recording -- DM_PLY1, DM_PLY2: Play back macro in slot 1 or 2 - -## Getting Started - -1. Clone the QMK firmware repository -2. Navigate to `keyboards/tssouthpaw` -3. Compile with `qmk compile -kb tssouthpaw -km default` -4. Flash using QMK Toolbox or command line - -## RGB Matrix Control - -Use RGB keycodes on the FN layer: -- RM+, RGB_MOD: next RGB mode -- RM-, RGB_RMOD: previous RGB mode -- V+, RGB_VAI: RGB value up (brightness ↑) -- V-, RGB_VAD: RGB value down (brightness ↓) -- H+, RGB_HUI: RGB hue up -- H-, RGB_HUD: RGB hue down - -## Technical Specifications - -- **MCU**: RP2040 (Raspberry Pi) -- **USB**: Type-C connector -- **RGB**: WS2812B LEDs (104 individually addressable) -- **Matrix**: 6×21 (126 keys) -- **Rotary Encoder**: Resolution 4 steps per detent -- **Memory**: 16MB flash storage - -## Troubleshooting - -### Common Issues - -- **RGB Not Working**: Check if RGB matrix is enabled in your QMK build -- **Timer Not Persisting**: Ensure EEPROM is properly initialized -- **Rotary Encoder Issues**: Verify pin assignments in config.h - -### Reset Options - -- **Bootloader Mode**: To enter bootloader mode, press the reset button while plugging in the keyboard -- **EEPROM Reset**: Hold ESC while plugging in the keyboard to reset all settings to default - -## License - -This keyboard firmware is released under the GPL v2 license. - -## Credits - -Created by TS Design Works LLC -Work Timer inspiration from https://unnecessaryinventions.com/ = https://youtu.be/UTSBCMNLqcw?si=LyVwGeiBSx44G1sk \ No newline at end of file +* 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/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 4674909d93d..8aadedd62ed 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -20,8 +20,8 @@ "suspend_wakeup_delay": 200 }, "ws2812": { - "driver": "vendor", - "pin": "GP27" + "driver": "vendor", + "pin": "GP27" }, "encoder": { "enabled": true, @@ -42,32 +42,10 @@ "debounce": 5, "rgb_matrix": { "driver": "ws2812", - "led_count": 104, "timeout": 600000, "sleep": true, "led_process_limit": 8, - "led_flush_limit": 16, - "max_brightness": 250, "default_mode": "SOLID_COLOR", - "typing_heatmap": { - "decrease_delay_ms": 50, - "spread": 40, - "area_limit": 16, - "increase_step": 32 - }, - "keyreleases": true, - "react_on_keyup": true, - "default": { - "hue": 0, - "sat": 255, - "val": 128, - "speed": 100 - }, - "startup": { - "hue": 85, - "sat": 255, - "val": 128 - }, "animations": { "solid_color": true, "breathing": true, diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk index c5f47ea24a8..f5d7b01cfaf 100644 --- a/keyboards/tssouthpaw/rules.mk +++ b/keyboards/tssouthpaw/rules.mk @@ -1,24 +1 @@ -# Include custom features -SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c - -# MCU name -MCU = RP2040 - -# Platform specification -PLATFORM = CHIBIOS - -# Features not in info.json -DYNAMIC_MACRO_ENABLE = yes -KEY_LOCK_ENABLE = yes - -# Disabled features to save space -CONSOLE_ENABLE = no -COMMAND_ENABLE = no -SPACE_CADET_ENABLE = no -GRAVE_ESC_ENABLE = no -MAGIC_ENABLE = no -MUSIC_ENABLE = no -LEADER_ENABLE = no -MIDI_ENABLE = no -BLUETOOTH_ENABLE = no -AUDIO_ENABLE = no \ No newline at end of file +SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c \ No newline at end of file diff --git a/keyboards/tssouthpaw/tssouthpaw.c b/keyboards/tssouthpaw/tssouthpaw.c index a5f9aece424..da297eb5e36 100644 --- a/keyboards/tssouthpaw/tssouthpaw.c +++ b/keyboards/tssouthpaw/tssouthpaw.c @@ -18,9 +18,6 @@ #include "rgb_effects/rgb_effects.h" #include "rgb_effects/work_timer.h" - // Forward declaration for the weak function - bool suspend_wakeup_condition_user(void); - /** * Keyboard initialization * Called once at startup @@ -36,46 +33,16 @@ // Load saved work timer state from EEPROM if applicable work_timer_init(); - // Ensure RGB minimum brightness setting is applied - rgb_matrix_set_suspend_state(false); - // Continue with any user-level initialization keyboard_post_init_user(); } - /** - * Check if the keyboard should wake from sleep - * This function is called by the QMK core during suspend - */ - bool suspend_wakeup_condition_kb(void) { - // Wake the keyboard if a timer pulse is active - if (is_timer_pulse_active()) { - // Explicitly wake the keyboard by returning true AND - // setting suspend state to false for RGB matrix - rgb_matrix_set_suspend_state(false); - - // Force appropriate 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 true; - } - - // Otherwise defer to the user function - return suspend_wakeup_condition_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()) { - // Keep RGB matrix enabled for timer notifications - rgb_matrix_set_suspend_state(false); - // Ensure RGB matrix has reasonable brightness for notifications uint8_t val = rgb_matrix_get_val(); if (val < RGB_MATRIX_MINIMUM_BRIGHTNESS) { @@ -123,10 +90,6 @@ } // Default implementations for weak functions - __attribute__((weak)) bool suspend_wakeup_condition_user(void) { - return false; - } - __attribute__((weak)) void keyboard_post_init_user(void) {} __attribute__((weak)) void suspend_power_down_user(void) {} - __attribute__((weak)) void suspend_wakeup_init_user(void) {} \ No newline at end of file + __attribute__((weak)) void suspend_wakeup_init_user(void) {} From bf4c7069bb0886365e214e3e1310c8b139a1175d Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Wed, 7 May 2025 21:41:03 -0400 Subject: [PATCH 46/54] Fix missing newline at end of keyboard.json file --- keyboards/tssouthpaw/keyboard.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 8aadedd62ed..779fb767b2f 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -310,4 +310,4 @@ ] } } -} \ No newline at end of file +} From 9f754ce8b38246ddeea2c778302fbb93acd1a4de Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 13 May 2025 20:50:01 -0400 Subject: [PATCH 47/54] Add initial rgb_effects.c file for TSSouthpaw keyboard --- keyboards/tssouthpaw/keyboard.json | 1 - .../tssouthpaw/keymaps/default/rgb_effects.c | 0 keyboards/tssouthpaw/rgb_effects/work_timer.c | 1286 +++++++++-------- 3 files changed, 653 insertions(+), 634 deletions(-) create mode 100644 keyboards/tssouthpaw/keymaps/default/rgb_effects.c diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 779fb767b2f..01c40b5b91c 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -42,7 +42,6 @@ "debounce": 5, "rgb_matrix": { "driver": "ws2812", - "timeout": 600000, "sleep": true, "led_process_limit": 8, "default_mode": "SOLID_COLOR", diff --git a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c new file mode 100644 index 00000000000..e69de29bb2d diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c index efcd901e0ba..3916b551ff0 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.c +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -14,625 +14,626 @@ * along with this program. If not, see . */ - #include "work_timer.h" - #include "quantum.h" - #include "rgb_effects/rgb_effects.h" - #include "timer.h" - - // Bitpacked flags to save memory - typedef struct { - uint8_t active: 1; - uint8_t paused: 1; - uint8_t lunch_break: 1; - uint8_t mid_break: 1; - uint8_t lunch_warning_shown: 1; - uint8_t mid_break_warning_shown: 1; - uint8_t end_warning_shown: 1; - uint8_t pulse_active: 1; // Flag to track if any pulse is currently active - } work_timer_flags_t; - - // Work timer state structure - typedef struct { - work_timer_flags_t flags; - work_timer_type_t timer_type; - uint32_t start_time; - uint32_t elapsed_time; // Work time (excluding breaks) - uint32_t pause_time; - uint32_t break_start_time; // When current break started - uint32_t total_break_time; // Total accumulated break time - uint32_t timer_duration; // Total work time duration (excludes breaks) - uint32_t mid_break_start; // When in workday the break occurs - uint32_t mid_break_duration; - bool has_lunch_break; - } work_timer_t; - - // Global work timer state - static work_timer_t work_timer = { - .flags = {0}, - .timer_type = TIMER_TYPE_8HR, - .start_time = 0, - .elapsed_time = 0, - .pause_time = 0, - .break_start_time = 0, - .total_break_time = 0, - .timer_duration = TIMER_8HR_DURATION, - .mid_break_start = TIMER_8HR_DURATION / 2, // Default halfway point - .mid_break_duration = LUNCH_BREAK_DURATION, - .has_lunch_break = true - }; - - // Predefined RGB colors for timer states - static const rgb_color_t WORK_TIMER_START_COLOR = {WORK_TIMER_START_R, WORK_TIMER_START_G, WORK_TIMER_START_B}; - static const rgb_color_t WORK_TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MID_G, WORK_TIMER_MID_B}; - static const rgb_color_t WORK_TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B}; - static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B}; - static const rgb_color_t WORK_TIMER_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; - static const rgb_color_t WORK_TIMER_BREAK_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; - - // Function prototypes for internal functions - static void configure_timer_for_type(work_timer_type_t timer_type); - static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor); - static void save_work_timer_state(void); - static void load_work_timer_state(void); - static void update_pulse_active_state(void); - - /** - * Configure timer parameters based on timer type - */ - static void configure_timer_for_type(work_timer_type_t timer_type) { - work_timer.timer_type = timer_type; - - switch (timer_type) { - case TIMER_TYPE_30MIN: - work_timer.timer_duration = TIMER_30MIN_DURATION; - work_timer.mid_break_start = TIMER_30MIN_DURATION / 2; - work_timer.mid_break_duration = MID_BREAK_30MIN_DURATION; - work_timer.has_lunch_break = false; - break; - - case TIMER_TYPE_1HR: - work_timer.timer_duration = TIMER_1HR_DURATION; - work_timer.mid_break_start = TIMER_1HR_DURATION / 2; - work_timer.mid_break_duration = MID_BREAK_1HR_DURATION; - work_timer.has_lunch_break = false; - break; - - case TIMER_TYPE_4HR: - work_timer.timer_duration = TIMER_4HR_DURATION; - work_timer.mid_break_start = TIMER_4HR_DURATION / 2; - work_timer.mid_break_duration = MID_BREAK_4HR_DURATION; - work_timer.has_lunch_break = false; - break; - - case TIMER_TYPE_8HR: - work_timer.timer_duration = TIMER_8HR_DURATION; - work_timer.mid_break_start = TIMER_8HR_DURATION / 2; - work_timer.mid_break_duration = LUNCH_BREAK_DURATION; - work_timer.has_lunch_break = true; - break; - - case TIMER_TYPE_10HR: - work_timer.timer_duration = TIMER_10HR_DURATION; - work_timer.mid_break_start = TIMER_10HR_DURATION / 2; - work_timer.mid_break_duration = LUNCH_BREAK_DURATION; - work_timer.has_lunch_break = true; - break; - - default: - // Default to 8HR if something goes wrong - work_timer.timer_duration = TIMER_8HR_DURATION; - work_timer.mid_break_start = TIMER_8HR_DURATION / 2; - work_timer.mid_break_duration = LUNCH_BREAK_DURATION; - work_timer.has_lunch_break = true; - break; - } - } - - /** - * Save the work timer state to EEPROM - * Modern QMK using block-based EEPROM operations for RP2040 - */ - static void save_work_timer_state(void) { - // Create a buffer to store all our data - uint8_t buffer[19] = {0}; // 19 bytes total - - // Set the active flag - buffer[0] = work_timer.flags.active; - - // Only save time info if timer is active - if (work_timer.flags.active) { - // Save start time (4 bytes) - memcpy(&buffer[1], &work_timer.start_time, sizeof(uint32_t)); - - // Save elapsed time (4 bytes) - memcpy(&buffer[5], &work_timer.elapsed_time, sizeof(uint32_t)); - - // Save total break time (4 bytes) - memcpy(&buffer[9], &work_timer.total_break_time, sizeof(uint32_t)); - - // Save timer type (1 byte) - buffer[13] = (uint8_t)work_timer.timer_type; - - // Save break state (1 byte) - buffer[14] = (work_timer.flags.lunch_break ? 1 : 0) | - (work_timer.flags.mid_break ? 2 : 0); - - // Save break start time (4 bytes) - memcpy(&buffer[15], &work_timer.break_start_time, sizeof(uint32_t)); - } - - // Write all data at once to EEPROM - eeprom_update_block(buffer, (void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); - } - - /** - * Load the work timer state from EEPROM - * Modern QMK using block-based EEPROM operations for RP2040 - */ - static void load_work_timer_state(void) { - // Create a buffer to read all our data - uint8_t buffer[19] = {0}; // 19 bytes total - - // Read all data at once from EEPROM - eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); - - // Get the active flag - work_timer.flags.active = buffer[0]; - - // Only process the rest if timer was active - if (work_timer.flags.active) { - // Load timer type - work_timer_type_t saved_type = (work_timer_type_t)buffer[13]; - - // Apply configuration for this timer type - configure_timer_for_type(saved_type); - - // Get start time (4 bytes) - memcpy(&work_timer.start_time, &buffer[1], sizeof(uint32_t)); - - // Get the elapsed work time (4 bytes) - memcpy(&work_timer.elapsed_time, &buffer[5], sizeof(uint32_t)); - - // Get the total break time (4 bytes) - memcpy(&work_timer.total_break_time, &buffer[9], sizeof(uint32_t)); - - // Get break state (1 byte) - uint8_t break_state = buffer[14]; - work_timer.flags.lunch_break = (break_state & 1) > 0; - work_timer.flags.mid_break = (break_state & 2) > 0; - - // Get break start time (4 bytes) - memcpy(&work_timer.break_start_time, &buffer[15], sizeof(uint32_t)); - - // If in a break, adjust break_start_time to account for time powered off - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - uint32_t current_time = timer_read32(); - uint32_t time_off = current_time - work_timer.break_start_time; - - // If we've been off for less than the break duration, continue the break - if (work_timer.flags.lunch_break && - time_off < work_timer.mid_break_duration) { - // Stay in lunch break, adjust break_start_time - work_timer.break_start_time = current_time - time_off; - } - else if (work_timer.flags.mid_break && - time_off < work_timer.mid_break_duration) { - // Stay in mid-break, adjust break_start_time - work_timer.break_start_time = current_time - time_off; - } - else { - // Break would have ended while powered off - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - - // Add the remaining break time to total_break_time - // This is approximate but better than nothing - if (time_off < work_timer.mid_break_duration) { - work_timer.total_break_time += work_timer.mid_break_duration; - } - } - } - - // Calculate elapsed wall time (including time powered off) - uint32_t current_time = timer_read32(); - uint32_t wall_time_elapsed = current_time - work_timer.start_time; - - // Adjust start time to account for time powered off, preserving elapsed_time - work_timer.start_time = current_time - wall_time_elapsed; - - // Validate time values - if unreasonable, reset - if (work_timer.elapsed_time > work_timer.timer_duration) { - work_timer.flags.active = 0; - save_work_timer_state(); - } - - // Check for end warning state - if (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { - work_timer.flags.end_warning_shown = 1; - } - - // Update pulse active state - update_pulse_active_state(); - } - } - - /** - * Display progress bar with gradient colors - */ - static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor) { - // Calculate hour segments and LED positions - float hours_per_led = 1.0f / (float)num_leds; - - // Determine how many LEDs should be fully lit - uint8_t leds_lit = (uint8_t)(overall_progress / hours_per_led); - if (leds_lit > num_leds) leds_lit = num_leds; - - // Calculate progress within the current LED - float current_led_progress = (overall_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 led_position = (float)i / (float)(num_leds - 1); - - // Adjust transition points - more green-to-orange (first 80% of bar), - // and less orange-to-red (last 20% of bar) - if (led_position < 0.3f) { - // Scale to 0.0 - 1.0 for green-to-orange part (now 80% of the bar) - float adjusted_progress = led_position / 0.3f; - color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); - } - else { - float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% - color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); - } - - // Apply overall RGB brightness factor - color.r = (uint8_t)((float)color.r * brightness_factor); - color.g = (uint8_t)((float)color.g * brightness_factor); - color.b = (uint8_t)((float)color.b * brightness_factor); - } - else if (i == leds_lit && current_led_progress > 0.0f) { - // Current LED - partially lit based on progress - float led_position = (float)i / (float)(num_leds - 1); - - rgb_color_t full_color; - // Adjust transition points - more green-to-orange, less orange-to-red - if (led_position < 0.3f) { - // Scale to 0.0 - 1.0 for green-to-orange part - float adjusted_progress = led_position / 0.3f; - full_color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); - } - // Last 30% is orange-to-red gradient - else { - float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% - full_color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); - } - - // Dim the color based on progress within this LED and overall brightness - color.r = (uint8_t)((float)full_color.r * current_led_progress * brightness_factor); - color.g = (uint8_t)((float)full_color.g * current_led_progress * brightness_factor); - color.b = (uint8_t)((float)full_color.b * current_led_progress * brightness_factor); - } - - // Set the LED color - rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, color.b); - } - } - - /** - * Update the pulse active state - * This determines if the keyboard should wake from sleep for timer notification - */ - static void update_pulse_active_state(void) { - if (!work_timer.flags.active || work_timer.flags.paused) { - work_timer.flags.pulse_active = false; - return; - } - - // Check if any pulse condition is active - bool lunch_warning = false; - bool lunch_end_warning = false; - bool mid_point_warning = false; - bool end_warning = false; - - // For timers with lunch breaks - if (work_timer.has_lunch_break) { - // Lunch break warning (before lunch) - lunch_warning = !work_timer.flags.lunch_break && - (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start); - - // Lunch end warning (before end of lunch) - lunch_end_warning = work_timer.flags.lunch_break && - (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); - } else { - // Mid-point break warning for shorter timers - mid_point_warning = !work_timer.flags.mid_break && - (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start); - } - - // End timer warning (5 minutes before end) - end_warning = (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.timer_duration); - - // Set pulse active if any of these conditions are true - bool new_pulse_active = ( - work_timer.flags.mid_break || // Mid-point break pulse - work_timer.flags.lunch_break || // Lunch break - work_timer.flags.end_warning_shown || // End warning - lunch_warning || // Pre-lunch warning - lunch_end_warning || // End of lunch warning - mid_point_warning || // Mid-point warning for shorter timers - end_warning // End warning for all timers - ); - - // Force a wakeup signal if transitioning from inactive to active pulse - if (!work_timer.flags.pulse_active && new_pulse_active) { - // Explicitly wake up keyboard by toggling suspend state - rgb_matrix_set_suspend_state(false); - - // Force appropriate 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); - } - } - - work_timer.flags.pulse_active = new_pulse_active; - } - - /** - * Toggle the work timer on/off - */ - void toggle_work_timer(void) { - if (work_timer.flags.active) { - // If timer is active, stop it - work_timer.flags.active = 0; - work_timer.flags.pulse_active = 0; // Clear pulse active flag - save_work_timer_state(); - } else { - // If timer is inactive, just activate it with current settings - // This assumes the timer type and duration are already set - work_timer.flags.active = 1; - work_timer.flags.paused = 0; - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.flags.end_warning_shown = 0; - work_timer.flags.pulse_active = 0; - - work_timer.start_time = timer_read32(); - work_timer.elapsed_time = 0; - work_timer.pause_time = 0; - work_timer.break_start_time = 0; - work_timer.total_break_time = 0; - - save_work_timer_state(); - } - - // Ensure timer visual updates immediately after toggle - update_pulse_active_state(); - } - - /** - * Initialize the work timer - load state from EEPROM - */ - void work_timer_init(void) { - load_work_timer_state(); - } - - /** - * Start a specific timer type - */ - void start_timer(work_timer_type_t timer_type) { - // Configure timer parameters based on type - configure_timer_for_type(timer_type); - - // Reset timer state - work_timer.flags.active = 1; - work_timer.flags.paused = 0; - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.flags.end_warning_shown = 0; - work_timer.flags.pulse_active = 0; - - work_timer.start_time = timer_read32(); - work_timer.elapsed_time = 0; - work_timer.pause_time = 0; - work_timer.break_start_time = 0; - work_timer.total_break_time = 0; - - save_work_timer_state(); - - // Update pulse state immediately after starting timer - update_pulse_active_state(); - } - - /** - * Pause or resume the work timer - */ - void toggle_pause_work_timer(void) { - if (!work_timer.flags.active) return; - - if (!work_timer.flags.paused) { - // Pause the timer - work_timer.flags.paused = 1; - work_timer.pause_time = timer_read32(); - save_work_timer_state(); - } else { - // Resume the timer, adjust start time to account for pause duration - uint32_t pause_duration = timer_read32() - work_timer.pause_time; - work_timer.start_time += pause_duration; - - // If in a break, adjust break start time too - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - work_timer.break_start_time += pause_duration; - } - - work_timer.flags.paused = 0; - save_work_timer_state(); - } - - // Update pulse state after changing pause status - update_pulse_active_state(); - } - - /** - * Update the work timer state - */ - void update_work_timer(void) { - if (!work_timer.flags.active || work_timer.flags.paused) return; - - // Calculate wall time (real-world time) elapsed since start - uint32_t wall_time_elapsed = timer_read32() - work_timer.start_time; - - // Current wall time - uint32_t current_time = timer_read32(); - - // Process different timer states based on timer type - if (work_timer.has_lunch_break) { - // For timers with lunch breaks (8HR and 10HR) - - // Handle lunch break state transitions - if (!work_timer.flags.lunch_break) { - // Check if it's time to start lunch - if (!work_timer.flags.lunch_warning_shown && - work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start) { - // Pre-lunch warning (red pulse before lunch) - work_timer.flags.lunch_warning_shown = 1; - } - else if (work_timer.elapsed_time >= work_timer.mid_break_start) { - // Start lunch break - work_timer.flags.lunch_break = 1; - work_timer.break_start_time = current_time; - work_timer.flags.lunch_warning_shown = 0; // Reset for lunch end warning - - // Save state when entering lunch break - save_work_timer_state(); - } - } - else { - // Currently in lunch break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // Check for lunch end warning - if (!work_timer.flags.lunch_warning_shown && - break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { - // Pre-end warning (red pulse before end of lunch) - work_timer.flags.lunch_warning_shown = 1; - } - - // FIX: Emergency safety - force end lunch if it's been active way too long - // This addresses the stuck lunch mode issue - if (break_elapsed >= (work_timer.mid_break_duration * 2)) { - // Break has been active for twice as long as it should be - force exit - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.total_break_time += work_timer.mid_break_duration; // Use exact duration instead - save_work_timer_state(); - } - // Normal lunch break end check - else if (break_elapsed >= work_timer.mid_break_duration) { - // End lunch break - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; // FIX: Reset the warning flag - - // Add the break time to total_break_time - use the exact duration - // FIX: Use the exact lunch break duration instead of elapsed time - work_timer.total_break_time += work_timer.mid_break_duration; - - // Save state when exiting lunch break - save_work_timer_state(); - } - } - } - else { - // For shorter timers without lunch breaks (30MIN, 1HR, 4HR) - - // Handle mid-break state transitions - if (!work_timer.flags.mid_break) { - // Check if it's time to start mid-break - if (!work_timer.flags.mid_break_warning_shown && - work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start) { - // Mid-break warning - work_timer.flags.mid_break_warning_shown = 1; - } - else if (work_timer.elapsed_time >= work_timer.mid_break_start) { - // Start mid-break - work_timer.flags.mid_break = 1; - work_timer.break_start_time = current_time; - save_work_timer_state(); - } - } - else { - // Currently in mid-break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // FIX: Emergency safety - force end mid-break if it's been active way too long - if (break_elapsed >= (work_timer.mid_break_duration * 2)) { - // Break has been active for twice as long as it should be - force exit - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.total_break_time += work_timer.mid_break_duration; - save_work_timer_state(); - } - // Normal mid-break end check - else if (break_elapsed >= work_timer.mid_break_duration) { - // End mid-break - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; // FIX: Reset warning flag - - // Add the break time to total_break_time - work_timer.total_break_time += work_timer.mid_break_duration; - save_work_timer_state(); - } - } - } - - // Calculate true work time (excluding breaks) - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - // Currently in a break, so work time is wall time minus total breaks - // minus current break elapsed time - uint32_t current_break_elapsed = timer_elapsed32(work_timer.break_start_time); - work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time - current_break_elapsed; - } else { - // Not in a break, so work time is wall time minus total breaks - work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time; - } - - // Check for end of day warning (5 min before end) - if (!work_timer.flags.end_warning_shown && - work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { - work_timer.flags.end_warning_shown = 1; - } - - // Auto-stop after timer duration - if (work_timer.elapsed_time >= work_timer.timer_duration) { - work_timer.flags.active = 0; - save_work_timer_state(); - } - - // Update the pulse active state for wakeup - update_pulse_active_state(); - } - +#include "work_timer.h" +#include "quantum.h" +#include "rgb_effects/rgb_effects.h" +#include "timer.h" + +// Bitpacked flags to save memory +typedef struct { + uint8_t active: 1; + uint8_t paused: 1; + uint8_t lunch_break: 1; + uint8_t mid_break: 1; + uint8_t lunch_warning_shown: 1; + uint8_t mid_break_warning_shown: 1; + uint8_t end_warning_shown: 1; + uint8_t pulse_active: 1; // Flag to track if any pulse is currently active +} work_timer_flags_t; + +// Work timer state structure +typedef struct { + work_timer_flags_t flags; + work_timer_type_t timer_type; + uint32_t start_time; + uint32_t elapsed_time; // Work time (excluding breaks) + uint32_t pause_time; + uint32_t break_start_time; // When current break started + uint32_t total_break_time; // Total accumulated break time + uint32_t timer_duration; // Total work time duration (excludes breaks) + uint32_t mid_break_start; // When in workday the break occurs + uint32_t mid_break_duration; + bool has_lunch_break; +} work_timer_t; + +// Global work timer state +static work_timer_t work_timer = { + .flags = {0}, + .timer_type = TIMER_TYPE_8HR, + .start_time = 0, + .elapsed_time = 0, + .pause_time = 0, + .break_start_time = 0, + .total_break_time = 0, + .timer_duration = TIMER_8HR_DURATION, + .mid_break_start = TIMER_8HR_DURATION / 2, // Default halfway point + .mid_break_duration = LUNCH_BREAK_DURATION, + .has_lunch_break = true +}; + +// Predefined RGB colors for timer states +static const rgb_color_t WORK_TIMER_START_COLOR = {WORK_TIMER_START_R, WORK_TIMER_START_G, WORK_TIMER_START_B}; +static const rgb_color_t WORK_TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MID_G, WORK_TIMER_MID_B}; +static const rgb_color_t WORK_TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B}; +static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B}; +static const rgb_color_t WORK_TIMER_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; +static const rgb_color_t WORK_TIMER_BREAK_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; + +// Function prototypes for internal functions +static void configure_timer_for_type(work_timer_type_t timer_type); +static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor); +static void save_work_timer_state(void); +static void load_work_timer_state(void); +static void update_pulse_active_state(void); + +/** + * Configure timer parameters based on timer type + */ +static void configure_timer_for_type(work_timer_type_t timer_type) { + work_timer.timer_type = timer_type; + + switch (timer_type) { + case TIMER_TYPE_30MIN: + work_timer.timer_duration = TIMER_30MIN_DURATION; + work_timer.mid_break_start = TIMER_30MIN_DURATION / 2; + work_timer.mid_break_duration = MID_BREAK_30MIN_DURATION; + work_timer.has_lunch_break = false; + break; + + case TIMER_TYPE_1HR: + work_timer.timer_duration = TIMER_1HR_DURATION; + work_timer.mid_break_start = TIMER_1HR_DURATION / 2; + work_timer.mid_break_duration = MID_BREAK_1HR_DURATION; + work_timer.has_lunch_break = false; + break; + + case TIMER_TYPE_4HR: + work_timer.timer_duration = TIMER_4HR_DURATION; + work_timer.mid_break_start = TIMER_4HR_DURATION / 2; + work_timer.mid_break_duration = MID_BREAK_4HR_DURATION; + work_timer.has_lunch_break = false; + break; + + case TIMER_TYPE_8HR: + work_timer.timer_duration = TIMER_8HR_DURATION; + work_timer.mid_break_start = TIMER_8HR_DURATION / 2; + work_timer.mid_break_duration = LUNCH_BREAK_DURATION; + work_timer.has_lunch_break = true; + break; + + case TIMER_TYPE_10HR: + work_timer.timer_duration = TIMER_10HR_DURATION; + work_timer.mid_break_start = TIMER_10HR_DURATION / 2; + work_timer.mid_break_duration = LUNCH_BREAK_DURATION; + work_timer.has_lunch_break = true; + break; + + default: + // Default to 8HR if something goes wrong + work_timer.timer_duration = TIMER_8HR_DURATION; + work_timer.mid_break_start = TIMER_8HR_DURATION / 2; + work_timer.mid_break_duration = LUNCH_BREAK_DURATION; + work_timer.has_lunch_break = true; + break; + } +} + +/** + * Save the work timer state to EEPROM + * Modern QMK using block-based EEPROM operations for RP2040 + */ +static void save_work_timer_state(void) { + // Create a buffer to store all our data + uint8_t buffer[19] = {0}; // 19 bytes total + + // Set the active flag + buffer[0] = work_timer.flags.active; + + // Only save time info if timer is active + if (work_timer.flags.active) { + // Save start time (4 bytes) + memcpy(&buffer[1], &work_timer.start_time, sizeof(uint32_t)); + + // Save elapsed time (4 bytes) + memcpy(&buffer[5], &work_timer.elapsed_time, sizeof(uint32_t)); + + // Save total break time (4 bytes) + memcpy(&buffer[9], &work_timer.total_break_time, sizeof(uint32_t)); + + // Save timer type (1 byte) + buffer[13] = (uint8_t)work_timer.timer_type; + + // Save break state (1 byte) + buffer[14] = (work_timer.flags.lunch_break ? 1 : 0) | + (work_timer.flags.mid_break ? 2 : 0); + + // Save break start time (4 bytes) + memcpy(&buffer[15], &work_timer.break_start_time, sizeof(uint32_t)); + } + + // Write all data at once to EEPROM + eeprom_update_block(buffer, (void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); +} + +/** + * Load the work timer state from EEPROM + * Modern QMK using block-based EEPROM operations for RP2040 + */ +static void load_work_timer_state(void) { + // Create a buffer to read all our data + uint8_t buffer[19] = {0}; // 19 bytes total + + // Read all data at once from EEPROM + eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); + + // Get the active flag + work_timer.flags.active = buffer[0]; + + // Only process the rest if timer was active + if (work_timer.flags.active) { + // Load timer type + work_timer_type_t saved_type = (work_timer_type_t)buffer[13]; + + // Apply configuration for this timer type + configure_timer_for_type(saved_type); + + // Get start time (4 bytes) + memcpy(&work_timer.start_time, &buffer[1], sizeof(uint32_t)); + + // Get the elapsed work time (4 bytes) + memcpy(&work_timer.elapsed_time, &buffer[5], sizeof(uint32_t)); + + // Get the total break time (4 bytes) + memcpy(&work_timer.total_break_time, &buffer[9], sizeof(uint32_t)); + + // Get break state (1 byte) + uint8_t break_state = buffer[14]; + work_timer.flags.lunch_break = (break_state & 1) > 0; + work_timer.flags.mid_break = (break_state & 2) > 0; + + // Get break start time (4 bytes) + memcpy(&work_timer.break_start_time, &buffer[15], sizeof(uint32_t)); + + // If in a break, adjust break_start_time to account for time powered off + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + uint32_t current_time = timer_read32(); + uint32_t time_off = current_time - work_timer.break_start_time; + + // If we've been off for less than the break duration, continue the break + if (work_timer.flags.lunch_break && + time_off < work_timer.mid_break_duration) { + // Stay in lunch break, adjust break_start_time + work_timer.break_start_time = current_time - time_off; + } + else if (work_timer.flags.mid_break && + time_off < work_timer.mid_break_duration) { + // Stay in mid-break, adjust break_start_time + work_timer.break_start_time = current_time - time_off; + } + else { + // Break would have ended while powered off + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + + // Add the remaining break time to total_break_time + // This is approximate but better than nothing + if (time_off < work_timer.mid_break_duration) { + work_timer.total_break_time += work_timer.mid_break_duration; + } + } + } + + // Calculate elapsed wall time (including time powered off) + uint32_t current_time = timer_read32(); + uint32_t wall_time_elapsed = current_time - work_timer.start_time; + + // Adjust start time to account for time powered off, preserving elapsed_time + work_timer.start_time = current_time - wall_time_elapsed; + + // Validate time values - if unreasonable, reset + if (work_timer.elapsed_time > work_timer.timer_duration) { + work_timer.flags.active = 0; + save_work_timer_state(); + } + + // Check for end warning state + if (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + work_timer.flags.end_warning_shown = 1; + } + + // Update pulse active state + update_pulse_active_state(); + } +} + +/** + * Display progress bar with gradient colors + */ +static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor) { + // Calculate hour segments and LED positions + float hours_per_led = 1.0f / (float)num_leds; + + // Determine how many LEDs should be fully lit + uint8_t leds_lit = (uint8_t)(overall_progress / hours_per_led); + if (leds_lit > num_leds) leds_lit = num_leds; + + // Calculate progress within the current LED + float current_led_progress = (overall_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 led_position = (float)i / (float)(num_leds - 1); + + // Adjust transition points - more green-to-orange (first 80% of bar), + // and less orange-to-red (last 20% of bar) + if (led_position < 0.3f) { + // Scale to 0.0 - 1.0 for green-to-orange part (now 80% of the bar) + float adjusted_progress = led_position / 0.3f; + color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); + } + else { + float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% + color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); + } + + // Apply overall RGB brightness factor + color.r = (uint8_t)((float)color.r * brightness_factor); + color.g = (uint8_t)((float)color.g * brightness_factor); + color.b = (uint8_t)((float)color.b * brightness_factor); + } + else if (i == leds_lit && current_led_progress > 0.0f) { + // Current LED - partially lit based on progress + float led_position = (float)i / (float)(num_leds - 1); + + rgb_color_t full_color; + // Adjust transition points - more green-to-orange, less orange-to-red + if (led_position < 0.3f) { + // Scale to 0.0 - 1.0 for green-to-orange part + float adjusted_progress = led_position / 0.3f; + full_color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); + } + // Last 30% is orange-to-red gradient + else { + float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% + full_color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); + } + + // Dim the color based on progress within this LED and overall brightness + color.r = (uint8_t)((float)full_color.r * current_led_progress * brightness_factor); + color.g = (uint8_t)((float)full_color.g * current_led_progress * brightness_factor); + color.b = (uint8_t)((float)full_color.b * current_led_progress * brightness_factor); + } + + // Set the LED color + rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, color.b); + } +} + +/** + * Update the pulse active state + * This determines if the keyboard should wake from sleep for timer notification + */ +static void update_pulse_active_state(void) { + if (!work_timer.flags.active || work_timer.flags.paused) { + work_timer.flags.pulse_active = false; + return; + } + + // Check if any pulse condition is active + bool lunch_warning = false; + bool lunch_end_warning = false; + bool mid_point_warning = false; + bool end_warning = false; + + // For timers with lunch breaks + if (work_timer.has_lunch_break) { + // Lunch break warning (before lunch) + lunch_warning = !work_timer.flags.lunch_break && + (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start); + + // Lunch end warning (before end of lunch) + lunch_end_warning = work_timer.flags.lunch_break && + (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + } else { + // Mid-point break warning for shorter timers + mid_point_warning = !work_timer.flags.mid_break && + (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start); + } + + // End timer warning (5 minutes before end) + end_warning = (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.timer_duration); + + // Set pulse active if any of these conditions are true + bool new_pulse_active = ( + work_timer.flags.mid_break || // Mid-point break pulse + work_timer.flags.lunch_break || // Lunch break + work_timer.flags.end_warning_shown || // End warning + lunch_warning || // Pre-lunch warning + lunch_end_warning || // End of lunch warning + mid_point_warning || // Mid-point warning for shorter timers + end_warning // End warning for all timers + ); + + // Force a wakeup signal if transitioning from inactive to active pulse + if (!work_timer.flags.pulse_active && new_pulse_active) { + // Explicitly wake up keyboard by toggling suspend state + rgb_matrix_set_suspend_state(false); + + // Force appropriate 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); + } + } + + work_timer.flags.pulse_active = new_pulse_active; +} + +/** + * Toggle the work timer on/off + */ +void toggle_work_timer(void) { + if (work_timer.flags.active) { + // If timer is active, stop it + work_timer.flags.active = 0; + work_timer.flags.pulse_active = 0; // Clear pulse active flag + save_work_timer_state(); + } else { + // If timer is inactive, just activate it with current settings + // This assumes the timer type and duration are already set + work_timer.flags.active = 1; + work_timer.flags.paused = 0; + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.flags.end_warning_shown = 0; + work_timer.flags.pulse_active = 0; + + work_timer.start_time = timer_read32(); + work_timer.elapsed_time = 0; + work_timer.pause_time = 0; + work_timer.break_start_time = 0; + work_timer.total_break_time = 0; + + save_work_timer_state(); + } + + // Ensure timer visual updates immediately after toggle + update_pulse_active_state(); +} + +/** + * Initialize the work timer - load state from EEPROM + */ +void work_timer_init(void) { + load_work_timer_state(); +} + +/** + * Start a specific timer type + */ +void start_timer(work_timer_type_t timer_type) { + // Configure timer parameters based on type + configure_timer_for_type(timer_type); + + // Reset timer state + work_timer.flags.active = 1; + work_timer.flags.paused = 0; + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.flags.end_warning_shown = 0; + work_timer.flags.pulse_active = 0; + + work_timer.start_time = timer_read32(); + work_timer.elapsed_time = 0; + work_timer.pause_time = 0; + work_timer.break_start_time = 0; + work_timer.total_break_time = 0; + + save_work_timer_state(); + + // Update pulse state immediately after starting timer + update_pulse_active_state(); +} + +/** + * Pause or resume the work timer + */ +void toggle_pause_work_timer(void) { + if (!work_timer.flags.active) return; + + if (!work_timer.flags.paused) { + // Pause the timer + work_timer.flags.paused = 1; + work_timer.pause_time = timer_read32(); + save_work_timer_state(); + } else { + // Resume the timer, adjust start time to account for pause duration + uint32_t pause_duration = timer_read32() - work_timer.pause_time; + work_timer.start_time += pause_duration; + + // If in a break, adjust break start time too + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + work_timer.break_start_time += pause_duration; + } + + work_timer.flags.paused = 0; + save_work_timer_state(); + } + + // Update pulse state after changing pause status + update_pulse_active_state(); +} + +/** + * Update the work timer state + */ +void update_work_timer(void) { + if (!work_timer.flags.active || work_timer.flags.paused) return; + + // Calculate wall time (real-world time) elapsed since start + uint32_t wall_time_elapsed = timer_read32() - work_timer.start_time; + + // Current wall time + uint32_t current_time = timer_read32(); + + // Process different timer states based on timer type + if (work_timer.has_lunch_break) { + // For timers with lunch breaks (8HR and 10HR) + + // Handle lunch break state transitions + if (!work_timer.flags.lunch_break) { + // Check if it's time to start lunch + if (!work_timer.flags.lunch_warning_shown && + work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start) { + // Pre-lunch warning (red pulse before lunch) + work_timer.flags.lunch_warning_shown = 1; + } + else if (work_timer.elapsed_time >= work_timer.mid_break_start) { + // Start lunch break + work_timer.flags.lunch_break = 1; + work_timer.break_start_time = current_time; + work_timer.flags.lunch_warning_shown = 0; // Reset for lunch end warning + + // Save state when entering lunch break + save_work_timer_state(); + } + } + else { + // Currently in lunch break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // Check for lunch end warning + if (!work_timer.flags.lunch_warning_shown && + break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { + // Pre-end warning (red pulse before end of lunch) + work_timer.flags.lunch_warning_shown = 1; + } + + // FIX: Much more aggressive timeout - end lunch after just 5 seconds over + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // Break has been active for 5 seconds over its time - force exit + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.total_break_time += work_timer.mid_break_duration; // Use exact duration instead + + // Save state for persistence + save_work_timer_state(); + } + // Normal lunch break end check + else if (break_elapsed >= work_timer.mid_break_duration) { + // End lunch break + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; // FIX: Reset the warning flag + + // Add the break time to total_break_time - use the exact duration + // FIX: Use the exact lunch break duration instead of elapsed time + work_timer.total_break_time += work_timer.mid_break_duration; + + // Save state when exiting lunch break + save_work_timer_state(); + } + } + } + else { + // For shorter timers without lunch breaks (30MIN, 1HR, 4HR) + + // Handle mid-break state transitions + if (!work_timer.flags.mid_break) { + // Check if it's time to start mid-break + if (!work_timer.flags.mid_break_warning_shown && + work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_timer.elapsed_time < work_timer.mid_break_start) { + // Mid-break warning + work_timer.flags.mid_break_warning_shown = 1; + } + else if (work_timer.elapsed_time >= work_timer.mid_break_start) { + // Start mid-break + work_timer.flags.mid_break = 1; + work_timer.break_start_time = current_time; + save_work_timer_state(); + } + } + else { + // Currently in mid-break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // FIX: More aggressive timeout - force end mid-break after 5 seconds over time + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // Break has been active for 5 seconds over its time - force exit + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.total_break_time += work_timer.mid_break_duration; + save_work_timer_state(); + } + // Normal mid-break end check + else if (break_elapsed >= work_timer.mid_break_duration) { + // End mid-break + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; // FIX: Reset warning flag + + // Add the break time to total_break_time + work_timer.total_break_time += work_timer.mid_break_duration; + save_work_timer_state(); + } + } + } + + // Calculate true work time (excluding breaks) + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + // Currently in a break, so work time is wall time minus total breaks + // minus current break elapsed time + uint32_t current_break_elapsed = timer_elapsed32(work_timer.break_start_time); + work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time - current_break_elapsed; + } else { + // Not in a break, so work time is wall time minus total breaks + work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time; + } + + // Check for end of day warning (5 min before end) + if (!work_timer.flags.end_warning_shown && + work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + work_timer.flags.end_warning_shown = 1; + } + + // Auto-stop after timer duration + if (work_timer.elapsed_time >= work_timer.timer_duration) { + work_timer.flags.active = 0; + save_work_timer_state(); + } + + // Update the pulse active state for wakeup + update_pulse_active_state(); +} + /** * Check if any timer pulse is currently active * Used for wake-from-sleep functionality * * @return true if any timer pulse effect is active, false otherwise */ - bool is_timer_pulse_active(void) { +bool is_timer_pulse_active(void) { // If timer is not active or is paused, no pulse is active if (!work_timer.flags.active || work_timer.flags.paused) { return false; @@ -648,6 +649,45 @@ void handle_work_timer(void) { if (!work_timer.flags.active) return; + // FIX: Add a double-check for break state to ensure proper transitions + // This helps catch cases where the break may be stuck + if (work_timer.flags.lunch_break) { + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // If break has gone more than 5 seconds past its duration, force-end it + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // Exit the break immediately - this is a failsafe check + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.total_break_time += work_timer.mid_break_duration; + + // Make sure to save the state + save_work_timer_state(); + + // Return early to let the next frame handle normal display + return; + } + } + + // Similar check for mid-break + if (work_timer.flags.mid_break) { + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // If break has gone more than 5 seconds past its duration, force-end it + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // Exit the break immediately - this is a failsafe check + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.total_break_time += work_timer.mid_break_duration; + + // Make sure to save the state + save_work_timer_state(); + + // Return early to let the next frame handle normal display + return; + } + } + // Get current RGB matrix brightness value (0-255) uint8_t rgb_brightness = rgb_matrix_get_val(); float brightness_factor = (float)rgb_brightness / 255.0f; @@ -707,16 +747,6 @@ void handle_work_timer(void) { uint8_t lunch_pulse = abs((timer_read() / 10) % 510 - 255); float lunch_pulse_ratio = (float)lunch_pulse / 255.0f; - // FIXED: Track how long the lunch break has been active - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // FIXED: Emergency safety - if lunch is stuck for more than 2x the duration, force exit - if (break_elapsed > (work_timer.mid_break_duration * 2)) { - work_timer.flags.lunch_break = 0; - work_timer.total_break_time += work_timer.mid_break_duration; - return; // Exit and let the next update handle normal display - } - // Apply lunch break color to all progress bar LEDs for (uint8_t i = 0; i < num_leds; i++) { rgb_matrix_set_color(WORK_TIMER_LED_START + i, @@ -769,16 +799,6 @@ void handle_work_timer(void) { uint8_t mid_pulse = abs((timer_read() / 3) % 510 - 255); float mid_pulse_ratio = (float)mid_pulse / 255.0f; - // FIXED: Track how long the mid-break has been active - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // FIXED: Emergency safety - if mid-break is stuck for more than 2x the duration, force exit - if (break_elapsed > (work_timer.mid_break_duration * 2)) { - work_timer.flags.mid_break = 0; - work_timer.total_break_time += work_timer.mid_break_duration; - return; // Exit and let the next update handle normal display - } - // Apply pulsing effect to all progress bar LEDs for (uint8_t i = 0; i < num_leds; i++) { rgb_matrix_set_color(WORK_TIMER_LED_START + i, From 86bbf7946ea3e28cd27a74b75e52de5c7599bcb9 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Tue, 13 May 2025 22:01:52 -0400 Subject: [PATCH 48/54] Enhance work timer functionality with additional validation and state management --- keyboards/tssouthpaw/keyboard.json | 3 +- keyboards/tssouthpaw/rgb_effects/work_timer.c | 108 +++++++++++------- 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 01c40b5b91c..050f8d8c931 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -34,7 +34,8 @@ "caps_word": true, "extrakey": true, "nkro": true, - "rgb_matrix": true + "rgb_matrix": true, + "dynamic_macro": true }, "caps_word": { "enabled": true diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c index 3916b551ff0..0d096dc9dc9 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.c +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -253,6 +253,15 @@ static void load_work_timer_state(void) { // Update pulse active state update_pulse_active_state(); + + // Additional validation - ensure break state is consistent + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + // If in a break state but break_start_time is invalid, reset break state + if (work_timer.break_start_time == 0) { + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + } + } } } @@ -391,9 +400,17 @@ static void update_pulse_active_state(void) { */ void toggle_work_timer(void) { if (work_timer.flags.active) { - // If timer is active, stop it + // If timer is active, stop it and reset all state flags work_timer.flags.active = 0; + work_timer.flags.paused = 0; + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; + work_timer.flags.lunch_warning_shown = 0; + work_timer.flags.mid_break_warning_shown = 0; + work_timer.flags.end_warning_shown = 0; work_timer.flags.pulse_active = 0; // Clear pulse active flag + + // Save complete clean state to EEPROM save_work_timer_state(); } else { // If timer is inactive, just activate it with current settings @@ -536,7 +553,7 @@ void update_work_timer(void) { // Break has been active for 5 seconds over its time - force exit work_timer.flags.lunch_break = 0; work_timer.flags.lunch_warning_shown = 0; - work_timer.total_break_time += work_timer.mid_break_duration; // Use exact duration instead + work_timer.total_break_time += break_elapsed; // Add actual break time // Save state for persistence save_work_timer_state(); @@ -545,15 +562,22 @@ void update_work_timer(void) { else if (break_elapsed >= work_timer.mid_break_duration) { // End lunch break work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; // FIX: Reset the warning flag + work_timer.flags.lunch_warning_shown = 0; // Reset the warning flag - // Add the break time to total_break_time - use the exact duration - // FIX: Use the exact lunch break duration instead of elapsed time - work_timer.total_break_time += work_timer.mid_break_duration; + // Add the break time to total_break_time - use the exact elapsed time + // to ensure we account for all time accurately + work_timer.total_break_time += break_elapsed; // Save state when exiting lunch break save_work_timer_state(); } + // Extra validation - check if break_start_time is valid + if (work_timer.break_start_time == 0) { + // Invalid break start time, force end break + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; + save_work_timer_state(); + } } } else { @@ -584,17 +608,24 @@ void update_work_timer(void) { // Break has been active for 5 seconds over its time - force exit work_timer.flags.mid_break = 0; work_timer.flags.mid_break_warning_shown = 0; - work_timer.total_break_time += work_timer.mid_break_duration; + work_timer.total_break_time += break_elapsed; // Add actual break time save_work_timer_state(); } // Normal mid-break end check else if (break_elapsed >= work_timer.mid_break_duration) { // End mid-break work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; // FIX: Reset warning flag + work_timer.flags.mid_break_warning_shown = 0; // Reset warning flag - // Add the break time to total_break_time - work_timer.total_break_time += work_timer.mid_break_duration; + // Add the break time to total_break_time - use the actual elapsed time + work_timer.total_break_time += break_elapsed; + save_work_timer_state(); + } + // Extra validation - check if break_start_time is valid + if (work_timer.break_start_time == 0) { + // Invalid break start time, force end break + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; save_work_timer_state(); } } @@ -620,6 +651,9 @@ void update_work_timer(void) { // Auto-stop after timer duration if (work_timer.elapsed_time >= work_timer.timer_duration) { work_timer.flags.active = 0; + // Ensure all break states are cleared when timer ends + work_timer.flags.lunch_break = 0; + work_timer.flags.mid_break = 0; save_work_timer_state(); } @@ -647,43 +681,39 @@ bool is_timer_pulse_active(void) { * Handle the work timer visualization on LEDs */ void handle_work_timer(void) { - if (!work_timer.flags.active) return; - - // FIX: Add a double-check for break state to ensure proper transitions - // This helps catch cases where the break may be stuck - if (work_timer.flags.lunch_break) { - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // If break has gone more than 5 seconds past its duration, force-end it - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // Exit the break immediately - this is a failsafe check - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.total_break_time += work_timer.mid_break_duration; - - // Make sure to save the state - save_work_timer_state(); - - // Return early to let the next frame handle normal display - return; + if (!work_timer.flags.active) { + // If timer is not active, ensure all timer-related LEDs are cleared + const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; + for (uint8_t i = 0; i < num_leds; i++) { + rgb_matrix_set_color(WORK_TIMER_LED_START + i, 0, 0, 0); } + return; } - // Similar check for mid-break - if (work_timer.flags.mid_break) { + // Enhanced validation checks for break states - run on every visual update + // to catch and correct any inconsistent timer states + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + uint32_t max_break_time = work_timer.mid_break_duration * 2; // Extra safety margin - // If break has gone more than 5 seconds past its duration, force-end it - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // Exit the break immediately - this is a failsafe check - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.total_break_time += work_timer.mid_break_duration; + // If break has been active much longer than expected (2x duration), force end it + if (break_elapsed > max_break_time) { + // Force break to end - this is an emergency failsafe + if (work_timer.flags.lunch_break) { + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; + } - // Make sure to save the state + if (work_timer.flags.mid_break) { + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; + } + + // Add the actual break duration to total break time + work_timer.total_break_time += work_timer.mid_break_duration; save_work_timer_state(); - // Return early to let the next frame handle normal display + // Return early to let next frame handle normal display return; } } From d13b6fd64bc3fbc2d0430bec9a62011c2d3be45b Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Fri, 16 May 2025 06:58:42 -0400 Subject: [PATCH 49/54] Fix encoder pin configuration and enhance work timer functionality with improved state management and validation --- keyboards/tssouthpaw/keyboard.json | 2 +- .../tssouthpaw/keymaps/default/rgb_effects.c | 0 keyboards/tssouthpaw/rgb_effects/work_timer.c | 224 ++++++++---------- keyboards/tssouthpaw/rgb_effects/work_timer.h | 151 ++++++------ 4 files changed, 180 insertions(+), 197 deletions(-) delete mode 100644 keyboards/tssouthpaw/keymaps/default/rgb_effects.c diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 050f8d8c931..0e51f99c50b 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -26,7 +26,7 @@ "encoder": { "enabled": true, "rotary": [ - {"pin_a": "GP1", "pin_b": "GP0", "resolution": 4} + {"pin_a": "GP0", "pin_b": "GP1", "resolution": 4} ] }, "features": { diff --git a/keyboards/tssouthpaw/keymaps/default/rgb_effects.c b/keyboards/tssouthpaw/keymaps/default/rgb_effects.c deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c index 0d096dc9dc9..bbfcc8fa86a 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.c +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -334,7 +334,7 @@ static void display_progress_bar(uint8_t num_leds, float overall_progress, float /** * Update the pulse active state - * This determines if the keyboard should wake from sleep for timer notification + * This determines if any pulse effect is currently active */ static void update_pulse_active_state(void) { if (!work_timer.flags.active || work_timer.flags.paused) { @@ -370,7 +370,7 @@ static void update_pulse_active_state(void) { work_timer.elapsed_time < work_timer.timer_duration); // Set pulse active if any of these conditions are true - bool new_pulse_active = ( + work_timer.flags.pulse_active = ( work_timer.flags.mid_break || // Mid-point break pulse work_timer.flags.lunch_break || // Lunch break work_timer.flags.end_warning_shown || // End warning @@ -380,19 +380,7 @@ static void update_pulse_active_state(void) { end_warning // End warning for all timers ); - // Force a wakeup signal if transitioning from inactive to active pulse - if (!work_timer.flags.pulse_active && new_pulse_active) { - // Explicitly wake up keyboard by toggling suspend state - rgb_matrix_set_suspend_state(false); - - // Force appropriate 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); - } - } - - work_timer.flags.pulse_active = new_pulse_active; + // Removed wake functionality - no need to wake RGB matrix } /** @@ -412,9 +400,11 @@ void toggle_work_timer(void) { // Save complete clean state to EEPROM save_work_timer_state(); + + // Force immediate RGB refresh to restore normal LED state + rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); } else { - // If timer is inactive, just activate it with current settings - // This assumes the timer type and duration are already set + // If timer is inactive, activate it with current settings work_timer.flags.active = 1; work_timer.flags.paused = 0; work_timer.flags.lunch_break = 0; @@ -424,6 +414,7 @@ void toggle_work_timer(void) { work_timer.flags.end_warning_shown = 0; work_timer.flags.pulse_active = 0; + // Reset all time tracking variables work_timer.start_time = timer_read32(); work_timer.elapsed_time = 0; work_timer.pause_time = 0; @@ -433,7 +424,6 @@ void toggle_work_timer(void) { save_work_timer_state(); } - // Ensure timer visual updates immediately after toggle update_pulse_active_state(); } @@ -508,26 +498,37 @@ void toggle_pause_work_timer(void) { void update_work_timer(void) { if (!work_timer.flags.active || work_timer.flags.paused) return; - // Calculate wall time (real-world time) elapsed since start - uint32_t wall_time_elapsed = timer_read32() - work_timer.start_time; - - // Current wall time + // Current time uint32_t current_time = timer_read32(); + // Calculate work time (wall time minus breaks) + uint32_t work_time; + + if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + // If in a break, calculate work time as: + // (time since start - total break time - current break elapsed time) + uint32_t current_break_elapsed = timer_elapsed32(work_timer.break_start_time); + work_time = (current_time - work_timer.start_time) - work_timer.total_break_time - current_break_elapsed; + } else { + // Not in a break, work time is wall time minus total break time + work_time = (current_time - work_timer.start_time) - work_timer.total_break_time; + } + + // Store the calculated work time + work_timer.elapsed_time = work_time; + // Process different timer states based on timer type if (work_timer.has_lunch_break) { - // For timers with lunch breaks (8HR and 10HR) - // Handle lunch break state transitions if (!work_timer.flags.lunch_break) { // Check if it's time to start lunch if (!work_timer.flags.lunch_warning_shown && - work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start) { + work_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_time < work_timer.mid_break_start) { // Pre-lunch warning (red pulse before lunch) work_timer.flags.lunch_warning_shown = 1; } - else if (work_timer.elapsed_time >= work_timer.mid_break_start) { + else if (work_time >= work_timer.mid_break_start) { // Start lunch break work_timer.flags.lunch_break = 1; work_timer.break_start_time = current_time; @@ -538,45 +539,36 @@ void update_work_timer(void) { } } else { - // Currently in lunch break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // Check for lunch end warning - if (!work_timer.flags.lunch_warning_shown && - break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { - // Pre-end warning (red pulse before end of lunch) - work_timer.flags.lunch_warning_shown = 1; - } - - // FIX: Much more aggressive timeout - end lunch after just 5 seconds over - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // Break has been active for 5 seconds over its time - force exit - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.total_break_time += break_elapsed; // Add actual break time - - // Save state for persistence - save_work_timer_state(); - } - // Normal lunch break end check - else if (break_elapsed >= work_timer.mid_break_duration) { - // End lunch break - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; // Reset the warning flag - - // Add the break time to total_break_time - use the exact elapsed time - // to ensure we account for all time accurately - work_timer.total_break_time += break_elapsed; - - // Save state when exiting lunch break - save_work_timer_state(); - } - // Extra validation - check if break_start_time is valid + // First validate break_start_time before any other processing if (work_timer.break_start_time == 0) { // Invalid break start time, force end break work_timer.flags.lunch_break = 0; work_timer.flags.lunch_warning_shown = 0; save_work_timer_state(); + // Skip the rest of the break processing + } + else { + // Currently in lunch break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // Check for lunch end warning + if (!work_timer.flags.lunch_warning_shown && + break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { + // Pre-end warning (red pulse before end of lunch) + work_timer.flags.lunch_warning_shown = 1; + } + + // End lunch break after specified duration (plus a small grace period) + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // End lunch break + work_timer.flags.lunch_break = 0; + work_timer.flags.lunch_warning_shown = 0; + + // Add actual break duration to total break time + work_timer.total_break_time += break_elapsed; + + save_work_timer_state(); + } } } } @@ -587,12 +579,12 @@ void update_work_timer(void) { if (!work_timer.flags.mid_break) { // Check if it's time to start mid-break if (!work_timer.flags.mid_break_warning_shown && - work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start) { + work_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && + work_time < work_timer.mid_break_start) { // Mid-break warning work_timer.flags.mid_break_warning_shown = 1; } - else if (work_timer.elapsed_time >= work_timer.mid_break_start) { + else if (work_time >= work_timer.mid_break_start) { // Start mid-break work_timer.flags.mid_break = 1; work_timer.break_start_time = current_time; @@ -600,73 +592,56 @@ void update_work_timer(void) { } } else { - // Currently in mid-break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // FIX: More aggressive timeout - force end mid-break after 5 seconds over time - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // Break has been active for 5 seconds over its time - force exit - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.total_break_time += break_elapsed; // Add actual break time - save_work_timer_state(); - } - // Normal mid-break end check - else if (break_elapsed >= work_timer.mid_break_duration) { - // End mid-break - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; // Reset warning flag - - // Add the break time to total_break_time - use the actual elapsed time - work_timer.total_break_time += break_elapsed; - save_work_timer_state(); - } - // Extra validation - check if break_start_time is valid + // First validate break_start_time before any other processing if (work_timer.break_start_time == 0) { // Invalid break start time, force end break work_timer.flags.mid_break = 0; work_timer.flags.mid_break_warning_shown = 0; save_work_timer_state(); + // Skip the rest of the break processing + } + else { + // Currently in mid-break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // End mid-break after specified duration (plus a small grace period) + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // End mid-break + work_timer.flags.mid_break = 0; + work_timer.flags.mid_break_warning_shown = 0; + + // Add actual break duration to total break time + work_timer.total_break_time += break_elapsed; + save_work_timer_state(); + } } } } - // Calculate true work time (excluding breaks) - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - // Currently in a break, so work time is wall time minus total breaks - // minus current break elapsed time - uint32_t current_break_elapsed = timer_elapsed32(work_timer.break_start_time); - work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time - current_break_elapsed; - } else { - // Not in a break, so work time is wall time minus total breaks - work_timer.elapsed_time = wall_time_elapsed - work_timer.total_break_time; - } - // Check for end of day warning (5 min before end) if (!work_timer.flags.end_warning_shown && - work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + work_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { work_timer.flags.end_warning_shown = 1; } // Auto-stop after timer duration - if (work_timer.elapsed_time >= work_timer.timer_duration) { + if (work_time >= work_timer.timer_duration) { work_timer.flags.active = 0; - // Ensure all break states are cleared when timer ends work_timer.flags.lunch_break = 0; work_timer.flags.mid_break = 0; save_work_timer_state(); } - // Update the pulse active state for wakeup + // Update the pulse active state update_pulse_active_state(); } /** - * Check if any timer pulse is currently active - * Used for wake-from-sleep functionality - * - * @return true if any timer pulse effect is active, false otherwise - */ + * Check if any timer pulse is currently active + * Used for easier state checking + * + * @return true if any timer pulse effect is active, false otherwise + */ bool is_timer_pulse_active(void) { // If timer is not active or is paused, no pulse is active if (!work_timer.flags.active || work_timer.flags.paused) { @@ -681,15 +656,21 @@ bool is_timer_pulse_active(void) { * Handle the work timer visualization on LEDs */ void handle_work_timer(void) { + static bool was_active = false; + if (!work_timer.flags.active) { - // If timer is not active, ensure all timer-related LEDs are cleared - const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, 0, 0, 0); + // If the timer was just deactivated, force a complete RGB refresh + if (was_active) { + rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); + was_active = false; } + + // Instead of explicitly turning off LEDs, let the RGB system handle them return; } + was_active = true; + // Enhanced validation checks for break states - run on every visual update // to catch and correct any inconsistent timer states if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { @@ -722,13 +703,7 @@ void handle_work_timer(void) { uint8_t rgb_brightness = rgb_matrix_get_val(); float brightness_factor = (float)rgb_brightness / 255.0f; - // Ensure minimum brightness during notifications - // (RGB matrix must be awake for this to have an effect) - if (work_timer.flags.pulse_active && rgb_brightness < RGB_MATRIX_MINIMUM_BRIGHTNESS) { - rgb_brightness = RGB_MATRIX_MINIMUM_BRIGHTNESS; - brightness_factor = (float)RGB_MATRIX_MINIMUM_BRIGHTNESS / 255.0f; - // Don't permanently change the RGB settings, just adjust for calculations - } + // Simplified: No need to modify brightness - use whatever the system has // Number of LEDs in the progress bar const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; @@ -752,10 +727,11 @@ void handle_work_timer(void) { (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && work_timer.elapsed_time < work_timer.mid_break_start); - // Lunch-end warning (red pulse) + // Lunch-end warning (red pulse) - Add validation for break_start_time lunch_end_warning = work_timer.flags.lunch_break && + work_timer.break_start_time != 0 && (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); - + // Choose appropriate display based on current state if (lunch_warning || lunch_end_warning) { // Pre/Post lunch red warning pulse @@ -769,8 +745,8 @@ void handle_work_timer(void) { (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); } } - else if (work_timer.flags.lunch_break) { - // During lunch break - blue pulse + else if (work_timer.flags.lunch_break && work_timer.break_start_time != 0) { + // During lunch break - blue pulse, only if break_start_time is valid rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; // Use a slower pulse for regular lunch break @@ -821,7 +797,8 @@ void handle_work_timer(void) { (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); } } - else if (work_timer.flags.mid_break) { + // Mid-break active - only show if break_start_time is valid + else if (work_timer.flags.mid_break && work_timer.break_start_time != 0) { // Mid-break active - blue pulse rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; @@ -862,4 +839,7 @@ void handle_work_timer(void) { void work_timer_task(void) { // Update work timer state update_work_timer(); + + // Add explicit call to refresh RGB matrix if needed + // This might help ensure F1-F12 get proper colors after timer state changes } \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.h b/keyboards/tssouthpaw/rgb_effects/work_timer.h index 0b23fa80cbb..dc6b9e0c514 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.h +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.h @@ -14,78 +14,81 @@ * along with this program. If not, see . */ - #pragma once +#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 - - // Warning color (red) - #define WORK_TIMER_WARNING_R 255 - #define WORK_TIMER_WARNING_G 0 - #define WORK_TIMER_WARNING_B 0 - - // 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 - #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 - - // 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 pulse states - bool is_timer_pulse_active(void); \ No newline at end of file +#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 + +// Warning color (red) +#define WORK_TIMER_WARNING_R 255 +#define WORK_TIMER_WARNING_G 0 +#define WORK_TIMER_WARNING_B 0 + +// 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 +#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); \ No newline at end of file From cc50f2cd9289b8da954c4a59683ca24808b7f3b9 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Fri, 16 May 2025 07:16:34 -0400 Subject: [PATCH 50/54] Fix encoder pin order and enhance work timer structure with improved state management and validation --- keyboards/tssouthpaw/keyboard.json | 2 +- keyboards/tssouthpaw/rgb_effects/work_timer.c | 453 ++++++++---------- 2 files changed, 204 insertions(+), 251 deletions(-) diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 0e51f99c50b..050f8d8c931 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -26,7 +26,7 @@ "encoder": { "enabled": true, "rotary": [ - {"pin_a": "GP0", "pin_b": "GP1", "resolution": 4} + {"pin_a": "GP1", "pin_b": "GP0", "resolution": 4} ] }, "features": { diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c index bbfcc8fa86a..25217dded36 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.c +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -31,32 +31,28 @@ typedef struct { uint8_t pulse_active: 1; // Flag to track if any pulse is currently active } work_timer_flags_t; -// Work timer state structure +// Simplified work timer state structure typedef struct { work_timer_flags_t flags; work_timer_type_t timer_type; - uint32_t start_time; - uint32_t elapsed_time; // Work time (excluding breaks) - uint32_t pause_time; - uint32_t break_start_time; // When current break started - uint32_t total_break_time; // Total accumulated break time - uint32_t timer_duration; // Total work time duration (excludes breaks) - uint32_t mid_break_start; // When in workday the break occurs - uint32_t mid_break_duration; - bool has_lunch_break; + uint32_t start_time; // When timer was started + uint32_t end_time; // When timer should end (calculated) + uint32_t pause_time; // When pause started (if paused) + uint32_t break_start_time; // When current break started + uint32_t mid_break_time; // When mid-break should occur + uint32_t mid_break_duration; // How long the mid-break should last + bool has_lunch_break; // Whether this timer has a lunch break } work_timer_t; -// Global work timer state +// Global work timer state with simplified initialization static work_timer_t work_timer = { .flags = {0}, .timer_type = TIMER_TYPE_8HR, .start_time = 0, - .elapsed_time = 0, + .end_time = 0, .pause_time = 0, .break_start_time = 0, - .total_break_time = 0, - .timer_duration = TIMER_8HR_DURATION, - .mid_break_start = TIMER_8HR_DURATION / 2, // Default halfway point + .mid_break_time = 0, .mid_break_duration = LUNCH_BREAK_DURATION, .has_lunch_break = true }; @@ -67,7 +63,6 @@ static const rgb_color_t WORK_TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MI static const rgb_color_t WORK_TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B}; static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B}; static const rgb_color_t WORK_TIMER_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; -static const rgb_color_t WORK_TIMER_BREAK_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; // Function prototypes for internal functions static void configure_timer_for_type(work_timer_type_t timer_type); @@ -84,57 +79,56 @@ static void configure_timer_for_type(work_timer_type_t timer_type) { switch (timer_type) { case TIMER_TYPE_30MIN: - work_timer.timer_duration = TIMER_30MIN_DURATION; - work_timer.mid_break_start = TIMER_30MIN_DURATION / 2; + work_timer.mid_break_time = TIMER_30MIN_DURATION / 2; work_timer.mid_break_duration = MID_BREAK_30MIN_DURATION; work_timer.has_lunch_break = false; + work_timer.end_time = work_timer.start_time + TIMER_30MIN_DURATION; break; case TIMER_TYPE_1HR: - work_timer.timer_duration = TIMER_1HR_DURATION; - work_timer.mid_break_start = TIMER_1HR_DURATION / 2; + work_timer.mid_break_time = TIMER_1HR_DURATION / 2; work_timer.mid_break_duration = MID_BREAK_1HR_DURATION; work_timer.has_lunch_break = false; + work_timer.end_time = work_timer.start_time + TIMER_1HR_DURATION; break; case TIMER_TYPE_4HR: - work_timer.timer_duration = TIMER_4HR_DURATION; - work_timer.mid_break_start = TIMER_4HR_DURATION / 2; + work_timer.mid_break_time = TIMER_4HR_DURATION / 2; work_timer.mid_break_duration = MID_BREAK_4HR_DURATION; work_timer.has_lunch_break = false; + work_timer.end_time = work_timer.start_time + TIMER_4HR_DURATION; break; case TIMER_TYPE_8HR: - work_timer.timer_duration = TIMER_8HR_DURATION; - work_timer.mid_break_start = TIMER_8HR_DURATION / 2; + work_timer.mid_break_time = TIMER_8HR_DURATION / 2; work_timer.mid_break_duration = LUNCH_BREAK_DURATION; work_timer.has_lunch_break = true; + work_timer.end_time = work_timer.start_time + TIMER_8HR_DURATION; break; case TIMER_TYPE_10HR: - work_timer.timer_duration = TIMER_10HR_DURATION; - work_timer.mid_break_start = TIMER_10HR_DURATION / 2; + work_timer.mid_break_time = TIMER_10HR_DURATION / 2; work_timer.mid_break_duration = LUNCH_BREAK_DURATION; work_timer.has_lunch_break = true; + work_timer.end_time = work_timer.start_time + TIMER_10HR_DURATION; break; default: // Default to 8HR if something goes wrong - work_timer.timer_duration = TIMER_8HR_DURATION; - work_timer.mid_break_start = TIMER_8HR_DURATION / 2; + work_timer.mid_break_time = TIMER_8HR_DURATION / 2; work_timer.mid_break_duration = LUNCH_BREAK_DURATION; work_timer.has_lunch_break = true; + work_timer.end_time = work_timer.start_time + TIMER_8HR_DURATION; break; } } /** * Save the work timer state to EEPROM - * Modern QMK using block-based EEPROM operations for RP2040 */ static void save_work_timer_state(void) { // Create a buffer to store all our data - uint8_t buffer[19] = {0}; // 19 bytes total + uint8_t buffer[24] = {0}; // Increased from 21 to 24 bytes to have enough space // Set the active flag buffer[0] = work_timer.flags.active; @@ -144,21 +138,25 @@ static void save_work_timer_state(void) { // Save start time (4 bytes) memcpy(&buffer[1], &work_timer.start_time, sizeof(uint32_t)); - // Save elapsed time (4 bytes) - memcpy(&buffer[5], &work_timer.elapsed_time, sizeof(uint32_t)); - - // Save total break time (4 bytes) - memcpy(&buffer[9], &work_timer.total_break_time, sizeof(uint32_t)); + // Save end time (4 bytes) + memcpy(&buffer[5], &work_timer.end_time, sizeof(uint32_t)); // Save timer type (1 byte) - buffer[13] = (uint8_t)work_timer.timer_type; + buffer[9] = (uint8_t)work_timer.timer_type; + + // Save pause state and time (5 bytes) + buffer[10] = work_timer.flags.paused; + memcpy(&buffer[11], &work_timer.pause_time, sizeof(uint32_t)); // Save break state (1 byte) - buffer[14] = (work_timer.flags.lunch_break ? 1 : 0) | - (work_timer.flags.mid_break ? 2 : 0); + buffer[15] = (work_timer.flags.lunch_break ? 1 : 0) | + (work_timer.flags.mid_break ? 2 : 0); // Save break start time (4 bytes) - memcpy(&buffer[15], &work_timer.break_start_time, sizeof(uint32_t)); + memcpy(&buffer[16], &work_timer.break_start_time, sizeof(uint32_t)); + + // Save mid-break time (4 bytes) - fixed buffer position + memcpy(&buffer[20], &work_timer.mid_break_time, sizeof(uint32_t)); } // Write all data at once to EEPROM @@ -167,11 +165,10 @@ static void save_work_timer_state(void) { /** * Load the work timer state from EEPROM - * Modern QMK using block-based EEPROM operations for RP2040 */ static void load_work_timer_state(void) { // Create a buffer to read all our data - uint8_t buffer[19] = {0}; // 19 bytes total + uint8_t buffer[24] = {0}; // Increased from 21 to 24 bytes to match save function // Read all data at once from EEPROM eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); @@ -182,86 +179,61 @@ static void load_work_timer_state(void) { // Only process the rest if timer was active if (work_timer.flags.active) { // Load timer type - work_timer_type_t saved_type = (work_timer_type_t)buffer[13]; - - // Apply configuration for this timer type - configure_timer_for_type(saved_type); + work_timer_type_t saved_type = (work_timer_type_t)buffer[9]; // Get start time (4 bytes) memcpy(&work_timer.start_time, &buffer[1], sizeof(uint32_t)); - // Get the elapsed work time (4 bytes) - memcpy(&work_timer.elapsed_time, &buffer[5], sizeof(uint32_t)); + // Get end time (4 bytes) + memcpy(&work_timer.end_time, &buffer[5], sizeof(uint32_t)); - // Get the total break time (4 bytes) - memcpy(&work_timer.total_break_time, &buffer[9], sizeof(uint32_t)); + // Get pause state and time (5 bytes) + work_timer.flags.paused = buffer[10]; + memcpy(&work_timer.pause_time, &buffer[11], sizeof(uint32_t)); // Get break state (1 byte) - uint8_t break_state = buffer[14]; + uint8_t break_state = buffer[15]; work_timer.flags.lunch_break = (break_state & 1) > 0; work_timer.flags.mid_break = (break_state & 2) > 0; // Get break start time (4 bytes) - memcpy(&work_timer.break_start_time, &buffer[15], sizeof(uint32_t)); + memcpy(&work_timer.break_start_time, &buffer[16], sizeof(uint32_t)); - // If in a break, adjust break_start_time to account for time powered off + // Get mid-break time (4 bytes) - fixed buffer position + memcpy(&work_timer.mid_break_time, &buffer[20], sizeof(uint32_t)); + + // Apply configuration for this timer type (durations, etc.) + configure_timer_for_type(saved_type); + + // If currently in a break, we need to adjust for time passed since poweroff if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { uint32_t current_time = timer_read32(); uint32_t time_off = current_time - work_timer.break_start_time; - // If we've been off for less than the break duration, continue the break - if (work_timer.flags.lunch_break && - time_off < work_timer.mid_break_duration) { - // Stay in lunch break, adjust break_start_time - work_timer.break_start_time = current_time - time_off; - } - else if (work_timer.flags.mid_break && - time_off < work_timer.mid_break_duration) { - // Stay in mid-break, adjust break_start_time - work_timer.break_start_time = current_time - time_off; - } - else { - // Break would have ended while powered off + // If we've been powered off longer than the break duration, end the break + if (time_off > work_timer.mid_break_duration) { work_timer.flags.lunch_break = 0; work_timer.flags.mid_break = 0; - - // Add the remaining break time to total_break_time - // This is approximate but better than nothing - if (time_off < work_timer.mid_break_duration) { - work_timer.total_break_time += work_timer.mid_break_duration; - } } } - // Calculate elapsed wall time (including time powered off) + // Validate timer values uint32_t current_time = timer_read32(); - uint32_t wall_time_elapsed = current_time - work_timer.start_time; - // Adjust start time to account for time powered off, preserving elapsed_time - work_timer.start_time = current_time - wall_time_elapsed; - - // Validate time values - if unreasonable, reset - if (work_timer.elapsed_time > work_timer.timer_duration) { + // If end time is in the past, timer is done + if (work_timer.end_time <= current_time && !work_timer.flags.paused) { work_timer.flags.active = 0; save_work_timer_state(); } - // Check for end warning state - if (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + // Check for end warning state (within 5 minutes of end) + if (!work_timer.flags.paused && + current_time >= (work_timer.end_time - BREAK_WARNING_TIME)) { work_timer.flags.end_warning_shown = 1; } // Update pulse active state update_pulse_active_state(); - - // Additional validation - ensure break state is consistent - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - // If in a break state but break_start_time is invalid, reset break state - if (work_timer.break_start_time == 0) { - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - } - } } } @@ -342,32 +314,46 @@ static void update_pulse_active_state(void) { return; } + uint32_t current_time = timer_read32(); + // Check if any pulse condition is active bool lunch_warning = false; bool lunch_end_warning = false; bool mid_point_warning = false; bool end_warning = false; - // For timers with lunch breaks + // Mid-break (lunch) timing logic if (work_timer.has_lunch_break) { + // Time until mid-break (lunch) + uint32_t time_to_mid_break = 0; + if (current_time < work_timer.mid_break_time) { + time_to_mid_break = work_timer.mid_break_time - current_time; + } + // Lunch break warning (before lunch) lunch_warning = !work_timer.flags.lunch_break && - (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start); + (time_to_mid_break > 0 && time_to_mid_break < BREAK_WARNING_TIME); // Lunch end warning (before end of lunch) lunch_end_warning = work_timer.flags.lunch_break && - (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); - } else { + (timer_elapsed32(work_timer.break_start_time) >= + (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + } + else { + // Simple mid-break logic + uint32_t time_to_mid_break = 0; + if (current_time < work_timer.mid_break_time) { + time_to_mid_break = work_timer.mid_break_time - current_time; + } + // Mid-point break warning for shorter timers mid_point_warning = !work_timer.flags.mid_break && - (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start); + (time_to_mid_break > 0 && time_to_mid_break < BREAK_WARNING_TIME); } // End timer warning (5 minutes before end) - end_warning = (work_timer.elapsed_time >= (work_timer.timer_duration - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.timer_duration); + end_warning = (current_time >= (work_timer.end_time - BREAK_WARNING_TIME) && + current_time < work_timer.end_time); // Set pulse active if any of these conditions are true work_timer.flags.pulse_active = ( @@ -379,8 +365,6 @@ static void update_pulse_active_state(void) { mid_point_warning || // Mid-point warning for shorter timers end_warning // End warning for all timers ); - - // Removed wake functionality - no need to wake RGB matrix } /** @@ -396,15 +380,15 @@ void toggle_work_timer(void) { work_timer.flags.lunch_warning_shown = 0; work_timer.flags.mid_break_warning_shown = 0; work_timer.flags.end_warning_shown = 0; - work_timer.flags.pulse_active = 0; // Clear pulse active flag + work_timer.flags.pulse_active = 0; - // Save complete clean state to EEPROM + // Save clean state to EEPROM save_work_timer_state(); - // Force immediate RGB refresh to restore normal LED state + // Force immediate RGB refresh rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); } else { - // If timer is inactive, activate it with current settings + // If timer is inactive, start a new timer work_timer.flags.active = 1; work_timer.flags.paused = 0; work_timer.flags.lunch_break = 0; @@ -414,12 +398,26 @@ void toggle_work_timer(void) { work_timer.flags.end_warning_shown = 0; work_timer.flags.pulse_active = 0; - // Reset all time tracking variables + // Set timer start and calculate end time work_timer.start_time = timer_read32(); - work_timer.elapsed_time = 0; - work_timer.pause_time = 0; work_timer.break_start_time = 0; - work_timer.total_break_time = 0; + + // Set up mid-break time based on current time + uint32_t duration = 0; + switch (work_timer.timer_type) { + case TIMER_TYPE_30MIN: duration = TIMER_30MIN_DURATION; break; + case TIMER_TYPE_1HR: duration = TIMER_1HR_DURATION; break; + case TIMER_TYPE_4HR: duration = TIMER_4HR_DURATION; break; + case TIMER_TYPE_8HR: duration = TIMER_8HR_DURATION; break; + case TIMER_TYPE_10HR: duration = TIMER_10HR_DURATION; break; + default: duration = TIMER_8HR_DURATION; break; + } + + // Set mid-break time (halfway through timer) + work_timer.mid_break_time = work_timer.start_time + (duration / 2); + + // Set end time + work_timer.end_time = work_timer.start_time + duration; save_work_timer_state(); } @@ -438,9 +436,6 @@ void work_timer_init(void) { * Start a specific timer type */ void start_timer(work_timer_type_t timer_type) { - // Configure timer parameters based on type - configure_timer_for_type(timer_type); - // Reset timer state work_timer.flags.active = 1; work_timer.flags.paused = 0; @@ -452,10 +447,11 @@ void start_timer(work_timer_type_t timer_type) { work_timer.flags.pulse_active = 0; work_timer.start_time = timer_read32(); - work_timer.elapsed_time = 0; - work_timer.pause_time = 0; work_timer.break_start_time = 0; - work_timer.total_break_time = 0; + + // Configure timer parameters based on type + work_timer.timer_type = timer_type; + configure_timer_for_type(timer_type); save_work_timer_state(); @@ -475,9 +471,17 @@ void toggle_pause_work_timer(void) { work_timer.pause_time = timer_read32(); save_work_timer_state(); } else { - // Resume the timer, adjust start time to account for pause duration - uint32_t pause_duration = timer_read32() - work_timer.pause_time; - work_timer.start_time += pause_duration; + // Resume the timer - extend all time values by pause duration + uint32_t current_time = timer_read32(); + uint32_t pause_duration = current_time - work_timer.pause_time; + + // Extend end time by pause duration + work_timer.end_time += pause_duration; + + // If mid-break hasn't happened yet, extend that too + if (current_time < work_timer.mid_break_time) { + work_timer.mid_break_time += pause_duration; + } // If in a break, adjust break start time too if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { @@ -488,7 +492,6 @@ void toggle_pause_work_timer(void) { save_work_timer_state(); } - // Update pulse state after changing pause status update_pulse_active_state(); } @@ -498,141 +501,103 @@ void toggle_pause_work_timer(void) { void update_work_timer(void) { if (!work_timer.flags.active || work_timer.flags.paused) return; - // Current time uint32_t current_time = timer_read32(); - // Calculate work time (wall time minus breaks) - uint32_t work_time; - - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - // If in a break, calculate work time as: - // (time since start - total break time - current break elapsed time) - uint32_t current_break_elapsed = timer_elapsed32(work_timer.break_start_time); - work_time = (current_time - work_timer.start_time) - work_timer.total_break_time - current_break_elapsed; - } else { - // Not in a break, work time is wall time minus total break time - work_time = (current_time - work_timer.start_time) - work_timer.total_break_time; - } - - // Store the calculated work time - work_timer.elapsed_time = work_time; - // Process different timer states based on timer type if (work_timer.has_lunch_break) { // Handle lunch break state transitions if (!work_timer.flags.lunch_break) { // Check if it's time to start lunch if (!work_timer.flags.lunch_warning_shown && - work_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_time < work_timer.mid_break_start) { - // Pre-lunch warning (red pulse before lunch) + current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && + current_time < work_timer.mid_break_time) { + // Pre-lunch warning work_timer.flags.lunch_warning_shown = 1; } - else if (work_time >= work_timer.mid_break_start) { + else if (current_time >= work_timer.mid_break_time) { // Start lunch break work_timer.flags.lunch_break = 1; work_timer.break_start_time = current_time; work_timer.flags.lunch_warning_shown = 0; // Reset for lunch end warning - // Save state when entering lunch break + // Don't need to extend end time yet - will do that when break ends save_work_timer_state(); } } else { - // First validate break_start_time before any other processing - if (work_timer.break_start_time == 0) { - // Invalid break start time, force end break + // Currently in lunch break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // Check for lunch end warning + if (!work_timer.flags.lunch_warning_shown && + break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { + // Pre-end warning + work_timer.flags.lunch_warning_shown = 1; + } + + // End lunch break after duration (plus grace period) + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // End lunch break work_timer.flags.lunch_break = 0; work_timer.flags.lunch_warning_shown = 0; + + // Extend end time by break duration + work_timer.end_time += break_elapsed; + save_work_timer_state(); - // Skip the rest of the break processing - } - else { - // Currently in lunch break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // Check for lunch end warning - if (!work_timer.flags.lunch_warning_shown && - break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { - // Pre-end warning (red pulse before end of lunch) - work_timer.flags.lunch_warning_shown = 1; - } - - // End lunch break after specified duration (plus a small grace period) - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // End lunch break - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; - - // Add actual break duration to total break time - work_timer.total_break_time += break_elapsed; - - save_work_timer_state(); - } } } } else { - // For shorter timers without lunch breaks (30MIN, 1HR, 4HR) - - // Handle mid-break state transitions + // Handle mid-break state transitions for shorter timers if (!work_timer.flags.mid_break) { // Check if it's time to start mid-break if (!work_timer.flags.mid_break_warning_shown && - work_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_time < work_timer.mid_break_start) { + current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && + current_time < work_timer.mid_break_time) { // Mid-break warning work_timer.flags.mid_break_warning_shown = 1; } - else if (work_time >= work_timer.mid_break_start) { + else if (current_time >= work_timer.mid_break_time) { // Start mid-break work_timer.flags.mid_break = 1; work_timer.break_start_time = current_time; + work_timer.flags.mid_break_warning_shown = 0; save_work_timer_state(); } } else { - // First validate break_start_time before any other processing - if (work_timer.break_start_time == 0) { - // Invalid break start time, force end break + // Currently in mid-break + uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); + + // End mid-break after duration (plus grace period) + if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { + // End mid-break work_timer.flags.mid_break = 0; work_timer.flags.mid_break_warning_shown = 0; - save_work_timer_state(); - // Skip the rest of the break processing - } - else { - // Currently in mid-break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - // End mid-break after specified duration (plus a small grace period) - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // End mid-break - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; - - // Add actual break duration to total break time - work_timer.total_break_time += break_elapsed; - save_work_timer_state(); - } + // Extend end time by break duration + work_timer.end_time += break_elapsed; + + save_work_timer_state(); } } } - // Check for end of day warning (5 min before end) + // Check for end of day warning if (!work_timer.flags.end_warning_shown && - work_time >= (work_timer.timer_duration - BREAK_WARNING_TIME)) { + current_time >= (work_timer.end_time - BREAK_WARNING_TIME)) { work_timer.flags.end_warning_shown = 1; } - // Auto-stop after timer duration - if (work_time >= work_timer.timer_duration) { + // Auto-stop after timer ends + if (current_time >= work_timer.end_time) { work_timer.flags.active = 0; work_timer.flags.lunch_break = 0; work_timer.flags.mid_break = 0; save_work_timer_state(); } - // Update the pulse active state update_pulse_active_state(); } @@ -659,83 +624,71 @@ void handle_work_timer(void) { static bool was_active = false; if (!work_timer.flags.active) { - // If the timer was just deactivated, force a complete RGB refresh + // If the timer was just deactivated, force RGB refresh if (was_active) { rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); was_active = false; } - - // Instead of explicitly turning off LEDs, let the RGB system handle them return; } was_active = true; - // Enhanced validation checks for break states - run on every visual update - // to catch and correct any inconsistent timer states - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - uint32_t max_break_time = work_timer.mid_break_duration * 2; // Extra safety margin - - // If break has been active much longer than expected (2x duration), force end it - if (break_elapsed > max_break_time) { - // Force break to end - this is an emergency failsafe - if (work_timer.flags.lunch_break) { - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; - } - - if (work_timer.flags.mid_break) { - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; - } - - // Add the actual break duration to total break time - work_timer.total_break_time += work_timer.mid_break_duration; - save_work_timer_state(); - - // Return early to let next frame handle normal display - return; - } - } - - // Get current RGB matrix brightness value (0-255) + // Get current RGB matrix brightness uint8_t rgb_brightness = rgb_matrix_get_val(); float brightness_factor = (float)rgb_brightness / 255.0f; - // Simplified: No need to modify brightness - use whatever the system has - // Number of LEDs in the progress bar const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1; // Calculate overall progress (0.0 - 1.0) - float overall_progress = (float)work_timer.elapsed_time / (float)work_timer.timer_duration; + uint32_t current_time = timer_read32(); + float overall_progress; + + // Simple progress calculation based on total time range + if (work_timer.flags.paused) { + // When paused, use the pause time for calculation + overall_progress = (float)(work_timer.pause_time - work_timer.start_time) / + (float)(work_timer.end_time - work_timer.start_time); + } else { + // Normal operation - current progress through timer + overall_progress = (float)(current_time - work_timer.start_time) / + (float)(work_timer.end_time - work_timer.start_time); + } + if (overall_progress > 1.0f) overall_progress = 1.0f; - // Create a pulsing effect by varying brightness based on timer + // Create pulsing effect uint8_t pulse_brightness = abs((timer_read() / 4) % 510 - 255); float pulse_ratio = (float)pulse_brightness / 255.0f; // Check for various timer states bool lunch_warning = false; bool lunch_end_warning = false; + bool mid_break_warning = false; + bool end_warning = false; - // For timers with lunch breaks (8HR and 10HR) + // For timers with lunch breaks if (work_timer.has_lunch_break) { // Pre-lunch warning (red pulse) lunch_warning = !work_timer.flags.lunch_break && - (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start); + (current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && + current_time < work_timer.mid_break_time); - // Lunch-end warning (red pulse) - Add validation for break_start_time + // Lunch-end warning (red pulse) lunch_end_warning = work_timer.flags.lunch_break && work_timer.break_start_time != 0 && - (timer_elapsed32(work_timer.break_start_time) >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + (timer_elapsed32(work_timer.break_start_time) >= + (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + + // End warning + end_warning = current_time >= (work_timer.end_time - BREAK_WARNING_TIME) && + current_time < work_timer.end_time; // Choose appropriate display based on current state if (lunch_warning || lunch_end_warning) { // Pre/Post lunch red warning pulse - rgb_color_t pulse_color = WORK_TIMER_BREAK_WARNING_COLOR; + rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; // Apply pulsing effect to all progress bar LEDs for (uint8_t i = 0; i < num_leds; i++) { @@ -746,7 +699,7 @@ void handle_work_timer(void) { } } else if (work_timer.flags.lunch_break && work_timer.break_start_time != 0) { - // During lunch break - blue pulse, only if break_start_time is valid + // During lunch break - blue pulse rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; // Use a slower pulse for regular lunch break @@ -761,7 +714,7 @@ void handle_work_timer(void) { (uint8_t)((float)pulse_color.b * lunch_pulse_ratio * brightness_factor)); } } - else if (work_timer.flags.end_warning_shown) { + else if (work_timer.flags.end_warning_shown || end_warning) { // End of day warning - red pulse rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; @@ -780,14 +733,17 @@ void handle_work_timer(void) { } // For timers without lunch breaks (30MIN, 1HR, 4HR) else { - bool mid_break_warning = !work_timer.flags.mid_break && - (work_timer.elapsed_time >= (work_timer.mid_break_start - BREAK_WARNING_TIME) && - work_timer.elapsed_time < work_timer.mid_break_start); + mid_break_warning = !work_timer.flags.mid_break && + (current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && + current_time < work_timer.mid_break_time); + + end_warning = current_time >= (work_timer.end_time - BREAK_WARNING_TIME) && + current_time < work_timer.end_time; // Choose appropriate display based on current state if (mid_break_warning) { // Mid-break warning - red pulse - rgb_color_t pulse_color = WORK_TIMER_BREAK_WARNING_COLOR; + rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; // Apply pulsing effect to all progress bar LEDs for (uint8_t i = 0; i < num_leds; i++) { @@ -797,7 +753,7 @@ void handle_work_timer(void) { (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); } } - // Mid-break active - only show if break_start_time is valid + // Mid-break active else if (work_timer.flags.mid_break && work_timer.break_start_time != 0) { // Mid-break active - blue pulse rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; @@ -814,7 +770,7 @@ void handle_work_timer(void) { (uint8_t)((float)pulse_color.b * mid_pulse_ratio * brightness_factor)); } } - else if (work_timer.flags.end_warning_shown) { + else if (work_timer.flags.end_warning_shown || end_warning) { // End of timer warning - red pulse rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; @@ -839,7 +795,4 @@ void handle_work_timer(void) { void work_timer_task(void) { // Update work timer state update_work_timer(); - - // Add explicit call to refresh RGB matrix if needed - // This might help ensure F1-F12 get proper colors after timer state changes } \ No newline at end of file From 7f49c0408f446330462c8b92cc28be33d15ca577 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sat, 17 May 2025 15:25:55 -0400 Subject: [PATCH 51/54] Refactor work timer structure for improved state management and simplify color definitions. Remove unused warning color definitions and clarify break warning time comment. --- keyboards/tssouthpaw/rgb_effects/work_timer.c | 831 +++++------------- keyboards/tssouthpaw/rgb_effects/work_timer.h | 7 +- 2 files changed, 214 insertions(+), 624 deletions(-) diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c index 25217dded36..77492b87e37 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.c +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -19,237 +19,159 @@ #include "rgb_effects/rgb_effects.h" #include "timer.h" -// Bitpacked flags to save memory +// Simple work timer structure typedef struct { - uint8_t active: 1; - uint8_t paused: 1; - uint8_t lunch_break: 1; - uint8_t mid_break: 1; - uint8_t lunch_warning_shown: 1; - uint8_t mid_break_warning_shown: 1; - uint8_t end_warning_shown: 1; - uint8_t pulse_active: 1; // Flag to track if any pulse is currently active -} work_timer_flags_t; - -// Simplified work timer state structure -typedef struct { - work_timer_flags_t flags; - work_timer_type_t timer_type; - uint32_t start_time; // When timer was started - uint32_t end_time; // When timer should end (calculated) - uint32_t pause_time; // When pause started (if paused) - uint32_t break_start_time; // When current break started - uint32_t mid_break_time; // When mid-break should occur - uint32_t mid_break_duration; // How long the mid-break should last - bool has_lunch_break; // Whether this timer has a lunch break + // 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 work timer state with simplified initialization -static work_timer_t work_timer = { - .flags = {0}, - .timer_type = TIMER_TYPE_8HR, - .start_time = 0, - .end_time = 0, - .pause_time = 0, - .break_start_time = 0, - .mid_break_time = 0, - .mid_break_duration = LUNCH_BREAK_DURATION, - .has_lunch_break = true -}; +// Global timer state +static work_timer_t timer = {0}; -// Predefined RGB colors for timer states -static const rgb_color_t WORK_TIMER_START_COLOR = {WORK_TIMER_START_R, WORK_TIMER_START_G, WORK_TIMER_START_B}; -static const rgb_color_t WORK_TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MID_G, WORK_TIMER_MID_B}; -static const rgb_color_t WORK_TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B}; -static const rgb_color_t WORK_TIMER_LUNCH_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B}; -static const rgb_color_t WORK_TIMER_WARNING_COLOR = {WORK_TIMER_WARNING_R, WORK_TIMER_WARNING_G, WORK_TIMER_WARNING_B}; +// 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 prototypes for internal functions -static void configure_timer_for_type(work_timer_type_t timer_type); -static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor); -static void save_work_timer_state(void); -static void load_work_timer_state(void); -static void update_pulse_active_state(void); +// 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 timer parameters based on timer type + * Configure a timer with the specified type */ -static void configure_timer_for_type(work_timer_type_t timer_type) { - work_timer.timer_type = timer_type; +static void configure_timer(work_timer_type_t type) { + timer.type = type; - switch (timer_type) { + // Set break duration based on timer type + switch (type) { case TIMER_TYPE_30MIN: - work_timer.mid_break_time = TIMER_30MIN_DURATION / 2; - work_timer.mid_break_duration = MID_BREAK_30MIN_DURATION; - work_timer.has_lunch_break = false; - work_timer.end_time = work_timer.start_time + TIMER_30MIN_DURATION; + timer.break_time = MID_BREAK_30MIN_DURATION; break; case TIMER_TYPE_1HR: - work_timer.mid_break_time = TIMER_1HR_DURATION / 2; - work_timer.mid_break_duration = MID_BREAK_1HR_DURATION; - work_timer.has_lunch_break = false; - work_timer.end_time = work_timer.start_time + TIMER_1HR_DURATION; + timer.break_time = MID_BREAK_1HR_DURATION; break; case TIMER_TYPE_4HR: - work_timer.mid_break_time = TIMER_4HR_DURATION / 2; - work_timer.mid_break_duration = MID_BREAK_4HR_DURATION; - work_timer.has_lunch_break = false; - work_timer.end_time = work_timer.start_time + TIMER_4HR_DURATION; + timer.break_time = MID_BREAK_4HR_DURATION; break; case TIMER_TYPE_8HR: - work_timer.mid_break_time = TIMER_8HR_DURATION / 2; - work_timer.mid_break_duration = LUNCH_BREAK_DURATION; - work_timer.has_lunch_break = true; - work_timer.end_time = work_timer.start_time + TIMER_8HR_DURATION; + timer.break_time = LUNCH_BREAK_DURATION; break; case TIMER_TYPE_10HR: - work_timer.mid_break_time = TIMER_10HR_DURATION / 2; - work_timer.mid_break_duration = LUNCH_BREAK_DURATION; - work_timer.has_lunch_break = true; - work_timer.end_time = work_timer.start_time + TIMER_10HR_DURATION; + timer.break_time = LUNCH_BREAK_DURATION; break; default: - // Default to 8HR if something goes wrong - work_timer.mid_break_time = TIMER_8HR_DURATION / 2; - work_timer.mid_break_duration = LUNCH_BREAK_DURATION; - work_timer.has_lunch_break = true; - work_timer.end_time = work_timer.start_time + TIMER_8HR_DURATION; + 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 the work timer state to EEPROM + * Save timer state to EEPROM */ -static void save_work_timer_state(void) { - // Create a buffer to store all our data - uint8_t buffer[24] = {0}; // Increased from 21 to 24 bytes to have enough space +static void save_timer_state(void) { + uint8_t buffer[24] = {0}; - // Set the active flag - buffer[0] = work_timer.flags.active; + // Basic state + buffer[0] = timer.active ? 1 : 0; + buffer[1] = timer.paused ? 1 : 0; + buffer[2] = (uint8_t)timer.type; - // Only save time info if timer is active - if (work_timer.flags.active) { - // Save start time (4 bytes) - memcpy(&buffer[1], &work_timer.start_time, sizeof(uint32_t)); - - // Save end time (4 bytes) - memcpy(&buffer[5], &work_timer.end_time, sizeof(uint32_t)); - - // Save timer type (1 byte) - buffer[9] = (uint8_t)work_timer.timer_type; - - // Save pause state and time (5 bytes) - buffer[10] = work_timer.flags.paused; - memcpy(&buffer[11], &work_timer.pause_time, sizeof(uint32_t)); - - // Save break state (1 byte) - buffer[15] = (work_timer.flags.lunch_break ? 1 : 0) | - (work_timer.flags.mid_break ? 2 : 0); - - // Save break start time (4 bytes) - memcpy(&buffer[16], &work_timer.break_start_time, sizeof(uint32_t)); - - // Save mid-break time (4 bytes) - fixed buffer position - memcpy(&buffer[20], &work_timer.mid_break_time, sizeof(uint32_t)); - } + // 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 all data at once to EEPROM + // Write to EEPROM eeprom_update_block(buffer, (void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); } /** - * Load the work timer state from EEPROM + * Load timer state from EEPROM */ -static void load_work_timer_state(void) { - // Create a buffer to read all our data - uint8_t buffer[24] = {0}; // Increased from 21 to 24 bytes to match save function +static void load_timer_state(void) { + uint8_t buffer[24] = {0}; - // Read all data at once from EEPROM + // Read from EEPROM eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer)); - // Get the active flag - work_timer.flags.active = buffer[0]; + // Basic state + timer.active = buffer[0] ? true : false; + timer.paused = buffer[1] ? true : false; + timer.type = (work_timer_type_t)buffer[2]; - // Only process the rest if timer was active - if (work_timer.flags.active) { - // Load timer type - work_timer_type_t saved_type = (work_timer_type_t)buffer[9]; + // 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; - // Get start time (4 bytes) - memcpy(&work_timer.start_time, &buffer[1], sizeof(uint32_t)); - - // Get end time (4 bytes) - memcpy(&work_timer.end_time, &buffer[5], sizeof(uint32_t)); - - // Get pause state and time (5 bytes) - work_timer.flags.paused = buffer[10]; - memcpy(&work_timer.pause_time, &buffer[11], sizeof(uint32_t)); - - // Get break state (1 byte) - uint8_t break_state = buffer[15]; - work_timer.flags.lunch_break = (break_state & 1) > 0; - work_timer.flags.mid_break = (break_state & 2) > 0; - - // Get break start time (4 bytes) - memcpy(&work_timer.break_start_time, &buffer[16], sizeof(uint32_t)); - - // Get mid-break time (4 bytes) - fixed buffer position - memcpy(&work_timer.mid_break_time, &buffer[20], sizeof(uint32_t)); - - // Apply configuration for this timer type (durations, etc.) - configure_timer_for_type(saved_type); - - // If currently in a break, we need to adjust for time passed since poweroff - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { + // Verify timer is still valid + if (!timer.paused) { uint32_t current_time = timer_read32(); - uint32_t time_off = current_time - work_timer.break_start_time; - - // If we've been powered off longer than the break duration, end the break - if (time_off > work_timer.mid_break_duration) { - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; + if (current_time > timer.end_time) { + timer.active = false; + save_timer_state(); } } - - // Validate timer values - uint32_t current_time = timer_read32(); - - // If end time is in the past, timer is done - if (work_timer.end_time <= current_time && !work_timer.flags.paused) { - work_timer.flags.active = 0; - save_work_timer_state(); - } - - // Check for end warning state (within 5 minutes of end) - if (!work_timer.flags.paused && - current_time >= (work_timer.end_time - BREAK_WARNING_TIME)) { - work_timer.flags.end_warning_shown = 1; - } - - // Update pulse active state - update_pulse_active_state(); } } /** * Display progress bar with gradient colors */ -static void display_progress_bar(uint8_t num_leds, float overall_progress, float brightness_factor) { - // Calculate hour segments and LED positions +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 fully lit - uint8_t leds_lit = (uint8_t)(overall_progress / hours_per_led); + // 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 within the current LED - float current_led_progress = (overall_progress - (leds_lit * hours_per_led)) / hours_per_led; + // 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++) { @@ -257,46 +179,41 @@ static void display_progress_bar(uint8_t num_leds, float overall_progress, float if (i < leds_lit) { // Fully lit LED - calculate gradient color based on position - float led_position = (float)i / (float)(num_leds - 1); + float position = (float)i / (float)(num_leds - 1); - // Adjust transition points - more green-to-orange (first 80% of bar), - // and less orange-to-red (last 20% of bar) - if (led_position < 0.3f) { - // Scale to 0.0 - 1.0 for green-to-orange part (now 80% of the bar) - float adjusted_progress = led_position / 0.3f; - color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); - } - else { - float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% - color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); + // 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 overall RGB brightness factor - color.r = (uint8_t)((float)color.r * brightness_factor); - color.g = (uint8_t)((float)color.g * brightness_factor); - color.b = (uint8_t)((float)color.b * brightness_factor); + // 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) { - // Current LED - partially lit based on progress - float led_position = (float)i / (float)(num_leds - 1); - + // Partially lit LED + float position = (float)i / (float)(num_leds - 1); rgb_color_t full_color; - // Adjust transition points - more green-to-orange, less orange-to-red - if (led_position < 0.3f) { - // Scale to 0.0 - 1.0 for green-to-orange part - float adjusted_progress = led_position / 0.3f; - full_color = calculate_gradient_color(WORK_TIMER_START_COLOR, WORK_TIMER_MID_COLOR, adjusted_progress); - } - // Last 30% is orange-to-red gradient - else { - float adjusted_progress = (led_position - 0.3f) / 0.7f; // Scale to 0.0 - 1.0 for last 20% - full_color = calculate_gradient_color(WORK_TIMER_MID_COLOR, WORK_TIMER_END_COLOR, adjusted_progress); + + 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 the color based on progress within this LED and overall brightness - color.r = (uint8_t)((float)full_color.r * current_led_progress * brightness_factor); - color.g = (uint8_t)((float)full_color.g * current_led_progress * brightness_factor); - color.b = (uint8_t)((float)full_color.b * current_led_progress * brightness_factor); + // 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 @@ -305,326 +222,114 @@ static void display_progress_bar(uint8_t num_leds, float overall_progress, float } /** - * Update the pulse active state - * This determines if any pulse effect is currently active + * Initialize the work timer */ -static void update_pulse_active_state(void) { - if (!work_timer.flags.active || work_timer.flags.paused) { - work_timer.flags.pulse_active = false; - return; - } - - uint32_t current_time = timer_read32(); - - // Check if any pulse condition is active - bool lunch_warning = false; - bool lunch_end_warning = false; - bool mid_point_warning = false; - bool end_warning = false; - - // Mid-break (lunch) timing logic - if (work_timer.has_lunch_break) { - // Time until mid-break (lunch) - uint32_t time_to_mid_break = 0; - if (current_time < work_timer.mid_break_time) { - time_to_mid_break = work_timer.mid_break_time - current_time; - } - - // Lunch break warning (before lunch) - lunch_warning = !work_timer.flags.lunch_break && - (time_to_mid_break > 0 && time_to_mid_break < BREAK_WARNING_TIME); - - // Lunch end warning (before end of lunch) - lunch_end_warning = work_timer.flags.lunch_break && - (timer_elapsed32(work_timer.break_start_time) >= - (work_timer.mid_break_duration - BREAK_WARNING_TIME)); - } - else { - // Simple mid-break logic - uint32_t time_to_mid_break = 0; - if (current_time < work_timer.mid_break_time) { - time_to_mid_break = work_timer.mid_break_time - current_time; - } - - // Mid-point break warning for shorter timers - mid_point_warning = !work_timer.flags.mid_break && - (time_to_mid_break > 0 && time_to_mid_break < BREAK_WARNING_TIME); - } - - // End timer warning (5 minutes before end) - end_warning = (current_time >= (work_timer.end_time - BREAK_WARNING_TIME) && - current_time < work_timer.end_time); - - // Set pulse active if any of these conditions are true - work_timer.flags.pulse_active = ( - work_timer.flags.mid_break || // Mid-point break pulse - work_timer.flags.lunch_break || // Lunch break - work_timer.flags.end_warning_shown || // End warning - lunch_warning || // Pre-lunch warning - lunch_end_warning || // End of lunch warning - mid_point_warning || // Mid-point warning for shorter timers - end_warning // End warning for all timers - ); +void work_timer_init(void) { + load_timer_state(); } /** * Toggle the work timer on/off */ void toggle_work_timer(void) { - if (work_timer.flags.active) { - // If timer is active, stop it and reset all state flags - work_timer.flags.active = 0; - work_timer.flags.paused = 0; - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.flags.end_warning_shown = 0; - work_timer.flags.pulse_active = 0; + if (timer.active) { + // Turn off timer + timer.active = false; + timer.paused = false; - // Save clean state to EEPROM - save_work_timer_state(); + save_timer_state(); - // Force immediate RGB refresh + // Force RGB refresh rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); } else { - // If timer is inactive, start a new timer - work_timer.flags.active = 1; - work_timer.flags.paused = 0; - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.flags.end_warning_shown = 0; - work_timer.flags.pulse_active = 0; + // Start a new timer + timer.active = true; + timer.paused = false; + timer.start_time = timer_read32(); + timer.pause_time = 0; - // Set timer start and calculate end time - work_timer.start_time = timer_read32(); - work_timer.break_start_time = 0; - - // Set up mid-break time based on current time - uint32_t duration = 0; - switch (work_timer.timer_type) { - case TIMER_TYPE_30MIN: duration = TIMER_30MIN_DURATION; break; - case TIMER_TYPE_1HR: duration = TIMER_1HR_DURATION; break; - case TIMER_TYPE_4HR: duration = TIMER_4HR_DURATION; break; - case TIMER_TYPE_8HR: duration = TIMER_8HR_DURATION; break; - case TIMER_TYPE_10HR: duration = TIMER_10HR_DURATION; break; - default: duration = TIMER_8HR_DURATION; break; - } - - // Set mid-break time (halfway through timer) - work_timer.mid_break_time = work_timer.start_time + (duration / 2); - - // Set end time - work_timer.end_time = work_timer.start_time + duration; - - save_work_timer_state(); + // Configure for current timer type + configure_timer(timer.type); + save_timer_state(); } - - update_pulse_active_state(); -} - -/** - * Initialize the work timer - load state from EEPROM - */ -void work_timer_init(void) { - load_work_timer_state(); } /** * Start a specific timer type */ -void start_timer(work_timer_type_t timer_type) { - // Reset timer state - work_timer.flags.active = 1; - work_timer.flags.paused = 0; - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - work_timer.flags.lunch_warning_shown = 0; - work_timer.flags.mid_break_warning_shown = 0; - work_timer.flags.end_warning_shown = 0; - work_timer.flags.pulse_active = 0; +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; - work_timer.start_time = timer_read32(); - work_timer.break_start_time = 0; - - // Configure timer parameters based on type - work_timer.timer_type = timer_type; - configure_timer_for_type(timer_type); - - save_work_timer_state(); - - // Update pulse state immediately after starting timer - update_pulse_active_state(); + // Configure based on type + configure_timer(type); + save_timer_state(); } /** - * Pause or resume the work timer + * Pause or resume the timer */ void toggle_pause_work_timer(void) { - if (!work_timer.flags.active) return; + if (!timer.active) return; - if (!work_timer.flags.paused) { - // Pause the timer - work_timer.flags.paused = 1; - work_timer.pause_time = timer_read32(); - save_work_timer_state(); - } else { - // Resume the timer - extend all time values by pause duration + if (timer.paused) { + // Resume - calculate time paused and adjust timers uint32_t current_time = timer_read32(); - uint32_t pause_duration = current_time - work_timer.pause_time; + uint32_t pause_duration = current_time - timer.pause_time; - // Extend end time by pause duration - work_timer.end_time += pause_duration; + // Extend all time points by pause duration + timer.end_time += pause_duration; + timer.mid_point += pause_duration; + timer.mid_point_end += pause_duration; - // If mid-break hasn't happened yet, extend that too - if (current_time < work_timer.mid_break_time) { - work_timer.mid_break_time += pause_duration; - } - - // If in a break, adjust break start time too - if (work_timer.flags.lunch_break || work_timer.flags.mid_break) { - work_timer.break_start_time += pause_duration; - } - - work_timer.flags.paused = 0; - save_work_timer_state(); + timer.paused = false; + } else { + // Pause + timer.pause_time = timer_read32(); + timer.paused = true; } - update_pulse_active_state(); + save_timer_state(); } /** - * Update the work timer state + * Check if the blue pulse effect should be active */ -void update_work_timer(void) { - if (!work_timer.flags.active || work_timer.flags.paused) return; +bool is_timer_pulse_active(void) { + if (!timer.active || timer.paused) return false; uint32_t current_time = timer_read32(); - // Process different timer states based on timer type - if (work_timer.has_lunch_break) { - // Handle lunch break state transitions - if (!work_timer.flags.lunch_break) { - // Check if it's time to start lunch - if (!work_timer.flags.lunch_warning_shown && - current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && - current_time < work_timer.mid_break_time) { - // Pre-lunch warning - work_timer.flags.lunch_warning_shown = 1; - } - else if (current_time >= work_timer.mid_break_time) { - // Start lunch break - work_timer.flags.lunch_break = 1; - work_timer.break_start_time = current_time; - work_timer.flags.lunch_warning_shown = 0; // Reset for lunch end warning - - // Don't need to extend end time yet - will do that when break ends - save_work_timer_state(); - } - } - else { - // Currently in lunch break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // Check for lunch end warning - if (!work_timer.flags.lunch_warning_shown && - break_elapsed >= (work_timer.mid_break_duration - BREAK_WARNING_TIME)) { - // Pre-end warning - work_timer.flags.lunch_warning_shown = 1; - } - - // End lunch break after duration (plus grace period) - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // End lunch break - work_timer.flags.lunch_break = 0; - work_timer.flags.lunch_warning_shown = 0; - - // Extend end time by break duration - work_timer.end_time += break_elapsed; - - save_work_timer_state(); - } - } - } - else { - // Handle mid-break state transitions for shorter timers - if (!work_timer.flags.mid_break) { - // Check if it's time to start mid-break - if (!work_timer.flags.mid_break_warning_shown && - current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && - current_time < work_timer.mid_break_time) { - // Mid-break warning - work_timer.flags.mid_break_warning_shown = 1; - } - else if (current_time >= work_timer.mid_break_time) { - // Start mid-break - work_timer.flags.mid_break = 1; - work_timer.break_start_time = current_time; - work_timer.flags.mid_break_warning_shown = 0; - save_work_timer_state(); - } - } - else { - // Currently in mid-break - uint32_t break_elapsed = timer_elapsed32(work_timer.break_start_time); - - // End mid-break after duration (plus grace period) - if (break_elapsed >= (work_timer.mid_break_duration + 5000)) { - // End mid-break - work_timer.flags.mid_break = 0; - work_timer.flags.mid_break_warning_shown = 0; - - // Extend end time by break duration - work_timer.end_time += break_elapsed; - - save_work_timer_state(); - } - } - } - - // Check for end of day warning - if (!work_timer.flags.end_warning_shown && - current_time >= (work_timer.end_time - BREAK_WARNING_TIME)) { - work_timer.flags.end_warning_shown = 1; - } - - // Auto-stop after timer ends - if (current_time >= work_timer.end_time) { - work_timer.flags.active = 0; - work_timer.flags.lunch_break = 0; - work_timer.flags.mid_break = 0; - save_work_timer_state(); - } - - update_pulse_active_state(); + // Only return true when we're in the mid-point break window + return (current_time >= timer.mid_point && current_time < timer.mid_point_end); } /** - * Check if any timer pulse is currently active - * Used for easier state checking - * - * @return true if any timer pulse effect is active, false otherwise + * Update timer state */ -bool is_timer_pulse_active(void) { - // If timer is not active or is paused, no pulse is active - if (!work_timer.flags.active || work_timer.flags.paused) { - return false; - } +void update_work_timer(void) { + if (!timer.active || timer.paused) return; - // Return the pulse_active flag directly from the work timer state - return work_timer.flags.pulse_active; + 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 the work timer visualization on LEDs + * Handle work timer visualization */ void handle_work_timer(void) { static bool was_active = false; - if (!work_timer.flags.active) { - // If the timer was just deactivated, force RGB refresh + if (!timer.active) { if (was_active) { rgb_matrix_mode_noeeprom(rgb_matrix_get_mode()); was_active = false; @@ -634,165 +339,55 @@ void handle_work_timer(void) { was_active = true; - // Get current RGB matrix brightness + // 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 overall progress (0.0 - 1.0) - uint32_t current_time = timer_read32(); - float overall_progress; - - // Simple progress calculation based on total time range - if (work_timer.flags.paused) { - // When paused, use the pause time for calculation - overall_progress = (float)(work_timer.pause_time - work_timer.start_time) / - (float)(work_timer.end_time - work_timer.start_time); + // 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 { - // Normal operation - current progress through timer - overall_progress = (float)(current_time - work_timer.start_time) / - (float)(work_timer.end_time - work_timer.start_time); + // When active, use current time for progress calculation + progress = (float)(current_time - timer.start_time) / + (float)(timer.end_time - timer.start_time); } - if (overall_progress > 1.0f) overall_progress = 1.0f; + // Clamp progress to valid range + if (progress > 1.0f) progress = 1.0f; + if (progress < 0.0f) progress = 0.0f; - // Create pulsing effect - uint8_t pulse_brightness = abs((timer_read() / 4) % 510 - 255); - float pulse_ratio = (float)pulse_brightness / 255.0f; - - // Check for various timer states - bool lunch_warning = false; - bool lunch_end_warning = false; - bool mid_break_warning = false; - bool end_warning = false; - - // For timers with lunch breaks - if (work_timer.has_lunch_break) { - // Pre-lunch warning (red pulse) - lunch_warning = !work_timer.flags.lunch_break && - (current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && - current_time < work_timer.mid_break_time); + // 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; - // Lunch-end warning (red pulse) - lunch_end_warning = work_timer.flags.lunch_break && - work_timer.break_start_time != 0 && - (timer_elapsed32(work_timer.break_start_time) >= - (work_timer.mid_break_duration - BREAK_WARNING_TIME)); + // Slow pulse for break time + uint8_t break_pulse = timer_read() / 10; + float break_intensity = (float)(abs(break_pulse % 510 - 255)) / 255.0f; - // End warning - end_warning = current_time >= (work_timer.end_time - BREAK_WARNING_TIME) && - current_time < work_timer.end_time; - - // Choose appropriate display based on current state - if (lunch_warning || lunch_end_warning) { - // Pre/Post lunch red warning pulse - rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; - - // Apply pulsing effect to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); - } - } - else if (work_timer.flags.lunch_break && work_timer.break_start_time != 0) { - // During lunch break - blue pulse - rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; - - // Use a slower pulse for regular lunch break - uint8_t lunch_pulse = abs((timer_read() / 10) % 510 - 255); - float lunch_pulse_ratio = (float)lunch_pulse / 255.0f; - - // Apply lunch break color to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - (uint8_t)((float)pulse_color.r * lunch_pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.g * lunch_pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.b * lunch_pulse_ratio * brightness_factor)); - } - } - else if (work_timer.flags.end_warning_shown || end_warning) { - // End of day warning - red pulse - rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; - - // Apply pulsing effect to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); - } - } - else { - // Normal progress bar display - display_progress_bar(num_leds, overall_progress, brightness_factor); - } - } - // For timers without lunch breaks (30MIN, 1HR, 4HR) - else { - mid_break_warning = !work_timer.flags.mid_break && - (current_time >= (work_timer.mid_break_time - BREAK_WARNING_TIME) && - current_time < work_timer.mid_break_time); - - end_warning = current_time >= (work_timer.end_time - BREAK_WARNING_TIME) && - current_time < work_timer.end_time; - - // Choose appropriate display based on current state - if (mid_break_warning) { - // Mid-break warning - red pulse - rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; - - // Apply pulsing effect to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); - } - } - // Mid-break active - else if (work_timer.flags.mid_break && work_timer.break_start_time != 0) { - // Mid-break active - blue pulse - rgb_color_t pulse_color = WORK_TIMER_LUNCH_COLOR; - - // Use a faster pulse for mid-break reminder - uint8_t mid_pulse = abs((timer_read() / 3) % 510 - 255); - float mid_pulse_ratio = (float)mid_pulse / 255.0f; - - // Apply pulsing effect to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - (uint8_t)((float)pulse_color.r * mid_pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.g * mid_pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.b * mid_pulse_ratio * brightness_factor)); - } - } - else if (work_timer.flags.end_warning_shown || end_warning) { - // End of timer warning - red pulse - rgb_color_t pulse_color = WORK_TIMER_WARNING_COLOR; - - // Apply pulsing effect to all progress bar LEDs - for (uint8_t i = 0; i < num_leds; i++) { - rgb_matrix_set_color(WORK_TIMER_LED_START + i, - (uint8_t)((float)pulse_color.r * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.g * pulse_ratio * brightness_factor), - (uint8_t)((float)pulse_color.b * pulse_ratio * brightness_factor)); - } - } - else { - // Normal progress bar display - display_progress_bar(num_leds, overall_progress, brightness_factor); + // 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); } } /** - * Work timer task - can be called periodically + * Periodic timer task */ void work_timer_task(void) { - // Update work timer state update_work_timer(); } \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.h b/keyboards/tssouthpaw/rgb_effects/work_timer.h index dc6b9e0c514..2ac357db55b 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.h +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.h @@ -58,11 +58,6 @@ typedef enum { #define WORK_TIMER_LUNCH_G 0 #define WORK_TIMER_LUNCH_B 255 -// Warning color (red) -#define WORK_TIMER_WARNING_R 255 -#define WORK_TIMER_WARNING_G 0 -#define WORK_TIMER_WARNING_B 0 - // Standard timer durations (in milliseconds) // IMPORTANT: These represent actual working time (excluding breaks) #define TIMER_30MIN_DURATION 1800000 // 30 minutes @@ -72,7 +67,7 @@ typedef enum { #define TIMER_10HR_DURATION 36000000 // 10 hours (real world 11 hours with lunch) // Break durations -#define BREAK_WARNING_TIME 60000 // 60 seconds +#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 From 927051900789a011bfe84db5b2cedd0114b1de0e Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 18 May 2025 07:21:14 -0400 Subject: [PATCH 52/54] Update config.h --- keyboards/tssouthpaw/config.h | 1 - 1 file changed, 1 deletion(-) diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h index 94de51a3248..d1fbde9e0b5 100644 --- a/keyboards/tssouthpaw/config.h +++ b/keyboards/tssouthpaw/config.h @@ -48,4 +48,3 @@ // RP2040 Settings #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET // Enable double-tap reset #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 500U // Timeout window in ms - #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_LED GP25 // LED for reset indication \ No newline at end of file From 1653d6966ddf27fb1c033b6007685136031851de Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 18 May 2025 07:38:10 -0400 Subject: [PATCH 53/54] Refactor encoder update handling and improve work timer functionality. Update rules.mk for encoder map enablement and clean up unused code. --- keyboards/tssouthpaw/keymaps/default/keymap.c | 19 +++++++++---------- .../tssouthpaw/rgb_effects/rgb_effects.h | 3 ++- keyboards/tssouthpaw/rgb_effects/work_timer.c | 2 +- keyboards/tssouthpaw/rgb_effects/work_timer.h | 2 +- keyboards/tssouthpaw/rules.mk | 6 +++++- keyboards/tssouthpaw/tssouthpaw.c | 11 ++--------- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c index 747b21b73bd..1e3bb86c825 100644 --- a/keyboards/tssouthpaw/keymaps/default/keymap.c +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -38,15 +38,13 @@ KC_10HR, // 10-hour timer }; - // Function to handle rotary encoder updates - bool encoder_update_user(uint8_t index, bool clockwise) { - if (clockwise) { - tap_code(KC_VOLU); // Rotate right: Volume up - } else { - tap_code(KC_VOLD); // Rotate left: Volume down - } - return true; - } + // Replace encoder update function with encoder map + #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 @@ -169,4 +167,5 @@ 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 , _______, _______, _______ ), - }; \ No newline at end of file + }; + \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/rgb_effects.h b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h index 2d09c980ecf..2333833ea68 100644 --- a/keyboards/tssouthpaw/rgb_effects/rgb_effects.h +++ b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h @@ -81,4 +81,5 @@ 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 + } + \ No newline at end of file diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c index 77492b87e37..7b76452dce5 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.c +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c @@ -390,4 +390,4 @@ void handle_work_timer(void) { */ void work_timer_task(void) { update_work_timer(); -} \ No newline at end of file +} diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.h b/keyboards/tssouthpaw/rgb_effects/work_timer.h index 2ac357db55b..ea35f40a8dd 100644 --- a/keyboards/tssouthpaw/rgb_effects/work_timer.h +++ b/keyboards/tssouthpaw/rgb_effects/work_timer.h @@ -86,4 +86,4 @@ void toggle_work_timer(void); void work_timer_task(void); // Functions for checking timer states -bool is_timer_pulse_active(void); \ No newline at end of file +bool is_timer_pulse_active(void); diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk index f5d7b01cfaf..3e5c0c3c1ef 100644 --- a/keyboards/tssouthpaw/rules.mk +++ b/keyboards/tssouthpaw/rules.mk @@ -1 +1,5 @@ -SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c \ No newline at end of file +# This is the rules.mk file for the RGB Effects firmware. +SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c + +# Enable encoder map functionality +ENCODER_MAP_ENABLE = yes diff --git a/keyboards/tssouthpaw/tssouthpaw.c b/keyboards/tssouthpaw/tssouthpaw.c index da297eb5e36..54cab161306 100644 --- a/keyboards/tssouthpaw/tssouthpaw.c +++ b/keyboards/tssouthpaw/tssouthpaw.c @@ -62,9 +62,6 @@ * Power management function - Called when system is waking from sleep */ void suspend_wakeup_init_kb(void) { - // Re-enable RGB effects when computer wakes up - rgb_matrix_set_suspend_state(false); - // Make sure the timer pulse state is properly reflected // This ensures the timer visuals are immediately visible if (is_timer_pulse_active()) { @@ -81,15 +78,11 @@ /** * Regular task hook to ensure timer updates even during suspend */ - void housekeeping_task_user(void) { + void housekeeping_task_kb(void) { // Update work timer even during suspend if (is_timer_pulse_active()) { update_work_timer(); handle_work_timer(); } } - - // Default implementations for weak functions - __attribute__((weak)) void keyboard_post_init_user(void) {} - __attribute__((weak)) void suspend_power_down_user(void) {} - __attribute__((weak)) void suspend_wakeup_init_user(void) {} + \ No newline at end of file From 2dce308337cbf167cff60c5de8396e551c971330 Mon Sep 17 00:00:00 2001 From: TS Design Works <64564678+kthorpe88@users.noreply.github.com> Date: Sun, 18 May 2025 08:05:31 -0400 Subject: [PATCH 54/54] Refactor encoder settings and update rules.mk for encoder functionality. Remove unused encoder map configuration. --- keyboards/tssouthpaw/keyboard.json | 7 ++----- keyboards/tssouthpaw/keymaps/default/keymap.c | 2 -- keyboards/tssouthpaw/rules.mk | 4 ++-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json index 050f8d8c931..e6c2b73135a 100644 --- a/keyboards/tssouthpaw/keyboard.json +++ b/keyboards/tssouthpaw/keyboard.json @@ -24,7 +24,6 @@ "pin": "GP27" }, "encoder": { - "enabled": true, "rotary": [ {"pin_a": "GP1", "pin_b": "GP0", "resolution": 4} ] @@ -35,10 +34,8 @@ "extrakey": true, "nkro": true, "rgb_matrix": true, - "dynamic_macro": true - }, - "caps_word": { - "enabled": true + "dynamic_macro": true, + }, "debounce": 5, "rgb_matrix": { diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c index 1e3bb86c825..6bf5c995967 100644 --- a/keyboards/tssouthpaw/keymaps/default/keymap.c +++ b/keyboards/tssouthpaw/keymaps/default/keymap.c @@ -38,7 +38,6 @@ KC_10HR, // 10-hour timer }; - // Replace encoder update function with encoder map #ifdef ENCODER_MAP_ENABLE const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { [BASE] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, @@ -168,4 +167,3 @@ KC_NO , _______, _______, _______, RGB_RMOD, RGB_HUD, RGB_MOD, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , KC_WRKPAU, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______ ), }; - \ No newline at end of file diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk index 3e5c0c3c1ef..716da52f309 100644 --- a/keyboards/tssouthpaw/rules.mk +++ b/keyboards/tssouthpaw/rules.mk @@ -1,5 +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 map functionality -ENCODER_MAP_ENABLE = yes +# Enable encoder functionality +ENCODER_ENABLE = yes