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

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 error in value input change listener Created 3 years, 11 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 ValueDataType = {
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 * @param {!Array<number>} newValue
44 */
45 setArray: function(newValue) {
46 this.value_ = newValue;
47 },
48
49 /**
50 * Sets the value by converting the |newValue| string using the formatting
51 * specified by |valueDataType|.
52 * @param {!ValueDataType} valueDataType
53 * @param {string} newValue
54 */
55 setAs: function(valueDataType, newValue) {
56 switch (valueDataType) {
57 case ValueDataType.HEXADECIMAL:
58 this.setValueFromHex_(newValue);
59 break;
60
61 case ValueDataType.UTF8:
62 this.setValueFromUTF8_(newValue);
63 break;
64
65 case ValueDataType.DECIMAL:
66 this.setValueFromDecimal_(newValue);
67 break;
68 }
69 },
70
71 /**
72 * Gets the value as a string representing the given |valueDataType|.
73 * @param {!ValueDataType} valueDataType
74 * @return {string}
75 */
76 getAs: function(valueDataType) {
77 switch (valueDataType) {
78 case ValueDataType.HEXADECIMAL:
79 return this.toHex_();
80 break;
dpapad 2017/01/25 23:50:20 Since you are returning in previous line, this bre
mbrunson 2017/01/26 19:16:26 Done.
81
82 case ValueDataType.UTF8:
83 return this.toUTF8_();
84 break;
85
86 case ValueDataType.DECIMAL:
87 return this.toDecimal_();
88 break;
89 }
90 },
91
92 /**
93 * Converts the value to a hex string.
94 * @return {string}
95 * @private
96 */
97 toHex_: function() {
98 if (this.value_.length == 0)
99 return '';
100
101 return this.value_.reduce(function(result, value, index) {
102 return result + ('0' + value.toString(16)).substr(-2);
103 }, '0x');
104 },
105
106 /**
107 * Sets the value from a hex string.
108 * @return {string}
109 * @private
110 */
111 setValueFromHex_: function(newValue) {
112 if (!newValue) {
113 this.value_ = [];
114 return;
115 }
116
117 if (!newValue.startsWith('0x'))
118 throw new Error('Expected new value to start with "0x".');
119
120 var result = [];
121 for (var i = 2; i < newValue.length; i += 2) {
122 result.push(parseInt(newValue.substr(i, 2), 16));
123 }
124
125 this.value_ = result;
126 },
127
128 /**
129 * Converts the value to a UTF-8 encoded text string.
130 * @return {string}
131 * @private
132 */
133 toUTF8_: function() {
134 return this.value_.reduce(function(result, value) {
135 return result + String.fromCharCode(value);
136 }, '');
137 },
138
139 /**
140 * Sets the value from a UTF-8 encoded text string.
141 * @return {string}
142 * @private
143 */
144 setValueFromUTF8_: function(newValue) {
145 if (!newValue) {
146 this.value_ = [];
147 return;
148 }
149
150 this.value_ = Array.from(newValue).map(function(char) {
151 return char.charCodeAt(0);
152 });
153 },
154
155 /**
156 * Converts the value to a decimal string with numbers delimited by '-'.
157 * @return {string}
158 * @private
159 */
160 toDecimal_: function() {
161 return this.value_.join('-');
162 },
163
164 /**
165 * Sets the value from a decimal string delimited by '-'.
166 * @return {string}
167 * @private
168 */
169 setValueFromDecimal_: function(newValue) {
170 if (!newValue) {
171 this.value_ = [];
172 return;
173 }
174
175 if (!/^[0-9\-]*$/.test(newValue))
176 throw new Error('New value can only contain numbers and hyphens.');
177
178 this.value_ = newValue.split('-').map(function(val) {
179 return parseInt(val, 10);
180 });
181 },
182 };
183
184 /**
185 * A set of inputs that allow a user to request reads and writes of values.
186 * This control allows the value to be displayed in multiple forms
187 * as defined by the |ValueDataType| array. Values must be written
188 * in these formats. Read and write capability is controlled by a
189 * 'properties' bitfield provided by the characteristic.
190 * @constructor
191 */
192 var ValueControl = cr.ui.define('div');
193
194 ValueControl.prototype = {
195 __proto__: HTMLDivElement.prototype,
196
197 /**
198 * Decorates the element as a ValueControl. Creates the layout for the value
199 * control by creating a text input, select element, and two buttons for
200 * read/write requests. Event handlers are attached and references to these
201 * elements are stored for later use.
202 * @override
203 */
204 decorate: function() {
205 this.classList.add('value-control');
206
207 /** @private {!Value} */
208 this.value_ = new Value([]);
209 /** @private {?string} */
210 this.deviceAddress_ = null;
211 /** @private {?string} */
212 this.serviceId_ = null;
213 /** @private {?interfaces.BluetoothDevice.CharacteristicInfo} */
214 this.characteristicInfo_ = null;
215
216 this.unavailableMessage_ = document.createElement('h3');
217 this.unavailableMessage_.textContent = 'Value cannot be read or written.';
218
219 this.valueInput_ = document.createElement('input');
220 this.valueInput_.addEventListener('change', function() {
221 try {
222 this.value_.setAs(this.typeSelect_.value, this.valueInput_.value);
223 } catch (e) {
224 Snackbar.show(e.message, SnackbarType.ERROR);
225 }
226 }.bind(this));
227
228 this.typeSelect_ = document.createElement('select');
229
230 Object.keys(ValueDataType).forEach(function(key) {
231 var type = ValueDataType[key];
232 var option = document.createElement('option');
233 option.value = type;
234 option.text = type;
235 this.typeSelect_.add(option);
236 }, this);
237
238 this.typeSelect_.addEventListener('change', this.redraw.bind(this));
239
240 var inputDiv = document.createElement('div');
241 inputDiv.appendChild(this.valueInput_);
242 inputDiv.appendChild(this.typeSelect_);
243
244 this.readBtn_ = document.createElement('button');
245 this.readBtn_.textContent = 'Read';
246 this.readBtn_.addEventListener('click', this.readValue_.bind(this));
247
248 this.writeBtn_ = document.createElement('button');
249 this.writeBtn_.textContent = 'Write';
250 this.writeBtn_.addEventListener('click', this.writeValue_.bind(this));
251
252 var buttonsDiv = document.createElement('div');
253 buttonsDiv.appendChild(this.readBtn_);
254 buttonsDiv.appendChild(this.writeBtn_);
255
256 this.appendChild(this.unavailableMessage_);
257 this.appendChild(inputDiv);
258 this.appendChild(buttonsDiv);
259 },
260
261 /**
262 * Sets the settings used by the value control and redraws the control to
263 * match the read/write settings provided in
264 * |characteristicInfo.properties|.
265 * @param {string} deviceAddress
266 * @param {string} serviceId
267 * @param {!interfaces.BluetoothDevice.CharacteristicInfo}
268 * characteristicInfo
269 */
270 load: function(deviceAddress, serviceId, characteristicInfo) {
271 this.deviceAddress_ = deviceAddress;
272 this.serviceId_ = serviceId;
273 this.characteristicInfo_ = characteristicInfo;
274
275 this.redraw();
276 },
277
278 /**
279 * Redraws the value control with updated layout depending on the
280 * availability of reads and writes and the current cached value.
281 */
282 redraw: function() {
283 this.readBtn_.hidden = (this.characteristicInfo_.properties &
284 interfaces.BluetoothDevice.Property.READ) === 0;
285 this.writeBtn_.hidden = (this.characteristicInfo_.properties &
286 interfaces.BluetoothDevice.Property.WRITE) === 0;
287
288 var isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden;
289 this.unavailableMessage_.hidden = isAvailable;
290 this.valueInput_.hidden = !isAvailable;
291 this.typeSelect_.hidden = !isAvailable;
292
293 if (!isAvailable)
294 return;
295
296 this.valueInput_.value = this.value_.getAs(this.typeSelect_.value);
297 },
298
299 /**
300 * Sets the value of the control.
301 * @param {!Array<number>} value
302 */
303 setValue: function(value) {
304 this.value_.setArray(value);
305 this.redraw();
306 },
307
308 /**
309 * Gets an error string describing the given |result| code.
310 * @param {!interfaces.BluetoothDevice.GattResult} result
311 * @private
312 */
313 getErrorString_: function(result) {
314 // TODO(crbug.com/663394): Replace with more descriptive error
315 // messages.
316 var GattResult = interfaces.BluetoothDevice.GattResult;
317 return Object.keys(GattResult).find(function(key) {
318 return GattResult[key] === result;
319 });
320 },
321
322 /**
323 * Called when the read button is pressed. Connects to the device and
324 * retrieves the current value of the characteristic in the |service_id|
325 * with id |characteristic_id|
326 * @private
327 */
328 readValue_: function() {
329 this.readBtn_.disabled = true;
330
331 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
332 return device.readValueForCharacteristic(
333 this.serviceId_, this.characteristicInfo_.id);
334 }.bind(this)).then(function(response) {
335 this.readBtn_.disabled = false;
336
337 if (response.result === interfaces.BluetoothDevice.GattResult.SUCCESS) {
338 this.setValue(response.value);
339 Snackbar.show(
340 this.deviceAddress_ + ': Read succeeded', SnackbarType.SUCCESS);
341 return;
342 }
343
344 var errorString = this.getErrorString_(response.result);
345 Snackbar.show(
346 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
347 'Retry', this.readValue_.bind(this));
348 }.bind(this));
349 },
350
351 /**
352 * Called when the write button is pressed. Connects to the device and
353 * retrieves the current value of the characteristic in the |service_id|
354 * with id |characteristic_id|
355 * @private
356 */
357 writeValue_: function() {
358 this.writeBtn_.disabled = true;
359
360 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
361 return device.writeValueForCharacteristic(
362 this.serviceId_, this.characteristicInfo_.id,
363 this.value_.getArray());
364 }.bind(this)).then(function(response) {
365 this.writeBtn_.disabled = false;
366
367 if (response.result === interfaces.BluetoothDevice.GattResult.SUCCESS) {
368 Snackbar.show(
369 this.deviceAddress_ + ': Write succeeded', SnackbarType.SUCCESS);
370 return;
371 }
372
373 var errorString = this.getErrorString_(response.result);
374 Snackbar.show(
375 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
376 'Retry', this.writeValue_.bind(this));
377 }.bind(this));
378 },
379 }
380
381 return {
382 ValueControl: ValueControl,
383 ValueDataType: ValueDataType,
384 };
385 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698