• 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();
    
About

Avatar for Gordon @Gordon started