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

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

Powered by Google App Engine
This is Rietveld 408576698