Skip to content

Instantly share code, notes, and snippets.

@niutech
Last active January 2, 2025 14:25
Show Gist options
  • Save niutech/10dd68b5e8fb8642f7f96c9be802adb7 to your computer and use it in GitHub Desktop.
Save niutech/10dd68b5e8fb8642f7f96c9be802adb7 to your computer and use it in GitHub Desktop.
Lego Duplo Train remote controller using BLE with Espruino and Waveshare Joystick for Micro:bit v2
/*
Lego Duplo Train BLE remote controller using BLE with Espruino and Waveshare Joystick for Micro:bit v2
(c) 2024 Jerzy Głowacki
MIT License
*/
const SCREEN = {
READY: " \n" +
" \n" +
"11111\n" +
" \n" +
" \n",
UP_HALF: " \n" +
" 1 \n" +
"11111\n" +
" \n" +
" \n",
UP_FULL: " 1 \n" +
" 111 \n" +
"11111\n" +
" \n" +
" \n",
DOWN_HALF: " \n" +
" \n" +
"11111\n" +
" 1 \n" +
" \n",
DOWN_FULL: " \n" +
" \n" +
"11111\n" +
" 111 \n" +
" 1 \n"
};
let JoyStick_P = D8;
let JoyStick_X = D1;
let JoyStick_Y = D2;
let KEY_A = D5;
let KEY_B = D11;
let KEY_C = D15;
let KEY_D = D14;
let KEY_E = D13;
let KEY_F = D12;
pinMode(JoyStick_P, 'input_pullup');
pinMode(KEY_A, 'input_pullup');
pinMode(KEY_B, 'input_pullup');
pinMode(KEY_C, 'input_pullup');
pinMode(KEY_D, 'input_pullup');
pinMode(KEY_E, 'input_pullup');
pinMode(KEY_F, 'input_pullup');
function onKeyPressed(key, cb) {
let pin;
if (key == 'P') {
pin = JoyStick_P;
} else if (key == 'A') {
pin = KEY_A;
} else if (key == 'B') {
pin = KEY_B;
} else if (key == 'C') {
pin = KEY_C;
} else if (key == 'D') {
pin = KEY_D;
} else if (key == 'E') {
pin = KEY_E;
} else if (key == 'F') {
pin = KEY_F;
}
setWatch(cb, pin, {
repeat: true,
edge: 'falling',
debounce: 100
});
}
function debounce(func, timeout = 500) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(function() { func.apply(this, args); }, timeout);
};
}
/////////////////////////////////////////////////////////
const devName = "84:72:93:4f:cb:98";
const serviceUUID = "00001623-1212-efde-1623-785feabcd123";
const characteristicUUID = "00001624-1212-efde-1623-785feabcd123";
let gatt;
let char;
let connected = false;
let sending = false;
let lighting = false;
function connectTrain() {
if (connected) return;
console.log("Starting connection...");
return NRF.connect(devName).then(function(g) {
console.log("Connecting to " + devName);
gatt = g;
return gatt.getPrimaryService(serviceUUID);
}).then(function(service) {
return service.getCharacteristic(characteristicUUID);
}).then(function(characteristic) {
char = characteristic;
return characteristic.readValue();
}).then(function() {
console.log("Train connected");
connected = true;
});
}
function disconnectTrain() {
if (!connected) return;
return gatt.disconnect().then(function() {
console.log("Train disconnected");
connected = false;
});
}
function sendToTrain(value) {
if (sending) return;
sending = true;
return char.writeValue(new Uint8Array([0x0a, 0x00, 0x41, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01])).then(function() {
return char.writeValue(value);
}).then(function() {
sending = false;
}).catch(function() {
sending = false;
});
}
function play(sound) {
let value;
if (sound == 'steam') {
value = 0x0a;
} else if (sound == 'horn') {
value = 0x09;
} else if (sound == 'water') {
value = 0x07;
} else if (sound == 'departure') {
value = 0x05;
} else if (sound == 'brake') {
value = 0x03;
}
return sendToTrain(new Uint8Array([0x08, 0x00, 0x81, 0x01, 0x11, 0x51, 0x01, value]));
}
function light(isOn) {
let value = isOn ? 0x0a : 0x00;
lighting = isOn;
return sendToTrain(new Uint8Array([0x08, 0x00, 0x81, 0x11, 0x11, 0x51, 0x00, value]));
}
function moveTrain(speed) {
// speed range: <-0.5, 0.5>
let value, screen;
if (speed < -0.4) {
value = 0x9c;
screen = SCREEN.DOWN_FULL;
} else if (speed < -0.3) {
value = 0xce;
screen = SCREEN.DOWN_HALF;
} else if (speed < -0.1) {
value = 0xe2;
screen = SCREEN.DOWN_HALF;
} else if (speed < 0.1) {
value = 0x00;
screen = SCREEN.READY;
} else if (speed < 0.3) {
value = 0x1e;
screen = SCREEN.UP_HALF;
} else if (speed < 0.4) {
value = 0x32;
screen = SCREEN.UP_HALF;
} else {
value = 0x64;
screen = SCREEN.UP_FULL;
}
show(screen);
return sendToTrain(new Uint8Array([0x08, 0x00, 0x81, 0x00, 0x01, 0x51, 0x00, value]));
}
function ready() {
connectTrain().then(function() {
show(SCREEN.READY);
onKeyPressed('P', function() {
console.log('Pressed P');
play('departure');
});
onKeyPressed('A', function() {
console.log('Pressed A');
play('break');
});
onKeyPressed('B', function() {
console.log('Pressed B');
play('departure');
});
onKeyPressed('C', function() {
console.log('Pressed C');
light(!lighting);
});
onKeyPressed('D', function() {
console.log('Pressed D');
play('water');
});
onKeyPressed('E', function() {
console.log('Pressed E');
play('horn');
});
onKeyPressed('F', function() {
console.log('Pressed F');
play('steam');
});
setInterval(function() {
debounce(moveTrain(analogRead(JoyStick_Y) - 0.5));
}, 100);
}).catch(function(e) {
console.error(e);
console.log('Retrying...');
ready();
});
}
ready();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment