28 |
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/joypad.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------