diff --git a/src/Kaleidoscope-Model01-TestMode.h b/src/Kaleidoscope-Model01-TestMode.h
new file mode 100644
index 00000000..55ed92cb
--- /dev/null
+++ b/src/Kaleidoscope-Model01-TestMode.h
@@ -0,0 +1,19 @@
+/* Kaleidoscope-Model01-TestMode - A factory test mode for the Model 01.
+ * Copyright (C) 2017-2018 Keyboard.io, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "kaleidoscope/plugin/Model01-TestMode.h"
diff --git a/src/kaleidoscope/plugin/Model01-TestMode.cpp b/src/kaleidoscope/plugin/Model01-TestMode.cpp
new file mode 100644
index 00000000..b7bf3cce
--- /dev/null
+++ b/src/kaleidoscope/plugin/Model01-TestMode.cpp
@@ -0,0 +1,166 @@
+/* Kaleidoscope-Model01-TestMode - A factory test mode for the Model 01.
+ * Copyright (C) 2017-2018 Keyboard.io, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "Kaleidoscope.h"
+#include "Kaleidoscope-Model01-TestMode.h"
+#include "Kaleidoscope-LEDEffect-Rainbow.h"
+
+namespace kaleidoscope {
+namespace plugin {
+
+constexpr uint8_t CHATTER_CYCLE_LIMIT = 30;
+constexpr uint8_t TOGGLED_OFF = 2;
+constexpr uint8_t TOGGLED_ON = 1;
+constexpr uint8_t HELD = 3;
+constexpr uint8_t RELEASED = 0;
+
+EventHandlerResult TestMode::beforeReportingState() {
+ if (KeyboardHardware.isKeyswitchPressed(R0C0) &&
+ KeyboardHardware.isKeyswitchPressed(R0C6) &&
+ KeyboardHardware.isKeyswitchPressed(R3C6) &&
+ KeyboardHardware.pressedKeyswitchCount() == 3) {
+ run_tests();
+ }
+ return EventHandlerResult::OK;
+}
+
+void TestMode::waitForKeypress() {
+ for (uint8_t temp = 0; temp < 8; temp++) {
+ KeyboardHardware.readMatrix();
+ }
+ while (1) {
+ KeyboardHardware.readMatrix();
+ if (KeyboardHardware.isKeyswitchPressed(R3C6)
+ && KeyboardHardware.pressedKeyswitchCount() == 1
+ && KeyboardHardware.previousLeftHandState.all == 0) {
+ break;
+ }
+ }
+}
+
+void TestMode::set_leds(cRGB color) {
+ ::LEDControl.set_all_leds_to(color);
+ ::LEDControl.syncLeds();
+ waitForKeypress();
+}
+
+void TestMode::test_leds(void) {
+ constexpr cRGB red = CRGB(201, 0, 0);
+ constexpr cRGB blue = CRGB(0, 0, 201);
+ constexpr cRGB green = CRGB(0, 201, 0);
+ constexpr cRGB brightWhite = CRGB(160, 160, 160);
+
+ // make all the LEDs bright red
+ set_leds(red);
+ // make all the LEDs bright green
+ set_leds(green);
+ // make all the LEDs bright blue
+ set_leds(blue);
+ // make all the LEDs bright white (1.6A)
+ set_leds(brightWhite);
+ // rainbow for 10 seconds
+ for (auto i = 0; i < 1000; i++) {
+ ::LEDRainbowEffect.update();
+ ::LEDControl.syncLeds();
+ }
+ waitForKeypress();
+}
+
+
+
+void TestMode::handleKeyEvent(side_data_t *side, keydata_t *oldState, keydata_t *newState, uint8_t row, uint8_t col, uint8_t col_offset) {
+
+ constexpr cRGB red = CRGB(201, 0, 0);
+ constexpr cRGB blue = CRGB(0, 0, 201);
+ constexpr cRGB green = CRGB(0, 201, 0);
+
+ const uint8_t keynum = (row * 8) + (col);
+
+ const uint8_t keyState = ((bitRead(oldState->all, keynum) << 1) |
+ (bitRead(newState->all, keynum) << 0));
+ if (keyState == TOGGLED_ON) {
+ if (side->cyclesSinceStateChange[keynum] < CHATTER_CYCLE_LIMIT) {
+ bitSet(side->badKeys, keynum);
+ }
+ side->cyclesSinceStateChange[keynum] = 0;
+ } else if (side->cyclesSinceStateChange[keynum] <= CHATTER_CYCLE_LIMIT) {
+ side->cyclesSinceStateChange[keynum]++;
+ }
+
+
+
+ // If the key is held down
+ if (keyState == HELD) {
+ KeyboardHardware.setCrgbAt(row, col_offset - col, green);
+ }
+ // If we triggered chatter detection ever on this key
+ else if (bitRead(side->badKeys, keynum) == 1) {
+ KeyboardHardware.setCrgbAt(row, col_offset - col, red);
+ }
+
+ // If the key was just released
+ else if (keyState == TOGGLED_OFF) {
+ KeyboardHardware.setCrgbAt(row, col_offset - col, blue);
+ }
+}
+
+
+void TestMode::testMatrix() {
+ // Reset bad keys from previous tests.
+ side_data_t left = {{0}, 0};
+ side_data_t right = {{0}, 0};
+
+
+ ::LEDControl.set_all_leds_to(200, 0, 0);
+ // Clear out the key event buffer so we don't get messed up information from
+ // taps during LED test mode.
+ while (1) {
+ KeyboardHardware.readMatrix();
+ if (KeyboardHardware.isKeyswitchPressed(R0C0) &&
+ KeyboardHardware.isKeyswitchPressed(R0C6) &&
+ KeyboardHardware.isKeyswitchPressed(R3C6) &&
+ KeyboardHardware.pressedKeyswitchCount() == 3) {
+ break;
+ }
+ for (byte row = 0; row < 4; row++) {
+ for (byte col = 0; col < 8; col++) {
+ handleKeyEvent(&left, &(KeyboardHardware.previousLeftHandState), &(KeyboardHardware.leftHandState), row, col, 7);
+ handleKeyEvent(&right, &(KeyboardHardware.previousRightHandState), &(KeyboardHardware.rightHandState), row, col, 15);
+ }
+ }
+ ::LEDControl.syncLeds();
+ }
+}
+
+void TestMode::toggle_programming_leds_on() {
+ PORTD |= (1 << 5);
+ PORTB |= (1 << 0);
+}
+
+void TestMode::run_tests() {
+ // Serial.println("Running tests");
+ toggle_programming_leds_on();
+ // Disable debouncing
+ KeyboardHardware.setKeyscanInterval(2);
+ test_leds();
+ testMatrix();
+ // Serial.println("Done running tests");
+}
+
+}
+}
+
+kaleidoscope::plugin::TestMode TestMode;
diff --git a/src/kaleidoscope/plugin/Model01-TestMode.h b/src/kaleidoscope/plugin/Model01-TestMode.h
new file mode 100644
index 00000000..a3cbae0c
--- /dev/null
+++ b/src/kaleidoscope/plugin/Model01-TestMode.h
@@ -0,0 +1,53 @@
+/* Kaleidoscope-Model01-TestMode - A factory test mode for the Model 01.
+ * Copyright (C) 2017-2018 Keyboard.io, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#ifndef ARDUINO_AVR_MODEL01
+#error The Kaleidoscope-Model01-TestMode plugin was designed for the Keyboardio Model01, and does not work with any other hardware.
+#endif
+
+#include
+#include "Kaleidoscope.h"
+
+namespace kaleidoscope {
+namespace plugin {
+
+class TestMode : public kaleidoscope::Plugin {
+ public:
+ typedef struct {
+ uint8_t cyclesSinceStateChange[32];
+ uint32_t badKeys;
+
+ } side_data_t;
+
+ TestMode(void) {};
+
+ EventHandlerResult beforeReportingState();
+
+ private:
+ static void run_tests();
+ static void test_leds();
+ static void testMatrix();
+ static void toggle_programming_leds_on();
+ static void handleKeyEvent(side_data_t *side, keydata_t *oldState, keydata_t *newState, uint8_t row, uint8_t col, uint8_t col_offset);
+ static void waitForKeypress();
+ static void set_leds(cRGB color);
+};
+}
+}
+
+extern kaleidoscope::plugin::TestMode TestMode;