Buy Now

Puck.js is an easy to use, programmable Bluetooth sensor and button

You can customise Puck.js with JavaScript or a graphical programming language (Blockly). All you need is a Web Browser (Chrome, Edge or Opera) and you can upload code wirelessly! Puck.js comes with bluetooth low energy, accelerometer, gyro, 3 axis compass, temperature sensor, IR, NFC and more!

Just got your Puck.js? Click here to get started!



  • Bluetooth Low Energy
  • Espruino JavaScript interpreter pre-installed
  • nRF52832 SoC - 64MHz ARM Cortex M4, 64kB RAM, 512kB Flash
  • ABS plastic rear case with lanyard mount
  • Silicone cover with tactile button
  • IR Transmitter
  • Built in thermometer, light and battery level sensors
  • Red, Green and Blue LEDs
  • NFC tag programmable from JavaScript
  • Pin capable of capacitive sensing
  • Weight: 14g in plastic case, 20g in packaging
  • Dimensions of cardboard box: 64mm x 62mm x 16mm
  • Dimensions of plastic case: 36mm dia, 12.5mm thick
  • Dimensions of bare PCB: 29mm dia, 9mm thick
Features Puck.js v1 Puck.js v2 Puck.js v2.1
0.1" GPIO 8 7 7
SMD GPIO 9 2 2
Magnetometer MAG3110 LIS3MDLTR MMC5603NJ
Accelerometer No LSM6DS3TR-C LSM6DS3TR-C
Temperature Sensor nRF52 (uncalibrated) PCT2075TP (calibrated) PCT2075TP (calibrated)
MOSFET Output No 1x 1x


You can also buy kits of different color cases for Puck.js from the Espruino Shop.

Black cases Color case kit

Turning Puck.js on

Puck.js is either supplied with a separate battery, or assembled with the battery inside and a clear plastic tab between the battery and PCB to keep it turned off. To turn it on, you need to:

  • Pull the silicone case off the top,
  • Tip the PCB out
  • If a battery is installed, push it out from the back with a blunt object and make sure the clear plastic tab is removed
  • Push the battery back in, with the + side of the battery (with the writing on) facing away from the Puck.js PCB:

Puck.js PCB and battery rear view Puck.js PCB and battery front view

  • As the battery is inserted the red LED should flash once, very briefly.
  • If the green LED lights or red stays lit, it is because you have accidentally pressed the button down while inserting the battery. Remove the battery and try again, making sure the button next to the battery connector isn't pressed.
  • Reassemble (note that the battery should be facing the black plastic case, and the button (next to the battery) should be positioned as far away from the 'step' in the case as possible:

Positioning in case

Note: Do not re-fit the PCB upside-down or force it into the case. If positioned correctly it should slide in. Forcing the PCB or fitting it upside-down could damage the aerial which will stop Puck.js's Bluetooth from working correctly.

Resetting Puck.js

Occasionally you may want to reset Puck.js. To do this:

  • Take the PCB out of the silicone case
  • Place your finger nail between the Battery and the PCB, near the Puck.js 1.x text
  • Gently pull the battery away from the PCB for 1 second and release
  • The red LED should then flash, indicating that Puck.js had rebooted.

If you are not able to do that, you can always remove the battery by pushing it out of the holder from behind with matchstick or biro. When you re-insert it, Puck.js will have reset.

Hard Reset

Perform the steps for a Reset (see above) but keep the button held for around 10 seconds.

The green LED should light, followed by all 3 LEDs, then the red LED will blink 5 times.

Release the button at least 1 second after the blinking has stopped - this will clear out any previously saved code and bonding data that could have caused you problems.

Note: If you release the button when all 3 LEDs are on then a self-test will be performed. The green LED will blink on success, or red on failure. Saved code will not be loaded from flash, but will not be erased from flash either - a subsequent reset will start Espruino up loading the saved code as normal.

Note: The initial Puck.js v2 ships with 2v05 firmware, and the self-test may report that IR LEDs are disconnected or the Blue LED voltage is wrong. This is not the case, and later firmwares have updated self test code.


First, it's best to check out the Getting Started Guide

Tutorials using Puck.js:

Tutorials using Bluetooth LE:

Tutorials using Bluetooth LE and functionality that may not be part of Puck.js:


Hover the mouse over a pin function for more information. Clicking in a function will tell you how to use it in Espruino.

  • Purple boxes show pins that are used for other functionality on the board. You should avoid using these unless you know that the marked device is not used.
  • ! boxes contain extra information about the pin. Hover your mouse over them to see it.
  • 3.3v boxes mark pins that are not 5v tolerant (they only take inputs from 0 - 3.3v, not 0 - 5v).
  • GND is ground (0v).
  • ADC is an Analog to Digital Converter (for reading analog voltages)
  • USART is a 2 wire peripheral for Serial Data.

Puck.js 2

D28 3.3v ! ADC USART1 TX
D29 3.3v ADC USART1 RX
D30 3.3v ADC
D31 3.3v ADC
D11 3.3v ! CAPSENSE
D23 3.3v
D24 3.3v
D26 ! IR

Pins not on connectors

D0 3.3v BTN1
D3 3.3v ADC LED3
D4 3.3v ADC LED2
D5 3.3v ADC LED1
D6 3.3v TEMP
D7 3.3v TEMP
D8 3.3v TEMP
D9 3.3v NFC
D10 3.3v NFC
D13 3.3v ACCEL
D14 3.3v ACCEL
D15 3.3v ACCEL
D16 3.3v !
D17 3.3v MAG
D18 3.3v MAG
D19 3.3v MAG
D20 3.3v MAG
D21 3.3v MAG
D22 3.3v !
D25 3.3v IR
D27 3.3v

Puck.js 1

D28 3.3v ! ADC USART1 TX
D29 3.3v ADC USART1 RX
D30 3.3v ADC
D31 3.3v ADC
D6 3.3v TEMP
D7 3.3v TEMP
D8 3.3v TEMP
D11 3.3v ! CAPSENSE
D13 3.3v ACCEL
D14 3.3v ACCEL
D16 3.3v !
D23 3.3v
D24 3.3v
D27 3.3v
ACCEL 3.3v D15

Puck.js Lite

D28 3.3v ! ADC USART1 TX
D29 3.3v ADC USART1 RX
D30 3.3v ADC
D31 3.3v ADC

Note: Puck.js has one available I2C, SPI and USART (and infinite software SPI and I2C). Unlike STM32-based Espruino boards, these peripherals can be used on any pin.



On-board LEDs, Button and GPIO

Puck.js contains LEDs and a button that can be accessed in the same way as other Espruino devices.


  • You can use digitalWrite(LED1,1) to turn the first (red) LED on, and digitalWrite(LED1,0) to turn it off. You can also use LED2 for green and LED3 for blue, and for convenience LED is defined (which is the same as LED1).
  • You can also use LED1.write(1) and LED1.write(0) to get the same effect
  • As well as LED1.set() and LED1.reset() to get the same effect
  • Finally, you can control all 3 LEDs at once using a binary number if you want to with digitalWrite([LED3,LED2,LED1], 7)


  • You can access the button's state with digitalRead(BTN) or BTN.read() (the two commands are identical). BTN1 is also defined, and is the same as BTN.
  • Polling to get the button state wastes power, so it's better to use setWatch to call a function whenever the button changes state:
setWatch(function() {
}, BTN, {edge:"rising", debounce:50, repeat:true});

GPIO pins

GPIO pins are numbered D0 to D31. Their numbers are usually written on the PCB.

You can use the same digitalWrite/digitalRead commands with these that you do with the buttons, but you can also use PWM, I2C, SPI and Analog.

On-board peripherals

Puck.js's on-board peripherals are exposed by special-purpose functions


You can use Puck.mag() to return one magnetometer reading (with x, y, and z axes).

However you can also leave the magnetometer on permanently and use it to wake Puck.js up whenever it gets a reading. See Puck.magOn()

Puck.on('mag', function(xyz) {
// Turn events off with Puck.magOff();

If you have issues using the magnetometer, please check your battery percentage with E.getBattery() to ensure that it has over 30% charge remaining. When the battery is almost empty the magnetometer can stop working correctly.

NOTE: If you have a Puck.js v2 with the original firmware then magnetometer power usage will be higher than normal (~350uA vs 40uA). To fix this add require("puckjsv2-2v05-fix") to the beginning of your program.

Field detection

You can use the puckjsv2-mag-level (About Modules) module to configure the Puck.js magnetometer for ultra low power mode (40uA on Puck.js v2.0) while acting like a reed switch (detecting when field strength is high or low).

Puck.on('field',function(m) {
  digitalPulse(m.state ? LED1 : LED2, 1, 100);
// turn off with require("puckjsv2-mag-level").off();

Note: The low power mode is opnly available on Puck.js v2.0. On Puck.js v1 and v2.1 the same functionality is implemented, but in software. The actual difference in power usage is relatively small.

Note: older versions of this library created a Puck.on('mag' event, but while it's still sent on Puck.js v2.0 for compatibility, we'd recommend using the new Puck.on('field' event.

/* Configure magnetometer in low power mode. When the field
strength is greater than the supplied threshold the Puck.on('mag')
event is fired with:

  x,y,z, // int: field strength
  state, // bool: is field strength greater than thresh in any axis?
  xp,yp,zp // bool: is positive field greater than the threshold
  xn,yn,zn // bool: is negative field greater than the threshold

Can be called with options, which contains:

options = {
  thresh : int // (default 0x2000) threshold for field strength events
  x : // (default true) trigger on X axis
  y : // (default true) trigger on Y axis
  z : // (default true) trigger on Z axis

NOTE: On v2.0 you can configure the hardware to watch for
a magnetic field. On other versions you have to do it in software
(which this module does)
exports.on = function (options) { ... }

// Turn magnetometer off
exports.off = function () { ... }

Direct configuration

For more advanced usage you can also use Puck.magWr(reg,data) and Puck.magRd(reg) to configure the accelerometer chip exactly as required (using the datasheet)..


Puck.js v2.0 / v2.1 only Puck.js v2 has an accelerometer and Gyro (the LSM6DS3TR-C)

You can use Puck.accel() to return one accelerometer/gyro reading, for example:

  "acc": { "x": 253, "y": -663, "z": 16249 },
  "gyro": { "x": 551, "y": 2604, "z": 4265 }

However you can also leave the accelerometer on permanently and use it to wake Puck.js up whenever it gets a reading. See Puck.accelOn()

Puck.accelOn(); // default is 12.5Hz, with gyro
// or Puck.accelOn(1.6); for 1.6Hz low power, without gyro
Puck.on('accel', function(a) {
// Turn events off with Puck.accelOff();

NOTE: If you have a Puck.js v2 with the original firmware then accelerometer power usage will be higher than normal for low data rates (~800uA vs 40uA). To fix this add require("puckjsv2-2v05-fix") to the beginning of your program.

Movement detection

Puck.js v2 / v2.1 only You can use the puckjsv2-accel-movement (About Modules) module to configure the Puck.js accelerometer for ultra low power mode (40uA) while detecting movement:

var idleTimeout;
Puck.on('accel',function(a) {
  if (idleTimeout) clearTimeout(idleTimeout);
  else print("Motion", a);
  idleTimeout = setTimeout(function() {
    idleTimeout = undefined;
// turn off with require("puckjsv2-accel-movement").off();
/* Configure acclerometer to listen for movement. When configured,
power usage is around 40uA. Movement events fire the `Puck.accel`

options = {
  duration : int (0..15, default 2) how long must the movement go on for before triggering
  threshold : int (0..63, default 2) how much movement is required
  lowPower : bool - if true, use 0.6Hz vs 12.5Hz sample rate
               Threshold will need to be higher (10+) to work reliably 
exports.on = function (options) { ... }

// Turn accelerometer off
exports.off = function () { ... }

Significant Movement detection

Puck.js v2 / v2.1 only You can use the puckjsv2-accel-bigmovement (About Modules) module to configure the Puck.js accelerometer for ultra low power mode (40uA) while detecting when the Puck has been moved significantly for more than a few seconds.

Puck.on('accel',function(a) {
// turn off with require("puckjsv2-accel-bigmovement").off();
/* Configure acclerometer to listen for when the device is
moved significantly for a while. When configured,
power usage is around 50uA, and the `Puck.accel`
event is fired when movement happens.

options = { unused };
exports.on = function (options) { ... }

// Turn accelerometer off
exports.off = function () { ... }

Step counting

Puck.js v2 / v2.1 only You can use the puckjsv2-accel-steps (About Modules) module to configure the Puck.js accelerometer for ultra low power mode (40uA) while detecting movement:

var steps = 0;
Puck.on('accel',function(a) {
// turn off with require("puckjsv2-accel-steps").off();
/* Configure acclerometer to listen for steps. When configured,
power usage is around 50uA. Step events fire the `Puck.accel`

options = { unused };
exports.on = function (options) { ... }

// Turn accelerometer off
exports.off = function () { ... }

Tilt detection

Puck.js v2 / v2.1 only You can use the puckjsv2-accel-tilt (About Modules) module to configure the Puck.js accelerometer for ultra low power mode (40uA) while detecting when it has been rotated by more than 35 degrees:

Puck.on('accel',function(a) {
// turn off with require("puckjsv2-accel-tilt").off();
/* Configure acclerometer to listen for when the device is
tilted by more than 35 degrees. When configured,
power usage is around 50uA. Tilt events fire the `Puck.accel`

options = { unused };
exports.on = function (options) { ... }

// Turn accelerometer off
exports.off = function () { ... }

Direct configuration

Puck.js v2 / v2.1 only For more advanced usage you can also use Puck.accelWr(reg,data) and Puck.accelRd(reg) to configure the accelerometer chip exactly as required (using the datasheet). ST has an app note on possible configurations.

IR / Infrared

To transmit an IR signal, you just need to call Puck.IR([...]) with an array of times in milliseconds. They alternate between the time the signal should be on and off - eg. [on, off, on, off, on, etc].

For example the command to turn on a cheap IR lightbulb is:


You can sometimes work this information out based on details online (for instance Pronto codes), however it's often easier to measure it by attaching an IR receiver to your Puck.js.

Puck.js's IR has a range of around 1.5 meters, so it needs to be relatively close to the device it's controlling - however range can be increased slightly by removing the silicone cover.

NFC - Near Field Communications

To set Puck.js up to redirect to a new NFC URL, just use NRF.nfcURL(...):


or to turn off, call it with no arguments:


Other NFC functionality is available - check out the reference for NRF.nfc* methods and events, and see the Web NFC page for information on transferring data via NFC.

Light sensor

To get a light value you can simply call Puck.light().

This returns an (uncalibrated) value between 0 and 1.

The Puck doesn't have a dedicated light sensor, but the LEDs are connected to analog-capable pins (D5=red, D4=green, D3=blue). LEDs actually generate a slight voltage when light is on them, and this can be read back to get a light value. Puck.light() used the red LED (D5) for this.

While Puck.light() requires you to poll, it is possible to use the nRF52's low power comparator on the LED's pin to allow the Puck to go to sleep and wake up when there's a change in light:

// D5 (LED1) is used for sensing
// D1 is used as an output which is also watched with setWatch to detect a state change
var ll = require("NRF52LL");
// set up D1 as an output
// create a 'toggle' task for pin D1
var tog = ll.gpiote(7, {type:"task",pin:D1,lo2hi:1,hi2lo:1,initialState:0});
// compare D5 against vref/16  (vref:8 would be vref/2)
var comp = ll.lpcomp({pin:D5,vref:1,hyst:true});
// use a PPI to trigger the toggle event
ll.ppiEnable(0, comp.eCross, tog.tOut);
// Detect a change on D1
setWatch(function() {
  print("Light level changed");
}, D1, {repeat:true});


Bluetooth is provided by the NRF object.

Bluetooth itself is quite complicated, so it's best to refer to the tutorials above, or check the documentation on NRF.requestDevice for an example of how to connect to another device.


Temperature can be accessed with E.getTemperature(). It returns the temperature in degrees C.

  • Puck.js v2: this uses a PCT2075TP temperature sensor
  • Puck.js v1: this uses an on-die temperature sensor. It is accurate to ~1 degree C for changes in temperature, however the absolute values can be 3-4 degrees C different. For best accuracy, work out each Puck.js's temperature offset by calling E.getTemperature() when it is at a known temperature.

Battery level

Battery level (based on a normal CR2032 battery) can be accessed with E.getBattery(). You can also get the battery voltage using NRF.getBattery().

Capacitive sense

Puck.js contains a capacitive sense pin D11. To use it, simply add some wire (preferably attached to something with a large surface area) to the D11 pin. Then call Puck.capSense() - this will return an integer value that rises as the capacitance attached to D11 increases.


Puck.js v2 / v2.1 only On Puck.js v2 there's a pin called FET (available from JS and marked on the Puck.js PCB). This is connected to a MOSFET (NTZD3154) that is capable of pulling the FET down to GND with around 200mA.

  • FET.set() will turn the FET on (shorting FET to GND)
  • FET.reset() will turn the FET off (leaving FET floating)

See the Puck.js FET page for more information.

Serial Console

When power is first applied, Puck.js checks if pin D28 is at 3.3v (which will be the case if it is connected to a Serial port's transmit line). If it is, it initialises the on-chip UART on D28 (Puck.js RX) and D29 (Puck.js TX) and puts the Espruino console (REPL) on it at 9600 baud.

To use it, connect to a 3.3v output USB to TTL converter as follows:

Puck.js USB->TTL converter
D28 RX ( -> PC )
D29 TX ( <- PC )
3V 3.3v (Optional - to run without a battery)

You can now use the normal Espruino Web IDE, or a serial terminal application at 9600 baud.

When you connect via Bluetooth, the console will automatically move over. To stop this, execute Serial1.setConsole(true) to force the console to stay on Serial1.

Note: Serial1 is not enabled by default because it requires the high speed oscillator to stay on, which increases power draw a huge amount. If you connect the UART but don't power down and power on Puck.js, you won't get a serial port.

Power Consumption

Puck.js's power consumption depends a huge amount on not just how much JavaScript code you execute, but how much you transmit, how often, and at what power level.

Nordic provides a tool to work out power consumption, for advertising, but values are roughly:

  • Not doing anything - 3uA
    • NRF.sleep();
  • Not doing anything, watching the button for presses - 12uA
    • NRF.sleep(); setWatch(..., BTN1);
  • Advertising, 375ms 0dBm (nonconnectable, nonscannable) - 22uA
    • NRF.setAdvertising({},{connectable:false, scannable:false});
  • Advertising, 375ms 0dBm (nonconnectable, nonscannable), watching the button - 27uA
    • NRF.setAdvertising({},{connectable:false, scannable:false}); setWatch(..., BTN1);
  • Advertising, 2000ms 0dBm (nonconnectable, nonscannable) - under 10uA
    • NRF.setAdvertising({},{connectable:false, scannable:false, interval:2000});
  • Advertising, 375ms 0dBm (connectable) - default - 40uA
  • Advertising, magnetometer reading 0.63 Hz (Puck.magOn()) - 60uA
  • Advertising, magnetometer reading 10 Hz - 200uA
  • Connected via BLE default - 600uA
  • Connected via BLE default, inactive for 1 minute - 32uA
  • Connected via BLE, NRF.setConnectionInterval(200) - 40uA
  • One LED lit - 1000-2000uA
  • 100% CPU usage running JavaScript - 4000uA
  • All LEDs lit, 100% CPU usage running JavaScript - 10000uA
  • No LEDs lit, using NRF.findDevices to scan for devices - 12000uA
  • Puck.js v2: Accelerometer on Puck.accelOn() (12.5Hz) : 350uA
  • Puck.js v2: Accelerometer on Puck.accelOn(1.6) (1.6Hz, no gyro) : 40uA
  • Puck.js v2: Accelerometer on, movement/tilt/step detect : 40uA

Note: The first Puck.js v2 units shipped with 2v05 firmware which wasn't as efficient with the accelerometer/magnetometer. See the magnetometer/accelerometer notes above for the minor code change required to fix this.

Puck.js sends advertising data without ever executing JavaScript. To get the best power consumption, make sure your code executes as rarely as possible.

Firmware Updates

Note: On Puck.js v2 do not install firmwares 2v04 and earlier. See 'recovery after 2v04 installation' below.

Please see the Firmware Update page for detailed instructions on Flashing.

Normal and Minimal Firmwares

We provide two distinct firmwares for Puck.js devices:

Filename Espruino BLE NFC Neopixel JIT Graphics HTTP Crypto Flash Storage
espruino_2vxx_puckjs.zip X X X X X X X X 40kb
espruino_2vxx_puckjs_minimal.zip X X X X 96kb

Puck.js come pre-installed with the first (normal) firmware, but if you don't need the extra features like Graphics and HTTP (which you're unlikely to use unless you are soldering external hardware on to the Puck) then you can install the minimal build. This removes those features and makes the saved flash memory space available to you via the Storage module, which you can then use for (for example) data logging.


Please check out the Bluetooth Troubleshooting or General Troubleshooting pages.

Recovery after 2v04 Installation

On Puck.js v2 (v2.1 is unaffected), you should not install firmwares 2v04 and earlier. The pin D21/nRESET is used on Puck.js v2, and is pulled low by default.

Pre-2v05 firmwares that are not designed for Puck.js v2 configure D21/nRESET as a reset pin. This then forces the chip into a reset state, effectively 'bricking' your Puck.js.

In the very rare case that an old firmware was installed, you can recover your Puck.js by temporarily connecting pin D21 on the bluetooth module to the 3v pin on the Puck. You can then flash an up to date firmware via DFU, rebooting into Espruno, connecting and running the following code (which resets the reset pin configuration flag):

setTimeout(function() { NRF.restart(function(){
poke32(0x4001e504,2);while(!peek32(0x4001e400)); // enable flash erase
poke32(0x4001e514,1);while(!peek32(0x4001e400)); // erase whole uicr
poke32(0x4001e504,1);while(!peek32(0x4001e400)); // enable flash writing
poke32(0x10001014,0x78000);while(!peek32(0x4001e400)); // set bootloader address
poke32(0x10001018,0x7E000);while(!peek32(0x4001e400)); // set mbr settings
poke32(0x1000120c,0xfffffffe);while(!peek32(0x4001e400)); // NFC pins as GPIO
poke32(0x4001e504, 0);while(!peek32(0x4001e400)); // disable flash writing
}) }, 2000);NRF.disconnect();

Other Official Espruino Boards

This page is auto-generated from GitHub. If you see any mistakes or have suggestions, please let us know.