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+ }
0 commit comments