The goal of this project is to use a Bluetooth GAN smart Rubik's cube as a game controller.
The idea is:
- Track cube movements in the browser using Andy Fedotov's GAN Web Bluetooth demo.
- Forward each move over a WebSocket to a local Node script.
- Map each cube move to a keyboard key in
index.js, so the cube "presses" keys.
Most of the heavy lifting is done by Andy Fedotov's package that connects GAN cubes to a web app and exposes move events:
https://github.com/afedotov/gan-web-bluetooth
GAN demo site used here:
https://afedotov.github.io/gan-cube-sample/
Video of cube being used to play tetris:
https://youtube.com/shorts/Aap78g6D09s?feature=share
- Node.js installed
- A supported GAN smart cube (for example GAN356 i Carry)
- A browser that supports Web Bluetooth (Chrome recommended)
- macOS if you use the provided AppleScript based
index.js(other platforms will need a different keypress implementation)
Create a new directory and initialize it:
mkdir cube-controller
cd cube-controller
npm init -y
npm install wsDownload the provided index.js, which listens for cube moves from WebSocket and turns them into keypresses. You can edit the file to change which key each side maps to. The current index.js file is designed for Tetris.
node index.jsYou should see:
WebSocket server listening on ws://localhost:8081
Open the GAN cube sample site in Chrome: https://afedotov.github.io/gan-cube-sample/
Connect your cube using the UI on that page. You may need to enter the cube Bluetooth MAC address in the site, depending on your model and browser.
Open Chrome DevTools and go to the Console tab.
With the cube connected and DevTools open on the demo site, paste this snippet into the console:
(() => {
const ws = new WebSocket("ws://localhost:8081");
ws.onopen = () => console.log("[WS] open");
ws.onerror = (e) => console.log("[WS] error", e);
ws.onclose = () => console.log("[WS] close");
const origLog = console.log;
console.log = function (...args) {
// Look through every argument that gets logged
for (const a of args) {
if (
a &&
typeof a === "object" &&
a.type === "MOVE" &&
typeof a.move === "string"
) {
// caught a cube MOVE object like {type:"MOVE", move:"R'"}
if (ws.readyState === WebSocket.OPEN) {
origLog("[HOOK] sending move to Node:", a.move);
ws.send(a.move);
} else {
origLog("[HOOK] WS not open yet, move:", a.move);
}
}
}
origLog.apply(console, args);
};
})();When you twist the cube, you should see in the browser console:
[HOOK] sending move to Node: R'
And in the terminal where node.js is running:
[ws] Browser connected
[ws] got message: "R'"
[pressKey] Running: tell application "System Events" to key code 56
The move to key mapping is fully customizable in index.js.
This implementation uses AppleScript and System Events on macOS to generate keystrokes. It can type into any focused text field and some games, but not all games will treat these as real movement keys.
You will need to allow your terminal application to control the computer in macOS Privacy and Security settings so System Events can send keystrokes.