Building a keyboard filter part 2
If you haven't read part 1, please read this first.
A change of plan
In part one I wanted to build a PS/2 in, PS/2 out filter. I mentioned also a quick test with the PS2KeyAdvanced library.
Turned out, the library is great, but does too much. It does handle things like the Keyboard LED and other stuff I don't need.
So I looked for other libraries and found the PSKeyboardHost library (also available in the library manager).
And this comes with a fully working PS/2 to USB example I can build upon. But it needs an Arduino with a USB host, like the Arduino Leonardo,
Pro Micro or a clone. Originally this was planned for iteration 2, but since this works fine (in fact, I'm using it to type this), I'll skip iteration 1.
Stripping the example down to the minimum
#include "ps2_Keyboard.h"
#include "ps2_NullDiagnostics.h"
#include "ps2_UsbTranslator.h"
#include "HID-Project.h"
static ps2::NullDiagnostics nd;
static ps2::UsbTranslator<ps2::NullDiagnostics> keyMapping(nd);
static ps2::Keyboard<4, 2, 16, ps2::NullDiagnostics> ps2Keyboard(nd);
static ps2::UsbKeyboardLeds ledValueLastSentToPs2 = ps2::UsbKeyboardLeds::none;
uint8_t activeFilter = 255;
// the setup function runs once when you press reset or power the board
void setup() {
ps2Keyboard.begin();
BootKeyboard.begin();
}
void loop() {
ps2::UsbKeyboardLeds newLedState = (ps2::UsbKeyboardLeds)BootKeyboard.getLeds();
if (newLedState != ledValueLastSentToPs2) {
ps2Keyboard.sendLedStatus(keyMapping.translateLeds(newLedState));
ledValueLastSentToPs2 = newLedState;
}
ps2::KeyboardOutput scanCode = ps2Keyboard.readScanCode();
if (scanCode != ps2::KeyboardOutput::none && scanCode != ps2::KeyboardOutput::garbled) {
ps2::UsbKeyAction action = keyMapping.translatePs2Keycode(scanCode);
KeyboardKeycode hidCode = (KeyboardKeycode)action.hidCode;
if (activeFilter != 255) {
// insert filter function
}
switch (action.gesture) {
case ps2::UsbKeyAction::KeyDown:
if (hidCode != KeyboardKeycode::KEY_RESERVED) {
BootKeyboard.press(hidCode);
}
break;
case ps2::UsbKeyAction::KeyUp:
if (hidCode != KeyboardKeycode::KEY_RESERVED) {
BootKeyboard.release(hidCode);
}
break;
}
}
}
Basically all it does is to check if the activeFilter variable is set to something other than 255 and if so, the we will call the filter function here.
If the filter function want to filter it, we will overwrite the key with KeyboardKeycode::KEY_RESERVED = 0.
The current function will come later.
If the key was not set to KEY_RESERVED (a.k.a. filtered), the command will be forwarded to the USB port.
Give it a try, all keys should work.
The filter function(s)
Now it is time for the filter function.
class Filter {
private:
uint8_t m_keys[8] = {0};
uint8_t m_number;
uint8_t m_numKeys;
public:
Filter(uint8_t number) {
m_number = number;
m_numKeys = 0;
}
bool loadFromEEPROM() {
return true;
}
void clear() {
for (uint8_t i = 0; i < m_numKeys; ++i) {
m_keys[i] = 0;
}
m_numKeys = 0;
}
void addKey(uint8_t key) {
if (m_numKeys < 7) {
m_keys[m_numKeys++] = key;
}
}
bool writeToEEPROM() {
return true;
}
bool isFiltered(uint8_t key) {
for (uint8_t i = 0; i < m_numKeys; ++i) {
if (m_keys[i] == key) {
return true;
}
}
return false;
}
};
#define NUM_FILTERS 12
Filter* filters[NUM_FILTERS] = {0};
This needs to be added to before the setup() function.
This is the core of the "product". You can define up to 12 filters, each of them can filter up to 8 keys.
If you want to add a key, call the addKey() function with the key to filter. The isFiltered function checks the previously added keys and return true if the key should be filtered, false if not.
Now we need to set it up in setup() function:
for (uint8_t i = 0; i < NUM_FILTERS; ++i) {
filters[i] = new Filter(i);
filters[i]->loadFromEEPROM();
}
filters[11]->clear();
filters[11]->addKey(KEY_E);
activeFilter = 11;
This includes a dummy hard coded filter on number 12. It filters out all "e".
The load and save from EEPROM will be done later.
Now we finally have to ask our filter function in the main loop
if (activeFilter != 255) {
if (filters[activeFilter]->isFiltered(hidCode)) {
hidCode = KeyboardKeycode::KEY_RESERVED;
}
}
Now a quick compile and upload, and tst how it works: looks fin to m, no mor in the output. Who nds that lttr anyway.
OK, back to the unfiltered keyboard...
Toggling filters and programming
As written in part 1, I want to program the filter by pressing left shift and scroll lock for 5 seconds, right shift and scroll lock for 1 second to use the filter.
For this we need a small state machine, sounds complicated, but is quite easy. You define several states and a condition to move to another state. You start at stage 0, if the condition is met, you move to the next one. In our case, a flow diagram shows the states and conditions best:
It starts with the key pressed or released event. Stage 0 is shown. It checks if the left shift key was pressed, if so, set state to 1 and send the key to the computer. Same for right key, except state is 6.
This is the workflow for selecting a filter, in state 6 we expect a scroll lock key down. If something else happens (other key pressed or shift released), go back to stage 0 and abort the process. If we got a scroll lock press, set state to 7. This is repeated until we go back to stage 0 by aborting the process or by finishing it.
Similar process for programming, just more steps:
The actual implementation will be done in part 3 in this series.