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

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: DCHECK device 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 this.value_ = this.convertValueToArray_();
54 }.bind(this));
55
56 this.typeSelect_ = document.createElement('select');
57
58 VALUE_DATA_TYPES.forEach(function(type) {
59 var option = document.createElement('option');
60 option.value = type;
61 option.text = type;
62 this.typeSelect_.add(option);
63 }, this);
64
65 this.typeSelect_.addEventListener('change', this.redraw.bind(this));
66
67 var inputDiv = document.createElement('div');
68 inputDiv.appendChild(this.valueInput_);
69 inputDiv.appendChild(this.typeSelect_);
70
71 this.readBtn_ = document.createElement('button');
72 this.readBtn_.textContent = 'Read';
73 this.readBtn_.addEventListener('click', this.readValue_.bind(this));
74
75 this.writeBtn_ = document.createElement('button');
76 this.writeBtn_.textContent = 'Write';
77 this.writeBtn_.addEventListener('click', this.writeValue_.bind(this));
78
79 var buttonsDiv = document.createElement('div');
80 buttonsDiv.appendChild(this.readBtn_);
81 buttonsDiv.appendChild(this.writeBtn_);
82
83 this.appendChild(this.unavailableMessage_);
84 this.appendChild(inputDiv);
85 this.appendChild(buttonsDiv);
86 },
87
88 /**
89 * Sets the settings used by the value control and redraws the control to
90 * match the read/write settings provided in
91 * |characteristicInfo.permissions|.
92 * @param {string} deviceAddress
93 * @param {string} serviceId
94 * @param {!interfaces.BluetoothDevice.CharacteristicInfo}
95 * characteristicInfo
96 */
97 load: function(deviceAddress, serviceId, characteristicInfo) {
98 this.deviceAddress_ = deviceAddress;
99 this.serviceId_ = serviceId;
100 this.characteristicInfo_ = characteristicInfo;
101
102 this.redraw();
103 },
104
105 /**
106 * Redraws the value control with updated layout depending on the
107 * availability of reads and writes and the current cached value.
108 */
109 redraw: function() {
110 this.readBtn_.hidden = (this.characteristicInfo_.properties &
111 interfaces.BluetoothDevice.Property.READ) === 0;
112 this.writeBtn_.hidden = (this.characteristicInfo_.properties &
113 interfaces.BluetoothDevice.Property.WRITE) === 0;
114
115 var isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden;
116 this.unavailableMessage_.hidden = isAvailable;
117 this.valueInput_.hidden = !isAvailable;
118 this.typeSelect_.hidden = !isAvailable;
119
120 if (!isAvailable) return;
121
122 var type = this.typeSelect_.selectedOptions[0].value;
123
124 switch (type) {
125 case 'Hexadecimal':
126 this.valueInput_.value = this.value_.reduce(
127 function(result, value, index) {
128 var answer = result + ('0' + value.toString(16)).substr(-2);
129 if (index === 0)
130 return '0x' + answer;
131
132 return answer;
133 }, '');
ortuno 2017/01/22 22:38:01 Why not make this '0x'? instead of having checking
mbrunson 2017/01/24 00:25:31 What if |value_| is empty? I didn't want '0x' prin
134 break;
135
136 case 'UTF-8':
137 this.valueInput_.value = this.value_.reduce(function(result, value) {
138 return result + String.fromCharCode(value);
139 }, '');
140 break;
141
142 case 'Decimal':
143 this.valueInput_.value = this.value_.reduce(
144 function(result, value, index) {
145 if (index === this.value_.length - 1)
146 return result + value.toString();
147 else
148 return result + value.toString() + '-';
149 }.bind(this), '');
150 break;
151 }
152 },
153
154 /**
155 * Sets the value of the control.
156 * @param {!Array<number>} value
157 */
158 setValue: function(value) {
159 this.value_ = value;
160 this.redraw();
161 },
162
163 /**
164 * Converts the current value of the text input into an array of numbers
165 * using the currently selected format in the type select element. The
166 * numbers are created such that they will not exceed 8-bits in length to be
167 * compatible with the expected data type of values set in a characteristic.
168 * @return {!Array<number>}
169 * @private
170 */
171 convertValueToArray_: function() {
172 var value = this.valueInput_.value;
173 if (!value) return;
174
175 var type = this.typeSelect_.selectedOptions[0].value;
176 switch (type) {
177 case 'Hexadecimal':
178 value = value.replace('0x', '');
ortuno 2017/01/22 22:38:01 We should show an error if the string in value is
mbrunson 2017/01/24 00:25:31 Done.
179 return Array.from(value).reduce(function(result, value, index) {
ortuno 2017/01/22 22:38:01 I appreciate the use of reduce but I think that ju
mbrunson 2017/01/24 00:25:31 Done.
180 if (index % 2 == 0)
181 result.push(parseInt(value, 16) << 4);
182 else
183 result[Math.floor(index/2)] += parseInt(value, 16);
184 return result;
ortuno 2017/01/22 22:38:01 What's our current policy on tests? Should we have
mbrunson 2017/01/24 00:25:31 Added tests. Caught some issues. :-) Done.
185 }, []);
186
187 case 'UTF-8':
188 return Array.from(value).map(function(char) {
189 return char.charCodeAt(0);
190 });
191
192 case 'Decimal':
193 return value.split('-').map(parseInt);
194 }
195 },
196
197 /**
198 * Called when the read button is pressed. Connects to the device and
199 * retrieves the current value of the characteristic in the |service_id|
200 * with id |characteristic_id|
201 * @private
202 */
203 readValue_: function() {
204 this.readBtn_.disabled = true;
205
206 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
ortuno 2017/01/22 22:38:01 Why do we need to connect? Can't we just check if
mbrunson 2017/01/24 00:25:31 The connectToDevice function does that internally.
207 return device.readValueForCharacteristic(
208 this.serviceId_, this.characteristicInfo_.id);
209 }.bind(this)).then(function(response) {
210 this.readBtn_.disabled = false;
211 var GattResult = interfaces.BluetoothDevice.GattResult;
212
213 if (response.result === GattResult.SUCCESS) {
214 this.setValue(response.value);
215 Snackbar.show(
216 this.deviceAddress_ + ': Read succeeded', SnackbarType.SUCCESS);
217 return;
218 }
219
220 // TODO(crbug.com/663394): Replace with more descriptive error
221 // messages.
222 var errorString = Object.keys(GattResult).find(function(key) {
223 return GattResult[key] === response.result;
224 });
225
226 Snackbar.show(
227 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
228 'Retry', this.readValue_.bind(this));
229 }.bind(this));
230 },
231
232 /**
233 * Called when the write button is pressed. Connects to the device and
234 * retrieves the current value of the characteristic in the |service_id|
235 * with id |characteristic_id|
236 * @private
237 */
238 writeValue_: function() {
239 this.writeBtn_.disabled = true;
240
241 device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
242 return device.writeValueForCharacteristic(
243 this.serviceId_, this.characteristicInfo_.id, this.value_);
244 }.bind(this)).then(function(response) {
245 this.writeBtn_.disabled = false;
246 var GattResult = interfaces.BluetoothDevice.GattResult;
247
248 if (response.result === GattResult.SUCCESS) {
249 Snackbar.show(
250 this.deviceAddress_ + ': Write succeeded', SnackbarType.SUCCESS);
251 return;
252 }
253
254 // TODO(crbug.com/663394): Replace with more descriptive error
255 // messages.
256 var errorString = Object.keys(GattResult).find(function(key) {
257 return GattResult[key] === response.result;
258 });
259
260 Snackbar.show(
261 this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
262 'Retry', this.writeValue_.bind(this));
263 }.bind(this));
264 },
265 }
266
267 return {
268 ValueControl: ValueControl,
269 };
270 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698