Skip to content

Commit e8a4df8

Browse files
Project files were uploaded
1 parent b0d1fdd commit e8a4df8

File tree

7 files changed

+509
-0
lines changed

7 files changed

+509
-0
lines changed

godice.js

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
2+
/**
3+
* @class
4+
* The main GoDice class that can be used to connect a new die, send and recieve messages.
5+
*/
6+
class GoDice {
7+
8+
messageIdentifiers = {
9+
BATTERY_LEVEL: 3,
10+
DICE_COLOUR: 23,
11+
SET_LED: 8,
12+
}
13+
14+
diceColour = {
15+
BLACK: 0,
16+
RED: 1,
17+
GREEN: 2,
18+
BLUE: 3,
19+
YELLOW: 4,
20+
ORANGE: 5,
21+
}
22+
23+
bluetoothDevice;
24+
GoDiceService;
25+
CubeCharacteristics;
26+
GlobalDeviceId;
27+
diceId;
28+
rolledValue = 0;
29+
30+
onRollStart(){};
31+
onBatteryLevel(){};
32+
onDiceColor(){};
33+
onStable(){};
34+
onFakeStable(){};
35+
onTiltStable(){};
36+
onMoveStable(){};
37+
onDiceConnected(){};
38+
39+
/******* API functions *******/
40+
41+
/**
42+
* Request for the die battery, that should follow by corresponding "BatteryLevel" event (response).
43+
*/
44+
getBatteryLevel() {
45+
console.log(this)
46+
this.sendMessage([this.messageIdentifiers.BATTERY_LEVEL]);
47+
}
48+
49+
/**
50+
* Request for the die color, that should follow by corresponding "DiceColor" event (response).
51+
*/
52+
getDiceColor() {
53+
console.log(this)
54+
this.sendMessage([this.messageIdentifiers.DICE_COLOUR]);
55+
}
56+
57+
/**
58+
* Open a connection dialog to connect a single GoDice, after successfull connection it will follow by corresponding "DiceConnected" event (response).
59+
*/
60+
requestDevice() {
61+
return navigator.bluetooth.requestDevice({
62+
filters: [{ namePrefix: 'GoDice_' }],
63+
optionalServices: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e']
64+
})
65+
.then(device => {
66+
this.GlobalDeviceId = device.id.toString();
67+
this.bluetoothDevice = device;
68+
this.bluetoothDevice.addEventListener('gattserverdisconnected', this.onDisconnected);
69+
this.connectDeviceAndCacheCharacteristics();
70+
});
71+
}
72+
73+
/**
74+
* Turn On/Off RGB LEDs, will turn off if led1 and led2 are null
75+
* @param {Array} led1 - an array to control the 1st LED in the following format '[R,G,B]'
76+
* where R,G and B are numbers in the range of 0-255
77+
* @param {Array} led2 - an array to control the 2nd LED in the following format '[R,G,B]'
78+
* where R,G and B are numbers in the range of 0-255
79+
*/
80+
setLed(led1, led2) {
81+
console.log(led1, led2)
82+
let adjustedLed1 = (!led1) ? [0, 0, 0] : led1;
83+
let adjustedLed2 = (!led2) ? [0, 0, 0] : led2;
84+
adjustedLed1 = adjustedLed1.map((i) => Math.max(Math.min(i, 255), 0));
85+
adjustedLed2 = adjustedLed2.map((i) => Math.max(Math.min(i, 255), 0));
86+
if (adjustedLed1.length === 1) adjustedLed1.push(adjustedLed1[0], adjustedLed1[0]);
87+
if (adjustedLed2.length === 1) adjustedLed2.push(adjustedLed2[0], adjustedLed2[0]);
88+
89+
const messageArray = [this.messageIdentifiers.SET_LED, ...adjustedLed1, ...adjustedLed2];
90+
console.debug("messageArray", messageArray);
91+
this.sendMessage(messageArray);
92+
}
93+
94+
/******* Internal Helper Functions *******/
95+
96+
// Send generic message to the die
97+
sendMessage(messageArray) {
98+
if (!this.GoDiceService) {
99+
return Promise.reject(new Error('No Cube characteristic selected yet.'));
100+
}
101+
return this.GoDiceService.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e')
102+
.then(controlPoint => {
103+
console.debug("messageArray", messageArray);
104+
const byteMessage = new Uint8Array(messageArray);
105+
return controlPoint.writeValue(byteMessage).then(response => {
106+
console.debug("after write response", response);
107+
});
108+
});
109+
}
110+
111+
// Change angles to fixed value
112+
getAngleValue(val) {
113+
if (val <= 20 && val >= 0) {
114+
return "255";
115+
}
116+
if (val <= 255 && val >= 230) {
117+
return "255";
118+
}
119+
if (val <= 85 && val >= 40) {
120+
return "64";
121+
}
122+
if (val <= 215 && val >= 185) {
123+
return "192";
124+
}
125+
126+
}
127+
128+
getXyzFromBytes(data, startByte) {
129+
const x = data.getUint8(startByte);
130+
const y = data.getUint8(startByte + 1);
131+
const z = data.getUint8(startByte + 2);
132+
return [x, y, z]
133+
}
134+
135+
// Get D6 number from acc raw data
136+
getD6Value(data, startByte) {
137+
const xyzArray = this.getXyzFromBytes(data, startByte)
138+
const coord = `${this.getAngleValue(xyzArray[0])}-${this.getAngleValue(xyzArray[1])}-${this.getAngleValue(xyzArray[2])}`;
139+
switch (coord) {
140+
case ("192-255-255"):
141+
return 1;
142+
case ("255-255-64"):
143+
return 2;
144+
case ("255-64-255"):
145+
return 3;
146+
case ("255-192-255"):
147+
return 4;
148+
case ("255-255-192"):
149+
return 5;
150+
case ("64-255-255"):
151+
return 6;
152+
153+
default:
154+
//сonsole.log("no hit coord",coord);
155+
return "";
156+
}
157+
}
158+
159+
// Get a message fromn the die and fire the matchig event
160+
parseMessage(data, deviceId) {
161+
try {
162+
console.debug("data: ", data);
163+
console.debug("deviceId: ", deviceId);
164+
const firstByte = data.getUint8(0);
165+
if (firstByte === 82) {
166+
this.onRollStart(deviceId);
167+
return;
168+
}
169+
170+
const secondByte = data.getUint8(1);
171+
const thirdByte = data.getUint8(2);
172+
173+
if (firstByte === 66 && secondByte === 97 && thirdByte === 116) {
174+
this.onBatteryLevel(deviceId, data.getUint8(3));
175+
}
176+
177+
if (firstByte === 67 && secondByte === 111 && thirdByte === 108) {
178+
this.onDiceColor(deviceId, data.getUint8(3));
179+
}
180+
181+
if (firstByte === 83) {
182+
const diceCurrentNumber = this.getD6Value(data, 1);
183+
const xyzArray = this.getXyzFromBytes(data, 1)
184+
this.rolledValue = diceCurrentNumber;
185+
if (parseInt(diceCurrentNumber) > 0) {
186+
this.onStable(deviceId, diceCurrentNumber, xyzArray);
187+
}
188+
}
189+
190+
if (firstByte === 70 && secondByte === 83) {
191+
const diceCurrentNumber = this.getD6Value(data, 2);
192+
const xyzArray = this.getXyzFromBytes(data, 2)
193+
this.rolledValue = diceCurrentNumber;
194+
this.onFakeStable(deviceId, diceCurrentNumber, xyzArray);
195+
}
196+
197+
if (firstByte === 84 && secondByte === 83) {
198+
const xyzArray = this.getXyzFromBytes(data, 2)
199+
this.onTiltStable(deviceId, xyzArray);
200+
}
201+
202+
if (firstByte === 77 && secondByte === 83) {
203+
const diceCurrentNumber = this.getD6Value(data, 2);
204+
const xyzArray = this.getXyzFromBytes(data, 2)
205+
this.rolledValue = diceCurrentNumber;
206+
this.onMoveStable(deviceId, diceCurrentNumber, xyzArray);
207+
}
208+
209+
}
210+
catch (err) {
211+
console.error("err", err);
212+
}
213+
}
214+
215+
/******** Bluetooth Low Energy (BLE) implementation ********/
216+
handleNotificationChanged(event) {
217+
this.parseMessage(event.target.value, event.target.service.device.id.toString());
218+
}
219+
220+
handleNotificationChanged = this.handleNotificationChanged.bind(this);
221+
222+
connectDeviceAndCacheCharacteristics() {
223+
console.debug('Connecting to GATT Server...');
224+
return this.bluetoothDevice.gatt.connect()
225+
.then(server => {
226+
return server.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
227+
})
228+
.then(service => {
229+
this.GoDiceService = service;
230+
return service.getCharacteristic("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
231+
})
232+
.then(characteristic => {
233+
this.CubeCharacteristics = characteristic;
234+
this.CubeCharacteristics.addEventListener('characteristicvaluechanged', this.handleNotificationChanged);
235+
return characteristic.getDescriptors();
236+
})
237+
.then(descriptors => {
238+
this.onStartNotificationsButtonClick();
239+
})
240+
}
241+
242+
243+
onStartNotificationsButtonClick() {
244+
console.debug('Starting Notifications...');
245+
this.CubeCharacteristics.startNotifications()
246+
.then(_ => {
247+
console.debug('onDiceConnected');
248+
this.onDiceConnected(this.GlobalDeviceId, this);
249+
})
250+
.catch(error => {
251+
console.error('Argh! ' + error);
252+
});
253+
}
254+
255+
onDisconnectButtonClick() {
256+
if (this.CubeCharacteristics) {
257+
this.CubeCharacteristics.removeEventListener('characteristicvaluechanged', this.handleNotificationChanged.bind(this));
258+
this.CubeCharacteristics = null;
259+
}
260+
if (this.bluetoothDevice === null)
261+
return;
262+
263+
if (this.bluetoothDevice.gatt.connected)
264+
this.bluetoothDevice.gatt.disconnect();
265+
else {
266+
console.debug('> Bluetooth Device is already disconnected');
267+
}
268+
269+
// Note that it doesn't disconnect device.
270+
this.bluetoothDevice = null;
271+
}
272+
273+
onDisconnected(event) {
274+
console.debug('> Bluetooth Device disconnected:' + event);
275+
}
276+
}

images/godice.png

16 KB
Loading

images/logo.png

14.1 KB
Loading

images/one.png

Lines changed: 1 addition & 0 deletions
Loading

index.html

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>GoDice API Demo</title>
6+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
7+
<link rel="stylesheet" href="style.css" type="text/css" media="all">
8+
<meta name="viewport" content="width=device-width, initial-scale=1">
9+
10+
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
11+
<script src="godice.js"></script>
12+
<script src="main.js"></script>
13+
</head>
14+
15+
<body>
16+
<div class="container">
17+
<nav class="navbar navbar-expand-lg navbar-light bg-light">
18+
<div class="container-fluid">
19+
<a class="navbar-brand logo" href="#"></a>
20+
<button onclick="openConnectionDialog()" type="button" class="btn btn-outline-primary">Connect GoDice</button>
21+
</div>
22+
</nav>
23+
<h3 id="gocube-message"></h3>
24+
<div id="dice-host"></div>
25+
</div>
26+
</body>
27+
28+
<style>
29+
.dice-name{
30+
font-size: 10px;
31+
font-weight: bold;
32+
margin: 20px auto;
33+
}
34+
.dice-wrapper{
35+
display: inline-block;
36+
width: 200px;
37+
height: 380px;
38+
border: 1px solid gray;
39+
border-radius: 5px;
40+
margin-right: 5px;
41+
text-align: center;
42+
vertical-align: top;
43+
}
44+
.dice-wrapper .btn{
45+
width: 90% !important;
46+
margin: 10px auto;
47+
}
48+
.dice-host {
49+
width: 100%;
50+
}
51+
</style>
52+

0 commit comments

Comments
 (0)