commit
						fc80aa9974
					
				| @ -1,3 +1,8 @@ | ||||
| SLEEP_LED_ENABLE = no  # Breathing sleep LED during USB suspend | ||||
| COMMAND_ENABLE   = no  # Commands for debug and configuration | ||||
| RGBLIGHT_ENABLE ?= yes | ||||
| MIDI_ENABLE ?= yes | ||||
| 
 | ||||
| ifndef MAKEFILE_INCLUDED | ||||
| 	include ../../../Makefile | ||||
| endif | ||||
| endif | ||||
|  | ||||
| @ -0,0 +1,9 @@ | ||||
| # Having a file like this allows you to override Makefile definitions
 | ||||
| # for your own particular keymap
 | ||||
| 
 | ||||
| SLEEP_LED_ENABLE = no  # Breathing sleep LED during USB suspend | ||||
| COMMAND_ENABLE   = no  # Commands for debug and configuration | ||||
| 
 | ||||
| ifndef QUANTUM_DIR | ||||
| 	include ../../../../Makefile | ||||
| endif | ||||
| Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB | 
| @ -0,0 +1,6 @@ | ||||
| RGBLIGHT_ENABLE ?= yes | ||||
| MIDI_ENABLE ?= yes | ||||
| 
 | ||||
| ifndef QUANTUM_DIR | ||||
| 	include ../../../../Makefile | ||||
| endif | ||||
| @ -0,0 +1,17 @@ | ||||
| #ifndef CONFIG_USER_H | ||||
| #define CONFIG_USER_H | ||||
| 
 | ||||
| #include "../../config.h" | ||||
| 
 | ||||
| /* ws2812 RGB LED */ | ||||
| #define RGB_DI_PIN D7 | ||||
| #define RGBLIGHT_ANIMATIONS | ||||
| #define RGBLED_NUM 15     // Number of LEDs
 | ||||
| #define RGBLIGHT_HUE_STEP 12 | ||||
| #define RGBLIGHT_SAT_STEP 255 | ||||
| #define RGBLIGHT_VAL_STEP 12 | ||||
| 
 | ||||
| #define RGB_MIDI | ||||
| #define RGBW_BB_TWI | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,79 @@ | ||||
| #include "ergodox.h" | ||||
| #include "debug.h" | ||||
| #include "action_layer.h" | ||||
| #include "version.h" | ||||
| #include "keymap_fr_ch.h" | ||||
| #include "keymap_french.h" | ||||
| #include "keymap_german.h" | ||||
| #include "keymap_german_ch.h" | ||||
| #include "keymap_nordic.h" | ||||
| #include "keymap_norwegian.h" | ||||
| #include "keymap_spanish.h" | ||||
| 
 | ||||
| const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | ||||
| 
 | ||||
| 
 | ||||
| [0] = KEYMAP(NO_LESS,KC_1,KC_2,KC_3,KC_4,KC_5,KC_BSPACE,KC_TAB,KC_Q,KC_W,KC_E,KC_R,KC_T,TG(1),KC_BSPACE,KC_A,KC_S,KC_D,KC_F,KC_G,SFT_T(NO_APOS),CTL_T(KC_Z),KC_X,KC_C,KC_V,KC_B,SFT_T(KC_EQUAL),MO(1),CTL_T(KC_GRAVE),KC_LGUI,KC_LEFT,KC_RIGHT,KC_ESCAPE,KC_CAPSLOCK,KC_HOME,KC_SPACE,KC_LGUI,KC_LALT,KC_DELETE,KC_6,KC_7,KC_8,KC_9,KC_0,NO_PLUS,TG(1),KC_Y,KC_U,KC_I,KC_O,KC_P,NO_AM,KC_H,KC_J,KC_K,KC_L,LT(2,NO_OSLH),NO_AE,SFT_T(KC_RBRC),KC_N,KC_M,KC_COMMA,KC_DOT,CTL_T(KC_SLASH),SFT_T(NO_APOS),KC_DOWN,KC_UP,NO_LPRN,NO_RPRN,MO(1),NO_QUOT,CTL_T(KC_ESCAPE),NO_APOS,KC_LALT,KC_LGUI,KC_ENTER), | ||||
| 
 | ||||
| [1] = KEYMAP(M(0),KC_F1,KC_F2,KC_F3,KC_F4,KC_F5,KC_BSPACE,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_BSPACE,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_LSHIFT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_LCTL,KC_LGUI,KC_LEFT,KC_RIGHT,KC_ESCAPE,KC_TRANSPARENT,KC_HOME,KC_SPACE,KC_LGUI,KC_LALT,KC_DELETE,KC_F6,KC_F7,KC_F8,KC_F9,KC_F10,KC_F11,KC_TRANSPARENT,KC_7,KC_8,KC_9,KC_TRANSPARENT,KC_TRANSPARENT,KC_F12,KC_4,KC_5,KC_6,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_0,KC_1,KC_2,KC_3,NO_LBRC,NO_RBRC,KC_LSHIFT,KC_COMMA,KC_DOT,LSFT(NO_LBRC),LSFT(NO_RBRC),KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_LALT,KC_LGUI,KC_ENTER), | ||||
| 
 | ||||
| [2] = KEYMAP(KC_ESCAPE,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_MS_UP,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_MS_LEFT,KC_MS_DOWN,KC_MS_RIGHT,KC_TRANSPARENT,KC_LSHIFT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_LCTL,KC_LALT,KC_LGUI,KC_MS_BTN1,KC_MS_BTN2,KC_ESCAPE,KC_TRANSPARENT,KC_TRANSPARENT,KC_SPACE,KC_LGUI,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_MEDIA_PREV_TRACK,KC_MEDIA_PLAY_PAUSE,KC_MEDIA_NEXT_TRACK,KC_TRANSPARENT,KC_TRANSPARENT,KC_AUDIO_VOL_UP,KC_AUDIO_VOL_DOWN,KC_AUDIO_MUTE,KC_TRANSPARENT,KC_TRANSPARENT,KC_TRANSPARENT,KC_ESCAPE,KC_MS_WH_UP,KC_MS_WH_DOWN,KC_MS_ACCEL0,KC_MS_ACCEL1), | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| const uint16_t PROGMEM fn_actions[] = { | ||||
|   [1] = ACTION_LAYER_TAP_TOGGLE(1) | ||||
| }; | ||||
| 
 | ||||
| const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) | ||||
| { | ||||
|       switch(id) { | ||||
|         case 0: | ||||
|         if (record->event.pressed) { | ||||
|           SEND_STRING (QMK_KEYBOARD "/" QMK_KEYMAP " @ " QMK_VERSION); | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|     return MACRO_NONE; | ||||
| }; | ||||
| 
 | ||||
| void matrix_scan_user(void) { | ||||
| 
 | ||||
|     uint8_t layer = biton32(layer_state); | ||||
| 
 | ||||
|     ergodox_board_led_off(); | ||||
|     ergodox_right_led_1_off(); | ||||
|     ergodox_right_led_2_off(); | ||||
|     ergodox_right_led_3_off(); | ||||
|     switch (layer) { | ||||
|         case 1: | ||||
|             ergodox_right_led_1_on(); | ||||
|             break; | ||||
|         case 2: | ||||
|             ergodox_right_led_2_on(); | ||||
|             break; | ||||
|         case 3: | ||||
|             ergodox_right_led_3_on(); | ||||
|             break; | ||||
|         case 4: | ||||
|             ergodox_right_led_1_on(); | ||||
|             ergodox_right_led_2_on(); | ||||
|             break; | ||||
|         case 5: | ||||
|             ergodox_right_led_1_on(); | ||||
|             ergodox_right_led_3_on(); | ||||
|             break; | ||||
|         case 6: | ||||
|             ergodox_right_led_2_on(); | ||||
|             ergodox_right_led_3_on(); | ||||
|             break; | ||||
|         case 7: | ||||
|             ergodox_right_led_1_on(); | ||||
|             ergodox_right_led_2_on(); | ||||
|             ergodox_right_led_3_on(); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| @ -0,0 +1,151 @@ | ||||
| #include "ergodox.h" | ||||
| #include "debug.h" | ||||
| #include "action_layer.h" | ||||
| #include "version.h" | ||||
| 
 | ||||
| enum custom_keycodes { | ||||
|   PLACEHOLDER = SAFE_RANGE, // can always be here
 | ||||
|   RGB_FF0000, | ||||
|   RGB_00FF00, | ||||
|   RGB_0000FF, | ||||
|   RGB_FFFFFF, | ||||
|   RGB_TOGGLE, | ||||
|   LED1, | ||||
|   LED2, | ||||
|   LED3 | ||||
| }; | ||||
| 
 | ||||
| const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | ||||
| 
 | ||||
| [0] = KEYMAP( | ||||
|         RGB_TOGGLE, RGB_FF0000, RGB_00FF00, RGB_0000FF, RGB_FFFFFF, KC_5, KC_LPRN, | ||||
|         KC_GRAVE,   KC_A,       KC_B,       KC_C,       KC_D,       KC_E, KC_EXLM, | ||||
|         KC_HASH,    KC_J,       KC_K,       KC_L,       KC_M,       KC_N, | ||||
|         KC_AMPR,    KC_T,       KC_U,       KC_V,       KC_W,       KC_X, KC_DLR, | ||||
|         KC_PIPE,    KC_R,       KC_PLUS,    KC_LCBR,    KC_RCBR, | ||||
| 
 | ||||
|                                             KC_F, KC_G, | ||||
|                                                   KC_H, | ||||
|                                      KC_P,  KC_O, KC_I, | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|                                                // RIGHT HAND
 | ||||
|                                                KC_RPRN,       KC_6, KC_7, KC_8,    KC_9,   KC_0,    KC_MINS, | ||||
|                                                KC_AT,         KC_F, KC_G, KC_H,    KC_I,   KC_COLN, KC_BSLS, | ||||
|                                                               KC_O, KC_P, KC_Q,    KC_R,   KC_S,    KC_QUOT, | ||||
|                                                LSFT(KC_COMM), KC_Y, KC_Z, KC_COMM, KC_DOT, KC_SLSH, KC_ASTR, | ||||
|                                                               KC_A, KC_B, KC_C,    KC_D,   KC_PIPE, | ||||
| 
 | ||||
|                                                LED1, KC_E, | ||||
|                                                LED2, | ||||
|                                                LED3, KC_J, KC_K | ||||
|     ) | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const uint16_t PROGMEM fn_actions[] = { | ||||
|   [1] = ACTION_LAYER_TAP_TOGGLE(1) | ||||
| }; | ||||
| 
 | ||||
| const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) | ||||
| { | ||||
|       switch(id) { | ||||
|         case 0: | ||||
|         if (record->event.pressed) { | ||||
|           SEND_STRING (QMK_KEYBOARD "/" QMK_KEYMAP " @ " QMK_VERSION); | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|     return MACRO_NONE; | ||||
| }; | ||||
| 
 | ||||
| bool status_led1_on = false, status_led2_on = false, status_led3_on = false; | ||||
| bool process_record_user(uint16_t keycode, keyrecord_t *record) { | ||||
|   switch (keycode) { | ||||
|     // dynamically generate these.
 | ||||
|     case RGB_FF0000: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef RGBLIGHT_ENABLE | ||||
|           EZ_RGB(0xff0000); | ||||
|           register_code(KC_1); unregister_code(KC_1); | ||||
|         #endif | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case RGB_00FF00: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef RGBLIGHT_ENABLE | ||||
|           EZ_RGB(0x00ff00); | ||||
|           register_code(KC_2); unregister_code(KC_2); | ||||
|         #endif | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case RGB_0000FF: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef RGBLIGHT_ENABLE | ||||
|           EZ_RGB(0x0000ff); | ||||
|           register_code(KC_3); unregister_code(KC_3); | ||||
|         #endif | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case RGB_FFFFFF: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef RGBLIGHT_ENABLE | ||||
|           EZ_RGB(0xffffff); | ||||
|           register_code(KC_4); unregister_code(KC_4); | ||||
|         #endif | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case RGB_TOGGLE: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef RGBLIGHT_ENABLE | ||||
|           rgblight_toggle(); | ||||
|           register_code(KC_EQL); unregister_code(KC_EQL); | ||||
|         #endif | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case LED1: | ||||
|       if (record->event.pressed) { | ||||
|         if(status_led1_on) { | ||||
|         ergodox_right_led_1_off(); | ||||
|         status_led1_on = false; | ||||
|         } else { | ||||
|         ergodox_right_led_1_on(); | ||||
|         status_led1_on = true; | ||||
|         } | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case LED2: | ||||
|       if (record->event.pressed) { | ||||
|         if(status_led2_on) { | ||||
|         ergodox_right_led_2_off(); | ||||
|         status_led2_on = false; | ||||
|         } else { | ||||
|         ergodox_right_led_2_on(); | ||||
|         status_led2_on = true; | ||||
|         } | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case LED3: | ||||
|       if (record->event.pressed) { | ||||
|         if(status_led3_on) { | ||||
|         ergodox_right_led_3_off(); | ||||
|         status_led3_on = false; | ||||
|         } else { | ||||
|         ergodox_right_led_3_on(); | ||||
|         status_led3_on = true; | ||||
|         } | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| # Robot test layout | ||||
| 
 | ||||
| Use this layout if you like to pretend you're [Norman](https://www.youtube.com/watch?v=-sbxFBay-tg), the ErgoDox EZ manufacturing robot. | ||||
| 
 | ||||
| It's really meant just for internal use, but we're posting it on GitHub anyway, because hurray to open source. :) | ||||
| @ -0,0 +1,29 @@ | ||||
| # Please remove if no longer applicable
 | ||||
| $(warning THIS FILE MAY BE TOO LARGE FOR YOUR KEYBOARD) | ||||
| $(warning Please disable some options in the Makefile to resolve) | ||||
| 
 | ||||
| 
 | ||||
| # Build Options
 | ||||
| #   change to "no" to disable the options, or define them in the Makefile in 
 | ||||
| #   the appropriate keymap folder that will get included automatically
 | ||||
| #
 | ||||
| BOOTMAGIC_ENABLE = no       # Virtual DIP switch configuration(+1000) | ||||
| MOUSEKEY_ENABLE = yes       # Mouse keys(+4700) | ||||
| EXTRAKEY_ENABLE = yes       # Audio control and System control(+450) | ||||
| CONSOLE_ENABLE = no         # Console for debug(+400) | ||||
| COMMAND_ENABLE = yes        # Commands for debug and configuration | ||||
| NKRO_ENABLE = yes            # Nkey Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work | ||||
| BACKLIGHT_ENABLE = yes      # Enable keyboard backlight functionality | ||||
| MIDI_ENABLE = no            # MIDI controls | ||||
| AUDIO_ENABLE = yes           # Audio output on port C6 | ||||
| UNICODE_ENABLE = no         # Unicode | ||||
| BLUETOOTH_ENABLE = no       # Enable Bluetooth with the Adafruit EZ-Key HID | ||||
| RGBLIGHT_ENABLE = no        # Enable WS2812 RGB underlight.  Do not enable this with audio at the same time. | ||||
| PRINTING_ENABLE = yes | ||||
| 
 | ||||
| # Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE
 | ||||
| SLEEP_LED_ENABLE = no    # Breathing sleep LED during USB suspend | ||||
| 
 | ||||
| ifndef QUANTUM_DIR | ||||
| 	include ../../../../Makefile | ||||
| endif | ||||
| @ -0,0 +1,23 @@ | ||||
| #ifndef CONFIG_USER_H | ||||
| #define CONFIG_USER_H | ||||
| 
 | ||||
| #include "../../config.h" | ||||
| 
 | ||||
| #      define SERIAL_UART_BAUD 19200 | ||||
| #      define SERIAL_UART_DATA UDR1 | ||||
| #      define SERIAL_UART_UBRR (F_CPU / (16UL * SERIAL_UART_BAUD) - 1) | ||||
| #      define SERIAL_UART_RXD_VECT USART1_RX_vect | ||||
| #      define SERIAL_UART_TXD_READY (UCSR1A & _BV(UDRE1)) | ||||
| #      define SERIAL_UART_INIT() do { \ | ||||
|             /* baud rate */ \ | ||||
|             UBRR1L = SERIAL_UART_UBRR; \ | ||||
|             /* baud rate */ \ | ||||
|             UBRR1H = SERIAL_UART_UBRR >> 8; \ | ||||
|             /* enable TX */ \ | ||||
|             UCSR1B = _BV(TXEN1); \ | ||||
|             /* 8-bit data */ \ | ||||
|             UCSR1C = _BV(UCSZ11) | _BV(UCSZ10); \ | ||||
|             sei(); \ | ||||
|         } while(0) | ||||
| 
 | ||||
|  #endif | ||||
| @ -0,0 +1,314 @@ | ||||
| // This is the canonical layout file for the Quantum project. If you want to add another keyboard,
 | ||||
| // this is the style you want to emulate.
 | ||||
| 
 | ||||
| #include "planck.h" | ||||
| #include "action_layer.h" | ||||
| #ifdef AUDIO_ENABLE | ||||
|   #include "audio.h" | ||||
| #endif | ||||
| #include "eeconfig.h" | ||||
| 
 | ||||
| extern keymap_config_t keymap_config; | ||||
| 
 | ||||
| // Each layer gets a name for readability, which is then used in the keymap matrix below.
 | ||||
| // The underscores don't mean anything - you can have a layer called STUFF or any other name.
 | ||||
| // Layer names don't all need to be of the same length, obviously, and you can also skip them
 | ||||
| // entirely and just use numbers.
 | ||||
| #define _QWERTY 0 | ||||
| #define _COLEMAK 1 | ||||
| #define _DVORAK 2 | ||||
| #define _LOWER 3 | ||||
| #define _RAISE 4 | ||||
| #define _PLOVER 5 | ||||
| #define _ADJUST 16 | ||||
| 
 | ||||
| enum planck_keycodes { | ||||
|   QWERTY = SAFE_RANGE, | ||||
|   COLEMAK, | ||||
|   DVORAK, | ||||
|   PLOVER, | ||||
|   LOWER, | ||||
|   RAISE, | ||||
|   BACKLIT, | ||||
|   EXT_PLV | ||||
| }; | ||||
| 
 | ||||
| // Fillers to make layering more clear
 | ||||
| #define _______ KC_TRNS | ||||
| #define XXXXXXX KC_NO | ||||
| 
 | ||||
| const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | ||||
| 
 | ||||
| /* Qwerty
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * | Tab  |   Q  |   W  |   E  |   R  |   T  |   Y  |   U  |   I  |   O  |   P  | Bksp | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * | Esc  |   A  |   S  |   D  |   F  |   G  |   H  |   J  |   K  |   L  |   ;  |  "   | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * | Shift|   Z  |   X  |   C  |   V  |   B  |   N  |   M  |   ,  |   .  |   /  |Enter | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * | Brite| Ctrl | Alt  | GUI  |Lower |    Space    |Raise | Left | Down |  Up  |Right | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| [_QWERTY] = { | ||||
|   {KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_BSPC}, | ||||
|   {KC_ESC,  KC_A,    KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT}, | ||||
|   {KC_LSFT, KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH, KC_ENT }, | ||||
|   {BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER,   KC_SPC,  KC_SPC,  RAISE,   KC_LEFT, KC_DOWN, KC_UP,   KC_RGHT} | ||||
| }, | ||||
| 
 | ||||
| /* Colemak
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * | Tab  |   Q  |   W  |   F  |   P  |   G  |   J  |   L  |   U  |   Y  |   ;  | Bksp | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * | Esc  |   A  |   R  |   S  |   T  |   D  |   H  |   N  |   E  |   I  |   O  |  "   | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * | Shift|   Z  |   X  |   C  |   V  |   B  |   K  |   M  |   ,  |   .  |   /  |Enter | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * | Brite| Ctrl | Alt  | GUI  |Lower |    Space    |Raise | Left | Down |  Up  |Right | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| [_COLEMAK] = { | ||||
|   {KC_TAB,  KC_Q,    KC_W,    KC_F,    KC_P,    KC_G,    KC_J,    KC_L,    KC_U,    KC_Y,    KC_SCLN, KC_BSPC}, | ||||
|   {KC_ESC,  KC_A,    KC_R,    KC_S,    KC_T,    KC_D,    KC_H,    KC_N,    KC_E,    KC_I,    KC_O,    KC_QUOT}, | ||||
|   {KC_LSFT, KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_K,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH, KC_ENT }, | ||||
|   {BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER,   KC_SPC,  KC_SPC,  RAISE,   KC_LEFT, KC_DOWN, KC_UP,   KC_RGHT} | ||||
| }, | ||||
| 
 | ||||
| /* Dvorak
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * | Tab  |   "  |   ,  |   .  |   P  |   Y  |   F  |   G  |   C  |   R  |   L  | Bksp | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * | Esc  |   A  |   O  |   E  |   U  |   I  |   D  |   H  |   T  |   N  |   S  |  /   | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * | Shift|   ;  |   Q  |   J  |   K  |   X  |   B  |   M  |   W  |   V  |   Z  |Enter | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * | Brite| Ctrl | Alt  | GUI  |Lower |    Space    |Raise | Left | Down |  Up  |Right | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| [_DVORAK] = { | ||||
|   {KC_TAB,  KC_QUOT, KC_COMM, KC_DOT,  KC_P,    KC_Y,    KC_F,    KC_G,    KC_C,    KC_R,    KC_L,    KC_BSPC}, | ||||
|   {KC_ESC,  KC_A,    KC_O,    KC_E,    KC_U,    KC_I,    KC_D,    KC_H,    KC_T,    KC_N,    KC_S,    KC_SLSH}, | ||||
|   {KC_LSFT, KC_SCLN, KC_Q,    KC_J,    KC_K,    KC_X,    KC_B,    KC_M,    KC_W,    KC_V,    KC_Z,    KC_ENT }, | ||||
|   {BACKLIT, KC_LCTL, KC_LALT, KC_LGUI, LOWER,   KC_SPC,  KC_SPC,  RAISE,   KC_LEFT, KC_DOWN, KC_UP,   KC_RGHT} | ||||
| }, | ||||
| 
 | ||||
| /* Lower
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * |   ~  |   !  |   @  |   #  |   $  |   %  |   ^  |   &  |   *  |   (  |   )  | Bksp | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * | Del  |  F1  |  F2  |  F3  |  F4  |  F5  |  F6  |   _  |   +  |   {  |   }  |  |   | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * |      |  F7  |  F8  |  F9  |  F10 |  F11 |  F12 |ISO ~ |ISO | |      |      |Enter | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * |      |      |      |      |      |             |      | Next | Vol- | Vol+ | Play | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| [_LOWER] = { | ||||
|   {KC_TILD, KC_EXLM, KC_AT,   KC_HASH, KC_DLR,  KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_BSPC}, | ||||
|   {KC_DEL,  KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,   KC_F6,   KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_PIPE}, | ||||
|   {_______, KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,  KC_F12,S(KC_NUHS),S(KC_NUBS),_______, _______, _______}, | ||||
|   {_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY} | ||||
| }, | ||||
| 
 | ||||
| /* Raise
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * |   `  |   1  |   2  |   3  |   4  |   5  |   6  |   7  |   8  |   9  |   0  | Bksp | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * | Del  |  F1  |  F2  |  F3  |  F4  |  F5  |  F6  |   -  |   =  |   [  |   ]  |  \   | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * |      |  F7  |  F8  |  F9  |  F10 |  F11 |  F12 |ISO # |ISO / |      |      |Enter | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * |      |      |      |      |      |             |      | Next | Vol- | Vol+ | Play | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| [_RAISE] = { | ||||
|   {KC_GRV,  KC_1,    KC_2,    KC_3,    KC_4,    KC_5,    KC_6,    KC_7,    KC_8,    KC_9,    KC_0,    KC_BSPC}, | ||||
|   {KC_DEL,  KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,   KC_F6,   KC_MINS, KC_EQL,  KC_LBRC, KC_RBRC, KC_BSLS}, | ||||
|   {_______, KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,  KC_F12,  KC_NUHS, KC_NUBS, _______, _______, _______}, | ||||
|   {_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY} | ||||
| }, | ||||
| 
 | ||||
| /* Plover layer (http://opensteno.org)
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * |   #  |   #  |   #  |   #  |   #  |   #  |   #  |   #  |   #  |   #  |   #  |   #  | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * |      |   S  |   T  |   P  |   H  |   *  |   *  |   F  |   P  |   L  |   T  |   D  | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * |TogOut|   S  |   K  |   W  |   R  |   *  |   *  |   R  |   B  |   G  |   S  |   Z  | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * | Exit |      |      |   A  |   O  |             |   E  |   U  |      |      |      | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| 
 | ||||
| [_PLOVER] = { | ||||
|   {KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1,    KC_1   }, | ||||
|   {XXXXXXX, KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC}, | ||||
|   {XXXXXXX, KC_A,    KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT}, | ||||
|   {EXT_PLV, XXXXXXX, XXXXXXX, KC_C,    KC_V,    XXXXXXX, XXXXXXX, KC_N,    KC_M,    XXXXXXX, XXXXXXX, XXXXXXX} | ||||
| }, | ||||
| 
 | ||||
| /* Adjust (Lower + Raise)
 | ||||
|  * ,-----------------------------------------------------------------------------------. | ||||
|  * |      | Reset|      | Print|no prnt |    |      |      |      |      |      |  Del | | ||||
|  * |------+------+------+------+------+-------------+------+------+------+------+------| | ||||
|  * |      |      |      |Aud on|Audoff|AGnorm|AGswap|Qwerty|Colemk|Dvorak|Plover|      | | ||||
|  * |------+------+------+------+------+------|------+------+------+------+------+------| | ||||
|  * |      |Voice-|Voice+|Mus on|Musoff|MIDIon|MIDIof|      |      |      |      |      | | ||||
|  * |------+------+------+------+------+------+------+------+------+------+------+------| | ||||
|  * |      |      |      |      |      |             |      |      |      |      |      | | ||||
|  * `-----------------------------------------------------------------------------------' | ||||
|  */ | ||||
| [_ADJUST] = { | ||||
|   {_______, RESET,   _______, PRINT_ON, PRINT_OFF, _______, _______, _______, _______, _______, _______, KC_DEL}, | ||||
|   {_______, _______, _______, AU_ON,   AU_OFF,  AG_NORM, AG_SWAP, QWERTY,  COLEMAK, DVORAK,  PLOVER,  _______}, | ||||
|   {_______, MUV_DE,  MUV_IN,  MU_ON,   MU_OFF,  MI_ON,   MI_OFF,  _______, _______, _______, _______, _______}, | ||||
|   {_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #ifdef AUDIO_ENABLE | ||||
| 
 | ||||
| float tone_startup[][2]    = SONG(STARTUP_SOUND); | ||||
| float tone_qwerty[][2]     = SONG(QWERTY_SOUND); | ||||
| float tone_dvorak[][2]     = SONG(DVORAK_SOUND); | ||||
| float tone_colemak[][2]    = SONG(COLEMAK_SOUND); | ||||
| float tone_plover[][2]     = SONG(PLOVER_SOUND); | ||||
| float tone_plover_gb[][2]  = SONG(PLOVER_GOODBYE_SOUND); | ||||
| float music_scale[][2]     = SONG(MUSIC_SCALE_SOUND); | ||||
| 
 | ||||
| float tone_goodbye[][2] = SONG(GOODBYE_SOUND); | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| void persistant_default_layer_set(uint16_t default_layer) { | ||||
|   eeconfig_update_default_layer(default_layer); | ||||
|   default_layer_set(default_layer); | ||||
| } | ||||
| 
 | ||||
| bool process_record_user(uint16_t keycode, keyrecord_t *record) { | ||||
|   switch (keycode) { | ||||
|     case QWERTY: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef AUDIO_ENABLE | ||||
|           PLAY_NOTE_ARRAY(tone_qwerty, false, 0); | ||||
|         #endif | ||||
|         persistant_default_layer_set(1UL<<_QWERTY); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case COLEMAK: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef AUDIO_ENABLE | ||||
|           PLAY_NOTE_ARRAY(tone_colemak, false, 0); | ||||
|         #endif | ||||
|         persistant_default_layer_set(1UL<<_COLEMAK); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case DVORAK: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef AUDIO_ENABLE | ||||
|           PLAY_NOTE_ARRAY(tone_dvorak, false, 0); | ||||
|         #endif | ||||
|         persistant_default_layer_set(1UL<<_DVORAK); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case LOWER: | ||||
|       if (record->event.pressed) { | ||||
|         layer_on(_LOWER); | ||||
|         update_tri_layer(_LOWER, _RAISE, _ADJUST); | ||||
|       } else { | ||||
|         layer_off(_LOWER); | ||||
|         update_tri_layer(_LOWER, _RAISE, _ADJUST); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case RAISE: | ||||
|       if (record->event.pressed) { | ||||
|         layer_on(_RAISE); | ||||
|         update_tri_layer(_LOWER, _RAISE, _ADJUST); | ||||
|       } else { | ||||
|         layer_off(_RAISE); | ||||
|         update_tri_layer(_LOWER, _RAISE, _ADJUST); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case BACKLIT: | ||||
|       if (record->event.pressed) { | ||||
|         register_code(KC_RSFT); | ||||
|         #ifdef BACKLIGHT_ENABLE | ||||
|           backlight_step(); | ||||
|         #endif | ||||
|       } else { | ||||
|         unregister_code(KC_RSFT); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case PLOVER: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef AUDIO_ENABLE | ||||
|           stop_all_notes(); | ||||
|           PLAY_NOTE_ARRAY(tone_plover, false, 0); | ||||
|         #endif | ||||
|         layer_off(_RAISE); | ||||
|         layer_off(_LOWER); | ||||
|         layer_off(_ADJUST); | ||||
|         layer_on(_PLOVER); | ||||
|         if (!eeconfig_is_enabled()) { | ||||
|             eeconfig_init(); | ||||
|         } | ||||
|         keymap_config.raw = eeconfig_read_keymap(); | ||||
|         keymap_config.nkro = 1; | ||||
|         eeconfig_update_keymap(keymap_config.raw); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|     case EXT_PLV: | ||||
|       if (record->event.pressed) { | ||||
|         #ifdef AUDIO_ENABLE | ||||
|           PLAY_NOTE_ARRAY(tone_plover_gb, false, 0); | ||||
|         #endif | ||||
|         layer_off(_PLOVER); | ||||
|       } | ||||
|       return false; | ||||
|       break; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void matrix_init_user(void) { | ||||
|     #ifdef AUDIO_ENABLE | ||||
|         startup_user(); | ||||
|     #endif | ||||
| } | ||||
| 
 | ||||
| #ifdef AUDIO_ENABLE | ||||
| 
 | ||||
| void startup_user() | ||||
| { | ||||
|     _delay_ms(20); // gets rid of tick
 | ||||
|     PLAY_NOTE_ARRAY(tone_startup, false, 0); | ||||
| } | ||||
| 
 | ||||
| void shutdown_user() | ||||
| { | ||||
|     PLAY_NOTE_ARRAY(tone_goodbye, false, 0); | ||||
|     _delay_ms(150); | ||||
|     stop_all_notes(); | ||||
| } | ||||
| 
 | ||||
| void music_on_user(void) | ||||
| { | ||||
|     music_scale_user(); | ||||
| } | ||||
| 
 | ||||
| void music_scale_user(void) | ||||
| { | ||||
|     PLAY_NOTE_ARRAY(music_scale, false, 0); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,2 @@ | ||||
| # The Default Planck Layout | ||||
| 
 | ||||
| @ -1,25 +1,3 @@ | ||||
| 
 | ||||
| 
 | ||||
| # Build Options
 | ||||
| #   change to "no" to disable the options, or define them in the Makefile in 
 | ||||
| #   the appropriate keymap folder that will get included automatically
 | ||||
| #
 | ||||
| BOOTMAGIC_ENABLE = no       # Virtual DIP switch configuration(+1000) | ||||
| MOUSEKEY_ENABLE = yes       # Mouse keys(+4700) | ||||
| EXTRAKEY_ENABLE = yes       # Audio control and System control(+450) | ||||
| CONSOLE_ENABLE = no         # Console for debug(+400) | ||||
| COMMAND_ENABLE = yes        # Commands for debug and configuration | ||||
| NKRO_ENABLE = yes            # Nkey Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work | ||||
| BACKLIGHT_ENABLE = yes      # Enable keyboard backlight functionality | ||||
| MIDI_ENABLE = no            # MIDI controls | ||||
| AUDIO_ENABLE = yes           # Audio output on port C6 | ||||
| UNICODE_ENABLE = no         # Unicode | ||||
| BLUETOOTH_ENABLE = no       # Enable Bluetooth with the Adafruit EZ-Key HID | ||||
| RGBLIGHT_ENABLE = no        # Enable WS2812 RGB underlight.  Do not enable this with audio at the same time. | ||||
| 
 | ||||
| # Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE
 | ||||
| SLEEP_LED_ENABLE = no    # Breathing sleep LED during USB suspend | ||||
| 
 | ||||
| ifndef QUANTUM_DIR | ||||
| 	include ../../../../Makefile | ||||
| endif | ||||
| @ -0,0 +1,178 @@ | ||||
| #include "api.h" | ||||
| #include "quantum.h" | ||||
| 
 | ||||
| void dword_to_bytes(uint32_t dword, uint8_t * bytes) { | ||||
|     bytes[0] = (dword >> 24) & 0xFF; | ||||
|     bytes[1] = (dword >> 16) & 0xFF;  | ||||
|     bytes[2] = (dword >> 8) & 0xFF;  | ||||
|     bytes[3] = (dword >> 0) & 0xFF;  | ||||
| } | ||||
| 
 | ||||
| uint32_t bytes_to_dword(uint8_t * bytes, uint8_t index) { | ||||
|     return ((uint32_t)bytes[index + 0] << 24) | ((uint32_t)bytes[index + 1] << 16) | ((uint32_t)bytes[index + 2] << 8) | (uint32_t)bytes[index + 3]; | ||||
| } | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| bool process_api_quantum(uint8_t length, uint8_t * data) { | ||||
|     return process_api_keyboard(length, data); | ||||
| } | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| bool process_api_keyboard(uint8_t length, uint8_t * data) { | ||||
|     return process_api_user(length, data); | ||||
| } | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| bool process_api_user(uint8_t length, uint8_t * data) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void process_api(uint16_t length, uint8_t * data) { | ||||
|     // SEND_STRING("\nRX: ");
 | ||||
|     // for (uint8_t i = 0; i < length; i++) {
 | ||||
|     //     send_byte(data[i]);
 | ||||
|     //     SEND_STRING(" ");
 | ||||
|     // }
 | ||||
|     if (!process_api_quantum(length, data)) | ||||
|         return; | ||||
| 
 | ||||
|     switch (data[0]) { | ||||
|         case MT_SET_DATA: | ||||
|             switch (data[1]) { | ||||
|                 case DT_DEFAULT_LAYER: { | ||||
|                     eeconfig_update_default_layer(data[2]); | ||||
|                     default_layer_set((uint32_t)(data[2])); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_KEYMAP_OPTIONS: { | ||||
|                     eeconfig_update_keymap(data[2]); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_RGBLIGHT: { | ||||
|                     #ifdef RGBLIGHT_ENABLE | ||||
|                         uint32_t rgblight = bytes_to_dword(data, 2); | ||||
|                         rgblight_update_dword(rgblight); | ||||
|                     #endif | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         case MT_GET_DATA: | ||||
|             switch (data[1]) { | ||||
|                 case DT_HANDSHAKE: { | ||||
|                     MT_GET_DATA_ACK(DT_HANDSHAKE, NULL, 0); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_DEBUG: { | ||||
|                     uint8_t debug_bytes[1] = { eeprom_read_byte(EECONFIG_DEBUG) }; | ||||
|                     MT_GET_DATA_ACK(DT_DEBUG, debug_bytes, 1); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_DEFAULT_LAYER: { | ||||
|                     uint8_t default_bytes[1] = { eeprom_read_byte(EECONFIG_DEFAULT_LAYER) }; | ||||
|                     MT_GET_DATA_ACK(DT_DEFAULT_LAYER, default_bytes, 1); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_CURRENT_LAYER: { | ||||
|                     uint8_t layer_state_bytes[4]; | ||||
|                     dword_to_bytes(layer_state, layer_state_bytes); | ||||
|                     MT_GET_DATA_ACK(DT_CURRENT_LAYER, layer_state_bytes, 4); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_AUDIO: { | ||||
|                     #ifdef AUDIO_ENABLE | ||||
|                         uint8_t audio_bytes[1] = { eeprom_read_byte(EECONFIG_AUDIO) }; | ||||
|                         MT_GET_DATA_ACK(DT_AUDIO, audio_bytes, 1); | ||||
|                     #else | ||||
|                         MT_GET_DATA_ACK(DT_AUDIO, NULL, 0); | ||||
|                     #endif | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_BACKLIGHT: { | ||||
|                     #ifdef BACKLIGHT_ENABLE | ||||
|                         uint8_t backlight_bytes[1] = { eeprom_read_byte(EECONFIG_BACKLIGHT) }; | ||||
|                         MT_GET_DATA_ACK(DT_BACKLIGHT, backlight_bytes, 1); | ||||
|                     #else | ||||
|                         MT_GET_DATA_ACK(DT_BACKLIGHT, NULL, 0); | ||||
|                     #endif | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_RGBLIGHT: { | ||||
|                     #ifdef RGBLIGHT_ENABLE | ||||
|                         uint8_t rgblight_bytes[4]; | ||||
|                         dword_to_bytes(eeconfig_read_rgblight(), rgblight_bytes); | ||||
|                         MT_GET_DATA_ACK(DT_RGBLIGHT, rgblight_bytes, 4); | ||||
|                     #else | ||||
|                         MT_GET_DATA_ACK(DT_RGBLIGHT, NULL, 0); | ||||
|                     #endif | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_KEYMAP_OPTIONS: { | ||||
|                     uint8_t keymap_bytes[1] = { eeconfig_read_keymap() }; | ||||
|                     MT_GET_DATA_ACK(DT_KEYMAP_OPTIONS, keymap_bytes, 1); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_KEYMAP_SIZE: { | ||||
|                     uint8_t keymap_size[2] = {MATRIX_ROWS, MATRIX_COLS}; | ||||
|                     MT_GET_DATA_ACK(DT_KEYMAP_SIZE, keymap_size, 2); | ||||
|                     break; | ||||
|                 } | ||||
|                 case DT_KEYMAP: { | ||||
|                     uint8_t keymap_data[MATRIX_ROWS * MATRIX_COLS * 4 + 3]; | ||||
|                     keymap_data[0] = data[2]; | ||||
|                     keymap_data[1] = MATRIX_ROWS; | ||||
|                     keymap_data[2] = MATRIX_COLS; | ||||
|                     for (int i = 0; i < MATRIX_ROWS; i++) { | ||||
|                         for (int j = 0; j < MATRIX_COLS; j++) { | ||||
|                             keymap_data[3 + (i*MATRIX_COLS*2) + (j*2)] = pgm_read_word(&keymaps[data[2]][i][j]) >> 8; | ||||
|                             keymap_data[3 + (i*MATRIX_COLS*2) + (j*2) + 1] = pgm_read_word(&keymaps[data[2]][i][j]) & 0xFF; | ||||
|                         } | ||||
|                     } | ||||
|                     MT_GET_DATA_ACK(DT_KEYMAP, keymap_data, MATRIX_ROWS * MATRIX_COLS * 4 + 3); | ||||
|                     // uint8_t keymap_data[5];
 | ||||
|                     // keymap_data[0] = data[2];
 | ||||
|                     // keymap_data[1] = data[3];
 | ||||
|                     // keymap_data[2] = data[4];
 | ||||
|                     // keymap_data[3] = pgm_read_word(&keymaps[data[2]][data[3]][data[4]]) >> 8;
 | ||||
|                     // keymap_data[4] = pgm_read_word(&keymaps[data[2]][data[3]][data[4]]) & 0xFF;
 | ||||
| 
 | ||||
|                     // MT_GET_DATA_ACK(DT_KEYMAP, keymap_data, 5);
 | ||||
|                     break; | ||||
|                 } | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|             break; | ||||
|         case MT_SET_DATA_ACK: | ||||
|         case MT_GET_DATA_ACK: | ||||
|             break; | ||||
|         case MT_SEND_DATA: | ||||
|             break; | ||||
|         case MT_SEND_DATA_ACK: | ||||
|             break; | ||||
|         case MT_EXE_ACTION: | ||||
|             break; | ||||
|         case MT_EXE_ACTION_ACK: | ||||
|             break; | ||||
|         case MT_TYPE_ERROR: | ||||
|             break; | ||||
|         default: ; // command not recognised
 | ||||
|             SEND_BYTES(MT_TYPE_ERROR, DT_NONE, data, length); | ||||
|             break; | ||||
| 
 | ||||
|         // #ifdef RGBLIGHT_ENABLE
 | ||||
|         // case 0x27: ; // RGB LED functions
 | ||||
|         //     switch (*data++) {
 | ||||
|         //         case 0x00: ; // Update HSV
 | ||||
|         //             rgblight_sethsv((data[0] << 8 | data[1]) % 360, data[2], data[3]);
 | ||||
|         //             break;
 | ||||
|         //         case 0x01: ; // Update RGB
 | ||||
|         //             break;
 | ||||
|         //         case 0x02: ; // Update mode
 | ||||
|         //             rgblight_mode(data[0]);
 | ||||
|         //             break;
 | ||||
|         //     }
 | ||||
|         //     break;
 | ||||
|         // #endif
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,59 @@ | ||||
| #ifndef _API_H_ | ||||
| #define _API_H_ | ||||
| 
 | ||||
| #include "lufa.h" | ||||
| 
 | ||||
| enum MESSAGE_TYPE { | ||||
|     MT_GET_DATA =      0x10, // Get data from keyboard
 | ||||
|     MT_GET_DATA_ACK =  0x11, // returned data to process (ACK)
 | ||||
|     MT_SET_DATA =      0x20, // Set data on keyboard
 | ||||
|     MT_SET_DATA_ACK =  0x21, // returned data to confirm (ACK)
 | ||||
|     MT_SEND_DATA =     0x30, // Sending data/action from keyboard
 | ||||
|     MT_SEND_DATA_ACK = 0x31, // returned data/action confirmation (ACK)
 | ||||
|     MT_EXE_ACTION =    0x40, // executing actions on keyboard
 | ||||
|     MT_EXE_ACTION_ACK =0x41, // return confirmation/value (ACK)
 | ||||
|     MT_TYPE_ERROR =    0x80 // type not recofgnised (ACK)
 | ||||
| }; | ||||
| 
 | ||||
| enum DATA_TYPE { | ||||
|     DT_NONE = 0x00, | ||||
|     DT_HANDSHAKE, | ||||
|     DT_DEFAULT_LAYER, | ||||
|     DT_CURRENT_LAYER, | ||||
|     DT_KEYMAP_OPTIONS, | ||||
|     DT_BACKLIGHT, | ||||
|     DT_RGBLIGHT, | ||||
|     DT_UNICODE, | ||||
|     DT_DEBUG, | ||||
|     DT_AUDIO, | ||||
|     DT_QUANTUM_ACTION, | ||||
|     DT_KEYBOARD_ACTION, | ||||
|     DT_USER_ACTION, | ||||
|     DT_KEYMAP_SIZE, | ||||
|     DT_KEYMAP | ||||
| }; | ||||
| 
 | ||||
| void dword_to_bytes(uint32_t dword, uint8_t * bytes); | ||||
| uint32_t bytes_to_dword(uint8_t * bytes, uint8_t index); | ||||
| 
 | ||||
| #define MT_GET_DATA(data_type, data, length) SEND_BYTES(MT_GET_DATA, data_type, data, length) | ||||
| #define MT_GET_DATA_ACK(data_type, data, length) SEND_BYTES(MT_GET_DATA_ACK, data_type, data, length) | ||||
| #define MT_SET_DATA(data_type, data, length) SEND_BYTES(MT_SET_DATA, data_type, data, length) | ||||
| #define MT_SET_DATA_ACK(data_type, data, length) SEND_BYTES(MT_SET_DATA_ACK, data_type, data, length) | ||||
| #define MT_SEND_DATA(data_type, data, length) SEND_BYTES(MT_SEND_DATA, data_type, data, length) | ||||
| #define MT_SEND_DATA_ACK(data_type, data, length) SEND_BYTES(MT_SEND_DATA_ACK, data_type, data, length) | ||||
| #define MT_EXE_ACTION(data_type, data, length) SEND_BYTES(MT_EXE_ACTION, data_type, data, length) | ||||
| #define MT_EXE_ACTION_ACK(data_type, data, length) SEND_BYTES(MT_EXE_ACTION_ACK, data_type, data, length) | ||||
| 
 | ||||
| void process_api(uint16_t length, uint8_t * data); | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| bool process_api_quantum(uint8_t length, uint8_t * data); | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| bool process_api_keyboard(uint8_t length, uint8_t * data); | ||||
| 
 | ||||
| __attribute__ ((weak)) | ||||
| bool process_api_user(uint8_t length, uint8_t * data); | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,29 @@ | ||||
| #include "api_sysex.h" | ||||
| 
 | ||||
| void send_bytes_sysex(uint8_t message_type, uint8_t data_type, uint8_t * bytes, uint16_t length) { | ||||
|     // SEND_STRING("\nTX: ");
 | ||||
|     // for (uint8_t i = 0; i < length; i++) {
 | ||||
|     //     send_byte(bytes[i]);
 | ||||
|     //     SEND_STRING(" ");
 | ||||
|     // }
 | ||||
|     uint8_t * precode = malloc(sizeof(uint8_t) * (length + 2)); | ||||
|     precode[0] = message_type; | ||||
|     precode[1] = data_type; | ||||
|     memcpy(precode + 2, bytes, length); | ||||
|     uint8_t * encoded = malloc(sizeof(uint8_t) * (sysex_encoded_length(length + 2))); | ||||
|     uint16_t encoded_length = sysex_encode(encoded, precode, length + 2); | ||||
|     uint8_t * array = malloc(sizeof(uint8_t) * (encoded_length + 5)); | ||||
|     array[0] = 0xF0; | ||||
|     array[1] = 0x00; | ||||
|     array[2] = 0x00; | ||||
|     array[3] = 0x00; | ||||
|     array[encoded_length + 4] = 0xF7; | ||||
|     memcpy(array + 4, encoded, encoded_length); | ||||
|     midi_send_array(&midi_device, encoded_length + 5, array); | ||||
| 
 | ||||
|     // SEND_STRING("\nTD: ");
 | ||||
|     // for (uint8_t i = 0; i < encoded_length + 5; i++) {
 | ||||
|     //     send_byte(array[i]);
 | ||||
|     //     SEND_STRING(" ");
 | ||||
|     // }
 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| #ifndef _API_SYSEX_H_ | ||||
| #define _API_SYSEX_H_ | ||||
| 
 | ||||
| #include "api.h" | ||||
| 
 | ||||
| void send_bytes_sysex(uint8_t message_type, uint8_t data_type, uint8_t * bytes, uint16_t length); | ||||
| 
 | ||||
| #define SEND_BYTES(mt, dt, b, l) send_bytes_sysex(mt, dt, b, l) | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
| // Some helpers for controlling gpio pins
 | ||||
| #include <avr/io.h> | ||||
| 
 | ||||
| enum { | ||||
|   PinDirectionInput = 0, | ||||
|   PinDirectionOutput = 1, | ||||
|   PinLevelHigh = 1, | ||||
|   PinLevelLow = 0, | ||||
| }; | ||||
| 
 | ||||
| // ex: pinMode(B0, PinDirectionOutput);
 | ||||
| static inline void pinMode(uint8_t pin, int mode) { | ||||
|   uint8_t bv = _BV(pin & 0xf); | ||||
|   if (mode == PinDirectionOutput) { | ||||
|     _SFR_IO8((pin >> 4) + 1) |= bv; | ||||
|   } else { | ||||
|     _SFR_IO8((pin >> 4) + 1) &= ~bv; | ||||
|     _SFR_IO8((pin >> 4) + 2) &= ~bv; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // ex: digitalWrite(B0, PinLevelHigh);
 | ||||
| static inline void digitalWrite(uint8_t pin, int mode) { | ||||
|   uint8_t bv = _BV(pin & 0xf); | ||||
|   if (mode == PinLevelHigh) { | ||||
|     _SFR_IO8((pin >> 4) + 2) |= bv; | ||||
|   } else { | ||||
|     _SFR_IO8((pin >> 4) + 2) &= ~bv; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Return true if the pin is HIGH
 | ||||
| // digitalRead(B0)
 | ||||
| static inline bool digitalRead(uint8_t pin) { | ||||
|   return _SFR_IO8(pin >> 4) & _BV(pin & 0xf); | ||||
| } | ||||
| @ -0,0 +1,254 @@ | ||||
| #include "process_printer.h" | ||||
| #include "action_util.h" | ||||
| 
 | ||||
| bool printing_enabled = false; | ||||
| uint8_t character_shift = 0; | ||||
| 
 | ||||
| void enabled_printing() { | ||||
| 	printing_enabled = true; | ||||
| 	serial_init(); | ||||
| } | ||||
| 
 | ||||
| void disable_printing() { | ||||
| 	printing_enabled = false; | ||||
| } | ||||
| 
 | ||||
| uint8_t shifted_numbers[10] = {0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29}; | ||||
| 
 | ||||
| // uint8_t keycode_to_ascii[0xFF][2];
 | ||||
| 
 | ||||
| // keycode_to_ascii[KC_MINS] = {0x2D, 0x5F};
 | ||||
| 
 | ||||
| void print_char(char c) { | ||||
| 	USB_Disable(); | ||||
| 	serial_send(c); | ||||
| 	USB_Init(); | ||||
| } | ||||
| 
 | ||||
| void print_box_string(uint8_t text[]) { | ||||
| 	uint8_t len = strlen(text); | ||||
| 	uint8_t out[len * 3 + 8]; | ||||
| 	out[0] = 0xDA; | ||||
| 	for (uint8_t i = 0; i < len; i++) { | ||||
| 		out[i+1] = 0xC4; | ||||
| 	} | ||||
| 	out[len + 1] = 0xBF; | ||||
| 	out[len + 2] = '\n'; | ||||
| 
 | ||||
| 	out[len + 3] = 0xB3; | ||||
| 	for (uint8_t i = 0; i < len; i++) { | ||||
| 		out[len + 4 + i] = text[i]; | ||||
| 	} | ||||
| 	out[len * 2 + 4] = 0xB3; | ||||
| 	out[len * 2 + 5] = '\n'; | ||||
| 
 | ||||
| 
 | ||||
| 	out[len * 2 + 6] = 0xC0; | ||||
| 	for (uint8_t i = 0; i < len; i++) { | ||||
| 		out[len * 2 + 7 + i] = 0xC4; | ||||
| 	} | ||||
| 	out[len * 3 + 7] = 0xD9; | ||||
| 	out[len * 3 + 8] = '\n'; | ||||
| 
 | ||||
| 	print_string(out);  | ||||
| } | ||||
| 
 | ||||
| void print_string(char c[]) { | ||||
| 	for(uint8_t i = 0; i < strlen(c); i++) | ||||
| 		print_char(c[i]); | ||||
| } | ||||
| 
 | ||||
| bool process_printer(uint16_t keycode, keyrecord_t *record) { | ||||
| 	if (keycode == PRINT_ON) { | ||||
| 		enabled_printing(); | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (keycode == PRINT_OFF) { | ||||
| 		disable_printing(); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (printing_enabled) { | ||||
| 		switch(keycode) { | ||||
| 			case KC_EXLM ... KC_RPRN: | ||||
| 			case KC_UNDS: | ||||
| 			case KC_PLUS: | ||||
| 			case KC_LCBR: | ||||
| 			case KC_RCBR: | ||||
| 			case KC_PIPE: | ||||
| 			case KC_TILD: | ||||
| 				keycode &= 0xFF; | ||||
| 			case KC_LSFT: | ||||
| 			case KC_RSFT: | ||||
| 				if (record->event.pressed) { | ||||
| 					character_shift++; | ||||
| 				} else { | ||||
| 					character_shift--; | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		switch(keycode) { | ||||
| 			case KC_F1: | ||||
| 				if (record->event.pressed) { | ||||
| 					print_box_string("This is a line of text!"); | ||||
| 				} | ||||
| 				return false; | ||||
| 			case KC_ESC: | ||||
| 				if (record->event.pressed) { | ||||
| 					print_char(0x1B); | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_SPC: | ||||
| 				if (record->event.pressed) { | ||||
| 					print_char(0x20); | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_A ... KC_Z: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x41 + (keycode - KC_A)); | ||||
| 					} else { | ||||
| 						print_char(0x61 + (keycode - KC_A)); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_1 ... KC_0: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 							print_char(shifted_numbers[keycode - KC_1]); | ||||
| 					} else { | ||||
| 							print_char(0x30 + ((keycode - KC_1 + 1) % 10)); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_ENT: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x0C); | ||||
| 					} else { | ||||
| 						print_char(0x0A); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_BSPC: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x18); | ||||
| 					} else { | ||||
| 						print_char(0x1A); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_DOT: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x3E); | ||||
| 					} else { | ||||
| 						print_char(0x2E); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_COMM: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x3C); | ||||
| 					} else { | ||||
| 						print_char(0x2C); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_SLSH: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x3F); | ||||
| 					} else { | ||||
| 						print_char(0x2F); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_QUOT: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x22); | ||||
| 					} else { | ||||
| 						print_char(0x27); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_GRV: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7E); | ||||
| 					} else { | ||||
| 						print_char(0x60); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_MINS: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x5F); | ||||
| 					} else { | ||||
| 						print_char(0x2D); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_EQL: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x2B); | ||||
| 					} else { | ||||
| 						print_char(0x3D); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_LBRC: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7B); | ||||
| 					} else { | ||||
| 						print_char(0x5B); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_RBRC: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7D); | ||||
| 					} else { | ||||
| 						print_char(0x5D); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_BSLS: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7C); | ||||
| 					} else { | ||||
| 						print_char(0x5C); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	return true; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| #ifndef PROCESS_PRINTER_H | ||||
| #define PROCESS_PRINTER_H | ||||
| 
 | ||||
| #include "quantum.h" | ||||
| 
 | ||||
| #include "protocol/serial.h" | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,260 @@ | ||||
| #include "process_printer.h" | ||||
| #include "action_util.h" | ||||
| 
 | ||||
| bool printing_enabled = false; | ||||
| uint8_t character_shift = 0; | ||||
| 
 | ||||
| #define SERIAL_PIN_DDR DDRD | ||||
| #define SERIAL_PIN_PORT PORTD | ||||
| #define SERIAL_PIN_MASK _BV(PD3) | ||||
| #define SERIAL_DELAY 52 | ||||
| 
 | ||||
| inline static | ||||
| void serial_delay(void) { | ||||
|   _delay_us(SERIAL_DELAY); | ||||
| } | ||||
| 
 | ||||
| inline static | ||||
| void serial_high(void) { | ||||
|   SERIAL_PIN_PORT |= SERIAL_PIN_MASK; | ||||
| } | ||||
| 
 | ||||
| inline static | ||||
| void serial_low(void) { | ||||
|   SERIAL_PIN_PORT &= ~SERIAL_PIN_MASK; | ||||
| } | ||||
| 
 | ||||
| inline static | ||||
| void serial_output(void) { | ||||
|   SERIAL_PIN_DDR |= SERIAL_PIN_MASK; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void enabled_printing() { | ||||
| 	printing_enabled = true; | ||||
| 	serial_output(); | ||||
| 	serial_high(); | ||||
| } | ||||
| 
 | ||||
| void disable_printing() { | ||||
| 	printing_enabled = false; | ||||
| } | ||||
| 
 | ||||
| uint8_t shifted_numbers[10] = {0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29}; | ||||
| 
 | ||||
| // uint8_t keycode_to_ascii[0xFF][2];
 | ||||
| 
 | ||||
| // keycode_to_ascii[KC_MINS] = {0x2D, 0x5F};
 | ||||
| 
 | ||||
| void print_char(char c) { | ||||
|   uint8_t b = 8; | ||||
|   serial_output(); | ||||
|   while( b-- ) { | ||||
|     if(c & (1 << b)) { | ||||
|       serial_high(); | ||||
|     } else { | ||||
|       serial_low(); | ||||
|     } | ||||
|     serial_delay(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void print_string(char c[]) { | ||||
| 	for(uint8_t i = 0; i < strlen(c); i++) | ||||
| 		print_char(c[i]); | ||||
| } | ||||
| 
 | ||||
| bool process_printer(uint16_t keycode, keyrecord_t *record) { | ||||
| 	if (keycode == PRINT_ON) { | ||||
| 		enabled_printing(); | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (keycode == PRINT_OFF) { | ||||
| 		disable_printing(); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (printing_enabled) { | ||||
| 		switch(keycode) { | ||||
| 			case KC_EXLM ... KC_RPRN: | ||||
| 			case KC_UNDS: | ||||
| 			case KC_PLUS: | ||||
| 			case KC_LCBR: | ||||
| 			case KC_RCBR: | ||||
| 			case KC_PIPE: | ||||
| 			case KC_TILD: | ||||
| 				keycode &= 0xFF; | ||||
| 			case KC_LSFT: | ||||
| 			case KC_RSFT: | ||||
| 				if (record->event.pressed) { | ||||
| 					character_shift++; | ||||
| 				} else { | ||||
| 					character_shift--; | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		switch(keycode) { | ||||
| 			case KC_F1: | ||||
| 				if (record->event.pressed) { | ||||
| 					print_string("This is a line of text!\n\n\n"); | ||||
| 				} | ||||
| 				return false; | ||||
| 			case KC_ESC: | ||||
| 				if (record->event.pressed) { | ||||
| 					print_char(0x1B); | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_SPC: | ||||
| 				if (record->event.pressed) { | ||||
| 					print_char(0x20); | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_A ... KC_Z: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x41 + (keycode - KC_A)); | ||||
| 					} else { | ||||
| 						print_char(0x61 + (keycode - KC_A)); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_1 ... KC_0: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 							print_char(shifted_numbers[keycode - KC_1]); | ||||
| 					} else { | ||||
| 							print_char(0x30 + ((keycode - KC_1 + 1) % 10)); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_ENT: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x0C); | ||||
| 					} else { | ||||
| 						print_char(0x0A); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_BSPC: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x18); | ||||
| 					} else { | ||||
| 						print_char(0x1A); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_DOT: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x3E); | ||||
| 					} else { | ||||
| 						print_char(0x2E); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_COMM: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x3C); | ||||
| 					} else { | ||||
| 						print_char(0x2C); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_SLSH: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x3F); | ||||
| 					} else { | ||||
| 						print_char(0x2F); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_QUOT: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x22); | ||||
| 					} else { | ||||
| 						print_char(0x27); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_GRV: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7E); | ||||
| 					} else { | ||||
| 						print_char(0x60); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_MINS: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x5F); | ||||
| 					} else { | ||||
| 						print_char(0x2D); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_EQL: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x2B); | ||||
| 					} else { | ||||
| 						print_char(0x3D); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_LBRC: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7B); | ||||
| 					} else { | ||||
| 						print_char(0x5B); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_RBRC: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7D); | ||||
| 					} else { | ||||
| 						print_char(0x5D); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 			case KC_BSLS: | ||||
| 				if (record->event.pressed) { | ||||
| 					if (character_shift) { | ||||
| 						print_char(0x7C); | ||||
| 					} else { | ||||
| 						print_char(0x5C); | ||||
| 					} | ||||
| 				} | ||||
| 				return false; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	return true; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,805 @@ | ||||
| #include "adafruit_ble.h" | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <alloca.h> | ||||
| #include <util/delay.h> | ||||
| #include <util/atomic.h> | ||||
| #include "debug.h" | ||||
| #include "pincontrol.h" | ||||
| #include "timer.h" | ||||
| #include "action_util.h" | ||||
| #include "ringbuffer.hpp" | ||||
| #include <string.h> | ||||
| 
 | ||||
| // These are the pin assignments for the 32u4 boards.
 | ||||
| // You may define them to something else in your config.h
 | ||||
| // if yours is wired up differently.
 | ||||
| #ifndef AdafruitBleResetPin | ||||
| #define AdafruitBleResetPin D4 | ||||
| #endif | ||||
| 
 | ||||
| #ifndef AdafruitBleCSPin | ||||
| #define AdafruitBleCSPin    B4 | ||||
| #endif | ||||
| 
 | ||||
| #ifndef AdafruitBleIRQPin | ||||
| #define AdafruitBleIRQPin   E6 | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| #define SAMPLE_BATTERY | ||||
| #define ConnectionUpdateInterval 1000 /* milliseconds */ | ||||
| 
 | ||||
| static struct { | ||||
|   bool is_connected; | ||||
|   bool initialized; | ||||
|   bool configured; | ||||
| 
 | ||||
| #define ProbedEvents 1 | ||||
| #define UsingEvents 2 | ||||
|   bool event_flags; | ||||
| 
 | ||||
| #ifdef SAMPLE_BATTERY | ||||
|   uint16_t last_battery_update; | ||||
|   uint32_t vbat; | ||||
| #endif | ||||
|   uint16_t last_connection_update; | ||||
| } state; | ||||
| 
 | ||||
| // Commands are encoded using SDEP and sent via SPI
 | ||||
| // https://github.com/adafruit/Adafruit_BluefruitLE_nRF51/blob/master/SDEP.md
 | ||||
| 
 | ||||
| #define SdepMaxPayload 16 | ||||
| struct sdep_msg { | ||||
|   uint8_t type; | ||||
|   uint8_t cmd_low; | ||||
|   uint8_t cmd_high; | ||||
|   struct __attribute__((packed)) { | ||||
|     uint8_t len:7; | ||||
|     uint8_t more:1; | ||||
|   }; | ||||
|   uint8_t payload[SdepMaxPayload]; | ||||
| } __attribute__((packed)); | ||||
| 
 | ||||
| // The recv latency is relatively high, so when we're hammering keys quickly,
 | ||||
| // we want to avoid waiting for the responses in the matrix loop.  We maintain
 | ||||
| // a short queue for that.  Since there is quite a lot of space overhead for
 | ||||
| // the AT command representation wrapped up in SDEP, we queue the minimal
 | ||||
| // information here.
 | ||||
| 
 | ||||
| enum queue_type { | ||||
|   QTKeyReport, // 1-byte modifier + 6-byte key report
 | ||||
|   QTConsumer,  // 16-bit key code
 | ||||
| #ifdef MOUSE_ENABLE | ||||
|   QTMouseMove, // 4-byte mouse report
 | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| struct queue_item { | ||||
|   enum queue_type queue_type; | ||||
|   uint16_t added; | ||||
|   union __attribute__((packed)) { | ||||
|     struct __attribute__((packed)) { | ||||
|       uint8_t modifier; | ||||
|       uint8_t keys[6]; | ||||
|     } key; | ||||
| 
 | ||||
|     uint16_t consumer; | ||||
|     struct __attribute__((packed)) { | ||||
|       uint8_t x, y, scroll, pan; | ||||
|     } mousemove; | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| // Items that we wish to send
 | ||||
| static RingBuffer<queue_item, 40> send_buf; | ||||
| // Pending response; while pending, we can't send any more requests.
 | ||||
| // This records the time at which we sent the command for which we
 | ||||
| // are expecting a response.
 | ||||
| static RingBuffer<uint16_t, 2> resp_buf; | ||||
| 
 | ||||
| static bool process_queue_item(struct queue_item *item, uint16_t timeout); | ||||
| 
 | ||||
| enum sdep_type { | ||||
|   SdepCommand = 0x10, | ||||
|   SdepResponse = 0x20, | ||||
|   SdepAlert = 0x40, | ||||
|   SdepError = 0x80, | ||||
|   SdepSlaveNotReady = 0xfe, // Try again later
 | ||||
|   SdepSlaveOverflow = 0xff, // You read more data than is available
 | ||||
| }; | ||||
| 
 | ||||
| enum ble_cmd { | ||||
|   BleInitialize = 0xbeef, | ||||
|   BleAtWrapper = 0x0a00, | ||||
|   BleUartTx = 0x0a01, | ||||
|   BleUartRx = 0x0a02, | ||||
| }; | ||||
| 
 | ||||
| enum ble_system_event_bits { | ||||
|   BleSystemConnected = 0, | ||||
|   BleSystemDisconnected = 1, | ||||
|   BleSystemUartRx = 8, | ||||
|   BleSystemMidiRx = 10, | ||||
| }; | ||||
| 
 | ||||
| // The SDEP.md file says 2MHz but the web page and the sample driver
 | ||||
| // both use 4MHz
 | ||||
| #define SpiBusSpeed 4000000 | ||||
| 
 | ||||
| #define SdepTimeout 150 /* milliseconds */ | ||||
| #define SdepShortTimeout 10 /* milliseconds */ | ||||
| #define SdepBackOff 25 /* microseconds */ | ||||
| #define BatteryUpdateInterval 10000 /* milliseconds */ | ||||
| 
 | ||||
| static bool at_command(const char *cmd, char *resp, uint16_t resplen, | ||||
|                        bool verbose, uint16_t timeout = SdepTimeout); | ||||
| static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, | ||||
|                          bool verbose = false); | ||||
| 
 | ||||
| struct SPI_Settings { | ||||
|   uint8_t spcr, spsr; | ||||
| }; | ||||
| 
 | ||||
| static struct SPI_Settings spi; | ||||
| 
 | ||||
| // Initialize 4Mhz MSBFIRST MODE0
 | ||||
| void SPI_init(struct SPI_Settings *spi) { | ||||
|   spi->spcr = _BV(SPE) | _BV(MSTR); | ||||
|   spi->spsr = _BV(SPI2X); | ||||
| 
 | ||||
|   static_assert(SpiBusSpeed == F_CPU / 2, "hard coded at 4Mhz"); | ||||
| 
 | ||||
|   ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { | ||||
|     // Ensure that SS is OUTPUT High
 | ||||
|     digitalWrite(B0, PinLevelHigh); | ||||
|     pinMode(B0, PinDirectionOutput); | ||||
| 
 | ||||
|     SPCR |= _BV(MSTR); | ||||
|     SPCR |= _BV(SPE); | ||||
|     pinMode(B1 /* SCK */, PinDirectionOutput); | ||||
|     pinMode(B2 /* MOSI */, PinDirectionOutput); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static inline void SPI_begin(struct SPI_Settings*spi) { | ||||
|   SPCR = spi->spcr; | ||||
|   SPSR = spi->spsr; | ||||
| } | ||||
| 
 | ||||
| static inline uint8_t SPI_TransferByte(uint8_t data) { | ||||
|   SPDR = data; | ||||
|   asm volatile("nop"); | ||||
|   while (!(SPSR & _BV(SPIF))) { | ||||
|     ; // wait
 | ||||
|   } | ||||
|   return SPDR; | ||||
| } | ||||
| 
 | ||||
| static inline void spi_send_bytes(const uint8_t *buf, uint8_t len) { | ||||
|   if (len == 0) return; | ||||
|   const uint8_t *end = buf + len; | ||||
|   while (buf < end) { | ||||
|     SPDR = *buf; | ||||
|     while (!(SPSR & _BV(SPIF))) { | ||||
|       ; // wait
 | ||||
|     } | ||||
|     ++buf; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static inline uint16_t spi_read_byte(void) { | ||||
|   return SPI_TransferByte(0x00 /* dummy */); | ||||
| } | ||||
| 
 | ||||
| static inline void spi_recv_bytes(uint8_t *buf, uint8_t len) { | ||||
|   const uint8_t *end = buf + len; | ||||
|   if (len == 0) return; | ||||
|   while (buf < end) { | ||||
|     SPDR = 0; // write a dummy to initiate read
 | ||||
|     while (!(SPSR & _BV(SPIF))) { | ||||
|       ; // wait
 | ||||
|     } | ||||
|     *buf = SPDR; | ||||
|     ++buf; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| #if 0 | ||||
| static void dump_pkt(const struct sdep_msg *msg) { | ||||
|   print("pkt: type="); | ||||
|   print_hex8(msg->type); | ||||
|   print(" cmd="); | ||||
|   print_hex8(msg->cmd_high); | ||||
|   print_hex8(msg->cmd_low); | ||||
|   print(" len="); | ||||
|   print_hex8(msg->len); | ||||
|   print(" more="); | ||||
|   print_hex8(msg->more); | ||||
|   print("\n"); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| // Send a single SDEP packet
 | ||||
| static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) { | ||||
|   SPI_begin(&spi); | ||||
| 
 | ||||
|   digitalWrite(AdafruitBleCSPin, PinLevelLow); | ||||
|   uint16_t timerStart = timer_read(); | ||||
|   bool success = false; | ||||
|   bool ready = false; | ||||
| 
 | ||||
|   do { | ||||
|     ready = SPI_TransferByte(msg->type) != SdepSlaveNotReady; | ||||
|     if (ready) { | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     // Release it and let it initialize
 | ||||
|     digitalWrite(AdafruitBleCSPin, PinLevelHigh); | ||||
|     _delay_us(SdepBackOff); | ||||
|     digitalWrite(AdafruitBleCSPin, PinLevelLow); | ||||
|   } while (timer_elapsed(timerStart) < timeout); | ||||
| 
 | ||||
|   if (ready) { | ||||
|     // Slave is ready; send the rest of the packet
 | ||||
|     spi_send_bytes(&msg->cmd_low, | ||||
|                    sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len); | ||||
|     success = true; | ||||
|   } | ||||
| 
 | ||||
|   digitalWrite(AdafruitBleCSPin, PinLevelHigh); | ||||
| 
 | ||||
|   return success; | ||||
| } | ||||
| 
 | ||||
| static inline void sdep_build_pkt(struct sdep_msg *msg, uint16_t command, | ||||
|                                   const uint8_t *payload, uint8_t len, | ||||
|                                   bool moredata) { | ||||
|   msg->type = SdepCommand; | ||||
|   msg->cmd_low = command & 0xff; | ||||
|   msg->cmd_high = command >> 8; | ||||
|   msg->len = len; | ||||
|   msg->more = (moredata && len == SdepMaxPayload) ? 1 : 0; | ||||
| 
 | ||||
|   static_assert(sizeof(*msg) == 20, "msg is correctly packed"); | ||||
| 
 | ||||
|   memcpy(msg->payload, payload, len); | ||||
| } | ||||
| 
 | ||||
| // Read a single SDEP packet
 | ||||
| static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) { | ||||
|   bool success = false; | ||||
|   uint16_t timerStart = timer_read(); | ||||
|   bool ready = false; | ||||
| 
 | ||||
|   do { | ||||
|     ready = digitalRead(AdafruitBleIRQPin); | ||||
|     if (ready) { | ||||
|       break; | ||||
|     } | ||||
|     _delay_us(1); | ||||
|   } while (timer_elapsed(timerStart) < timeout); | ||||
| 
 | ||||
|   if (ready) { | ||||
|     SPI_begin(&spi); | ||||
| 
 | ||||
|     digitalWrite(AdafruitBleCSPin, PinLevelLow); | ||||
| 
 | ||||
|     do { | ||||
|       // Read the command type, waiting for the data to be ready
 | ||||
|       msg->type = spi_read_byte(); | ||||
|       if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) { | ||||
|         // Release it and let it initialize
 | ||||
|         digitalWrite(AdafruitBleCSPin, PinLevelHigh); | ||||
|         _delay_us(SdepBackOff); | ||||
|         digitalWrite(AdafruitBleCSPin, PinLevelLow); | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       // Read the rest of the header
 | ||||
|       spi_recv_bytes(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload))); | ||||
| 
 | ||||
|       // and get the payload if there is any
 | ||||
|       if (msg->len <= SdepMaxPayload) { | ||||
|         spi_recv_bytes(msg->payload, msg->len); | ||||
|       } | ||||
|       success = true; | ||||
|       break; | ||||
|     } while (timer_elapsed(timerStart) < timeout); | ||||
| 
 | ||||
|     digitalWrite(AdafruitBleCSPin, PinLevelHigh); | ||||
|   } | ||||
|   return success; | ||||
| } | ||||
| 
 | ||||
| static void resp_buf_read_one(bool greedy) { | ||||
|   uint16_t last_send; | ||||
|   if (!resp_buf.peek(last_send)) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (digitalRead(AdafruitBleIRQPin)) { | ||||
|     struct sdep_msg msg; | ||||
| 
 | ||||
| again: | ||||
|     if (sdep_recv_pkt(&msg, SdepTimeout)) { | ||||
|       if (!msg.more) { | ||||
|         // We got it; consume this entry
 | ||||
|         resp_buf.get(last_send); | ||||
|         dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send)); | ||||
|       } | ||||
| 
 | ||||
|       if (greedy && resp_buf.peek(last_send) && digitalRead(AdafruitBleIRQPin)) { | ||||
|         goto again; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|   } else if (timer_elapsed(last_send) > SdepTimeout * 2) { | ||||
|     dprintf("waiting_for_result: timeout, resp_buf size %d\n", | ||||
|             (int)resp_buf.size()); | ||||
| 
 | ||||
|     // Timed out: consume this entry
 | ||||
|     resp_buf.get(last_send); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void send_buf_send_one(uint16_t timeout = SdepTimeout) { | ||||
|   struct queue_item item; | ||||
| 
 | ||||
|   // Don't send anything more until we get an ACK
 | ||||
|   if (!resp_buf.empty()) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (!send_buf.peek(item)) { | ||||
|     return; | ||||
|   } | ||||
|   if (process_queue_item(&item, timeout)) { | ||||
|     // commit that peek
 | ||||
|     send_buf.get(item); | ||||
|     dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size()); | ||||
|   } else { | ||||
|     dprint("failed to send, will retry\n"); | ||||
|     _delay_ms(SdepTimeout); | ||||
|     resp_buf_read_one(true); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void resp_buf_wait(const char *cmd) { | ||||
|   bool didPrint = false; | ||||
|   while (!resp_buf.empty()) { | ||||
|     if (!didPrint) { | ||||
|       dprintf("wait on buf for %s\n", cmd); | ||||
|       didPrint = true; | ||||
|     } | ||||
|     resp_buf_read_one(true); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static bool ble_init(void) { | ||||
|   state.initialized = false; | ||||
|   state.configured = false; | ||||
|   state.is_connected = false; | ||||
| 
 | ||||
|   pinMode(AdafruitBleIRQPin, PinDirectionInput); | ||||
|   pinMode(AdafruitBleCSPin, PinDirectionOutput); | ||||
|   digitalWrite(AdafruitBleCSPin, PinLevelHigh); | ||||
| 
 | ||||
|   SPI_init(&spi); | ||||
| 
 | ||||
|   // Perform a hardware reset
 | ||||
|   pinMode(AdafruitBleResetPin, PinDirectionOutput); | ||||
|   digitalWrite(AdafruitBleResetPin, PinLevelHigh); | ||||
|   digitalWrite(AdafruitBleResetPin, PinLevelLow); | ||||
|   _delay_ms(10); | ||||
|   digitalWrite(AdafruitBleResetPin, PinLevelHigh); | ||||
| 
 | ||||
|   _delay_ms(1000); // Give it a second to initialize
 | ||||
| 
 | ||||
|   state.initialized = true; | ||||
|   return state.initialized; | ||||
| } | ||||
| 
 | ||||
| static inline uint8_t min(uint8_t a, uint8_t b) { | ||||
|   return a < b ? a : b; | ||||
| } | ||||
| 
 | ||||
| static bool read_response(char *resp, uint16_t resplen, bool verbose) { | ||||
|   char *dest = resp; | ||||
|   char *end = dest + resplen; | ||||
| 
 | ||||
|   while (true) { | ||||
|     struct sdep_msg msg; | ||||
| 
 | ||||
|     if (!sdep_recv_pkt(&msg, 2 * SdepTimeout)) { | ||||
|       dprint("sdep_recv_pkt failed\n"); | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if (msg.type != SdepResponse) { | ||||
|       *resp = 0; | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     uint8_t len = min(msg.len, end - dest); | ||||
|     if (len > 0) { | ||||
|       memcpy(dest, msg.payload, len); | ||||
|       dest += len; | ||||
|     } | ||||
| 
 | ||||
|     if (!msg.more) { | ||||
|       // No more data is expected!
 | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Ensure the response is NUL terminated
 | ||||
|   *dest = 0; | ||||
| 
 | ||||
|   // "Parse" the result text; we want to snip off the trailing OK or ERROR line
 | ||||
|   // Rewind past the possible trailing CRLF so that we can strip it
 | ||||
|   --dest; | ||||
|   while (dest > resp && (dest[0] == '\n' || dest[0] == '\r')) { | ||||
|     *dest = 0; | ||||
|     --dest; | ||||
|   } | ||||
| 
 | ||||
|   // Look back for start of preceeding line
 | ||||
|   char *last_line = strrchr(resp, '\n'); | ||||
|   if (last_line) { | ||||
|     ++last_line; | ||||
|   } else { | ||||
|     last_line = resp; | ||||
|   } | ||||
| 
 | ||||
|   bool success = false; | ||||
|   static const char kOK[] PROGMEM = "OK"; | ||||
| 
 | ||||
|   success = !strcmp_P(last_line, kOK ); | ||||
| 
 | ||||
|   if (verbose || !success) { | ||||
|     dprintf("result: %s\n", resp); | ||||
|   } | ||||
|   return success; | ||||
| } | ||||
| 
 | ||||
| static bool at_command(const char *cmd, char *resp, uint16_t resplen, | ||||
|                        bool verbose, uint16_t timeout) { | ||||
|   const char *end = cmd + strlen(cmd); | ||||
|   struct sdep_msg msg; | ||||
| 
 | ||||
|   if (verbose) { | ||||
|     dprintf("ble send: %s\n", cmd); | ||||
|   } | ||||
| 
 | ||||
|   if (resp) { | ||||
|     // They want to decode the response, so we need to flush and wait
 | ||||
|     // for all pending I/O to finish before we start this one, so
 | ||||
|     // that we don't confuse the results
 | ||||
|     resp_buf_wait(cmd); | ||||
|     *resp = 0; | ||||
|   } | ||||
| 
 | ||||
|   // Fragment the command into a series of SDEP packets
 | ||||
|   while (end - cmd > SdepMaxPayload) { | ||||
|     sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, SdepMaxPayload, true); | ||||
|     if (!sdep_send_pkt(&msg, timeout)) { | ||||
|       return false; | ||||
|     } | ||||
|     cmd += SdepMaxPayload; | ||||
|   } | ||||
| 
 | ||||
|   sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, end - cmd, false); | ||||
|   if (!sdep_send_pkt(&msg, timeout)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   if (resp == NULL) { | ||||
|     auto now = timer_read(); | ||||
|     while (!resp_buf.enqueue(now)) { | ||||
|       resp_buf_read_one(false); | ||||
|     } | ||||
|     auto later = timer_read(); | ||||
|     if (TIMER_DIFF_16(later, now) > 0) { | ||||
|       dprintf("waited %dms for resp_buf\n", TIMER_DIFF_16(later, now)); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   return read_response(resp, resplen, verbose); | ||||
| } | ||||
| 
 | ||||
| bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) { | ||||
|   auto cmdbuf = (char *)alloca(strlen_P(cmd) + 1); | ||||
|   strcpy_P(cmdbuf, cmd); | ||||
|   return at_command(cmdbuf, resp, resplen, verbose); | ||||
| } | ||||
| 
 | ||||
| bool adafruit_ble_is_connected(void) { | ||||
|   return state.is_connected; | ||||
| } | ||||
| 
 | ||||
| bool adafruit_ble_enable_keyboard(void) { | ||||
|   char resbuf[128]; | ||||
| 
 | ||||
|   if (!state.initialized && !ble_init()) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   state.configured = false; | ||||
| 
 | ||||
|   // Disable command echo
 | ||||
|   static const char kEcho[] PROGMEM = "ATE=0"; | ||||
|   // Make the advertised name match the keyboard
 | ||||
|   static const char kGapDevName[] PROGMEM = | ||||
|       "AT+GAPDEVNAME=" STR(PRODUCT) " " STR(DESCRIPTION); | ||||
|   // Turn on keyboard support
 | ||||
|   static const char kHidEnOn[] PROGMEM = "AT+BLEHIDEN=1"; | ||||
| 
 | ||||
|   // Adjust intervals to improve latency.  This causes the "central"
 | ||||
|   // system (computer/tablet) to poll us every 10-30 ms.  We can't
 | ||||
|   // set a smaller value than 10ms, and 30ms seems to be the natural
 | ||||
|   // processing time on my macbook.  Keeping it constrained to that
 | ||||
|   // feels reasonable to type to.
 | ||||
|   static const char kGapIntervals[] PROGMEM = "AT+GAPINTERVALS=10,30,,"; | ||||
| 
 | ||||
|   // Reset the device so that it picks up the above changes
 | ||||
|   static const char kATZ[] PROGMEM = "ATZ"; | ||||
| 
 | ||||
|   // Turn down the power level a bit
 | ||||
|   static const char kPower[] PROGMEM = "AT+BLEPOWERLEVEL=-12"; | ||||
|   static PGM_P const configure_commands[] PROGMEM = { | ||||
|     kEcho, | ||||
|     kGapIntervals, | ||||
|     kGapDevName, | ||||
|     kHidEnOn, | ||||
|     kPower, | ||||
|     kATZ, | ||||
|   }; | ||||
| 
 | ||||
|   uint8_t i; | ||||
|   for (i = 0; i < sizeof(configure_commands) / sizeof(configure_commands[0]); | ||||
|        ++i) { | ||||
|     PGM_P cmd; | ||||
|     memcpy_P(&cmd, configure_commands + i, sizeof(cmd)); | ||||
| 
 | ||||
|     if (!at_command_P(cmd, resbuf, sizeof(resbuf))) { | ||||
|       dprintf("failed BLE command: %S: %s\n", cmd, resbuf); | ||||
|       goto fail; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   state.configured = true; | ||||
| 
 | ||||
|   // Check connection status in a little while; allow the ATZ time
 | ||||
|   // to kick in.
 | ||||
|   state.last_connection_update = timer_read(); | ||||
| fail: | ||||
|   return state.configured; | ||||
| } | ||||
| 
 | ||||
| static void set_connected(bool connected) { | ||||
|   if (connected != state.is_connected) { | ||||
|     if (connected) { | ||||
|       print("****** BLE CONNECT!!!!\n"); | ||||
|     } else { | ||||
|       print("****** BLE DISCONNECT!!!!\n"); | ||||
|     } | ||||
|     state.is_connected = connected; | ||||
| 
 | ||||
|     // TODO: if modifiers are down on the USB interface and
 | ||||
|     // we cut over to BLE or vice versa, they will remain stuck.
 | ||||
|     // This feels like a good point to do something like clearing
 | ||||
|     // the keyboard and/or generating a fake all keys up message.
 | ||||
|     // However, I've noticed that it takes a couple of seconds
 | ||||
|     // for macOS to to start recognizing key presses after BLE
 | ||||
|     // is in the connected state, so I worry that doing that
 | ||||
|     // here may not be good enough.
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void adafruit_ble_task(void) { | ||||
|   char resbuf[48]; | ||||
| 
 | ||||
|   if (!state.configured && !adafruit_ble_enable_keyboard()) { | ||||
|     return; | ||||
|   } | ||||
|   resp_buf_read_one(true); | ||||
|   send_buf_send_one(SdepShortTimeout); | ||||
| 
 | ||||
|   if (resp_buf.empty() && (state.event_flags & UsingEvents) && | ||||
|       digitalRead(AdafruitBleIRQPin)) { | ||||
|     // Must be an event update
 | ||||
|     if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) { | ||||
|       uint32_t mask = strtoul(resbuf, NULL, 16); | ||||
| 
 | ||||
|       if (mask & BleSystemConnected) { | ||||
|         set_connected(true); | ||||
|       } else if (mask & BleSystemDisconnected) { | ||||
|         set_connected(false); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (timer_elapsed(state.last_connection_update) > ConnectionUpdateInterval) { | ||||
|     bool shouldPoll = true; | ||||
|     if (!(state.event_flags & ProbedEvents)) { | ||||
|       // Request notifications about connection status changes.
 | ||||
|       // This only works in SPIFRIEND firmware > 0.6.7, which is why
 | ||||
|       // we check for this conditionally here.
 | ||||
|       // Note that at the time of writing, HID reports only work correctly
 | ||||
|       // with Apple products on firmware version 0.6.7!
 | ||||
|       // https://forums.adafruit.com/viewtopic.php?f=8&t=104052
 | ||||
|       if (at_command_P(PSTR("AT+EVENTENABLE=0x1"), resbuf, sizeof(resbuf))) { | ||||
|         at_command_P(PSTR("AT+EVENTENABLE=0x2"), resbuf, sizeof(resbuf)); | ||||
|         state.event_flags |= UsingEvents; | ||||
|       } | ||||
|       state.event_flags |= ProbedEvents; | ||||
| 
 | ||||
|       // leave shouldPoll == true so that we check at least once
 | ||||
|       // before relying solely on events
 | ||||
|     } else { | ||||
|       shouldPoll = false; | ||||
|     } | ||||
| 
 | ||||
|     static const char kGetConn[] PROGMEM = "AT+GAPGETCONN"; | ||||
|     state.last_connection_update = timer_read(); | ||||
| 
 | ||||
|     if (at_command_P(kGetConn, resbuf, sizeof(resbuf))) { | ||||
|       set_connected(atoi(resbuf)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| #ifdef SAMPLE_BATTERY | ||||
|   // I don't know if this really does anything useful yet; the reported
 | ||||
|   // voltage level always seems to be around 3200mV.  We may want to just rip
 | ||||
|   // this code out.
 | ||||
|   if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval && | ||||
|       resp_buf.empty()) { | ||||
|     state.last_battery_update = timer_read(); | ||||
| 
 | ||||
|     if (at_command_P(PSTR("AT+HWVBAT"), resbuf, sizeof(resbuf))) { | ||||
|       state.vbat = atoi(resbuf); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static bool process_queue_item(struct queue_item *item, uint16_t timeout) { | ||||
|   char cmdbuf[48]; | ||||
|   char fmtbuf[64]; | ||||
| 
 | ||||
|   // Arrange to re-check connection after keys have settled
 | ||||
|   state.last_connection_update = timer_read(); | ||||
| 
 | ||||
| #if 1 | ||||
|   if (TIMER_DIFF_16(state.last_connection_update, item->added) > 0) { | ||||
|     dprintf("send latency %dms\n", | ||||
|             TIMER_DIFF_16(state.last_connection_update, item->added)); | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
|   switch (item->queue_type) { | ||||
|     case QTKeyReport: | ||||
|       strcpy_P(fmtbuf, | ||||
|           PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); | ||||
|       snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->key.modifier, | ||||
|                item->key.keys[0], item->key.keys[1], item->key.keys[2], | ||||
|                item->key.keys[3], item->key.keys[4], item->key.keys[5]); | ||||
|       return at_command(cmdbuf, NULL, 0, true, timeout); | ||||
| 
 | ||||
|     case QTConsumer: | ||||
|       strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); | ||||
|       snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->consumer); | ||||
|       return at_command(cmdbuf, NULL, 0, true, timeout); | ||||
| 
 | ||||
| #ifdef MOUSE_ENABLE | ||||
|     case QTMouseMove: | ||||
|       strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); | ||||
|       snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->mousemove.x, | ||||
|           item->mousemove.y, item->mousemove.scroll, item->mousemove.pan); | ||||
|       return at_command(cmdbuf, NULL, 0, true, timeout); | ||||
| #endif | ||||
|     default: | ||||
|       return true; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, | ||||
|                             uint8_t nkeys) { | ||||
|   struct queue_item item; | ||||
|   bool didWait = false; | ||||
| 
 | ||||
|   item.queue_type = QTKeyReport; | ||||
|   item.key.modifier = hid_modifier_mask; | ||||
|   item.added = timer_read(); | ||||
| 
 | ||||
|   while (nkeys >= 0) { | ||||
|     item.key.keys[0] = keys[0]; | ||||
|     item.key.keys[1] = nkeys >= 1 ? keys[1] : 0; | ||||
|     item.key.keys[2] = nkeys >= 2 ? keys[2] : 0; | ||||
|     item.key.keys[3] = nkeys >= 3 ? keys[3] : 0; | ||||
|     item.key.keys[4] = nkeys >= 4 ? keys[4] : 0; | ||||
|     item.key.keys[5] = nkeys >= 5 ? keys[5] : 0; | ||||
| 
 | ||||
|     if (!send_buf.enqueue(item)) { | ||||
|       if (!didWait) { | ||||
|         dprint("wait for buf space\n"); | ||||
|         didWait = true; | ||||
|       } | ||||
|       send_buf_send_one(); | ||||
|       continue; | ||||
|     } | ||||
| 
 | ||||
|     if (nkeys <= 6) { | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     nkeys -= 6; | ||||
|     keys += 6; | ||||
|   } | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration) { | ||||
|   struct queue_item item; | ||||
| 
 | ||||
|   item.queue_type = QTConsumer; | ||||
|   item.consumer = keycode; | ||||
| 
 | ||||
|   while (!send_buf.enqueue(item)) { | ||||
|     send_buf_send_one(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| #ifdef MOUSE_ENABLE | ||||
| bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, | ||||
|                                   int8_t pan) { | ||||
|   struct queue_item item; | ||||
| 
 | ||||
|   item.queue_type = QTMouseMove; | ||||
|   item.mousemove.x = x; | ||||
|   item.mousemove.y = y; | ||||
|   item.mousemove.scroll = scroll; | ||||
|   item.mousemove.pan = pan; | ||||
| 
 | ||||
|   while (!send_buf.enqueue(item)) { | ||||
|     send_buf_send_one(); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| uint32_t adafruit_ble_read_battery_voltage(void) { | ||||
|   return state.vbat; | ||||
| } | ||||
| 
 | ||||
| bool adafruit_ble_set_mode_leds(bool on) { | ||||
|   if (!state.configured) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // The "mode" led is the red blinky one
 | ||||
|   at_command_P(on ? PSTR("AT+HWMODELED=1") : PSTR("AT+HWMODELED=0"), NULL, 0); | ||||
| 
 | ||||
|   // Pin 19 is the blue "connected" LED; turn that off too.
 | ||||
|   // When turning LEDs back on, don't turn that LED on if we're
 | ||||
|   // not connected, as that would be confusing.
 | ||||
|   at_command_P(on && state.is_connected ? PSTR("AT+HWGPIO=19,1") | ||||
|                                         : PSTR("AT+HWGPIO=19,0"), | ||||
|                NULL, 0); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| // https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel
 | ||||
| bool adafruit_ble_set_power_level(int8_t level) { | ||||
|   char cmd[46]; | ||||
|   if (!state.configured) { | ||||
|     return false; | ||||
|   } | ||||
|   snprintf(cmd, sizeof(cmd), "AT+BLEPOWERLEVEL=%d", level); | ||||
|   return at_command(cmd, NULL, 0, false); | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| /* Bluetooth Low Energy Protocol for QMK.
 | ||||
|  * Author: Wez Furlong, 2016 | ||||
|  * Supports the Adafruit BLE board built around the nRF51822 chip. | ||||
|  */ | ||||
| #pragma once | ||||
| #ifdef ADAFRUIT_BLE_ENABLE | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* Instruct the module to enable HID keyboard support and reset */ | ||||
| extern bool adafruit_ble_enable_keyboard(void); | ||||
| 
 | ||||
| /* Query to see if the BLE module is connected */ | ||||
| extern bool adafruit_ble_query_is_connected(void); | ||||
| 
 | ||||
| /* Returns true if we believe that the BLE module is connected.
 | ||||
|  * This uses our cached understanding that is maintained by | ||||
|  * calling ble_task() periodically. */ | ||||
| extern bool adafruit_ble_is_connected(void); | ||||
| 
 | ||||
| /* Call this periodically to process BLE-originated things */ | ||||
| extern void adafruit_ble_task(void); | ||||
| 
 | ||||
| /* Generates keypress events for a set of keys.
 | ||||
|  * The hid modifier mask specifies the state of the modifier keys for | ||||
|  * this set of keys. | ||||
|  * Also sends a key release indicator, so that the keys do not remain | ||||
|  * held down. */ | ||||
| extern bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, | ||||
|                                    uint8_t nkeys); | ||||
| 
 | ||||
| /* Send a consumer keycode, holding it down for the specified duration
 | ||||
|  * (milliseconds) */ | ||||
| extern bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration); | ||||
| 
 | ||||
| #ifdef MOUSE_ENABLE | ||||
| /* Send a mouse/wheel movement report.
 | ||||
|  * The parameters are signed and indicate positive of negative direction | ||||
|  * change. */ | ||||
| extern bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, | ||||
|                                          int8_t pan); | ||||
| #endif | ||||
| 
 | ||||
| /* Compute battery voltage by reading an analog pin.
 | ||||
|  * Returns the integer number of millivolts */ | ||||
| extern uint32_t adafruit_ble_read_battery_voltage(void); | ||||
| 
 | ||||
| extern bool adafruit_ble_set_mode_leds(bool on); | ||||
| extern bool adafruit_ble_set_power_level(int8_t level); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif // ADAFRUIT_BLE_ENABLE
 | ||||
| @ -0,0 +1,66 @@ | ||||
| #pragma once | ||||
| // A simple ringbuffer holding Size elements of type T
 | ||||
| template <typename T, uint8_t Size> | ||||
| class RingBuffer { | ||||
|  protected: | ||||
|   T buf_[Size]; | ||||
|   uint8_t head_{0}, tail_{0}; | ||||
|  public: | ||||
|   inline uint8_t nextPosition(uint8_t position) { | ||||
|     return (position + 1) % Size; | ||||
|   } | ||||
| 
 | ||||
|   inline uint8_t prevPosition(uint8_t position) { | ||||
|     if (position == 0) { | ||||
|       return Size - 1; | ||||
|     } | ||||
|     return position - 1; | ||||
|   } | ||||
| 
 | ||||
|   inline bool enqueue(const T &item) { | ||||
|     static_assert(Size > 1, "RingBuffer size must be > 1"); | ||||
|     uint8_t next = nextPosition(head_); | ||||
|     if (next == tail_) { | ||||
|       // Full
 | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     buf_[head_] = item; | ||||
|     head_ = next; | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   inline bool get(T &dest, bool commit = true) { | ||||
|     auto tail = tail_; | ||||
|     if (tail == head_) { | ||||
|       // No more data
 | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     dest = buf_[tail]; | ||||
|     tail = nextPosition(tail); | ||||
| 
 | ||||
|     if (commit) { | ||||
|       tail_ = tail; | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   inline bool empty() const { return head_ == tail_; } | ||||
| 
 | ||||
|   inline uint8_t size() const { | ||||
|     int diff = head_ - tail_; | ||||
|     if (diff >= 0) { | ||||
|       return diff; | ||||
|     } | ||||
|     return Size + diff; | ||||
|   } | ||||
| 
 | ||||
|   inline T& front() { | ||||
|     return buf_[tail_]; | ||||
|   } | ||||
| 
 | ||||
|   inline bool peek(T &item) { | ||||
|     return get(item, false); | ||||
|   } | ||||
| }; | ||||
					Loading…
					
					
				
		Reference in new issue
	
	 Priyadi Iman Nurcahyo
						Priyadi Iman Nurcahyo