Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 }); | |
| OLD | NEW |