/* Copyright (C) 2024 Jonathan Gobbo This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Configuration.Config; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Configuration.StrengthTarget; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Gestures.InteractionType; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmibuds5pro.prefs.Gestures.Position; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Authentication; import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Message; import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.MessageType; import nodomain.freeyourgadget.gadgetbridge.service.devices.redmibuds5pro.protocol.Opcode; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class RedmiBuds5ProProtocol extends GBDeviceProtocol { private static final Logger LOG = LoggerFactory.getLogger(RedmiBuds5ProProtocol.class); final UUID UUID_DEVICE_CTRL = UUID.fromString("0000fd2d-0000-1000-8000-00805f9b34fb"); private byte sequenceNumber = 0; protected RedmiBuds5ProProtocol(GBDevice device) { super(device); } public byte[] encodeStartAuthentication() { byte[] authRnd = Authentication.getRandomChallenge(); LOG.debug("[AUTH] Sending challenge: {}", hexdump(authRnd)); byte[] payload = new byte[17]; payload[0] = 0x01; System.arraycopy(authRnd, 0, payload, 1, 16); return new Message(MessageType.PHONE_REQUEST, Opcode.AUTH_CHALLENGE, sequenceNumber++, payload).encode(); } @Override public byte[] encodeSendConfiguration(String config) { switch (config) { case PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL: return encodeSetAmbientSoundControl(); case PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH: return encodeSetEffectStrength(config, StrengthTarget.ANC); case PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH: return encodeSetEffectStrength(config, StrengthTarget.TRANSPARENCY); case PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING: return encodeSetBooleanConfig(config, Config.ADAPTIVE_ANC); // case PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING: // return encodeSetBooleanConfig(config, Config.CUSTOMIZED_ANC); case PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT: return encodeSetGesture(config, InteractionType.SINGLE, Position.LEFT); case PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT: return encodeSetGesture(config, InteractionType.SINGLE, Position.RIGHT); case PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT: return encodeSetGesture(config, InteractionType.DOUBLE, Position.LEFT); case PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT: return encodeSetGesture(config, InteractionType.DOUBLE, Position.RIGHT); case PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT: return encodeSetGesture(config, InteractionType.TRIPLE, Position.LEFT); case PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT: return encodeSetGesture(config, InteractionType.TRIPLE, Position.RIGHT); case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT: return encodeSetGesture(config, InteractionType.LONG, Position.LEFT); case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT: return encodeSetGesture(config, InteractionType.LONG, Position.RIGHT); case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT: return encodeSetLongGestureMode(config, Position.LEFT); case PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT: return encodeSetLongGestureMode(config, Position.RIGHT); case PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION: return encodeSetEarDetection(); case PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL: return encodeSetBooleanConfig(config, Config.AUTO_ANSWER); case PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION: return encodeSetBooleanConfig(config, Config.DOUBLE_CONNECTION); case PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND: return encodeSetBooleanConfig(config, Config.ADAPTIVE_SOUND); case PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET: return encodeSetIntegerConfig(config, Config.EQ_PRESET); case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k: case PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k: return encodeSetCustomEqualizer(); default: LOG.debug("Unsupported config: {}", config); } return super.encodeSendConfiguration(config); } public byte[] encodeSetCustomEqualizer() { Prefs prefs = getDevicePrefs(); List bands = List.of(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k, PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k); byte[] eqCurve = new byte[10]; for (int i = 0; i < 10; i++) { eqCurve[i] = (byte) Integer.parseInt(prefs.getString(bands.get(i), "0")); } return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{ 0x24, 0x00, 0x37, 0x05, 0x01, 0x01, 0x0A, 0x00, 0x3E, eqCurve[0], 0x00, 0x7D, eqCurve[1], 0x00, (byte) 0xFA, eqCurve[2], 0x01, (byte) 0xF4, eqCurve[3], 0x03, (byte) 0xE8, eqCurve[4], 0x07, (byte) 0xE0, eqCurve[5], 0x0F, (byte) 0xA0, eqCurve[6], 0x1F, 0x40, eqCurve[7], 0x2E, (byte) 0xE0, eqCurve[8], 0x3E, (byte) 0x80, eqCurve[9] }).encode(); } public byte[] encodeSetEarDetection() { Prefs prefs = getDevicePrefs(); byte value = (byte) (prefs.getBoolean(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION, false) ? 0x00 : 0x01); return new Message(MessageType.PHONE_REQUEST, Opcode.ANC, sequenceNumber++, new byte[]{0x02, 0x06, value}).encode(); } public byte[] encodeSetLongGestureMode(String config, Position position) { Prefs prefs = getDevicePrefs(); byte value = (byte) Integer.parseInt(prefs.getString(config, "7")); byte[] payload = new byte[]{0x04, 0x00, 0x0a, (byte) 0xFF, (byte) 0xFF}; if (position == Position.LEFT) { payload[3] = value; } else { payload[4] = value; } return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, payload).encode(); } public byte[] encodeSetGesture(String config, InteractionType interactionType, Position position) { Prefs prefs = getDevicePrefs(); byte value = (byte) Integer.parseInt(prefs.getString(config, "1")); byte[] payload = new byte[]{0x05, 0x00, 0x02, interactionType.value, (byte) 0xFF, (byte) 0xFF}; if (position == Position.LEFT) { payload[4] = value; } else { payload[5] = value; } return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, payload).encode(); } public byte[] encodeSetEffectStrength(String pref, StrengthTarget effect) { Prefs prefs = getDevicePrefs(); byte mode = (byte) Integer.parseInt(prefs.getString(pref, "0")); return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x04, 0x00, 0x0b, effect.value, mode}).encode(); } public byte[] encodeSetIntegerConfig(String pref, Config config) { Prefs prefs = getDevicePrefs(); byte value = (byte) Integer.parseInt(prefs.getString(pref, "0")); return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x03, 0x00, config.value, value}).encode(); } public byte[] encodeSetBooleanConfig(String pref, Config config) { Prefs prefs = getDevicePrefs(); byte value = (byte) (prefs.getBoolean(pref, false) ? 0x01 : 0x00); return new Message(MessageType.PHONE_REQUEST, Opcode.SET_CONFIG, sequenceNumber++, new byte[]{0x03, 0x00, config.value, value}).encode(); } public byte[] encodeGetConfig() { List configs = List.of(Config.EFFECT_STRENGTH, Config.ADAPTIVE_ANC, // Config.CUSTOMIZED_ANC, Config.GESTURES, Config.LONG_GESTURES, Config.EAR_DETECTION, Config.DOUBLE_CONNECTION, Config.AUTO_ANSWER, Config.ADAPTIVE_SOUND, Config.EQ_PRESET, Config.EQ_CURVE); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { for (Config config : configs) { Message message = new Message(MessageType.PHONE_REQUEST, Opcode.GET_CONFIG, sequenceNumber++, new byte[]{0x00, config.value}); outputStream.write(message.encode()); } } catch (IOException e) { throw new RuntimeException(e); } return outputStream.toByteArray(); } public byte[] encodeSetAmbientSoundControl() { Prefs prefs = getDevicePrefs(); byte mode = (byte) Integer.parseInt(prefs.getString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, "0")); return new Message(MessageType.PHONE_REQUEST, Opcode.ANC, sequenceNumber++, new byte[]{0x02, 0x04, mode}).encode(); } public void decodeGetConfig(byte[] configPayload) { SharedPreferences preferences = getDevicePrefs().getPreferences(); Editor editor = preferences.edit(); Config config = Config.fromCode(configPayload[2]); switch (config) { case GESTURES: editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_LEFT, Integer.toString(configPayload[4])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_SINGLE_TAP_RIGHT, Integer.toString(configPayload[5])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_LEFT, Integer.toString(configPayload[7])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_DOUBLE_TAP_RIGHT, Integer.toString(configPayload[8])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_LEFT, Integer.toString(configPayload[10])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_TRIPLE_TAP_RIGHT, Integer.toString(configPayload[11])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_LEFT, Integer.toString(configPayload[13])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_MODE_RIGHT, Integer.toString(configPayload[14])); break; case AUTO_ANSWER: editor.putBoolean(PREF_REDMI_BUDS_5_PRO_AUTO_REPLY_PHONECALL, configPayload[3] == 0x01); break; case DOUBLE_CONNECTION: editor.putBoolean(PREF_REDMI_BUDS_5_PRO_DOUBLE_CONNECTION, configPayload[3] == 0x01); break; case EQ_PRESET: editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_PRESET, Integer.toString(configPayload[3])); break; case LONG_GESTURES: editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_LEFT, Integer.toString(configPayload[3])); editor.putString(PREF_REDMI_BUDS_5_PRO_CONTROL_LONG_TAP_SETTINGS_RIGHT, Integer.toString(configPayload[4])); break; case EFFECT_STRENGTH: byte mode = configPayload[4]; if (configPayload[3] == StrengthTarget.ANC.value) { editor.putString(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH, Integer.toString(mode)); } else if (configPayload[3] == StrengthTarget.TRANSPARENCY.value) { editor.putString(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH, Integer.toString(mode)); } break; case ADAPTIVE_ANC: editor.putBoolean(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_NOISE_CANCELLING, configPayload[3] == 0x01); break; case ADAPTIVE_SOUND: editor.putBoolean(PREF_REDMI_BUDS_5_PRO_ADAPTIVE_SOUND, configPayload[3] == 0x01); break; case EQ_CURVE: editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_62, Integer.toString(configPayload[12] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_125, Integer.toString(configPayload[15] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_250, Integer.toString(configPayload[18] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_500, Integer.toString(configPayload[21] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_1k, Integer.toString(configPayload[24] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_2k, Integer.toString(configPayload[27] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_4k, Integer.toString(configPayload[30] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_8k, Integer.toString(configPayload[33] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_12k, Integer.toString(configPayload[36] & 0xFF)); editor.putString(PREF_REDMI_BUDS_5_PRO_EQUALIZER_BAND_16k, Integer.toString(configPayload[39] & 0xFF)); break; // case CUSTOMIZED_ANC: // editor.putBoolean(PREF_REDMI_BUDS_5_PRO_PERSONALIZED_NOISE_CANCELLING, configPayload[3] == 0x01); // break; default: LOG.debug("Unhandled device update: {}", hexdump(configPayload)); } editor.apply(); } private GBDeviceEventBatteryInfo parseBatteryInfo(byte batteryInfo, int index) { if (batteryInfo == (byte) 0xff) { return null; } GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo(); batteryEvent.state = (batteryInfo & 128) != 0 ? BatteryState.BATTERY_CHARGING : BatteryState.BATTERY_NORMAL; batteryEvent.batteryIndex = index; batteryEvent.level = (batteryInfo & 127); LOG.debug("Battery {}: {}", index, batteryEvent.level); return batteryEvent; } private GBDeviceEvent[] decodeDeviceInfo(byte[] deviceInfoPayload) { List events = new ArrayList<>(); GBDeviceEventVersionInfo info = new GBDeviceEventVersionInfo(); byte[] fw = new byte[4]; byte[] vidPid = new byte[4]; byte[] batteryData = new byte[3]; int i = 0; while (i < deviceInfoPayload.length) { byte len = deviceInfoPayload[i]; byte index = deviceInfoPayload[i + 1]; switch (index) { case 0x01: System.arraycopy(deviceInfoPayload, i + 2, fw, 0, 4); break; case 0x03: System.arraycopy(deviceInfoPayload, i + 2, vidPid, 0, 4); break; case 0x07: System.arraycopy(deviceInfoPayload, i + 2, batteryData, 0, 3); break; } i += len + 1; } String fwVersion1 = ((fw[0] >> 4) & 0xF) + "." + (fw[0] & 0xF) + "." + ((fw[1] >> 4) & 0xF) + "." + (fw[1] & 0xF); String fwVersion2 = ((fw[2] >> 4) & 0xF) + "." + (fw[2] & 0xF) + "." + ((fw[3] >> 4) & 0xF) + "." + (fw[3] & 0xF); String hwVersion = String.format("VID: 0x%02X%02X, PID: 0x%02X%02X", vidPid[0], vidPid[1], vidPid[2], vidPid[3]); info.fwVersion = fwVersion1; info.fwVersion2 = fwVersion2; info.hwVersion = hwVersion; events.add(parseBatteryInfo(batteryData[0], 1)); events.add(parseBatteryInfo(batteryData[1], 2)); events.add(parseBatteryInfo(batteryData[2], 0)); events.add(info); return events.toArray(new GBDeviceEvent[0]); } private void decodeDeviceRunInfo(byte[] deviceRunInfoPayload) { int i = 0; while (i < deviceRunInfoPayload.length) { byte len = deviceRunInfoPayload[i]; byte index = deviceRunInfoPayload[i + 1]; SharedPreferences preferences = getDevicePrefs().getPreferences(); Editor editor = preferences.edit(); switch (index) { case 0x09: byte mode = deviceRunInfoPayload[i + 2]; editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(mode)); break; case 0x0A: editor.putBoolean(PREF_REDMI_BUDS_5_PRO_WEARING_DETECTION, deviceRunInfoPayload[i + 2] == 0x00); } editor.apply(); i += len + 1; } } private GBDeviceEvent[] decodeDeviceUpdate(Message updateMessage) { byte[] updatePayload = updateMessage.getPayload(); List events = new ArrayList<>(); int i = 0; while (i < updatePayload.length) { byte len = updatePayload[i]; byte index = updatePayload[i + 1]; switch (index) { case 0x00: events.add(parseBatteryInfo(updatePayload[i + 2], 1)); events.add(parseBatteryInfo(updatePayload[i + 3], 2)); events.add(parseBatteryInfo(updatePayload[i + 4], 0)); break; case 0x04: SharedPreferences preferences = getDevicePrefs().getPreferences(); Editor editor = preferences.edit(); byte mode = updatePayload[i + 2]; editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(mode)); editor.apply(); break; default: LOG.debug("Unimplemented device update: {}", hexdump(updatePayload)); } i += len + 1; } events.add(new GBDeviceEventSendBytes(new Message(MessageType.RESPONSE, Opcode.REPORT_STATUS, updateMessage.getSequenceNumber(), new byte[]{}).encode())); return events.toArray(new GBDeviceEvent[0]); } private GBDeviceEvent[] decodeNotifyConfig(Message notifyMessage) { byte[] notifyPayload = notifyMessage.getPayload(); List events = new ArrayList<>(); int i = 0; while (i < notifyPayload.length) { byte len = notifyPayload[i]; byte index = notifyPayload[i + 2]; switch (index) { case 0x0C: LOG.debug("Received earbuds position info"); /* e.g. 0C 03 0011 wearing left, wearing right, left in case, right in case */ break; case 0x0B: SharedPreferences preferences = getDevicePrefs().getPreferences(); Editor editor = preferences.edit(); byte soundCtrlMode = notifyPayload[i + 3]; editor.putString(PREF_REDMI_BUDS_5_PRO_AMBIENT_SOUND_CONTROL, Integer.toString(soundCtrlMode)); byte mode = notifyPayload[i + 4]; if (notifyPayload[i + 3] == 0x01) { editor.putString(PREF_REDMI_BUDS_5_PRO_NOISE_CANCELLING_STRENGTH, Integer.toString(mode)); } else { editor.putString(PREF_REDMI_BUDS_5_PRO_TRANSPARENCY_STRENGTH, Integer.toString(mode)); } editor.apply(); break; } i += len + 1; } events.add(new GBDeviceEventSendBytes(new Message(MessageType.RESPONSE, Opcode.NOTIFY_CONFIG, notifyMessage.getSequenceNumber(), new byte[]{}).encode())); return events.toArray(new GBDeviceEvent[0]); } private GBDeviceEvent[] handleAuthentication(Message authMessage) { List events = new ArrayList<>(); switch (authMessage.getOpcode()) { case AUTH_CHALLENGE: if (authMessage.getType() == MessageType.RESPONSE) { LOG.debug("[AUTH] Received Challenge Response"); /* Should check if equal, but does not really matter */ LOG.debug("[AUTH] Sending authentication confirmation"); events.add(new GBDeviceEventSendBytes(new Message(MessageType.PHONE_REQUEST, Opcode.AUTH_CONFIRM, sequenceNumber++, new byte[]{0x01, 0x00}).encode())); } else { byte[] responsePayload = authMessage.getPayload(); byte[] challenge = new byte[16]; System.arraycopy(responsePayload, 1, challenge, 0, 16); LOG.info("[AUTH] Received Challenge: {}", hexdump(challenge)); Authentication auth = new Authentication(); byte[] challengeResponse = auth.computeChallengeResponse(challenge); LOG.info("[AUTH] Sending Challenge Response: {}", hexdump(challengeResponse)); byte[] payload = new byte[17]; payload[0] = 0x01; System.arraycopy(challengeResponse, 0, payload, 1, 16); Message res = new Message(MessageType.RESPONSE, Opcode.AUTH_CHALLENGE, authMessage.getSequenceNumber(), payload); events.add(new GBDeviceEventSendBytes(res.encode())); } break; case AUTH_CONFIRM: if (authMessage.getType() == MessageType.RESPONSE) { LOG.debug("[AUTH] Confirmed first authentication step"); } else { LOG.debug("[AUTH] Received authentication confirmation"); Message res = new Message(MessageType.RESPONSE, Opcode.AUTH_CONFIRM, authMessage.getSequenceNumber(), new byte[]{0x01}); LOG.debug("[AUTH] Sending final authentication confirmation"); events.add(new GBDeviceEventSendBytes(res.encode())); LOG.debug("[INIT] Sending device info request"); Message info = new Message(MessageType.PHONE_REQUEST, Opcode.GET_DEVICE_INFO, sequenceNumber++, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); events.add(new GBDeviceEventSendBytes(info.encode())); LOG.debug("[INIT] Sending device run info request"); Message runInfo = new Message(MessageType.PHONE_REQUEST, Opcode.GET_DEVICE_RUN_INFO, sequenceNumber++, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); events.add(new GBDeviceEventSendBytes(runInfo.encode())); LOG.debug("[INIT] Sending configuration request"); events.add(new GBDeviceEventSendBytes(encodeGetConfig())); } break; } return events.toArray(new GBDeviceEvent[0]); } @Override public GBDeviceEvent[] decodeResponse(byte[] responseData) { LOG.debug("Incoming message: {}", hexdump(responseData)); List events = new ArrayList<>(); List incomingMessages = Message.splitPiggybackedMessages(responseData); for (Message message : incomingMessages) { LOG.debug("Parsed message: {}", message); switch (message.getOpcode()) { case AUTH_CHALLENGE: case AUTH_CONFIRM: events.addAll(Arrays.asList(handleAuthentication(message))); break; case GET_DEVICE_INFO: LOG.debug("[INIT] Received device info"); if (getDevice().getState() != State.INITIALIZED) { events.addAll(Arrays.asList(decodeDeviceInfo(message.getPayload()))); LOG.debug("[INIT] Device Initialized"); events.add(new GBDeviceEventUpdateDeviceState(State.INITIALIZED)); } break; case GET_DEVICE_RUN_INFO: LOG.debug("[INIT] Received device run info"); decodeDeviceRunInfo(message.getPayload()); break; case REPORT_STATUS: events.addAll(Arrays.asList(decodeDeviceUpdate(message))); break; case GET_CONFIG: decodeGetConfig(message.getPayload()); break; case NOTIFY_CONFIG: events.addAll(Arrays.asList(decodeNotifyConfig(message))); break; default: LOG.debug("Unhandled message: {}", message); break; } } return events.toArray(new GBDeviceEvent[0]); } }