Last active
January 2, 2025 14:25
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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