Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ Press __connect__ and select the BleenyButton serial interface (shown as somethi

The UI will show you the possible options.

### Key combination assignment

Key 1 and Key 2 support any key or key combination (e.g. `Alt+H`, `Ctrl+Shift+Z`, `F13`). In the web UI, click the **Key 1** or **Key 2** field and press the desired key (with any modifiers held down) — the field auto-detects and displays the combination. Click **Update** to send it to the device, then **Store new settings** to save.

The serial command format is `1:<keycode>:<modifier>` where both values are decimal USB HID codes (e.g. `1:11:4` sets Key 1 to `Alt+H`). Modifier bits: Ctrl=0x01, Shift=0x02, Alt=0x04, GUI/Win=0x08.

> **Note:** Some browser/OS shortcuts (e.g. `Ctrl+W`, `Alt+F4`) are intercepted before reaching the web UI. Use the serial terminal for those combinations.

__Important:__ The settings are not saved automatically, you need to execute __Store new settings on the device__!

### Currently implemented settings
Expand All @@ -105,8 +113,8 @@ __Important:__ The settings are not saved automatically, you need to execute __S
| Inactivity time [ms] | i | Integer, 30000-600000 [ms] (30s - 10min) | Time before the BleenyButton enters the power-down mode. Will be reset by pressing the button or an established BLE connection |
| Connected device | d | Info | This information heavily depends on the BLE stack of the host device, it is not always available, even if a device is connected! |
| Reset paired devices | r | Action | Reset the BLE pairings |
| Key 1 | 1 | Selection: Space, Enter, 1, 2, Tab, F1, F2, F13, F14 | Select the keyboard key to be send when pressing the main button |
| Key 2 | 2 | Selection: Space, Enter, 1, 2, Tab, F1, F2, F13, F14 | If a second button is connected, this key will be sent. |
| Key 1 | 1 | Key combination (any key + optional modifiers Ctrl/Shift/Alt/GUI) | Select the keyboard key or key combination to send when pressing the main button. Use the web UI to auto-detect by pressing the desired key. |
| Key 2 | 2 | Key combination (any key + optional modifiers Ctrl/Shift/Alt/GUI) | If a second button is connected, this key combination will be sent. |
| Print out supported commands and build date | ? | Action | Print out possible commands |

The following commands are available when building with output enabled:
Expand All @@ -117,7 +125,7 @@ The following commands are available when building with output enabled:
| Output mode | o | Selection: click, toggle | Either click the output for 0.1s or toggle when the button is pressed (or command "Trigger output" is sent) |
| Mode 1 - Tremor timeout | t | Integer, 300-5000 [ms] (0.3-5s) | Defines the delay before another output trigger can happen, used in Mode 1 |
| Mode 3 - Auto click delay | p | Integer, 2-600 [s] | After the output is triggered, this time will pass until the output is triggered again. |
| Startup Mode | m | Integer, 1-3 | Select the output mode on startup. The mode can be changed with the second button on the bottom (PIN_MODE) |
| Startup Mode | m | Integer, 1-4 | Select the output mode on startup. The mode can be changed with the second button on the bottom (PIN_MODE) |

### Output mode description

Expand All @@ -133,6 +141,10 @@ Button presses are directly mapped to the output. Note: each edge (press / relea

A button press triggers the output. Afterwards, the button is locked for a given timeout, then another automatic trigger of the output happens.

#### Mode 4 - disabled

The relay output is fully disabled. Button presses only send the configured BLE key — no relay switching occurs.

# Acknowledgement

This work has been accomplished at the UAS Technikum Wien in course of the R&D-project [InDiKo](https://www.technikum-wien.at/en/research-projects/indiko/) (MA23 project 38-09), which is supported by the [City of Vienna](https://www.wien.gv.at/kontakte/ma23/index.html).
Expand Down
83 changes: 54 additions & 29 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,16 @@ bool sleepMode = false;
// define ASCII-key action for each button
#define NUM_BUTTONS 4
uint8_t button_map[NUM_BUTTONS] = {BUTTON1, BUTTON2, BUTTON3, BUTTON4};
//default key map (index in key_codes)
uint8_t key_map[NUM_BUTTONS] = {0, 1, 2, 3};
//overwrite the key_map (if set to >= 0), (index in key_codes)
int8_t key_code_map[NUM_BUTTONS] = {-1};
// HID keycode for each button (USB HID Usage Tables: A=0x04, Space=0x2C, Enter=0x28, ...)
uint8_t button_keycodes[NUM_BUTTONS] = {HID_KEY_SPACE, HID_KEY_ENTER, HID_KEY_1, HID_KEY_2};
// HID modifier byte per button (Ctrl=0x01, Shift=0x02, Alt=0x04, GUI=0x08; OR for combinations)
uint8_t button_modifiers[NUM_BUTTONS] = {0, 0, 0, 0};
// Action type per button: 0=key press, 1=left click, 2=right click, 3=scroll up, 4=scroll down
uint8_t button_actions[NUM_BUTTONS] = {0, 0, 0, 0};
uint8_t buttonStates = 0;

//this firmware supports following keycodes in the settings (printHelp(), parseCommands() -> enums of keys)
//Space, Enter, 1, 2, Tab, F1, F2, F13, F14
#define SELECTABLE_KEYS 9
uint8_t key_codes[SELECTABLE_KEYS] = {HID_KEY_SPACE, HID_KEY_ENTER, HID_KEY_1, HID_KEY_2, HID_KEY_TAB, HID_KEY_F1, HID_KEY_F2, HID_KEY_F13, HID_KEY_F14};

// Multi-key state (up to 6 simultaneous keys + modifiers)
// Multi-key state (up to 6 simultaneous keys)
static uint8_t active_keys[6] = {0};
static uint8_t modifiers = 0; // e.g. KEYBOARD_MODIFIER_LEFT_SHIFT

BLEDis bledis;
BLEHidAdafruit blehid;
Expand All @@ -66,7 +62,7 @@ void printHelp();
//different modes for the output
int mode = 0;
// how many modes are used
#define MODE_MAX 3
#define MODE_MAX 4
//pin for the mode switch button
uint8_t pin_mode = PIN_MODE;
//output mode: 0: click the output on each action; 1: toggle the output on each action
Expand Down Expand Up @@ -288,8 +284,16 @@ void loop()
#ifdef OUTPUT_ACTIVE
if(i == 0) handleOutput(true,false);
#endif
addActiveKey(key_codes[key_map[i]]);
blehid.keyboardReport(modifiers, active_keys);
switch (button_actions[i]) {
case 0: // key press
addActiveKey(button_keycodes[i]);
{ uint8_t mods = 0; for (uint8_t j = 0; j < NUM_BUTTONS; j++) if (buttonStates & (1 << j)) mods |= button_modifiers[j]; blehid.keyboardReport(mods, active_keys); }
break;
case 1: blehid.mouseButtonPress(MOUSE_BUTTON_LEFT); break;
case 2: blehid.mouseButtonPress(MOUSE_BUTTON_RIGHT); break;
case 3: blehid.mouseScroll( 1); break;
case 4: blehid.mouseScroll(-1); break;
}
// if (ENABLE_ACTIVITY_LED ) dToggle(LED_R);
if (ENABLE_DEBUG_OUTPUT) Serial.println("Button pressed");
} else if ( !pressed && (buttonStates & (1 << i)) ) {
Expand All @@ -299,8 +303,16 @@ void loop()
#ifdef OUTPUT_ACTIVE
if(i == 0) handleOutput(false,true);
#endif
removeActiveKey(key_codes[key_map[i]]);
blehid.keyboardReport(modifiers, active_keys);
switch (button_actions[i]) {
case 0: // key release
removeActiveKey(button_keycodes[i]);
{ uint8_t mods = 0; for (uint8_t j = 0; j < NUM_BUTTONS; j++) if (buttonStates & (1 << j)) mods |= button_modifiers[j]; blehid.keyboardReport(mods, active_keys); }
break;
case 1:
case 2: blehid.mouseButtonRelease(); break;
case 3:
case 4: break; // scroll: no action on release
}
//if (ENABLE_ACTIVITY_LED ) dToggle(LED_R);
if (ENABLE_DEBUG_OUTPUT) Serial.println("Button released");
}
Expand Down Expand Up @@ -363,11 +375,9 @@ bool storeSettings() {
snprintf(buffer,MAX_PARAM_LEN,"i:%d\n",sleep_timeout_ms);
file.write(buffer);

//save keycodes
//Note: currently only 2 buttons are saved, although it might be possible to
// configure all buttons, but in the UI only 2 are visible (for better overview)
//save button actions and keycodes (1-indexed to match serial command chars '1' and '2')
for(int i = 0; i<2; i++) {
snprintf(buffer,MAX_PARAM_LEN,"%d:%d\n",i,key_map[i]);
snprintf(buffer,MAX_PARAM_LEN,"%d:%d:%d:%d\n", i+1, button_actions[i], button_keycodes[i], button_modifiers[i]);
file.write(buffer);
}

Expand Down Expand Up @@ -439,13 +449,24 @@ void parseCommand(char *buf) {
Serial.print("New: "); Serial.println(sleep_timeout_ms);
break;

//handle button<->key code assignment
//handle button action assignment (format: N:<action>:<keycode>:<modifier>)
// actions: 0=key press, 1=left click, 2=right click, 3=scroll up, 4=scroll down
case '1':
case '2':
newValue = buf[0] - '1'; //get button index
Serial.print("Prev: "); Serial.println(key_map[newValue]);
key_map[newValue] = String(buf+2).toInt();
Serial.print("New: "); Serial.println(key_map[newValue]);
{
uint8_t btnIdx = buf[0] - '1';
char *sep1 = strchr(buf+2, ':');
char *sep2 = sep1 ? strchr(sep1+1, ':') : nullptr;
uint8_t newAction = (uint8_t)String(buf+2).toInt();
uint8_t newKeycode = sep1 ? (uint8_t)String(sep1+1).toInt() : 0;
uint8_t newModifier = sep2 ? (uint8_t)String(sep2+1).toInt() : 0;
if (newAction > 4) newAction = 0;
Serial.print("Prev: "); Serial.print(button_actions[btnIdx]); Serial.print(":"); Serial.print(button_keycodes[btnIdx]); Serial.print(":"); Serial.println(button_modifiers[btnIdx]);
button_actions[btnIdx] = newAction;
button_keycodes[btnIdx] = newKeycode;
button_modifiers[btnIdx] = newModifier;
Serial.print("New: "); Serial.print(button_actions[btnIdx]); Serial.print(":"); Serial.print(button_keycodes[btnIdx]); Serial.print(":"); Serial.println(button_modifiers[btnIdx]);
}
break;

#ifdef OUTPUT_ACTIVE
Expand Down Expand Up @@ -474,7 +495,7 @@ void parseCommand(char *buf) {
case 'm':
Serial.print("Prev: "); Serial.println(mode+1);
newValue = String(buf+2).toInt();
if(newValue >= 1 && newValue <= 3) mode = newValue-1;
if(newValue >= 1 && newValue <= 4) mode = newValue-1;
Serial.print("New: "); Serial.println(mode+1);
break;
case 'c':
Expand Down Expand Up @@ -512,15 +533,15 @@ void printHelp() {
Serial.print("i:<int>:Inactivity time [ms]:30000-600000:"); Serial.println(sleep_timeout_ms);
Serial.print("d:<info>:Connected device::"); Serial.println(central_name);
Serial.println("r:<none>:Reset paired devices");
Serial.print("1:<enum>:Key 1:Space,Enter,1,2,Tab,F1,F2,F13,F14:"); Serial.println(key_map[0]);
Serial.print("2:<enum>:Key 2:Space,Enter,1,2,Tab,F1,F2,F13,F14:"); Serial.println(key_map[1]);
Serial.print("1:<buttonaction>:Button 1:"); Serial.print(button_actions[0]); Serial.print(":"); Serial.print(button_keycodes[0]); Serial.print(":"); Serial.println(button_modifiers[0]);
Serial.print("2:<buttonaction>:Button 2:"); Serial.print(button_actions[1]); Serial.print(":"); Serial.print(button_keycodes[1]); Serial.print(":"); Serial.println(button_modifiers[1]);

#ifdef OUTPUT_ACTIVE
Serial.println("c:<none>:Trigger the output");
Serial.println("o:<enum>:Output mode:click,toggle");
Serial.print("t:<int>:Mode 1 - Tremor Timeout [ms]:300-5000:"); Serial.println(tremor_timeout_ms);
Serial.print("p:<int>:Mode 3 - Auto-Pause Timeout [s]:2-600:"); Serial.println(pause_timeout_s);
Serial.print("m:<int>:Startup Mode:1-3:"); Serial.println(mode+1);
Serial.print("m:<int>:Startup Mode:1-4:"); Serial.println(mode+1);
#endif
Serial.println("?:<none>:Print out supported commands and build date");
//examples for more commands (+types)
Expand Down Expand Up @@ -589,6 +610,10 @@ void handleOutput(bool pressed, bool released) {

break;

//output disabled: ignore all button events
case 3:
break;

default: break;
}
}
Expand Down
Loading