A connected Mölkky game with Bluetooth Low Energy and Node JS

Let’s play Mölkky without counting points by ourselves

--

If you already played Mölkky, you certainly heard: «How many points have we earned until now?» If you don’t know what is Mölkky: it’s a Finnish throwing game that needs to count points until 50. Most of the time, we are a lot of friends and it’s the aperitif time. So quickly, no one knows where we stand concerning the scores.

So I thought: « What about something that could count the points for us?»

It’s why I did a prototype with accelerometer sensors. We tested it in last november during the Xebicon 19 day.

This article explains each steps.

1. DEFINE THE PROTOTYPE

1.1 Rules of the game

The players use a throwing pin to try to knock over wooden pins, which are marked with numbers from 1 to 12. The first one to reach exactly 50 points wins the game.

At the beginning, pins are placed in a formation as follows:

In order to knock down pins, a first player launches the throwing pin on the twelve pins.

Three situations could then happen:

  1. two or more pins are knocked over: the team scores the number of pins that were knocked over.
  2. only one pin is knocked over: the team scores the amount of points that is marked on the pin
  3. no any pin is knocked over: the player could try again in the limit of 3 throwings in a row (this rule could be different)

Pins are stood up again exactly where it landed. This is how during the game the pins get scattered across the playing field. It’s then the turn of the other team to play.

A team that exceeds the score of 50, drops back to 25.

1.2. Gameplay we target versus the goal of the prototype

For our prototype, we should iterate in order to achieve to an ideal result. So we accept some differences between an ideal scenario and the scenario of our prototype:

+------------------------------+----------------------------------|
| IDEAL SCENARIO | PROTOTYPE SCENARIO |
+------------------------------+----------------------------------|
| The player launches the throwing pin on the pins |
+------------------------------+----------------------------------| | | A screen shows the knocked over |
| | pins and the points to score |
+------------------------------+----------------------------------+
| | The player accepts points |
| | on the device |
+------------------------------+----------------------------------+
| The screens displays the updated total scores |
+------------------------------+----------------------------------+
| This is the turn of the other team |
+------------------------------+----------------------------------+

2. HARDWARE

2. 1. Which thing and which technology for our Internet of Thing solution?

2.1.1. Gyroscope, accelerometer : which sensors do we need?

The first thing that crosses our mind when thinking about finding the position of a pin is “we need a gyroscope in the pin like in our smartphones!”

After some researches we understand that a smartphone combines data from several sensors to estimate its position. Contrary to what one might think, our smartphone does not have a gyroscope. Indeed, it has a gyrometer, an accelerometer and a magnetometer. The smartphone uses them to get the absolute value of pitch (movement from front to back) and roll (movement from right to left). [more information here in french or here in english]

Therefore I looked for an object fitted with those 3 sensors. Susbtantially, I googled “gyroscope sensors”. I explored a lot of tracks. I looked at Arduino and Raspberry. I often came accross sensors that seemed perfect likee the one below:

However I wanted something simpler: it was out of question for me to solder and manually assemble various electronic components with batteries in a pin.

2.1.2. Low power BLE wireless sensors and their limits

One thing leading to another, I discovered Espruino that started pleasing me. It dealed with Javascript and mobiles applications: for the front end developer that I am, it was really seducing me. So I looked for Bluetooth sensors. I found frameworks that make me want to work with: Evothings and Johnny-Five for example.

My search became more and more precise then I thought BLE sensors (Bluetooth Low Energy) were the key : we were going to need beacons with accelerometers! Since it’s low energy, a tiny battery was going to be perfect. A number of solutions are available: Kontact.io, Blue Net Beacon, Sensoro and espacially Estimote.

One day, throught discussion with colleagues, one of them said: “you will face an important limit: in the Bluetooth specifications it’s mentionned that you can’t connect more thant 7 devices to one another simulatenous” 😱

He was right :

A master BR/EDR Bluetooth device can communicate with a maximum of seven devices in a piconet (an ad-hoc computer network using Bluetooth technology), though not all devices reach this maximum.

[source]

But nevermind, I will go through a gateway!

2.1.3. Minew: a gateway and 12 beacons

While doing all of this research, I found a lot of poorly documented beacons but they were less expensive too. I decided to order some of them and try sold on Alibaba by Minew:

Beacons emit accelerometer data to the gateway via Bluetooth. Basically, it broadcast a combination of letters and numbers on a regular interval. The gateway “catches” the data then can transmit it to a server (cloud or local) via HTTP or MQTT protocols. Si the gateway needs to be connected to a WiFi network (or Ethernet)

2.2. From beacons to the positions

2.2.1. Configure beacons

To turn on a beacon, you should open, press the button and keep holding 3 seconds. The LED will light on for some seconds then the beacon starts to emit data. From that point, it is active.

We sill see what are this data and how to get it.

Minew make a mobile application (BeaconSET sur Google Play / BeaconSET sur l’Apple store) available to the public to configure beacons. Our interlocutor also gave us Android application sources that could be used as an example for other purposes than our own.

Changing password

By default the password is minew123, we should define a new one:

Emit accelerometer data

Beacons are used in a lot of domains in marketing (retails, events, etc). We can configure differents “slots” in the configurration interface of the mobile app. Here one of them interests us more particularly: ACC, for accelerometer. We want it to be active not when moving but on on a trigger action, so we set it to motion. We also delete other useless slots, except INFO that is used by the beacon itself.

Returning to the homepage of the app allows us to check which values are broadcasted by the beacon. We can now see movement values of the beacon along 3 orthogonal axes: X (abscissa), Y (ordinate) and especially Z (dimension):

We apply this configuration to each of our beacons:

2.2.2. Configure gateway

Local network and WiFi

Once the gateway is turned on, it provides a WiFi hotspot with SSID name “GW-XXXXXXXXXXXX”. We can connect to it, no password is required. Configuration interface is available at http://192.168.99.1.

The first things to do are :

  • securing the hotspot by setting a password and change the SSID name
  • setting a password on the admin interface

We now need our gateway to get a network connection. We can use an Ethernet or a WLAN network and we select the Repeater option.

It will ask whether to restart gateway and the network setting takes effect after restarting. If an IP address is now displayed then it means all is right :

How to receive the emitted packets?

At this point, our gateway should be able to scan all the information broadcasted by beacons. How to see it?

First, we are going to use the IoT uBeac platform, a data integration and visualization service. It might make us able to know if our gateway corrrectly transmit data that it receives. After creating an account, we configure a gateway and select our gateway type, Minew in our case:

The HTTP tab gives us the endpoint that should be copied. It will be useful to configure the interface of our gateway:

Now, let’s provide this endpoint to the url of the Services tab:

If we move the beacons, requests are displaying in real time :

We can also see what is the data of one request:

We will see later how to interprate this JSON.

3. SOFTWARE

So, the equipment is working well now. We just have to develop a software for our Mölkky.

3.1. Creating our endpoint to receive data

3.1.1. Setting up a Node server

Instead of the uBeac platform we used previously, we create a server that will provide an endpoint with POST method that will receive data repeatedly and process it. For that, we will use Express.

We create a route /api/minew :

import express from 'express';const router = express.Router();router.post('/', async (req, res) => {
const { body } = req;
console.log({ body });
res.end();
});
module.exports = router;

We can deploy our server on a Raspberry or through a host provider. Our endpoint here will be local: http://192.168.99.103:8888/api/minew and we set up in tthe gateway interface:

Here we go! Our server is logging a lot of information. Note that it also received data of the gateway and other BLE devices around the place:

Let’s improve the gateway configuration to filter data on our sensors. Disable the gateway data upload and provide beacons MAC addesses. Here an example for one beacon:

Alright, now our server logs only an empty object or the data of a beacon if we move it:

3.1.2. Decoding data

For now, we receive a incomprehensible data in the rawData key. It’s an encrypted signal called an advertising packet. To know more about this, I recommand this article “Understanding the different types of BLE Beacons”.

ReelyActive provides an online tool to decrypt rawData:

Here are the results and we can see the accelerometer values:

ReelyActive also provides an open source library that we can use in our NodeJS app: Advlib.

Let’s improve the logging of our route:

import advlib from 'advlib';
import express from 'express';
import isArray from 'lodash/isArray';
const router = express.Router();router.post('/', async (req, res) => {
const { body } = req;
if( isArray(body) && body.length > 0 ) {
body.forEach(({ rawData }) =>; {
if( rawData ) {
console.log(advlib.ble.data.process(rawData));
}
});
}
res.end();
});
module.exports = router;

Result:

POST /api/minew 200 0.650 ms - -
{ flags: [ 'LE General Discoverable Mode', 'BR/EDR Not Supported' ],
complete16BitUUIDs: 'ffe1',
serviceData:
{ uuid: 'ffe1',
data: 'a10364000a000000f0a246a23f23ac',
minew:
{ frameType: 'a1',
productModel: 3,
batteryPercent: 100,
accelerationX: 0.0390625,
accelerationY: 0,
accelerationZ: 0.9375,
macAddress: 'ac:23:3f:a2:46:a2' } } }

Clean our data by filtering useless information. We want to keep batteryPercent, mac and accelerationZ. Here SKITTLES is an imported constant that rely each MAC address to a pin number:

router.post('/', async (req, res) => {
const { body } = req;
// Look for data concerning skittles sensors
if (body && isArray(body) && body.length > 0) {
const skittles = body
.filter((sensor) => Object
.keys(SKITTLES)
.includes(sensor.mac));
// Look for skittles with raw data and transform raw data
if (skittles.length > 0) {
const skittlesInfo = skittles
.filter((skittle) => !isUndefined(skittle.rawData)
&& !isNull(skittle.rawData)
&& skittle.rawData.length > 0)
.map(({ mac, rawData }) => ({
mac,
...advlib.ble.data.process(rawData),
}))
.filter((skittle) => hasIn(skittle, 'serviceData.uuid')
&& hasIn(skittle, 'serviceData.data')
&& hasIn(skittle, 'serviceData.minew'))
.map(({ serviceData: { uuid, data, minew }, ...rest }) => ({
uuid,
data,
...minew,
...rest,
}))
.map((data) =>; omit(data, [
'data',
'frameType',
'productModel',
'accelerationX',
'accelerationY',
'macAddress',
'flags',
'complete16BitUUIDs',
'uuid',
]));
console.log({ skittlesInfo }); } else {
debug('No skittle sensors', skittles);
}
} else {
debug('No Body or Empty body');
}
res.end();
});

We get an array of objects for each received beacon:

{ skittlesInfo:
[ { batteryPercent: 100,
accelerationZ: 0.9296875,
mac: 'AC233FA246A2' } ] }

3.1.3. From acceleration to the position

The accelerometer gives us the direction in which the beacon is moving, it doesn’t detect a position but an acceleration on one of the 3 axes X, Y, Z.

So each beacon will send its speed changes and translational motions.

This is not enough for what we want: knowing if the beacon is standing up or knocked down. Fortunately, the accelerometer also detects the force of gravity acting on the beacon. This is a very important detail for us! Indeed, the value of the acceleration on the Z axis is always near 1 when the beacon doesn’t move. It’s the gravity acting on him. So we can deduct a stand up position.

We want to get an objet with the following structure:

{ 
XXXXXXXXXXX: { // adresse MAC
battery: 100,
position: 'KNOCKED_OVER', // 'KNOCKED_OVER' or 'UPRIGHT'
z: 0.9296875,
value: 12 // numero de la quille
},
...
}

If we save the last position of each pin on the server (almost in real time, depending on the interval that we set on the gateway), then we obtain all we what we need to create a Mölkky game.

Let’s regard a global object on our server called lastState. Give it an error gap with the POSITION_LEVEL constant (near 0.8) and add theses lines to our server:

skittlesInfo.forEach(({ mac, accelerationZ: z, batteryPercent: battery }) =>; {
lastState[mac] = {
...lastState[mac],
...(battery &&; { battery }),
...(z && {
position: z && = POSITION_LEVEL ? 'UPRIGHT' : 'KNOCKED_OVER',
z,
}),
value: SKITTLES[mac],
};
});

3.1.4. Socket.io for real time communication

We use Socket.io to emit the data for a client:

const io = req.app.get('socketio');
io.emit('UPDATE', lastState);

3.2. Application development

3.2.1. Server

The API for the game provides 4 endpoints (with POST method):

  • /start : to start a game
  • /score : to send score of the team that just played
  • /miss : to inform about a launch that missed pins
  • /reset : to restart a game or reinit a game when it has just finished

The endpoints are documented on Postman with their potential responses

A singleton for the current game

A singleton CurrentGame is instantiated when the /start endpoint is called. This instance contains all the information concerning the current game.

Routes to server front pages

Routes /molkky/game and/molkky/play render the front componentGame by providing itcurrentGame in context.

3.2.2. Client

Here we develop ou front applicaion with React. The Game component (the same we saw in rendering the endpoint in server side rendering) renders:

  • DataContext: a context that collect and save data concerning pins via Socket.io.
  • PlayContext: a context that contains data concerning the current game
  • Depending on routes, render different containers :
    -StartScreen: allows user to select the team that will starts the game, checks that every pins are stand up
    - PlayScreen: contains the game components. Display pins, score, buttons to validate score, modals, etc.

3.2.3. Screenshots of the app

4. REAL LIFE SITUATION

4.1. Include beacons into the pins

4.1.1. Experiments

We try different things before to create more robust pins. No comments:

4.1.2. The final wood pins

A gifted colleague in DIY + a Mölkky set with wood = it is done!

5. SOME FINAL REMARKS

5.1. We should improve reliability

During the use on the exhibition stand at the Xebicon we sometimes oberved that some beacons didn’t send information after having been knocked down or raised up. Is it because of the bump? Should we improve settings of transmission?

Furthermore, we would appreciate a system where data is sent when something really changes instead of a polling system at regular and multiple intervals. Maybe other technologies could be more appropriate. We should go further on the side of the Bluetooth Web API.

In conclusion, we can say that for beacons, reliability is:

  • reasonnable because we played several games
  • insufficient because we had to correct the detection several times

5.2. We should iterate to develop more features

We implemented a leaderboard for the day during the Xebicon to save scores via Firebase.

A lot of improvements and new features are possibles:

  • settings for rules of the game
  • playing with several individual players and more than 2 teams
  • a lot of fun things to do with the game stats
  • etc.

If you wish to test by your own or make a contribution, sources are available on the Github Molkky project here, with a menu to simulate beacons.

6. CONCLUDING REMARKS

Let’s face it: our Mölkky is far from being marketable. It’s not really conceivable you decided to go out on a playing field with the whole equipment: gateway, electricity, internet network. Even if you decided to use your smartphone as a 3g/4g hotspot.

But at Publicis Sapient Engineering, we love impossible tasks and above all we love working on challenges. Conference day like Xebicon is a way to share researches we do with crazy projects and share our passion. Who knows, maybe it will give you the wish to participate and improve them?

This post was initially published in french on Publicis Sapient Engineering blog.

--

--