* Don't write characteristics until authenticated. *
* Will disconnect in a minute if you don't authenticate. */ private boolean authenticated = true; /** * The two keys used for authentication */ private BigInteger key1; private BigInteger key2; public NutSupport() { super(LOG); addSupportedService(NutConstants.SERVICE_BATTERY); addSupportedService(NutConstants.SERVICE_DEVICE_INFO); addSupportedService(NutConstants.SERVICE_IMMEDIATE_ALERT); addSupportedService(NutConstants.SERVICE_LINK_LOSS); addSupportedService(NutConstants.SERVICE_PROPRIETARY_NUT); addSupportedService(NutConstants.SERVICE_UNKNOWN_2); deviceInfoProfile = new DeviceInfoProfile<>(this); deviceInfoProfile.addListener(listener); addSupportedProfile(deviceInfoProfile); batteryInfoProfile = new BatteryInfoProfile<>(this); batteryInfoProfile.addListener(listener); addSupportedProfile(batteryInfoProfile); } private void handleBatteryInfo(BatteryInfo info) { LOG.info("Received Nut battery info"); batteryCmd.level = (short) info.getPercentCharged(); handleGBDeviceEvent(batteryCmd); } private void handleDeviceInfo(DeviceInfo info) { LOG.info("Received Nut device info"); LOG.info(String.valueOf(info)); GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo(); if (info.getHardwareRevision() != null) { versionInfo.hwVersion = info.getHardwareRevision(); } if (info.getFirmwareRevision() != null) { versionInfo.fwVersion = info.getFirmwareRevision(); } handleGBDeviceEvent(versionInfo); } @Override protected TransactionBuilder initializeDevice(TransactionBuilder builder) { builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); // Init prefs prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); loadKeysFromPrefs(); LOG.debug("Requesting device info!"); deviceInfoProfile.requestDeviceInfo(builder); batteryInfoProfile.requestBatteryInfo(builder); // If this characteristic exists, it has proprietary Nut interface this.proprietary = (getCharacteristic(NutConstants.CHARAC_AUTH_STATUS) != null); if (proprietary) { this.authenticated = false; /** * Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} * Enables proprietary notification */ builder.notify(getCharacteristic(NutConstants.CHARAC_AUTH_STATUS), true); LOG.info("Enabled authentication status notify"); /** * Part of {@link NutConstants.SERVICE_UNKNOWN_2} * Enables button-press notify */ builder.notify(getCharacteristic(NutConstants.CHARAC_UNKNOWN_2), true); } else { /** * Part of {@link NutConstants.SERVICE_UNKNOWN_1_WEIRDNESS} * Enables button-press notify */ builder.notify(getCharacteristic(NutConstants.CHARAC_CHANGE_POWER), true); } readDeviceInfo(); return builder; } @Override public boolean useAutoConnect() { return true; } @Override public void onFindDevice(boolean enable) { deviceImmediateAlert(enable); } @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (super.onCharacteristicChanged(gatt, characteristic)) { return true; } UUID characteristicUUID = characteristic.getUuid(); if (characteristicUUID.equals(NutConstants.CHARAC_AUTH_STATUS)) { handleAuthResult(characteristic.getValue()); return true; } LOG.info("Unhandled characteristic changed: " + characteristicUUID); return false; } @Override public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (super.onCharacteristicRead(gatt, characteristic, status)) { return true; } UUID characteristicUUID = characteristic.getUuid(); if (characteristicUUID.equals(NutConstants.CHARAC_SYSTEM_ID)) { // TODO: Handle System ID read return true; } LOG.info("Unhandled characteristic read: " + characteristicUUID); return false; } /** * Enables or disables link loss alert */ private void deviceLinkLossAlert(boolean enable) { UUID charac; if (this.proprietary) { /** Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} */ charac = NutConstants.CHARAC_CHANGE_POWER; } else { /** Part of {@link NutConstants.SERVICE_IMMEDIATE_ALERT} */ charac = NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL; } byte[] payload = new byte[]{(byte) (enable ? 0x00 : 0x01)}; if (enable) { writeCharacteristic("Enable link loss alert", charac, payload); } else { writeCharacteristic("Disable link loss alert", charac, payload); } } /** * Should trigger an immediate alert * * @param enable turn on or not */ private void deviceImmediateAlert(boolean enable) { UUID charac; if (this.proprietary) { /** Part of {@link NutConstants.SERVICE_IMMEDIATE_ALERT} */ charac = NutConstants.CHARAC_LINK_LOSS_ALERT_LEVEL; if (!authenticated) { LOG.warn("Not authenticated, can't alert"); return; } } else { /** Part of {@link NutConstants.SERVICE_PROPRIETARY_NUT} */ charac = NutConstants.CHARAC_CHANGE_POWER; } if (enable) { writeCharacteristic("Start alert", charac, new byte[]{(byte) 0x04}); } else { writeCharacteristic("Stop alert", charac, new byte[]{(byte) 0x03}); } } /** * This will write a new key to the device *
* However, it is irreversible, * if you can't generate the right packets, * the device is basically bricked! *
* If you can generate the correct packets,
* it can be reset... somehow
*
* @param key key
*/
private void deviceWriteNewKey(byte[] key) {
// TODO: Determine each nuance of how this
// works before using it!
byte[] result_payload = new byte[key.length + 1];
result_payload[0] = (byte) 0x04;
System.arraycopy(key, 0, result_payload, 1, key.length);
writeCharacteristic("Write new key",
NutConstants.CHARAC_DFU_PW,
result_payload);
}
/**
* Turns the device off
*/
private void deviceShutdown() {
writeCharacteristic("Shutdown", NutConstants.CHARAC_CHANGE_POWER, new byte[]{0x06});
}
/**
* Switches the device to Nordic's DFU mode
*/
private void deviceDFU() {
writeCharacteristic("Enable DFU mode", NutConstants.CHARAC_DFU_PW, new byte[]{0x14});
}
/**
* Specifies how long the alert lasts
*
* @param duration in seconds (I think)
*/
private void deviceWriteAlertDuration(int duration) {
if (duration == 0) {
duration = 15;
}
UUID charac;
if (this.proprietary) {
charac = NutConstants.CHARAC_DFU_PW;
} else {
charac = NutConstants.CHARAC_CHANGE_POWER;
}
writeCharacteristic("Write alert duration",
charac,
new byte[]{37, (byte) duration});
}
private void readHardwareInfo() {
BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_HARDWARE_VERSION);
if (characteristic != null &&
((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0)) {
readCharacteristic("Device read hardware",
NutConstants.CHARAC_HARDWARE_VERSION);
}
BluetoothGattCharacteristic characteristic1 = getCharacteristic(NutConstants.CHARAC_MANUFACTURER_NAME);
if (characteristic1 != null &&
(characteristic1.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
readCharacteristic("Read manufacturer",
NutConstants.CHARAC_MANUFACTURER_NAME);
}
}
private void readFirmwareInfo() {
BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_FIRMWARE_VERSION);
if (characteristic != null &&
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
readCharacteristic("Read firmware version",
NutConstants.CHARAC_FIRMWARE_VERSION);
}
}
private void readBatteryInfo() {
BluetoothGattCharacteristic characteristic = getCharacteristic(NutConstants.CHARAC_BATTERY_INFO);
if (characteristic != null &&
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
readCharacteristic("Read battery info",
NutConstants.CHARAC_BATTERY_INFO);
}
}
/**
* Loads the three keys from device-specific shared preferences
*/
private void loadKeysFromPrefs() {
if (prefs != null) {
LOG.info("Reading keys");
key1 = new BigInteger(prefs.getString("nut_packet_key_1", "0"));
key2 = new BigInteger(prefs.getString("nut_packet_key_2", "0"));
if (key1.equals(BigInteger.ZERO) || key2.equals(BigInteger.ZERO)) {
byte[] challenge = NutKey.hexStringToByteArrayNut(prefs.getString("nut_packet_challenge", "00"));
byte[] response = NutKey.hexStringToByteArrayNut(prefs.getString("nut_response_challenge", "00"));
if (Arrays.equals(challenge, new byte[]{0x00}) ||
Arrays.equals(response, new byte[]{0x00})) {
GB.toast("No key available for the device", Toast.LENGTH_LONG, GB.ERROR);
return;
}
Map.Entry