added hardware
This commit is contained in:
BIN
arduino/Krabicka_hexagon.3mf
Normal file
BIN
arduino/Krabicka_hexagon.3mf
Normal file
Binary file not shown.
BIN
arduino/Krabicka_hexagon.FCStd
Normal file
BIN
arduino/Krabicka_hexagon.FCStd
Normal file
Binary file not shown.
BIN
arduino/Krabicka_hexagon.png
Normal file
BIN
arduino/Krabicka_hexagon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
50
arduino/README.md
Normal file
50
arduino/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Nexus Game Controller
|
||||||
|
|
||||||
|
A game controller using a Seeed Studio XIAO nRF52840 Sense board that emulates a Bluetooth keyboard.
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
- [Seeed Studio XIAO nRF52840 Sense](#https://www.seeedstudio.com/Seeed-XIAO-BLE-Sense-nRF52840-p-5253.html)
|
||||||
|
- 9 [push buttons](https://rpishop.cz/komponenty/6128-pimoroni-cerne-arkadove-tlacitko.html) connected to pins D1-D6 and D8-D10.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Bluetooth LE Keyboard:** Connects to any computer or mobile device as a standard keyboard.
|
||||||
|
- **Multi-Event Buttons:** Each of the 9 buttons supports three types of interactions:
|
||||||
|
- Single Click
|
||||||
|
- Double Click
|
||||||
|
- Long Press
|
||||||
|
- **Character Mapping:** Each button event sends a unique character.
|
||||||
|
- **Serial Debugging:** Outputs button events and Bluetooth connection status to the serial monitor.
|
||||||
|
|
||||||
|
## Button Mappings
|
||||||
|
|
||||||
|
| Button | Single Click | Double Click | Long Press |
|
||||||
|
|--------|--------------|--------------|------------|
|
||||||
|
| D1 | `a` | `b` | `c` |
|
||||||
|
| D2 | `d` | `e` | `f` |
|
||||||
|
| D3 | `g` | `h` | `i` |
|
||||||
|
| D4 | `j` | `k` | `l` |
|
||||||
|
| D5 | `m` | `n` | `o` |
|
||||||
|
| D6 | `p` | `q` | `r` |
|
||||||
|
| D8 | `s` | `t` | `u` |
|
||||||
|
| D9 | `v` | `w` | `x` |
|
||||||
|
| D10 | `y` | `z` | `1` |
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
1. **Setup Arduino IDE:**
|
||||||
|
- Install the "Seeed nRF52 Boards" package.
|
||||||
|
- Select "Seeed XIAO BLE Sense - nRF52840" as the board.
|
||||||
|
- Install the "Adafruit Bluefruit nRF52" library.
|
||||||
|
2. **Upload:** Compile and upload the [sketch](/arduino/sketch.ino) to your XIAO board.
|
||||||
|
3. **Connect:** Scan for Bluetooth devices on your computer or mobile device and connect to "Nexus Game Controller".
|
||||||
|
4. **Play:** Press the buttons to send keystrokes. Open the Serial Monitor at 115200 baud to see debug information.
|
||||||
|
|
||||||
|
## 3D Print
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can import the [3mf file](/arduino/Krabicka_hexagon.3mf) to your slicer for printing.
|
||||||
|
|
||||||
|
## Customize your design
|
||||||
|
|
||||||
|
Open the [3D model](/arduino/Krabicka_hexagon.FCStd) in FreeCAD to model your changes.
|
||||||
178
arduino/sketch.ino
Normal file
178
arduino/sketch.ino
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#include <bluefruit.h>
|
||||||
|
#include <bluefruit.h>
|
||||||
|
|
||||||
|
// Pin definitions for the 9 buttons
|
||||||
|
const int buttonPins[] = {1, 2, 3, 4, 5, 6, 8, 9, 10};
|
||||||
|
const int numButtons = sizeof(buttonPins) / sizeof(buttonPins[0]);
|
||||||
|
|
||||||
|
// Button state tracking
|
||||||
|
struct Button {
|
||||||
|
int pin;
|
||||||
|
bool lastState;
|
||||||
|
unsigned long lastDebounceTime;
|
||||||
|
unsigned long pressTime;
|
||||||
|
int clickCount;
|
||||||
|
bool isPressed;
|
||||||
|
bool longPressHandled;
|
||||||
|
};
|
||||||
|
|
||||||
|
Button buttons[numButtons];
|
||||||
|
|
||||||
|
// Debounce and timing constants
|
||||||
|
const unsigned long debounceDelay = 50;
|
||||||
|
const unsigned long doubleClickDelay = 400;
|
||||||
|
const unsigned long longPressDelay = 1000;
|
||||||
|
|
||||||
|
// BLE Keyboard object
|
||||||
|
BLEDis bledis;
|
||||||
|
BLEHidAdafruit blehid;
|
||||||
|
|
||||||
|
// Character mapping for each event
|
||||||
|
// 9 buttons * 3 events/button = 27 characters
|
||||||
|
const char eventChars[numButtons][3] = {
|
||||||
|
{'a', 'b', 'c'}, // Button D1: single, double, long
|
||||||
|
{'d', 'e', 'f'}, // Button D2: single, double, long
|
||||||
|
{'g', 'h', 'i'}, // Button D3: single, double, long
|
||||||
|
{'j', 'k', 'l'}, // Button D4: single, double, long
|
||||||
|
{'m', 'n', 'o'}, // Button D5: single, double, long
|
||||||
|
{'p', 'q', 'r'}, // Button D6: single, double, long
|
||||||
|
{'s', 't', 'u'}, // Button D8: single, double, long
|
||||||
|
{'v', 'w', 'x'}, // Button D9: single, double, long
|
||||||
|
{'y', 'z', '1'} // Button D10: single, double, long
|
||||||
|
};
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
//while ( !Serial ) delay(10); // Wait for serial port to connect.
|
||||||
|
|
||||||
|
Serial.println("Nexus Game Controller Starting...");
|
||||||
|
|
||||||
|
// Initialize buttons
|
||||||
|
for (int i = 0; i < numButtons; i++) {
|
||||||
|
buttons[i].pin = buttonPins[i];
|
||||||
|
pinMode(buttons[i].pin, INPUT_PULLUP);
|
||||||
|
buttons[i].lastState = HIGH;
|
||||||
|
buttons[i].lastDebounceTime = 0;
|
||||||
|
buttons[i].pressTime = 0;
|
||||||
|
buttons[i].clickCount = 0;
|
||||||
|
buttons[i].isPressed = false;
|
||||||
|
buttons[i].longPressHandled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup Bluetooth
|
||||||
|
Bluefruit.begin();
|
||||||
|
Bluefruit.setName("Nexus Game Controller");
|
||||||
|
Bluefruit.setTxPower(4);
|
||||||
|
|
||||||
|
// Configure and start BLE services
|
||||||
|
bledis.setManufacturer("Seeed Studio");
|
||||||
|
bledis.setModel("XIAO nRF52840 Sense");
|
||||||
|
bledis.begin();
|
||||||
|
|
||||||
|
blehid.begin();
|
||||||
|
|
||||||
|
// Start advertising
|
||||||
|
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||||
|
Bluefruit.Advertising.addTxPower();
|
||||||
|
Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD);
|
||||||
|
Bluefruit.Advertising.addService(blehid);
|
||||||
|
Bluefruit.Advertising.addName();
|
||||||
|
|
||||||
|
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||||
|
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||||
|
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast advertising mode
|
||||||
|
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||||
|
|
||||||
|
Serial.println("Advertising...");
|
||||||
|
|
||||||
|
// Set up connection/disconnection callbacks
|
||||||
|
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||||
|
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect_callback(uint16_t conn_handle) {
|
||||||
|
(void) conn_handle;
|
||||||
|
Serial.println("Bluetooth connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||||
|
(void) conn_handle;
|
||||||
|
(void) reason;
|
||||||
|
Serial.println("Bluetooth disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
for (int i = 0; i < numButtons; i++) {
|
||||||
|
handleButton(&buttons[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleButton(Button* b, int buttonIndex) {
|
||||||
|
bool reading = digitalRead(b->pin);
|
||||||
|
|
||||||
|
// Debounce logic
|
||||||
|
if (reading != b->lastState) {
|
||||||
|
b->lastDebounceTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((millis() - b->lastDebounceTime) > debounceDelay) {
|
||||||
|
// If the button state has been stable
|
||||||
|
if (reading == LOW && !b->isPressed) { // Button just pressed
|
||||||
|
b->isPressed = true;
|
||||||
|
b->pressTime = millis();
|
||||||
|
b->clickCount++;
|
||||||
|
b->longPressHandled = false;
|
||||||
|
} else if (reading == HIGH && b->isPressed) { // Button just released
|
||||||
|
b->isPressed = false;
|
||||||
|
if (!b->longPressHandled) {
|
||||||
|
// It's a click, but we need to wait for a potential double click
|
||||||
|
} else {
|
||||||
|
// This was a long press, so we do nothing on release
|
||||||
|
b->longPressHandled = false; // Reset for next time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for long press
|
||||||
|
if (b->isPressed && !b->longPressHandled && (millis() - b->pressTime > longPressDelay)) {
|
||||||
|
triggerEvent(buttonIndex, 2); // Long Press
|
||||||
|
b->longPressHandled = true;
|
||||||
|
b->clickCount = 0; // Reset click count after a long press
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for single/double click timeout
|
||||||
|
if (b->clickCount > 0 && !b->isPressed && (millis() - b->pressTime > doubleClickDelay)) {
|
||||||
|
if (!b->longPressHandled) {
|
||||||
|
if (b->clickCount == 1) {
|
||||||
|
triggerEvent(buttonIndex, 0); // Single Click
|
||||||
|
} else if (b->clickCount == 2) {
|
||||||
|
triggerEvent(buttonIndex, 1); // Double Click
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b->clickCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
b->lastState = reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
void triggerEvent(int buttonIndex, int eventType) {
|
||||||
|
// eventType: 0 = single, 1 = double, 2 = long
|
||||||
|
char key = eventChars[buttonIndex][eventType];
|
||||||
|
|
||||||
|
const char* eventStr[] = {"single click", "double click", "long press"};
|
||||||
|
|
||||||
|
Serial.print("Button D");
|
||||||
|
Serial.print(buttonPins[buttonIndex]);
|
||||||
|
Serial.print(" triggered ");
|
||||||
|
Serial.print(eventStr[eventType]);
|
||||||
|
Serial.print(" event. Character '");
|
||||||
|
Serial.print(key);
|
||||||
|
Serial.println("' has been sent.");
|
||||||
|
|
||||||
|
if (Bluefruit.connected()) {
|
||||||
|
blehid.keyPress(key);
|
||||||
|
delay(10); // a small delay to prevent flooding
|
||||||
|
blehid.keyRelease();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
1. [HID Smart Buttons](#hid-smart-buttons)
|
1. [HID Smart Buttons](#hid-smart-buttons)
|
||||||
2. [MQTT Remote Control](#mqtt-remote-control)
|
2. [MQTT Remote Control](#mqtt-remote-control)
|
||||||
- [Mosquitto Installation Guide](#mosquitto-installation-guide)
|
- [Mosquitto Installation Guide](#mosquitto-installation-guide)
|
||||||
- [Mosquitto MQTT Broker on Android 16's Native Linux VM](#mosquitto-mqtt-broker-on-android-16s-native-linux-vm)
|
- [Mosquitto MQTT Broker using `Termux` app](#mosquitto-mqtt-broker-using-termux-app)
|
||||||
- [Mosquitto MQTT Broker on older versions of Android then 16](#mosquitto-mqtt-broker-on-older-versions-of-android-then-16)
|
- [Mosquitto MQTT Broker with VPN on Android 16's Native Linux VM](#mosquitto-mqtt-broker-with-vpn-on-android-16s-native-linux-vm)
|
||||||
- [Android Shortcut Setup](#android-shortcut-setup)
|
- [Android Shortcut Setup](#android-shortcut-setup)
|
||||||
- [Configure `Quick Tap` gesture to trigger the shortcut](#configure-quick-tap-gesture-to-trigger-the-shortcut)
|
- [Configure `Quick Tap` gesture to trigger the shortcut](#configure-quick-tap-gesture-to-trigger-the-shortcut)
|
||||||
- [Testing with `mosquitto_pub` (via Termux)](#testing-with-mosquitto_pub-via-termux)
|
- [Testing with `mosquitto_pub` (via Termux)](#testing-with-mosquitto_pub-via-termux)
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
## HID Smart Buttons
|
## HID Smart Buttons
|
||||||
For an enhanced tactile experience, Nexus Timer supports Smart Buttons based on Bluetooth-connected microcontroller (e.g., XIAO nRF52840) implementing HID (Human Interface Device) protocol emulating a keyboard.
|
For an enhanced tactile experience, Nexus Timer supports Smart Buttons based on Bluetooth-connected microcontroller (e.g., XIAO nRF52840) implementing HID (Human Interface Device) protocol emulating a keyboard.
|
||||||
|
|
||||||
* **Buttons:** Connect 3 physical buttons, potentially extended (e.g., via 1.5m wires) for easy player access.
|
* **Buttons:** Connect 3 physical buttons, potentially extended e.g., via 1.5m wires for easy player access.
|
||||||
* **Configuration:**
|
* **Configuration:**
|
||||||
* **Player 1's Button:** Single Click: Emulates a key press (e.g., 'a'). Configure this as Player 1's "Pass Turn / My Pause" hotkey in the app.
|
* **Player 1's Button:** Single Click: Emulates a key press (e.g., 'a'). Configure this as Player 1's "Pass Turn / My Pause" hotkey in the app.
|
||||||
* **Player 2's Button:** Single Click: Emulates a key press (e.g., 'b'). Configure as Player 2's "Pass Turn / My Pause" hotkey.
|
* **Player 2's Button:** Single Click: Emulates a key press (e.g., 'b'). Configure as Player 2's "Pass Turn / My Pause" hotkey.
|
||||||
@@ -24,14 +24,49 @@ For an enhanced tactile experience, Nexus Timer supports Smart Buttons based on
|
|||||||
* **Player 3's Button:** Long Press: Emulates a key press (e.g., 's'). Configure as the "Global Stop/Pause All" hotkey in the app.
|
* **Player 3's Button:** Long Press: Emulates a key press (e.g., 's'). Configure as the "Global Stop/Pause All" hotkey in the app.
|
||||||
* **Player 3's Button:** Double Click: Emulates a key press (e.g., 'x'). Configure as the "Global Run All Timers" hotkey in the app.
|
* **Player 3's Button:** Double Click: Emulates a key press (e.g., 'x'). Configure as the "Global Run All Timers" hotkey in the app.
|
||||||
|
|
||||||
|
The code for the XIAO nRF52840 module with a 3D printing files can be found in the [arduino](/arduino) subdirectory.
|
||||||
|
|
||||||
## MQTT Remote Control
|
## MQTT Remote Control
|
||||||
Players can use their smartphones to send commands to Nexus Timer via MQTT. This requires an MQTT broker (like Mosquitto) on the same network.
|
Alternatively to a dedicated smart buttons, players can use their smartphones to send commands to Nexus Timer via MQTT. This requires an MQTT broker (like Mosquitto) on the same network.
|
||||||
|
|
||||||
### Mosquitto Installation Guide
|
### Mosquitto Installation Guide
|
||||||
|
|
||||||
#### Mosquitto MQTT Broker on Android 16's Native Linux VM
|
#### Mosquitto MQTT Broker using `Termux` app
|
||||||
|
|
||||||
This guide details how to install and configure the Mosquitto MQTT broker within the native `Linux Virtual Machine environment` introduced in Android 16. For older versions proceed to [Mosquitto MQTT Broker on older versions of Android then 16](#mosquitto-mqtt-broker-on-older-versions-of-android-then-16).
|
1. **Install Termux** from the [Play Store](https://play.google.com/store/apps/details?id=com.termux) and run.
|
||||||
|
2. **Update packages and install Mosquitto in Termux:**
|
||||||
|
```bash
|
||||||
|
pkg update && pkg upgrade
|
||||||
|
pkg install mosquitto
|
||||||
|
```
|
||||||
|
3. **Configure the MQTT Broker:**
|
||||||
|
```bash
|
||||||
|
nano $PREFIX/etc/mosquitto/mosquitto.conf
|
||||||
|
```
|
||||||
|
Add the following configuration, then save and exit:
|
||||||
|
```ini
|
||||||
|
# MQTT listener on port 1883
|
||||||
|
# MQTT connection from the HTTP Shortcut app
|
||||||
|
listener 1883 0.0.0.0
|
||||||
|
protocol mqtt
|
||||||
|
|
||||||
|
# WebSocket listener on port 9001
|
||||||
|
# MQTT over WebSocket connection from the PWA (Web App)
|
||||||
|
listener 9001 0.0.0.0
|
||||||
|
protocol websockets
|
||||||
|
|
||||||
|
# Allow clients to connect without username/password
|
||||||
|
allow_anonymous true
|
||||||
|
```
|
||||||
|
4. **Run Mosquitto with the configuration:**
|
||||||
|
```bash
|
||||||
|
mosquitto -c $PREFIX/etc/mosquitto/mosquitto.conf
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Mosquitto MQTT Broker with VPN on Android 16's Native Linux VM
|
||||||
|
|
||||||
|
This guide details how to install and configure the Mosquitto MQTT broker within the native `Linux Virtual Machine environment` introduced in Android 16. Note that ports exposed by the Mosquitto cannot be reached from LAN. The workarround is using a VPN (wireguard).
|
||||||
|
|
||||||
1. **Enable the Linux Development Environment**
|
1. **Enable the Linux Development Environment**
|
||||||
|
|
||||||
@@ -113,39 +148,6 @@ Your Mosquitto MQTT broker is now successfully configured and running on your An
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Mosquitto MQTT Broker on older versions of Android then 16
|
|
||||||
|
|
||||||
1. **Install Termux** from the [Play Store](https://play.google.com/store/apps/details?id=com.termux) and run.
|
|
||||||
2. **Update packages and install Mosquitto in Termux:**
|
|
||||||
```bash
|
|
||||||
pkg update && pkg upgrade
|
|
||||||
pkg install mosquitto
|
|
||||||
```
|
|
||||||
3. **Configure the MQTT Broker:**
|
|
||||||
```bash
|
|
||||||
nano $PREFIX/etc/mosquitto/mosquitto.conf
|
|
||||||
```
|
|
||||||
Add the following configuration, then save and exit:
|
|
||||||
```ini
|
|
||||||
# MQTT listener on port 1883
|
|
||||||
# MQTT connection from the HTTP Shortcut app
|
|
||||||
listener 1883 0.0.0.0
|
|
||||||
protocol mqtt
|
|
||||||
|
|
||||||
# WebSocket listener on port 9001
|
|
||||||
# MQTT over WebSocket connection from the PWA (Web App)
|
|
||||||
listener 9001 0.0.0.0
|
|
||||||
protocol websockets
|
|
||||||
|
|
||||||
# Allow clients to connect without username/password
|
|
||||||
allow_anonymous true
|
|
||||||
```
|
|
||||||
4. **Run Mosquitto with the configuration:**
|
|
||||||
```bash
|
|
||||||
mosquitto -c $PREFIX/etc/mosquitto/mosquitto.conf
|
|
||||||
```
|
|
||||||
---
|
|
||||||
|
|
||||||
### Android Shortcut Setup
|
### Android Shortcut Setup
|
||||||
* Install the `HTTP Shortcuts` app from the [Play Store](https://play.google.com/store/apps/details?id=ch.rmy.android.http_shortcuts).
|
* Install the `HTTP Shortcuts` app from the [Play Store](https://play.google.com/store/apps/details?id=ch.rmy.android.http_shortcuts).
|
||||||
* Create a new shortcut.
|
* Create a new shortcut.
|
||||||
|
|||||||
Reference in New Issue
Block a user