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

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: Add expectThrows, check thrown exceptions 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 /** @const {!Array<string>} */
14 var VALUE_DATA_TYPES = ['Hexadecimal', 'UTF-8', 'Decimal'];
15
16 /**
17 * A set of inputs that allow a user to request reads and writes of values.
18 * This control allows the value to be displayed in multiple forms
19 * as defined by the |VALUE_DATA_TYPES| array. Values must be written
20 * in these formats. Read and write capability is controlled by a
21 * 'permissions' bitfield provided by the characteristic.
22 * @constructor
23 */
24 var ValueControl = cr.ui.define('div');
25
26 ValueControl.prototype = {
27 __proto__: HTMLDivElement.prototype,
28
29 /**
30 * Decorates the element as a ValueControl. Creates the layout for the value
31 * control by creating a text input, select element, and two buttons for
32 * read/write requests. Event handlers are attached and references to these
33 * elements are stored for later use.
34 * @override
35 */
36 decorate: function() {
37 this.classList.add('value-control');
38
39 /** @private {!Array<number>} */
40 this.value_ = [];
41 /** @private {?string} */
42 this.deviceAddress_ = null;
43 /** @private {?string} */
44 this.serviceId_ = null;
45 /** @private {!interfaces.BluetoothDevice.CharacteristicInfo} */
46 this.characteristicInfo_ = null;
47
48 this.unavailableMessage_ = document.createElement('h3');
49 this.unavailableMessage_.textContent = 'Value cannot be read or written.';
50
51 this.valueInput_ = document.createElement('input');
52 this.valueInput_.addEventListener('change', function() {
53 try {
54 this.value_ = this.convertValueToArray_(this.valueInput_.value);
dpapad 2017/01/24 19:52:30 Why are we converting the input to an array, and t
mbrunson 2017/01/24 23:21:52 Having the array seemed to be a good middle ground
dpapad 2017/01/24 23:54:34 Trying to understand the tradeoffs here, but still
mbrunson 2017/01/25 02:43:36 Ok. I've added Value at the top of this file.
55 } catch (e) {
56 Snackbar.show(e.message, SnackbarType.ERROR);
57 }
58 }.bind(this));
59
60 this.typeSelect_ = document.createElement('select');
61
62 VALUE_DATA_TYPES.forEach(function(type) {
63 var option = document.createElement('option');
64 option.value = type;
65 option.text = type;
66 this.typeSelect_.add(option);
67 }, this);
68
69 this.typeSelect_.addEventListener('change', this.redraw.bind(this));
70
71 var inputDiv = document.createElement('div');
72 inputDiv.appendChild(this.valueInput_);
73 inputDiv.appendChild(this.typeSelect_);
74
75 this.readBtn_ = document.createElement('button');
76 this.readBtn_.textContent = 'Read';
77 this.readBtn_.addEventListener('click', this.readValue_.bind(this));
78
79 this.writeBtn_ = document.createElement('button');
80 this.writeBtn_.textContent = 'Write';
81 this.writeBtn_.addEventListener('click', this.writeValue_.bind(this));
82
83 var buttonsDiv = document.createElement('div');
84 buttonsDiv.appendChild(this.readBtn_);
85 buttonsDiv.appendChild(this.writeBtn_);
86
87 this.appendChild(this.unavailableMessage_);
88 this.appendChild(inputDiv);
89 this.appendChild(buttonsDiv);
90 },
91
92 /**
93 * Sets the settings used by the value control and redraws the control to
94 * match the read/write settings provided in
95 * |characteristicInfo.permissions|.
96 * @param {string} deviceAddress
97 * @param {string} serviceId
98 * @param {!interfaces.BluetoothDevice.CharacteristicInfo}
99 * characteristicInfo
100 */
101 load: function(deviceAddress, serviceId, characteristicInfo) {
102 this.deviceAddress_ = deviceAddress;
103 this.serviceId_ = serviceId;
104 this.characteristicInfo_ = characteristicInfo;
105
106 this.redraw();
107 },
108
109 /**
110 * Redraws the value control with updated layout depending on the
111 * availability of reads and writes and the current cached value.
112 */
113 redraw: function() {
114 this.readBtn_.hidden = (this.characteristicInfo_.properties &
115 interfaces.BluetoothDevice.Property.READ) === 0;
116 this.writeBtn_.hidden = (this.characteristicInfo_.properties &
117 interfaces.BluetoothDevice.Property.WRITE) === 0;
118
119 var isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden;
120 this.unavailableMessage_.hidden = isAvailable;
121 this.valueInput_.hidden = !isAvailable;
122 this.typeSelect_.hidden = !isAvailable;
123
124 if (!isAvailable) return;
125
126 var type = this.typeSelect_.selectedOptions[0].value;
127
128 switch (type) {
129 case 'Hexadecimal':
dpapad 2017/01/24 19:52:30 We are repeating those string literals multiple ti
mbrunson 2017/01/24 23:21:52 Done.
130 this.valueInput_.value = this.value_.reduce(
131 function(result, value, index) {
132 var answer = result + ('0' + value.toString(16)).substr(-2);
133 if (index === 0)
dpapad 2017/01/24 19:52:30 Nit: return index === 0 ? '0x' + answer : answer;
mbrunson 2017/01/24 23:21:52 Done.
134 return '0x' + answer;
135
136 return answer;
137 }, '');
138 break;
139
140 case 'UTF-8':
141 this.valueInput_.value = this.value_.reduce(function(result, value) {
142 return result + String.fromCharCode(value);
143 }, '');
144 break;
145
146 case 'Decimal':
147 this.valueInput_.value = this.value_.reduce(
148 function(result, value, index) {
dpapad 2017/01/24 19:52:30 Nit: return result + value.toString() + (index
mbrunson 2017/01/24 23:21:52 Done.
149 if (index === this.value_.length - 1)
150 return result + value.toString();
151 else
152 return result + value.toString() + '-';
153 }.bind(this), '');
154 break;
155 }
156 },
157
158 /**
159 * Sets the value of the control.
160 * @param {!Array<number>} value
161 */
162 setValue: function(value) {
163 this.value_ = value;
164 this.redraw();
165 },
166
167 /**
168 * Converts the current value of the text input into an array of numbers
169 * using the currently selected format in the type select element. The
170 * numbers are created such that they will not exceed 8-bits in length to be
171 * compatible with the expected data type of values set in a characteristic.
172 * @param {string} value
173 * @return {!Array<number>}
174 * @private
175 */
176 convertValueToArray_: function(value) {
177 if (!value)
178 return [];
179
180 var type = this.typeSelect_.selectedOptions[0].value;
dpapad 2017/01/24 19:52:30 Isn't this the same as this.typeSelect_.value?
mbrunson 2017/01/24 23:21:52 Done.
181 switch (type) {
182 case 'Hexadecimal':
183 if (!value.startsWith('0x'))
184 throw new Error('Expected value to start with "0x".');
185
186 var result = [];
187 for (var i = 2; i < value.length; i += 2) {
188 result.push(parseInt(value.substr(i, 2), 16));
189 }
190 return result;
191
192 case 'UTF-8':
193 return Array.from(value).map(function(char) {
194 return char.charCodeAt(0);
195 });
196
197 case 'Decimal':
198 if (!/^[0-9\-]*$/.test(value))
199 throw new Error('Value can only contain numbers and hyphens.');
200
201 return value.split('-').map(function(val) { return parseInt(val); });
202 }
203 },
204
205 /**
206 * Called when the read button is pressed. Connects to the device and
207 * retrieves the current value of the characteristic in the |service_id|
208 * with id |characteristic_id|
209 * @private
210 */
211 readValue_: function() {
212 this.readBtn_.disabled = true;
213
214 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
215 return device.readValueForCharacteristic(
216 this.serviceId_, this.characteristicInfo_.id);
217 }.bind(this)).then(function(response) {
218 this.readBtn_.disabled = false;
219 var GattResult = interfaces.BluetoothDevice.GattResult;
220
221 if (response.result === GattResult.SUCCESS) {
222 this.setValue(response.value);
223 Snackbar.show(
224 this.deviceAddress_ + ': Read succeeded', SnackbarType.SUCCESS);
225 return;
226 }
227
228 // TODO(crbug.com/663394): Replace with more descriptive error
229 // messages.
230 var errorString = Object.keys(GattResult).find(function(key) {
231 return GattResult[key] === response.result;
232 });
233
234 Snackbar.show(
235 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
236 'Retry', this.readValue_.bind(this));
237 }.bind(this));
238 },
239
240 /**
241 * Called when the write button is pressed. Connects to the device and
242 * retrieves the current value of the characteristic in the |service_id|
243 * with id |characteristic_id|
244 * @private
245 */
246 writeValue_: function() {
247 this.writeBtn_.disabled = true;
248
249 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
250 return device.writeValueForCharacteristic(
251 this.serviceId_, this.characteristicInfo_.id, this.value_);
252 }.bind(this)).then(function(response) {
253 this.writeBtn_.disabled = false;
254 var GattResult = interfaces.BluetoothDevice.GattResult;
255
256 if (response.result === GattResult.SUCCESS) {
257 Snackbar.show(
258 this.deviceAddress_ + ': Write succeeded', SnackbarType.SUCCESS);
259 return;
260 }
261
262 // TODO(crbug.com/663394): Replace with more descriptive error
263 // messages.
264 var errorString = Object.keys(GattResult).find(function(key) {
265 return GattResult[key] === response.result;
266 });
267
268 Snackbar.show(
269 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
270 'Retry', this.writeValue_.bind(this));
271 }.bind(this));
272 },
273 }
274
275 return {
276 ValueControl: ValueControl,
277 };
278 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698