Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: chrome/browser/resources/bluetooth_internals/value_control.js

Issue 2627243002: bluetooth: Add control for reading/writing of characteristics to internals page. (Closed)
Patch Set: Fix comments referring to permissions Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * Javascript for ValueControl, served from chrome://bluetooth-internals/.
7 */
8
9 cr.define('value_control', function() {
10 /** @const */ var Snackbar = snackbar.Snackbar;
11 /** @const */ var SnackbarType = snackbar.SnackbarType;
12
13 /** @enum {string} */
14 var ValueDataTypes = {
15 HEXADECIMAL: 'Hexadecimal',
16 UTF8: 'UTF-8',
17 DECIMAL: 'Decimal',
18 };
19
20 /**
21 * A container for an array value that needs to be converted to multiple
22 * display formats. Internally, the value is stored as an array and converted
23 * to the needed display type at runtime.
24 * @constructor
25 * @param {!Array<number>} initialValue
26 */
27 function Value(initialValue) {
28 /** @private {!Array<number>} */
29 this.value_ = initialValue;
30 }
31
32 Value.prototype = {
33 /**
34 * Gets the backing array value.
35 * @return {!Array<number>}
36 */
37 getArray: function() {
38 return this.value_;
39 },
40
41 /**
42 * Sets the backing array value.
43 * @return {!Array<number>}
44 */
45 setArray: function(newValue) {
46 this.value_ = newValue;
47 },
48
49 /**
50 * Converts the value to a hex string.
51 * @return {string}
52 */
53 toHex: function() {
54 return this.value_.reduce(function(result, value, index) {
55 var answer = result + ('0' + value.toString(16)).substr(-2);
56 return index === 0 ? '0x' + answer : answer;
57 }, '');
dpapad 2017/01/25 19:20:06 Can we use the reduce's initial value instead of c
mbrunson 2017/01/25 22:14:44 Done.
58 },
59
60 /**
61 * Sets the value from a hex string.
62 * @return {string}
63 */
64 setValueFromHex: function(newValue) {
65 if (!newValue) {
66 this.value_ = [];
67 return;
68 }
69
70 if (!newValue.startsWith('0x'))
71 throw new Error('Expected new value to start with "0x".');
72
73 var result = [];
74 for (var i = 2; i < newValue.length; i += 2) {
75 result.push(parseInt(newValue.substr(i, 2), 16));
76 }
77
78 this.value_ = result;
79 },
80
81 /**
82 * Converts the value to a UTF-8 encoded text string.
83 * @return {string}
84 */
85 toUTF8: function() {
86 return this.value_.reduce(function(result, value) {
87 return result + String.fromCharCode(value);
88 }, '');
89 },
90
91 /**
92 * Sets the value from a UTF-8 encoded text string.
93 * @return {string}
94 */
95 setValueFromUTF8: function(newValue) {
96 if (!newValue) {
97 this.value_ = [];
98 return;
99 }
100
101 this.value_ = Array.from(newValue).map(function(char) {
102 return char.charCodeAt(0);
103 });
104 },
105
106 /**
107 * Converts the value to a decimal string with numbers delimited by '-'.
108 * @return {string}
109 */
110 toDecimal: function() {
111 return this.value_.reduce(function(result, value, index) {
112 return result + value.toString() +
113 (index === this.value_.length - 1 ? '' : '-');
114 }.bind(this), '');
115 },
116
117 /**
118 * Sets the value from a decimal string delimited by '-'.
119 * @return {string}
120 */
121 setValueFromDecimal: function(newValue) {
122 if (!newValue) {
123 this.value_ = [];
124 return;
125 }
126
127 if (!/^[0-9\-]*$/.test(newValue))
128 throw new Error('New value can only contain numbers and hyphens.');
129
130 this.value_ = newValue.split('-').map(function(val) {
131 return parseInt(val);
dpapad 2017/01/25 19:20:07 Perhaps be more explicit, return parseInt(val, 10
mbrunson 2017/01/25 22:14:44 Done.
132 });
133 },
134 };
135
136 /**
137 * A set of inputs that allow a user to request reads and writes of values.
138 * This control allows the value to be displayed in multiple forms
139 * as defined by the |ValueDataTypes| array. Values must be written
140 * in these formats. Read and write capability is controlled by a
141 * 'properties' bitfield provided by the characteristic.
142 * @constructor
143 */
144 var ValueControl = cr.ui.define('div');
145
146 ValueControl.prototype = {
147 __proto__: HTMLDivElement.prototype,
148
149 /**
150 * Decorates the element as a ValueControl. Creates the layout for the value
151 * control by creating a text input, select element, and two buttons for
152 * read/write requests. Event handlers are attached and references to these
153 * elements are stored for later use.
154 * @override
155 */
156 decorate: function() {
157 this.classList.add('value-control');
158
159 /** @private {!Value} */
160 this.value_ = new Value([]);
161 /** @private {?string} */
162 this.deviceAddress_ = null;
163 /** @private {?string} */
164 this.serviceId_ = null;
165 /** @private {?interfaces.BluetoothDevice.CharacteristicInfo} */
166 this.characteristicInfo_ = null;
167
168 this.unavailableMessage_ = document.createElement('h3');
169 this.unavailableMessage_.textContent = 'Value cannot be read or written.';
170
171 this.valueInput_ = document.createElement('input');
172 this.valueInput_.addEventListener('change', function() {
173 try {
174 switch (this.typeSelect_.value) {
dpapad 2017/01/25 19:20:06 Optional nit: Can we move this switch logic within
mbrunson 2017/01/25 22:14:44 Done.
175 case ValueDataTypes.HEXADECIMAL:
176 this.value_.setValueFromHex(this.valueInput_.value);
177 break;
178
179 case ValueDataTypes.UTF8:
180 this.value_.setValueFromUTF8(this.valueInput_.value);
181 break;
182
183 case ValueDataTypes.DECIMAL:
184 this.value_.setValueFromDecimal(this.valueInput_.value);
185 break;
186 }
187 } catch (e) {
188 Snackbar.show(e.message, SnackbarType.ERROR);
189 }
190 }.bind(this));
191
192 this.typeSelect_ = document.createElement('select');
193
194 Object.keys(ValueDataTypes).forEach(function(key) {
195 var type = ValueDataTypes[key];
196 var option = document.createElement('option');
197 option.value = type;
198 option.text = type;
199 this.typeSelect_.add(option);
200 }, this);
201
202 this.typeSelect_.addEventListener('change', this.redraw.bind(this));
203
204 var inputDiv = document.createElement('div');
205 inputDiv.appendChild(this.valueInput_);
206 inputDiv.appendChild(this.typeSelect_);
207
208 this.readBtn_ = document.createElement('button');
209 this.readBtn_.textContent = 'Read';
210 this.readBtn_.addEventListener('click', this.readValue_.bind(this));
211
212 this.writeBtn_ = document.createElement('button');
213 this.writeBtn_.textContent = 'Write';
214 this.writeBtn_.addEventListener('click', this.writeValue_.bind(this));
215
216 var buttonsDiv = document.createElement('div');
217 buttonsDiv.appendChild(this.readBtn_);
218 buttonsDiv.appendChild(this.writeBtn_);
219
220 this.appendChild(this.unavailableMessage_);
221 this.appendChild(inputDiv);
222 this.appendChild(buttonsDiv);
223 },
224
225 /**
226 * Sets the settings used by the value control and redraws the control to
227 * match the read/write settings provided in
228 * |characteristicInfo.properties|.
229 * @param {string} deviceAddress
230 * @param {string} serviceId
231 * @param {!interfaces.BluetoothDevice.CharacteristicInfo}
232 * characteristicInfo
233 */
234 load: function(deviceAddress, serviceId, characteristicInfo) {
235 this.deviceAddress_ = deviceAddress;
236 this.serviceId_ = serviceId;
237 this.characteristicInfo_ = characteristicInfo;
238
239 this.redraw();
240 },
241
242 /**
243 * Redraws the value control with updated layout depending on the
244 * availability of reads and writes and the current cached value.
245 */
246 redraw: function() {
247 this.readBtn_.hidden = (this.characteristicInfo_.properties &
248 interfaces.BluetoothDevice.Property.READ) === 0;
249 this.writeBtn_.hidden = (this.characteristicInfo_.properties &
250 interfaces.BluetoothDevice.Property.WRITE) === 0;
251
252 var isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden;
253 this.unavailableMessage_.hidden = isAvailable;
254 this.valueInput_.hidden = !isAvailable;
255 this.typeSelect_.hidden = !isAvailable;
256
257 if (!isAvailable)
258 return;
259
260 var type = this.typeSelect_.selectedOptions[0].value;
dpapad 2017/01/25 19:20:06 Equivalent to this.typeSelect_.value
mbrunson 2017/01/25 22:14:44 Done.
261
262 switch (type) {
dpapad 2017/01/25 19:20:07 Similar optional nit here. Perhaps move this switc
mbrunson 2017/01/25 22:14:44 Done.
263 case ValueDataTypes.HEXADECIMAL:
264 this.valueInput_.value = this.value_.toHex();
265 break;
266
267 case ValueDataTypes.UTF8:
268 this.valueInput_.value = this.value_.toUTF8();
269 break;
270
271 case ValueDataTypes.DECIMAL:
272 this.valueInput_.value = this.value_.toDecimal();
273 break;
274 }
275 },
276
277 /**
278 * Sets the value of the control.
279 * @param {!Array<number>} value
280 */
281 setValue: function(value) {
282 this.value_.setArray(value);
283 this.redraw();
284 },
285
286 /**
287 * Called when the read button is pressed. Connects to the device and
288 * retrieves the current value of the characteristic in the |service_id|
289 * with id |characteristic_id|
290 * @private
291 */
292 readValue_: function() {
293 this.readBtn_.disabled = true;
294
295 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
296 return device.readValueForCharacteristic(
297 this.serviceId_, this.characteristicInfo_.id);
298 }.bind(this)).then(function(response) {
299 this.readBtn_.disabled = false;
300 var GattResult = interfaces.BluetoothDevice.GattResult;
301
302 if (response.result === GattResult.SUCCESS) {
303 this.setValue(response.value);
304 Snackbar.show(
305 this.deviceAddress_ + ': Read succeeded', SnackbarType.SUCCESS);
306 return;
307 }
308
309 // TODO(crbug.com/663394): Replace with more descriptive error
310 // messages.
311 var errorString = Object.keys(GattResult).find(function(key) {
312 return GattResult[key] === response.result;
313 });
314
315 Snackbar.show(
316 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
317 'Retry', this.readValue_.bind(this));
318 }.bind(this));
319 },
320
321 /**
322 * Called when the write button is pressed. Connects to the device and
323 * retrieves the current value of the characteristic in the |service_id|
324 * with id |characteristic_id|
325 * @private
326 */
327 writeValue_: function() {
328 this.writeBtn_.disabled = true;
329
330 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
331 return device.writeValueForCharacteristic(
332 this.serviceId_, this.characteristicInfo_.id,
333 this.value_.getArray());
334 }.bind(this)).then(function(response) {
335 this.writeBtn_.disabled = false;
336 var GattResult = interfaces.BluetoothDevice.GattResult;
337
338 if (response.result === GattResult.SUCCESS) {
339 Snackbar.show(
340 this.deviceAddress_ + ': Write succeeded', SnackbarType.SUCCESS);
341 return;
342 }
343
344 // TODO(crbug.com/663394): Replace with more descriptive error
345 // messages.
346 var errorString = Object.keys(GattResult).find(function(key) {
dpapad 2017/01/25 19:20:07 Can we make a helper getErrorString_(result) and r
mbrunson 2017/01/25 22:14:44 Done.
347 return GattResult[key] === response.result;
348 });
349
350 Snackbar.show(
351 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
352 'Retry', this.writeValue_.bind(this));
353 }.bind(this));
354 },
355 }
356
357 return {
358 ValueControl: ValueControl,
359 };
360 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698