-
• #2
Hi, Puck.js doesn't support bluetooth mesh, but it's easy enough to do one to many using bluetooth advertising, or even just to have one Puck as a 'master' and have that connect to the one it wants to light up, wait until the button is pressed and then connect to the next and make that light
I've just dug out some code I'd used here for something similar. Basically what happens is:
- Each Puck (when 'awake') scans for advertising packets with Espruino's 'manufacturer id' (0x0590) that contain their 'id' (last 2 blocks of the MAC address)
- When they see that they light up
- When they're pressed the light turns off and they advertise the fact, and how long it took before they were pressed
Then you have one 'master' device (I used an MDBT42 breakout attached to a 32x8 LED matrixhttps://www.espruino.com/MAX7219) that orchestrates the whole thing and displays the score.
Puck.js code:
var ADDR = NRF.getAddress().substr(12).split(":").map(n=>parseInt(n,16)); var ID = (ADDR[0]<<8) | ADDR[1]; var MAXIDLE = 60*15; // 15 minutes var idleTime = 0; var lastAdvertised = 0; var lit = false; var litTime; function showLight() { pinMode(LED,"output"); LED.set(); if (!lit) litTime = getTime(); lit = true; } function hideLight() { LED.reset(); pinMode(LED,"input_pullup"); lit = false; } function startScan() { lastAdvertised = undefined; NRF.setScan(function(d) { var m = d.manufacturerData; if (m[0]||m[1]) return; idleTime = 0; var id = (m[2]<<8)|m[3]; /*if (id!=lastAdvertised)*/ { lastAdvertised = id; if (id==ID) showLight(); else { hideLight(); stopAdvertisingPress(); } } }, { filters: [{ manufacturerData:{0x0590:{}} }] }); } function sleep() { pinMode(LED,"output"); LED.reset(); NRF.setScan(); } function wake() { pinMode(LED, "input_pullup"); startScan(); } function stopAdvertisingPress() { NRF.setAdvertising({},{ name:"BTK", interval:375 }); } setInterval(function() { idleTime++; if (idleTime>MAXIDLE) { idleTime = MAXIDLE; sleep(); } }, 1000); setWatch(function() { if (lit) { hideLight(); var time = Math.floor((getTime()-litTime)*1000); if (time>65535) time=65535; NRF.setAdvertising({},{ name:"BTK", manufacturer:0x0590, manufacturerData:[1,0,time>>8,time&255], whenConnected:true, interval:100 }); setTimeout(stopAdvertisingPress, 1500); } else { idleTime = 0; wake(); } }, BTN, {repeat:true}); NRF.setAdvertising({},{ name:"BTK", interval:375 });
MDBT42 breakout code:
require("Font6x8").add(Graphics); var spi = new SPI(); spi.setup({mosi:D4, sck:D11}); var disp = require("MAX7219").connect(spi, D5, 4 /* 4 chained devices */); disp.intensity(0.3); var g = Graphics.createArrayBuffer(32, 8, 1); g.flip = function() { disp.raw(g.buffer); }; // To send to the display var MAXTIME = 60; var lights = []; var currentLight = undefined; var lastLight = undefined; var score = 0; var timer = 0; var timerInterval; function addrToId(addr) { var d = addr.substr(12,5).split(":").map(n=>parseInt(n,16)); return (d[0]<<8)|d[1]; } function show(txt) { g.clear(1).setFont6x8().setFontAlign(0,-1).drawString(txt,16,0).flip(); } function show2(txta, txtb) { g.clear(1).setFont6x8().setFontAlign(-1,-1).drawString(txta,0,0); g.setFontAlign(1,-1).drawString(txtb,31,0).flip(); } function startDeviceScan() { show("Scan..."); NRF.findDevices(function(devices) { show(`Got ${devices.length}`); print(" "+devices.map(d=>d.id+" => "+addrToId(d.id)).join("\n ")); lights = devices.map( d => addrToId(d.id) ); if (lights.length>2) setTimeout(startGame, 1000); }, {timeout : 5000, filters : [{ namePrefix:"BTK" }] }); } function showNewLight() { var light; do { light = lights[Math.floor(Math.random()*lights.length*0.999)]; } while (light==currentLight || light==lastLight); lastLight = currentLight; currentLight = light; console.log("Light ", light); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,light>>8,light&0xFF], whenConnected:true, interval: 100 }); } function stopLights() { console.log("Stop Lights"); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,0,0], whenConnected:true, interval: 100 }); setTimeout(function() { NRF.setAdvertising({},{showName:true, interval:375}); }, 5000); } setWatch(showNewLight, BTN1, {repeat:true}); function startGame() { show("GO!"); timer = MAXTIME; score = 0; showNewLight(); NRF.setScan(function(d) { var m = d.manufacturerData; console.log(addrToId(d.id), m); if (addrToId(d.id) == currentLight) { var time = (m[2]<<8) | m[3]; console.log("Light pressed", time+" ms"); score++; show2(timer+"s", score); showNewLight(); } }, { filters: [{ manufacturerData:{0x0590:{}} }] }); if (timerInterval) clearInterval(); timerInterval = setInterval(function() { if (timer>0) { timer--; show2(timer+"s", score); if (timer==0) stopLights(); // turn lights off } else { show("="+score+"="); setTimeout(function() { show(""); }, 500); } }, 1000); } startDeviceScan();
- Each Puck (when 'awake') scans for advertising packets with Espruino's 'manufacturer id' (0x0590) that contain their 'id' (last 2 blocks of the MAC address)
-
• #3
Awesome, I’ll try it out, thank you. Could I use my phone or computer instead of the “master” device? That way everything would stay completely wireless.
Also, is it possible to do all this with puck.js lite or do I have to use the v2 version? -
• #4
is it possible to do all this with puck.js lite or do I have to use the v2 version?
Yes, the Lite is fine as all you're using is the button.
You'd need another Espruino device for the master device to have it run as-is, but you could for instance use a Pixl.js if you want a screen (https://www.espruino.com/Pixl.js), Bangle.js on your wrist (https://www.espruino.com/Bangle.js2) or just another Puck, and then arrange to connect your phone to that.
You could use one of the Pucks from the game as the master too - it's just the code would get a tiny bit more complex.
-
• #5
So, I used the code that was given to me by Gordon and tried it out using three pucks. Since I'm still learning to understand the programming language, I don't fully understand the code. But after I uploaded the code to the pucks and then disconnected them, they only started to blink red lightly after I pressed them. I have no idea what that means. I'm not sure if it worked.
Maybe it has to do with the puck "id" (last digits of the MAC address)?
Could anyone help me out? -
• #6
So I'm pretty sure what happens is:
- You have all the Pucks running the code acting as 'slaves' - tapping them once turns them on, and they turn off after some inactivity
- The MDBT42 breakout code runs and controls them and displays the score.
Without the MDBT42 breakout code the Pucks won't do anything, but you'd need the breakout and a display wired up (or maybe a Pixl.js or a Bangle.js).
However if you just want to try it, I've taken the code and removed all mention of the display hardware, so if you write this code onto one of the Puck.js and disconnect, it should be able to control the other two I think:
var MAXTIME = 60; var lights = []; var currentLight = undefined; var lastLight = undefined; var score = 0; var timer = 0; var timerInterval; function addrToId(addr) { var d = addr.substr(12,5).split(":").map(n=>parseInt(n,16)); return (d[0]<<8)|d[1]; } function show(txt) { } function show2(txta, txtb) { } function startDeviceScan() { show("Scan..."); NRF.findDevices(function(devices) { show(`Got ${devices.length}`); print(" "+devices.map(d=>d.id+" => "+addrToId(d.id)).join("\n ")); lights = devices.map( d => addrToId(d.id) ); if (lights.length>2) setTimeout(startGame, 1000); }, {timeout : 5000, filters : [{ namePrefix:"BTK" }] }); } function showNewLight() { var light; do { light = lights[Math.floor(Math.random()*lights.length*0.999)]; } while (light==currentLight || light==lastLight); lastLight = currentLight; currentLight = light; console.log("Light ", light); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,light>>8,light&0xFF], whenConnected:true, interval: 100 }); } function stopLights() { console.log("Stop Lights"); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,0,0], whenConnected:true, interval: 100 }); setTimeout(function() { NRF.setAdvertising({},{showName:true, interval:375}); }, 5000); } setWatch(showNewLight, BTN1, {repeat:true}); function startGame() { show("GO!"); timer = MAXTIME; score = 0; showNewLight(); NRF.setScan(function(d) { var m = d.manufacturerData; console.log(addrToId(d.id), m); if (addrToId(d.id) == currentLight) { var time = (m[2]<<8) | m[3]; console.log("Light pressed", time+" ms"); score++; show2(timer+"s", score); showNewLight(); } }, { filters: [{ manufacturerData:{0x0590:{}} }] }); if (timerInterval) clearInterval(); timerInterval = setInterval(function() { if (timer>0) { timer--; show2(timer+"s", score); if (timer==0) stopLights(); // turn lights off } else { show("="+score+"="); setTimeout(function() { show(""); }, 500); } }, 1000); } startDeviceScan();
- You have all the Pucks running the code acting as 'slaves' - tapping them once turns them on, and they turn off after some inactivity
-
• #7
Hi, thanks for the code, but unfortunately, it still didn't work. I did use pixl.js in the original code to show the score, but even with that, it still didn't work. I don't know why. Maybe I somehow uploaded it incorrectly? Is there perhaps a more accessible way by using Blockly?
-
• #8
Ok, maybe try this code on the Pixl:
require("Font6x8").add(Graphics); var MAXTIME = 60; var lights = []; var currentLight = undefined; var lastLight = undefined; var score = 0; var timer = 0; var timerInterval; function addrToId(addr) { var d = addr.substr(12,5).split(":").map(n=>parseInt(n,16)); return (d[0]<<8)|d[1]; } function show(txt) { g.clear(1).setFont6x8().setFontAlign(0,-1).drawString(txt,16,0).flip(); } function show2(txta, txtb) { g.clear(1).setFont6x8().setFontAlign(-1,-1).drawString(txta,0,0); g.setFontAlign(1,-1).drawString(txtb,31,0).flip(); } function startDeviceScan() { show("Scan..."); NRF.findDevices(function(devices) { show(`Got ${devices.length}`); print(" "+devices.map(d=>d.id+" => "+addrToId(d.id)).join("\n ")); lights = devices.map( d => addrToId(d.id) ); if (lights.length>2) setTimeout(startGame, 1000); }, {timeout : 5000, filters : [{ namePrefix:"BTK" }] }); } function showNewLight() { var light; do { light = lights[Math.floor(Math.random()*lights.length*0.999)]; } while (light==currentLight || light==lastLight); lastLight = currentLight; currentLight = light; console.log("Light ", light); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,light>>8,light&0xFF], whenConnected:true, interval: 100 }); } function stopLights() { console.log("Stop Lights"); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,0,0], whenConnected:true, interval: 100 }); setTimeout(function() { NRF.setAdvertising({},{showName:true, interval:375}); }, 5000); } setWatch(showNewLight, BTN1, {repeat:true}); function startGame() { show("GO!"); timer = MAXTIME; score = 0; showNewLight(); NRF.setScan(function(d) { var m = d.manufacturerData; console.log(addrToId(d.id), m); if (addrToId(d.id) == currentLight) { var time = (m[2]<<8) | m[3]; console.log("Light pressed", time+" ms"); score++; show2(timer+"s", score); showNewLight(); } }, { filters: [{ manufacturerData:{0x0590:{}} }] }); if (timerInterval) clearInterval(); timerInterval = setInterval(function() { if (timer>0) { timer--; show2(timer+"s", score); if (timer==0) stopLights(); // turn lights off } else { show("="+score+"="); setTimeout(function() { show(""); }, 500); } }, 1000); } startDeviceScan();
I tried it and it does seem to scan and display something.
Remember to get it to work with the Pucks they have to have been turned on by pressing them, and you need them and Pixl.js to be disconnected from the Web IDE
-
• #9
Thank you, I'll try it. But I don't use this code for the pucks; it's just for the pixl.js, right?
-
• #10
Yes - use the code I just posted for the Pixl, then the code in https://forum.espruino.com/comments/17472842/ for the Pucks
-
• #11
So I used the code for the pixl.js and it did display something. However I still have the same problem with the code for the pucks. They just blink red. Is there a different way?
-
• #12
Ok, I've just set the whole thing up here and it works for me.
The issue is that for it to work you need at least 3 Pucks, because it doesn't want to turn on a light that it just turned on time before last. I've just changed the logic so it should work:
// Pixl.js code to work with 2 lights only require("Font6x8").add(Graphics); var MAXTIME = 60; var lights = []; var currentLight = undefined; var lastLight = undefined; var score = 0; var timer = 0; var timerInterval; function addrToId(addr) { var d = addr.substr(12,5).split(":").map(n=>parseInt(n,16)); return (d[0]<<8)|d[1]; } function show(txt) { g.clear(1).setFont6x8().setFontAlign(0,-1).drawString(txt,16,0).flip(); } function show2(txta, txtb) { g.clear(1).setFont6x8().setFontAlign(-1,-1).drawString(txta,0,0); g.setFontAlign(1,-1).drawString(txtb,31,0).flip(); } function startDeviceScan() { show("Scan..."); NRF.findDevices(function(devices) { show(`Got ${devices.length}`); print(" "+devices.map(d=>d.id+" => "+addrToId(d.id)).join("\n ")); lights = devices.map( d => addrToId(d.id) ); if (lights.length>1) setTimeout(startGame, 1000); }, {timeout : 5000, filters : [{ namePrefix:"BTK" }] }); } function showNewLight() { var light; do { light = lights[Math.floor(Math.random()*lights.length*0.999)]; } while (light==currentLight); lastLight = currentLight; currentLight = light; console.log("Light ", light); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,light>>8,light&0xFF], whenConnected:true, interval: 100 }); } function stopLights() { console.log("Stop Lights"); NRF.setAdvertising({},{ showName:false, manufacturer:0x0590, manufacturerData:[0,0,0,0], whenConnected:true, interval: 100 }); setTimeout(function() { NRF.setAdvertising({},{showName:true, interval:375}); }, 5000); } setWatch(showNewLight, BTN1, {repeat:true}); function startGame() { show("GO!"); timer = MAXTIME; score = 0; showNewLight(); NRF.setScan(function(d) { var m = d.manufacturerData; console.log(addrToId(d.id), m); if (addrToId(d.id) == currentLight) { var time = (m[2]<<8) | m[3]; console.log("Light pressed", time+" ms"); score++; show2(timer+"s", score); showNewLight(); } }, { filters: [{ manufacturerData:{0x0590:{}} }] }); if (timerInterval) clearInterval(); timerInterval = setInterval(function() { if (timer>0) { timer--; show2(timer+"s", score); if (timer==0) stopLights(); // turn lights off } else { show("="+score+"="); setTimeout(function() { show(""); }, 500); } }, 1000); } startDeviceScan();
So... Write the code to the Pucks (you can write just to RAM) and press each one once to make the red LED 'glow' (it shouldn't be on full brightness).
Now write the code to the Pixl - it can make it more reliable if you write to flash, and then you can restart it just by unplug/replugging power.
It should say
Scan..
followed byGot 2
, and finally when a button is pressed123s 456
where 123s is the time takes to press the last button and 456 is the number of buttons pressed I think.There's some debug messages printed which do go on the Pixl's display, but you can just delete the print/console.log lines from the code above if it bothers you.
Again, this was written for connecting to a big LED display so the text is small and in the top left, but it's easy to change the
show
functions to make the text bigger -
• #13
Awesome, it sort of works! I have to press the first button on the pixl.js for another puck to light up and it somehow doesn't keep score. I'll figure it out though.
Thanks a lot for your help and patience, Gordon. -
• #14
Just a quick question. So far, I've always used RAM to upload the code to pucks.js and Flash for pixl.js. However, every time I use the training systems, I have to upload the code to each puck again. Can I use Flash instead, or would that drain the battery too much?
-
• #15
Can I use Flash instead, or would that drain the battery too much?
Yes, absolutely! It shouldn't make any noticeable difference to power usage - but as you say when you insert batteries it'll mean that the code loads straight away without the need for the IDE
I'm currently working on a project that requires several pucks.js. I want to create a training system using ten pucks. The idea is that I would program different exercises. For example, the one puck that lights up green has to be pressed, and shortly after, it turns off again, and another puck lights up. That means every puck has to give and receive information from the other pucks. I was thinking about using Bluetooth mesh for this. Would that be possible or is there a better method?
Could anyone help me?