├── .editorconfig ├── LICENSE.txt ├── README.md ├── bb8.webmanifest ├── icon-144x144.png ├── icon-180x180.png ├── icon-192x192.png ├── icon.svg ├── index.html ├── joypad.svg ├── main.css └── main.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BB-8 Control Web Bluetooth demo 2 | 3 | 4 | 5 | ## License 6 | 7 | Thee code in [`main.js`](https://github.com/operasoftware/bb8/blob/gh-pages/main.js) is based on [`https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-toy-bb8/index.html`](https://github.com/WebBluetoothCG/demos/blob/038f0869bcdcaa61780d7e1548d4b1d246f8fd97/bluetooth-toy-bb8/index.html#L162-L348) (copyright Google Inc. All Rights Reserved, Apache 2-licensed). Thus, the source code of this repository is available under the Apache 2 license as well. 8 | -------------------------------------------------------------------------------- /bb8.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "BB-8 Control", 3 | "name": "BB-8 Control", 4 | "display": "fullscreen", 5 | "icons": [ 6 | { 7 | "src": "icon-144x144.png", 8 | "sizes": "144x144", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "icon-180x180.png", 13 | "sizes": "180x180", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "icon-192x192.png", 18 | "sizes": "192x192", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "icon.svg", 23 | "sizes": "any", 24 | "type": "image/svg" 25 | } 26 | ], 27 | "start_url": ".", 28 | "theme_color": "#282c37", 29 | "background_color": "#282c37" 30 | } 31 | -------------------------------------------------------------------------------- /icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operasoftware/bb8/98b8e7abf115c3371819306ce4784ba6823cd91b/icon-144x144.png -------------------------------------------------------------------------------- /icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operasoftware/bb8/98b8e7abf115c3371819306ce4784ba6823cd91b/icon-180x180.png -------------------------------------------------------------------------------- /icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/operasoftware/bb8/98b8e7abf115c3371819306ce4784ba6823cd91b/icon-192x192.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | BB-8-196 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | BB-8 Control 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

BB-8 Control

14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /joypad.svg: -------------------------------------------------------------------------------- 1 | Circle -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: transparent; 3 | } 4 | 5 | html, button { 6 | text-transform: uppercase; 7 | font-weight: bold; 8 | color: #fff; 9 | font-family: sans-serif; 10 | } 11 | 12 | html { 13 | background: #282c37; 14 | } 15 | 16 | h1, 17 | main, 18 | div { 19 | padding: 1rem; 20 | } 21 | 22 | main, 23 | div { 24 | display: flex; 25 | flex-flow: row nowrap; 26 | justify-content: space-around; 27 | align-content: center; 28 | align-items: center; 29 | } 30 | 31 | main { 32 | background: linear-gradient(#444957, #282c38); 33 | } 34 | 35 | h1 { 36 | text-align: center; 37 | font-size: 1.5rem; 38 | margin-bottom: 0; 39 | } 40 | 41 | h1 span { 42 | display: inline-block; 43 | padding-bottom: 0.5em; 44 | border-bottom: 3px solid #f2712e; 45 | } 46 | 47 | button { 48 | background: #2f453f; 49 | border: 1px solid #628068; 50 | border-radius: 58px; 51 | padding: 1em 3em; 52 | } 53 | 54 | .button-connect { 55 | background: #2f453f; 56 | border-color: #628068; 57 | } 58 | 59 | .button-stop { 60 | background: #751729; 61 | border-color: #8a161c; 62 | } 63 | 64 | .button-aim { 65 | background: #4b505d; 66 | border-color: #848790; 67 | } 68 | 69 | .button-led { 70 | text-indent: -9999em; 71 | overflow: hidden; 72 | border-radius: 50%; 73 | padding: 0; 74 | width: 3em; 75 | height: 3em; 76 | border-color: #6f737d; 77 | position: relative; 78 | z-index: 10; 79 | background: transparent; 80 | } 81 | 82 | .button-led::before { 83 | border-radius: 50%; 84 | content: ''; 85 | display: block; 86 | position: absolute; 87 | z-index: -1; 88 | top: 5px; 89 | left: 5px; 90 | right: 5px; 91 | bottom: 5px; 92 | /*border:5px solid orange;*/ 93 | } 94 | 95 | .button-led-red::before { 96 | background: #fa2e2e; 97 | } 98 | 99 | .button-led-green::before { 100 | background: #6df99a; 101 | } 102 | 103 | .button-led-blue::before { 104 | background: #58a7e2; 105 | } 106 | 107 | .button-led-off::before { 108 | background: #9fa2a9; 109 | } 110 | 111 | .active { 112 | background: #58a7e2; 113 | } 114 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 'use strict'; 4 | 5 | if ( 6 | location.hostname.endsWith('.github.io') && 7 | location.protocol != 'https:' 8 | ) { 9 | location.protocol = 'https:'; 10 | } 11 | 12 | const elConnect = document.querySelector('#connect'); 13 | const elStop = document.querySelector('#stop'); 14 | const elAim = document.querySelector('#aim'); 15 | const elRed = document.querySelector('#red'); 16 | const elBlue = document.querySelector('#blue'); 17 | const elGreen = document.querySelector('#green'); 18 | const elOff = document.querySelector('#off'); 19 | const elJoypad = document.querySelector('#joypad'); 20 | 21 | if (navigator.vibrate) { 22 | [ 23 | elConnect, elStop, elAim, elRed, elBlue, 24 | elGreen, elOff 25 | ].forEach(function(element) { 26 | element.addEventListener('touchstart', function(event) { 27 | navigator.vibrate(15); 28 | }); 29 | }); 30 | } 31 | 32 | const state = { 33 | 'aim': false, 34 | 'busy': false, 35 | 'sequence': 0, 36 | }; 37 | 38 | let controlCharacteristic; 39 | let coreHeading; 40 | let gattServer; 41 | let robotService; 42 | let radioService; 43 | 44 | const setHeading = function(heading) { 45 | if (state.busy) { 46 | // Return if another operation pending 47 | return Promise.resolve(); 48 | } 49 | state.busy = true; 50 | const did = 0x02; 51 | const cid = 0x01; 52 | const data = new Uint16Array([heading]); 53 | 54 | sendCommand(did, cid, data).then(() => { 55 | state.busy = false; 56 | }) 57 | .catch(exception => { 58 | console.log(exception); 59 | }); 60 | }; 61 | 62 | // Code based on https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-toy-bb8/index.html 63 | const roll = function(heading, speed, rollState) { 64 | console.log('Roll heading=' + heading + ', speed=' + speed); 65 | if (state.busy) { 66 | // Return if another operation pending 67 | return Promise.resolve(); 68 | } 69 | coreHeading = heading; 70 | state.busy = true; 71 | const did = 0x02; // Virtual device ID 72 | const cid = 0x30; // Roll command 73 | // Roll command data: speed, heading (MSB), heading (LSB), state 74 | const data = new Uint8Array([speed, heading >> 8, heading & 0xFF, rollState]); 75 | sendCommand(did, cid, data).then(() => { 76 | state.busy = false; 77 | }) 78 | .catch(exception => { 79 | console.log(exception); 80 | }); 81 | }; 82 | 83 | const stopRolling = function() { 84 | if (state.busy) { 85 | setTimeout(stopRolling, 100); 86 | // Return if another operation pending 87 | return Promise.resolve(); 88 | } 89 | state.busy = true; 90 | const did = 0x02; // Virtual device ID 91 | const cid = 0x30; // Roll command 92 | // Roll command data: speed, heading (MSB), heading (LSB), state 93 | const data = new Uint8Array([ 94 | 100, coreHeading >> 8, coreHeading & 0xFF, 0 95 | ]); 96 | sendCommand(did, cid, data).then(() => { 97 | state.busy = false; 98 | }) 99 | .catch(exception => { 100 | console.log(exception); 101 | }); 102 | }; 103 | 104 | const setBackLed = function(brightness) { 105 | console.log('Set back led to ' + brightness); 106 | const did = 0x02; // Virtual device ID 107 | const cid = 0x21; // Set RGB LED Output command 108 | // Color command data: red, green, blue, flag 109 | const data = new Uint8Array([brightness]); 110 | return sendCommand(did, cid, data); 111 | }; 112 | 113 | // Code based on https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-toy-bb8/index.html 114 | const setColor = function(r, g, b) { 115 | console.log('Set color: r='+r+',g='+g+',b='+b); 116 | if (state.busy) { 117 | // Return if another operation pending 118 | return Promise.resolve(); 119 | } 120 | state.busy = true; 121 | const did = 0x02; // Virtual device ID 122 | const cid = 0x20; // Set RGB LED Output command 123 | // Color command data: red, green, blue, flag 124 | const data = new Uint8Array([r, g, b, 0]); 125 | sendCommand(did, cid, data).then(() => { 126 | state.busy = false; 127 | }) 128 | .catch(exception => { 129 | console.log(exception); 130 | }); 131 | }; 132 | 133 | // Code based on https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-toy-bb8/index.html 134 | const sendCommand = function(did, cid, data) { 135 | // Create client command packets 136 | // API docs: https://github.com/orbotix/DeveloperResources/blob/master/docs/Sphero_API_1.50.pdf 137 | // Next sequence number 138 | const seq = state.sequence & 0xFF; 139 | state.sequence += 1; 140 | // Start of packet #2 141 | let sop2 = 0xFC; 142 | sop2 |= 1; // Answer 143 | sop2 |= 2; // Reset timeout 144 | // Data length 145 | const dlen = data.byteLength + 1; 146 | const sum = data.reduce((a, b) => { 147 | return a + b; 148 | }); 149 | // Checksum 150 | const chk = ((sum + did + cid + seq + dlen) & 0xFF) ^ 0xFF; 151 | const checksum = new Uint8Array([chk]); 152 | const packets = new Uint8Array([0xFF, sop2, did, cid, seq, dlen]); 153 | // Append arrays: packet + data + checksum 154 | const array = new Uint8Array(packets.byteLength + data.byteLength + checksum.byteLength); 155 | array.set(packets, 0); 156 | array.set(data, packets.byteLength); 157 | array.set(checksum, packets.byteLength + data.byteLength); 158 | console.log('Sending', array); 159 | return controlCharacteristic.writeValue(array).then(() => { 160 | console.log('Command write done.'); 161 | }); 162 | }; 163 | 164 | // Code based on https://github.com/WebBluetoothCG/demos/blob/gh-pages/bluetooth-toy-bb8/index.html 165 | function connect() { 166 | if (!navigator.bluetooth) { 167 | console.log('Web Bluetooth API is not available.\n' + 168 | 'Please make sure the Web Bluetooth flag is enabled.'); 169 | return; 170 | } 171 | 172 | console.log('Requesting BB-8…'); 173 | 174 | const serviceA = '22bb746f-2bb0-7554-2d6f-726568705327'; 175 | const serviceB = '22bb746f-2ba0-7554-2d6f-726568705327'; 176 | const controlCharacteristicId = '22bb746f-2ba1-7554-2d6f-726568705327'; 177 | const antiDosCharacteristicId = '22bb746f-2bbd-7554-2d6f-726568705327'; 178 | const txPowerCharacteristicId = '22bb746f-2bb2-7554-2d6f-726568705327'; 179 | const wakeCpuCharacteristicId = '22bb746f-2bbf-7554-2d6f-726568705327'; 180 | navigator.bluetooth.requestDevice({ 181 | 'filters': [{ 'namePrefix': ['BB'] }], 182 | 'optionalServices': [ 183 | serviceA, 184 | serviceB 185 | ] 186 | }) 187 | .then(device => { 188 | console.log('Got device: ' + device.name); 189 | return device.gatt.connect(); 190 | }) 191 | .then(server => { 192 | console.log('Got server'); 193 | gattServer = server; 194 | return gattServer.getPrimaryService(serviceA); 195 | }) 196 | .then(service => { 197 | console.log('Got service'); 198 | // Developer mode sequence is sent to the radio service 199 | radioService = service; 200 | // Get Anti DOS characteristic 201 | return radioService.getCharacteristic(antiDosCharacteristicId); 202 | }) 203 | .then(characteristic => { 204 | console.log('> Found Anti DOS characteristic'); 205 | // Send special string 206 | let bytes = new Uint8Array('011i3'.split('').map(c => c.charCodeAt())); 207 | return characteristic.writeValue(bytes).then(() => { 208 | console.log('Anti DOS write done.'); 209 | }) 210 | }) 211 | .then(() => { 212 | // Get TX Power characteristic 213 | return radioService.getCharacteristic(txPowerCharacteristicId); 214 | }) 215 | .then(characteristic => { 216 | console.log('> Found TX Power characteristic'); 217 | const array = new Uint8Array([0x07]); 218 | return characteristic.writeValue(array).then(() => { 219 | console.log('TX Power write done.'); 220 | }) 221 | }) 222 | .then(() => { 223 | // Get Wake CPU characteristic 224 | return radioService.getCharacteristic(wakeCpuCharacteristicId); 225 | }) 226 | .then(characteristic => { 227 | console.log('> Found Wake CPU characteristic'); 228 | const array = new Uint8Array([0x01]); 229 | return characteristic.writeValue(array).then(() => { 230 | console.log('Wake CPU write done.'); 231 | }) 232 | }) 233 | .then(() => { 234 | // Get robot service 235 | return gattServer.getPrimaryService(serviceB) 236 | }) 237 | .then(service => { 238 | // Commands are sent to the robot service 239 | robotService = service; 240 | // Get Control characteristic 241 | return robotService.getCharacteristic(controlCharacteristicId); 242 | }) 243 | .then(characteristic => { 244 | console.log('> Found Control characteristic'); 245 | // Cache the characteristic 246 | controlCharacteristic = characteristic; 247 | return setColor(0, 250, 0); 248 | }) 249 | .catch(exception => { 250 | console.log(exception); 251 | }); 252 | }; 253 | 254 | elConnect.onclick = function() { 255 | connect(); 256 | }; 257 | 258 | elStop.onclick = function() { 259 | stopRolling(); 260 | }; 261 | 262 | elAim.onclick = function() { 263 | state.aim = !state.aim; 264 | if (state.aim) { 265 | setBackLed(0xff).then(() => setColor(0, 0, 0)); 266 | } else { 267 | setBackLed(0).then(() => setHeading(0)); 268 | } 269 | elAim.classList.toggle('active'); 270 | }; 271 | 272 | elRed.onclick = function() { 273 | setColor(255, 0, 0); 274 | }; 275 | 276 | elGreen.onclick = function() { 277 | setColor(0, 255, 0); 278 | }; 279 | 280 | elBlue.onclick = function() { 281 | setColor(0, 0, 255); 282 | }; 283 | 284 | elOff.onclick = function() { 285 | setColor(0, 0, 0); 286 | }; 287 | 288 | const radius = 150; 289 | 290 | const handleTouchEvent = function(event) { 291 | event.preventDefault(); 292 | if (event.targetTouches.length == 1) { 293 | const touch = event.targetTouches[0]; 294 | const x = touch.clientX - elJoypad.offsetLeft; 295 | const y = touch.pageY - elJoypad.offsetTop; 296 | // Notes: x and y are swapped here in order to get clockwise theta from Y-axis. 297 | const theta = Math.PI - Math.atan2(x - radius, y - radius); 298 | const degrees = theta * (180 / Math.PI); 299 | const tx = Math.abs(x - radius); 300 | const ty = Math.abs(y - radius); 301 | let speed = Math.sqrt(Math.pow(tx, 2) + Math.pow(ty, 2)); 302 | speed = speed / 150.0 * 255.0; 303 | console.log('event: ' + x + ', ' + y + ', d: ' + degrees + ' speed: ' + speed); 304 | if (state.aim) { 305 | roll(Math.round(degrees), 0, 1); 306 | } else { 307 | roll(Math.round(degrees), Math.round(speed), 1); 308 | } 309 | } 310 | }; 311 | 312 | elJoypad.ontouchstart = function(event) { 313 | handleTouchEvent(event); 314 | }; 315 | 316 | elJoypad.ontouchmove = function(event) { 317 | handleTouchEvent(event); 318 | } 319 | 320 | elJoypad.ontouchend = function(event) { 321 | event.preventDefault(); 322 | stopRolling(); 323 | }; 324 | 325 | }()); 326 | --------------------------------------------------------------------------------