From 5ff399673ac899ecb6976504ec1ae753e431d9be Mon Sep 17 00:00:00 2001 From: agent Date: Fri, 24 Jul 2026 11:03:36 +0000 Subject: [PATCH] =?UTF-8?q?feat(ppclock):=20batch=20JSONL=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=89=A7=E8=A1=8C=EF=BC=88=E5=8D=95=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=EF=BC=89=EF=BC=8C104=20=E6=B5=8B=E8=AF=95=E5=85=A8=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...bsg_top_ble_BleManager$gattCallback$1.java | 421 ++++++++++++++++++ ...bsg_top_ble_BleManager$scanCallback$1.java | 56 +++ ...qbsg_top_ble_BleSession$sendCommand$2.java | 188 ++++++++ ...bsg_top_ble_BleSession$sendEpdBytes$2.java | 113 +++++ src/ppclock/cli.py | 173 ++++--- tests/test_cli.py | 26 ++ 6 files changed, 906 insertions(+), 71 deletions(-) create mode 100644 analysis/apk/decomp/qbsg_top_ble_BleManager$gattCallback$1.java create mode 100644 analysis/apk/decomp/qbsg_top_ble_BleManager$scanCallback$1.java create mode 100644 analysis/apk/decomp/qbsg_top_ble_BleSession$sendCommand$2.java create mode 100644 analysis/apk/decomp/qbsg_top_ble_BleSession$sendEpdBytes$2.java diff --git a/analysis/apk/decomp/qbsg_top_ble_BleManager$gattCallback$1.java b/analysis/apk/decomp/qbsg_top_ble_BleManager$gattCallback$1.java new file mode 100644 index 0000000..7da577d --- /dev/null +++ b/analysis/apk/decomp/qbsg_top_ble_BleManager$gattCallback$1.java @@ -0,0 +1,421 @@ +package qbsg.top.ble; +public final class BleManager$gattCallback$1 extends android.bluetooth.BluetoothGattCallback { + final synthetic qbsg.top.ble.BleManager this$0; + + public BleManager$gattCallback$1(qbsg.top.ble.BleManager p1) + { + this.this$0 = p1; + return; + } + + private static final CharSequence onCharacteristicChanged$lambda$4(byte p1) + { + return String.format("%02x", java.util.Arrays.copyOf(new Object[] {Byte.valueOf(p1)}), 1)); + } + + public void onCharacteristicChanged(android.bluetooth.BluetoothGatt p2, android.bluetooth.BluetoothGattCharacteristic p3) + { + kotlin.jvm.internal.s.f(p2, "gatt"); + kotlin.jvm.internal.s.f(p3, "char"); + byte[] v0_2 = p3.getValue(); + if (v0_2 == null) { + v0_2 = new byte[0]; + } + this.onCharacteristicChanged(p2, p3, v0_2); + return; + } + + public void onCharacteristicChanged(android.bluetooth.BluetoothGatt p7, android.bluetooth.BluetoothGattCharacteristic p8, byte[] p9) + { + kotlin.jvm.internal.s.f(p7, "gatt"); + kotlin.jvm.internal.s.f(p8, "char"); + kotlin.jvm.internal.s.f(p9, "value"); + String v7_2 = p7.getDevice().getAddress(); + p6.g v0_4 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(v7_2)); + if (v0_4 != null) { + v0_4.forwardNotify(p8, p9); + } + String v8_2 = new StringBuilder(); + v8_2.append(""); + int v1 = p9.length; + int v2 = 0; + int v3 = 0; + while (v2 < v1) { + CharSequence v4_0 = p9[v2]; + v3++; + if (v3 > 1) { + v8_2.append(""); + } + v8_2.append(qbsg.top.ble.BleManager$gattCallback$1.onCharacteristicChanged$lambda$4(v4_0)); + v2++; + } + v8_2.append(""); + qbsg.top.ble.BleManager.access$get_messageEvents$p(this.this$0).tryEmit(new p6.g(v7_2, v8_2.toString())); + return; + } + + public void onCharacteristicRead(android.bluetooth.BluetoothGatt p4, android.bluetooth.BluetoothGattCharacteristic p5, int p6) + { + byte[] v1_1; + kotlin.jvm.internal.s.f(p4, "gatt"); + kotlin.jvm.internal.s.f(p5, "char"); + if (p6 != null) { + v1_1 = new byte[0]; + } else { + byte[] v1_2 = p5.getValue(); + if (v1_2 == null) { + } else { + v1_1 = ((byte[]) v1_2.clone()); + if (v1_1 == null) { + } + } + } + qbsg.top.ble.BleManager$AndroidGattOtaChannel v0_5 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(p4.getDevice().getAddress())); + if (v0_5 != null) { + v0_5.forwardCharacteristicRead(p5, v1_1, p6); + } + Object v4_4 = ((kotlinx.coroutines.channels.Channel) qbsg.top.ble.BleManager.access$getReadChannels$p(this.this$0).get(p4.getDevice().getAddress())); + if (v4_4 != null) { + kotlinx.coroutines.channels.ChannelResult.box-impl(v4_4.trySend-JP2dKIU(new p6.g(p5.getUuid(), v1_1))); + } + return; + } + + public void onCharacteristicRead(android.bluetooth.BluetoothGatt p3, android.bluetooth.BluetoothGattCharacteristic p4, byte[] p5, int p6) + { + byte[] v5_2; + kotlin.jvm.internal.s.f(p3, "gatt"); + kotlin.jvm.internal.s.f(p4, "char"); + kotlin.jvm.internal.s.f(p5, "value"); + if (p6 != null) { + v5_2 = new byte[0]; + } else { + v5_2 = ((byte[]) p5.clone()); + } + qbsg.top.ble.BleManager$AndroidGattOtaChannel v0_5 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(p3.getDevice().getAddress())); + if (v0_5 != null) { + v0_5.forwardCharacteristicRead(p4, v5_2, p6); + } + Object v3_4 = ((kotlinx.coroutines.channels.Channel) qbsg.top.ble.BleManager.access$getReadChannels$p(this.this$0).get(p3.getDevice().getAddress())); + if (v3_4 != null) { + kotlinx.coroutines.channels.ChannelResult.box-impl(v3_4.trySend-JP2dKIU(new p6.g(p4.getUuid(), v5_2))); + } + return; + } + + public void onCharacteristicWrite(android.bluetooth.BluetoothGatt p2, android.bluetooth.BluetoothGattCharacteristic p3, int p4) + { + kotlin.jvm.internal.s.f(p2, "gatt"); + kotlin.jvm.internal.s.f(p3, "char"); + int v2_8 = p2.getDevice().getAddress(); + boolean v0_3 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(v2_8)); + if (!v0_3) { + int v2_3; + int v2_2 = ((qbsg.top.ble.data.BleConnectionInfo) ((java.util.Map) qbsg.top.ble.BleManager.access$get_connections$p(this.this$0).getValue()).get(v2_8)); + qbsg.top.ble.BleSession v3_5 = 0; + if (v2_2 == 0) { + v2_3 = 0; + } else { + v2_3 = v2_2.getState(); + } + int v2_4; + if (!(v2_3 instanceof qbsg.top.ble.data.DeviceConnectionState$Connected)) { + v2_4 = 0; + } else { + v2_4 = ((qbsg.top.ble.data.DeviceConnectionState$Connected) v2_3); + } + int v2_5; + if (v2_4 == 0) { + v2_5 = 0; + } else { + v2_5 = v2_4.getSession(); + } + if ((v2_5 instanceof qbsg.top.ble.BleSession)) { + v3_5 = ((qbsg.top.ble.BleSession) v2_5); + } + if (v3_5 != null) { + int v2_6; + if (p4 != 0) { + v2_6 = 0; + } else { + v2_6 = 1; + } + v3_5.onWriteComplete$composeApp_release(v2_6); + } + return; + } else { + v0_3.forwardCharacteristicWrite(p3, p4); + return; + } + } + + public void onConnectionStateChange(android.bluetooth.BluetoothGatt p12, int p13, int p14) + { + qbsg.top.ble.data.BleDeviceInfo v0_5; + kotlin.jvm.internal.s.f(p12, "gatt"); + String v2_1 = p12.getDevice().getAddress(); + qbsg.top.ble.data.BleDeviceInfo v0_4 = ((qbsg.top.ble.data.BleConnectionInfo) ((java.util.Map) qbsg.top.ble.BleManager.access$get_connections$p(this.this$0).getValue()).get(v2_1)); + if (v0_4 == null) { + qbsg.top.ble.data.BleDeviceInfo v0_7 = p12.getDevice().getName(); + if (v0_7 == null) { + v0_7 = "\u672a\u77e5"; + } + kotlin.jvm.internal.s.c(v2_1); + int v3_0 = v2_1; + v2_1 = v3_0; + v0_5 = new qbsg.top.ble.data.BleDeviceInfo(v0_7, v3_0, 0, 0, 0, 0, 0, 120, 0); + } else { + v0_5 = v0_4.getDevice(); + if (v0_5 == null) { + } + } + if (p14 == 0) { + if (!qbsg.top.ble.BleManager.access$getUserInitiatedDisconnections$p(this.this$0).remove(v2_1)) { + qbsg.top.ble.data.DisconnectReason$GattError v12_10; + qbsg.top.ble.data.DisconnectReason$GattError v12_9 = ((qbsg.top.ble.data.BleConnectionInfo) ((java.util.Map) qbsg.top.ble.BleManager.access$get_connections$p(this.this$0).getValue()).get(v2_1)); + if (v12_9 == null) { + v12_10 = 0; + } else { + v12_10 = v12_9.getState(); + } + qbsg.top.ble.data.DisconnectReason$GattError v12_14; + if ((v12_10 instanceof qbsg.top.ble.data.DeviceConnectionState$Connected)) { + if (p13 != null) { + if (p13 == 8) { + v12_14 = new qbsg.top.ble.data.DisconnectReason$OutOfRange(v0_5); + int v14_4 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(v2_1)); + if (v14_4 != 0) { + v14_4.notifyDisconnect(p13); + } + qbsg.top.ble.BleManager v13_1 = this.this$0; + kotlin.jvm.internal.s.c(v2_1); + qbsg.top.ble.BleManager.access$cleanupDevice(v13_1, v2_1, 1, v12_14); + return; + } else { + if (p13 != 19) { + v12_14 = new qbsg.top.ble.data.DisconnectReason$GattError(v0_5, p13); + } + } + } + v12_14 = new qbsg.top.ble.data.DisconnectReason$DeviceDisconnected(v0_5); + } else { + v12_14 = new qbsg.top.ble.data.DisconnectReason$ConnectFailed(v0_5, p13); + } + } else { + qbsg.top.ble.BleManager v1_2 = this.this$0; + kotlin.jvm.internal.s.c(v2_1); + qbsg.top.ble.BleManager.cleanupDevice$default(v1_2, v2_1, 0, 0, 4, 0); + return; + } + } else { + if (p14 == 2) { + p12.discoverServices(); + return; + } else { + return; + } + } + } + + public void onDescriptorWrite(android.bluetooth.BluetoothGatt p2, android.bluetooth.BluetoothGattDescriptor p3, int p4) + { + kotlin.jvm.internal.s.f(p2, "gatt"); + kotlin.jvm.internal.s.f(p3, "descriptor"); + String v2_2 = p2.getDevice().getAddress(); + int v3_3 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(v2_2)); + if (v3_3 != 0) { + v3_3.forwardDescriptorWrite(p4); + } + qbsg.top.ble.BleManager.access$getDescriptorWriteQueue$p(this.this$0).poll(); + qbsg.top.ble.BleManager v4_1 = 0; + qbsg.top.ble.BleManager.access$setWritingDescriptor$p(this.this$0, 0); + qbsg.top.ble.BleManager.access$processDescriptorQueue(this.this$0); + int v3_11 = ((Integer) qbsg.top.ble.BleManager.access$getPendingCccdCount$p(this.this$0).get(v2_2)); + if (v3_11 != 0) { + v4_1 = v3_11.intValue(); + } + qbsg.top.ble.BleManager v4_2 = (v4_1 - 1); + if (v4_2 > null) { + qbsg.top.ble.BleManager.access$getPendingCccdCount$p(this.this$0).put(v2_2, Integer.valueOf(v4_2)); + return; + } else { + qbsg.top.ble.BleManager.access$getPendingCccdCount$p(this.this$0).remove(v2_2); + int v3_19 = ((String) qbsg.top.ble.BleManager.access$getPendingFirmware$p(this.this$0).remove(v2_2)); + if (v3_19 != 0) { + qbsg.top.ble.BleManager v4_4 = this.this$0; + kotlin.jvm.internal.s.c(v2_2); + if (v3_19.length() == 0) { + v3_19 = 0; + } + qbsg.top.ble.BleManager.access$updateDeviceInfo(v4_4, v2_2, v3_19); + } + return; + } + } + + public void onMtuChanged(android.bluetooth.BluetoothGatt p7, int p8, int p9) + { + int v1_1; + kotlin.jvm.internal.s.f(p7, "gatt"); + String v0_2 = p7.getDevice().getAddress(); + if (p9 != null) { + v1_1 = 23; + } else { + v1_1 = p8; + } + java.util.Iterator v2_2 = ((qbsg.top.ble.BleManager$AndroidGattOtaChannel) qbsg.top.ble.BleManager.access$getOtaChannels$p(this.this$0).get(v0_2)); + if (v2_2 != null) { + v2_2.forwardMtuChanged(p8, p9); + } + qbsg.top.ble.BleManager v8_7; + qbsg.top.ble.BleManager v8_6 = ((qbsg.top.ble.data.BleConnectionInfo) ((java.util.Map) qbsg.top.ble.BleManager.access$get_connections$p(this.this$0).getValue()).get(v0_2)); + java.util.concurrent.ConcurrentHashMap v9_1 = 0; + if (v8_6 == null) { + v8_7 = 0; + } else { + v8_7 = v8_6.getState(); + } + qbsg.top.ble.BleManager v8_8; + if (!(v8_7 instanceof qbsg.top.ble.data.DeviceConnectionState$Connected)) { + v8_8 = 0; + } else { + v8_8 = ((qbsg.top.ble.data.DeviceConnectionState$Connected) v8_7); + } + qbsg.top.ble.BleManager v8_9; + if (v8_8 == null) { + v8_9 = 0; + } else { + v8_9 = v8_8.getSession(); + } + qbsg.top.ble.BleManager v8_10; + if (!(v8_9 instanceof qbsg.top.ble.BleSession)) { + v8_10 = 0; + } else { + v8_10 = ((qbsg.top.ble.BleSession) v8_9); + } + if (v8_10 != null) { + v8_10.setNegotiatedMtu$composeApp_release(v1_1); + } + qbsg.top.ble.BleManager v8_12 = p7.getServices().iterator(); + int v1_0 = 0; + while (v8_12.hasNext()) { + java.util.Iterator v2_9 = ((android.bluetooth.BluetoothGattService) v8_12.next()).getCharacteristics().iterator(); + while (v2_9.hasNext()) { + boolean v3_2 = ((android.bluetooth.BluetoothGattCharacteristic) v2_9.next()); + qbsg.top.ble.BleManager v4_0 = v3_2.getProperties(); + if ((((v4_0 & 16) != 0) || ((v4_0 & 32) != 0)) && (qbsg.top.ble.BleManager.access$enqueueNotify(this.this$0, p7, v3_2))) { + v1_0++; + } + } + } + if (v1_0 != 0) { + qbsg.top.ble.BleManager.access$getPendingCccdCount$p(this.this$0).put(v0_2, Integer.valueOf(v1_0)); + return; + } else { + java.util.concurrent.ConcurrentHashMap v7_6 = ((String) qbsg.top.ble.BleManager.access$getPendingFirmware$p(this.this$0).remove(v0_2)); + if (v7_6 != null) { + qbsg.top.ble.BleManager v8_14 = this.this$0; + kotlin.jvm.internal.s.c(v0_2); + if (v7_6.length() != 0) { + v9_1 = v7_6; + } + qbsg.top.ble.BleManager.access$updateDeviceInfo(v8_14, v0_2, v9_1); + } + return; + } + } + + public void onServicesDiscovered(android.bluetooth.BluetoothGatt p19, int p20) + { + kotlin.jvm.internal.s.f(p19, "gatt"); + if (p20 == 0) { + Object v2_3; + String v5 = p19.getDevice().getAddress(); + Object v2_1 = p19.getService(qbsg.top.ble.BleManagerKt.access$getTX_SERVICE_UUID$p()); + if (v2_1 == null) { + v2_3 = 0; + } else { + v2_3 = v2_1.getCharacteristic(qbsg.top.ble.BleManagerKt.access$getTX_CHAR_UUID$p()); + } + kotlinx.coroutines.flow.MutableStateFlow v14 = qbsg.top.ble.BleManager.access$get_connections$p(this.this$0); + qbsg.top.ble.BleManager v15 = this.this$0; + while(true) { + Object v2_6; + Object v16_0; + java.util.Map v13; + kotlinx.coroutines.flow.MutableStateFlow v6_2; + qbsg.top.ble.data.BleConnectionInfo v3_8 = v14.getValue(); + java.util.Map v4_7 = ((java.util.Map) v3_8); + kotlinx.coroutines.flow.MutableStateFlow v6_1 = ((qbsg.top.ble.data.BleConnectionInfo) v4_7.get(v5)); + if (v6_1 == null) { + kotlinx.coroutines.flow.MutableStateFlow v6_3 = v3_8; + qbsg.top.ble.data.DeviceConnectionState$Connected v7_6 = p19.getDevice().getName(); + if (v7_6 == null) { + v7_6 = "\u8bbe\u5907"; + } + kotlin.jvm.internal.s.c(v5); + v13 = v4_7; + v16_0 = v2_3; + v2_6 = v6_3; + v6_2 = new qbsg.top.ble.data.BleDeviceInfo(v7_6, v5, 0, 0, 0, 0, 0, 120, 0); + } else { + v6_2 = v6_1.getDevice(); + if (v6_2 != null) { + v16_0 = v2_3; + v2_6 = v3_8; + v13 = v4_7; + } + } + android.bluetooth.BluetoothGattCharacteristic v8_3; + qbsg.top.ble.BleManager.access$get_targetSessionMac$p(v15).setValue(v5); + qbsg.top.ble.BleManager.access$get_activeDevice$p(v15).setValue(v6_2); + if (v16_0 != null) { + v8_3 = v16_0; + } else { + qbsg.top.ble.data.BleConnectionInfo v3_13 = p19.getServices(); + kotlin.jvm.internal.s.e(v3_13, "getServices(...)"); + java.util.Map v4_11 = new java.util.ArrayList(); + qbsg.top.ble.data.BleConnectionInfo v3_14 = v3_13.iterator(); + while (v3_14.hasNext()) { + qbsg.top.ble.data.DeviceConnectionState$Connected v7_4 = ((android.bluetooth.BluetoothGattService) v3_14.next()).getCharacteristics(); + kotlin.jvm.internal.s.e(v7_4, "getCharacteristics(...)"); + q6.o.f0(v4_11, v7_4); + } + qbsg.top.ble.data.BleConnectionInfo v3_15 = v4_11.size(); + qbsg.top.ble.data.DeviceConnectionState$Connected v7_9 = 0; + while (v7_9 < v3_15) { + android.bluetooth.BluetoothGattCharacteristic v8_4 = v4_11.get(v7_9); + v7_9++; + if ((((android.bluetooth.BluetoothGattCharacteristic) v8_4).getProperties() & 8) != 0) { + } + v8_3 = ((android.bluetooth.BluetoothGattCharacteristic) v8_4); + } + v8_4 = 0; + } + java.util.Map v4_5; + if (v8_3 == null) { + v4_5 = v13; + } else { + qbsg.top.ble.data.BleConnectionInfo v3_2; + qbsg.top.ble.data.BleConnectionInfo v3_1 = p19.getService(qbsg.top.ble.BleManagerKt.access$getEPD_SERVICE_UUID$p()); + if (v3_1 == null) { + v3_2 = 0; + } else { + v3_2 = v3_1.getCharacteristic(qbsg.top.ble.BleManagerKt.access$getEPD_CHAR_UUID$p()); + } + java.util.Map v4_2 = new qbsg.top.ble.BleSession(p19, v8_3, v3_2); + v4_2.setNegotiatedMtu$composeApp_release(23); + v4_5 = q6.y.L(v13, new p6.g(v5, new qbsg.top.ble.data.BleConnectionInfo(v6_2, new qbsg.top.ble.data.DeviceConnectionState$Connected(v4_2)))); + } + if (v14.compareAndSet(v2_6, v4_5)) { + break; + } + v2_3 = v16_0; + } + qbsg.top.ble.BleManager.access$startDeviceInfoRead(this.this$0, p19); + return; + } else { + return; + } + } +} diff --git a/analysis/apk/decomp/qbsg_top_ble_BleManager$scanCallback$1.java b/analysis/apk/decomp/qbsg_top_ble_BleManager$scanCallback$1.java new file mode 100644 index 0000000..bffa06f --- /dev/null +++ b/analysis/apk/decomp/qbsg_top_ble_BleManager$scanCallback$1.java @@ -0,0 +1,56 @@ +package qbsg.top.ble; +public final class BleManager$scanCallback$1 extends android.bluetooth.le.ScanCallback { + final synthetic qbsg.top.ble.BleManager this$0; + + public BleManager$scanCallback$1(qbsg.top.ble.BleManager p1) + { + this.this$0 = p1; + return; + } + + public void onScanResult(int p13, android.bluetooth.le.ScanResult p14) + { + qbsg.top.ble.data.BleDeviceInfo v2_1; + kotlin.jvm.internal.s.f(p14, "result"); + kotlinx.coroutines.flow.MutableStateFlow v13_4 = qbsg.top.ble.BleManager.access$get_rawScanResults$p(this.this$0); + java.util.ArrayList v0_3 = p14.getDevice().getAddress(); + kotlin.jvm.internal.s.e(v0_3, "getAddress(...)"); + qbsg.top.ble.data.BleDeviceInfo v2_0 = p14.getScanRecord(); + if (v2_0 == null) { + v2_1 = p14.getDevice().getName(); + } else { + v2_1 = v2_0.getDeviceName(); + if (v2_1 == null) { + } else { + if (i7.i.x0(v2_1)) { + v2_1 = 0; + } + if (v2_1 == null) { + } + } + } + v13_4.tryEmit(new qbsg.top.ble.data.BleScanRecord(v0_3, v2_1, p14.getRssi())); + kotlinx.coroutines.flow.MutableStateFlow v13_3 = p14.getDevice().getName(); + if (v13_3 == null) { + v13_3 = "<\u672a\u77e5\u8bbe\u5907>"; + } + boolean v3_2 = v13_3; + String v4_2 = p14.getDevice().getAddress(); + kotlin.jvm.internal.s.e(v4_2, "getAddress(...)"); + qbsg.top.ble.data.BleDeviceInfo v2_4 = new qbsg.top.ble.data.BleDeviceInfo(v3_2, v4_2, p14.getRssi(), 0, 0, 0, 0, 120, 0); + kotlinx.coroutines.flow.MutableStateFlow v13_7 = qbsg.top.ble.BleManager.access$get_scannedDevices$p(this.this$0); + do { + boolean v14_1 = v13_7.getValue(); + java.util.ArrayList v0_1 = ((java.util.List) v14_1); + if ((v0_1 == null) || (!v0_1.isEmpty())) { + java.util.Iterator v1_1 = v0_1.iterator(); + while (v1_1.hasNext()) { + if (kotlin.jvm.internal.s.b(((qbsg.top.ble.data.BleDeviceInfo) v1_1.next()).getAddress(), v2_4.getAddress())) { + } + } + v0_1 = q6.o.y0(v0_1, v2_4); + } + } while(!v13_7.compareAndSet(v14_1, v0_1)); + return; + } +} diff --git a/analysis/apk/decomp/qbsg_top_ble_BleSession$sendCommand$2.java b/analysis/apk/decomp/qbsg_top_ble_BleSession$sendCommand$2.java new file mode 100644 index 0000000..62e7cee --- /dev/null +++ b/analysis/apk/decomp/qbsg_top_ble_BleSession$sendCommand$2.java @@ -0,0 +1,188 @@ +package qbsg.top.ble; +final class BleSession$sendCommand$2 extends kotlin.coroutines.jvm.internal.SuspendLambda implements a7.e { + final synthetic java.util.List $chunks; + final synthetic String $hex; + int I$0; + int I$1; + int I$2; + int I$3; + Object L$0; + Object L$1; + Object L$2; + Object L$3; + Object L$4; + Object L$5; + int label; + final synthetic qbsg.top.ble.BleSession this$0; + + public BleSession$sendCommand$2(qbsg.top.ble.BleSession p1, java.util.List p2, String p3, kotlin.coroutines.Continuation p4) + { + this.this$0 = p1; + this.$chunks = p2; + this.$hex = p3; + super(2, p4); + return; + } + + public final kotlin.coroutines.Continuation create(Object p4, kotlin.coroutines.Continuation p5) + { + return new qbsg.top.ble.BleSession$sendCommand$2(this.this$0, this.$chunks, this.$hex, p5); + } + + public bridge synthetic Object invoke(Object p1, Object p2) + { + return this.invoke(((kotlinx.coroutines.CoroutineScope) p1), ((kotlin.coroutines.Continuation) p2)); + } + + public final Object invoke(kotlinx.coroutines.CoroutineScope p1, kotlin.coroutines.Continuation p2) + { + return ((qbsg.top.ble.BleSession$sendCommand$2) this.create(p1, p2)).invokeSuspend(p6.a0.a); + } + + public final Object invokeSuspend(Object p19) + { + int v16_0; + Object v8_0; + int v13_0; + Object v8_2; + int v0_5; + int v4_1; + int v7_0; + String v9_0; + String v2_7; + String v2_9; + String v9_2; + int v12_0; + int v11_0; + int v4_2; + kotlinx.coroutines.sync.Mutex v14_0; + int v7_1; + java.util.Iterator v10_3; + int v0_9 = kotlin.coroutines.intrinsics.IntrinsicsKt.getCOROUTINE_SUSPENDED(); + String v2_8 = this.label; + int v5_2 = 1; + if (v2_8 == null) { + q6.y.Y(p19); + String v2_1 = qbsg.top.ble.BleSession.access$getWriteMutex$p(this.this$0); + v9_0 = this.$chunks; + v8_0 = this.this$0; + v7_1 = this.$hex; + this.L$0 = v2_1; + this.L$1 = v9_0; + this.L$2 = v8_0; + this.L$3 = v7_1; + this.I$0 = 0; + this.label = 1; + if (v2_1.lock(0, this) != v0_9) { + v14_0 = v2_1; + v2_7 = 0; + v10_3 = v9_0.iterator(); + v11_0 = v7_1; + v12_0 = v8_0; + v13_0 = v9_0; + v9_2 = v2_7; + v2_9 = 0; + v8_2 = 0; + if (!v10_3.hasNext()) { + int v16_1 = v5_2; + int v0_14 = qbsg.top.ble.BleSession.access$getTag$p(v12_0); + String v2_12 = new StringBuilder(); + v2_12.append("\u6240\u6709\u5206\u5305\u53d1\u9001\u6210\u529f: "); + v2_12.append(v11_0); + android.util.Log.i(v0_14, v2_12.toString()); + v4_2 = v16_1; + } else { + v7_0 = (v2_9 + 1); + byte[] v15_1 = ((byte[]) v10_3.next()); + int v4_0 = qbsg.top.ble.BleSession.access$getTag$p(v12_0); + v16_0 = v5_2; + int v5_0 = v13_0.size(); + int v6_0 = v15_1.length; + String v3_1 = new StringBuilder(); + Throwable v17 = v0_9; + v3_1.append("\u6b63\u5728\u53d1\u9001\u5206\u5305 ("); + v3_1.append(v7_0); + v3_1.append("/"); + v3_1.append(v5_0); + v3_1.append("), \u957f\u5ea6: "); + v3_1.append(v6_0); + android.util.Log.v(v4_0, v3_1.toString()); + int v0_4 = qbsg.top.ble.BleSession.access$getRxtxChar$p(v12_0); + this.L$0 = v14_0; + this.L$1 = v13_0; + this.L$2 = v12_0; + this.L$3 = v11_0; + this.L$4 = v10_3; + this.L$5 = kotlin.coroutines.jvm.internal.SpillingKt.nullOutSpilledVariable(v15_1); + this.I$0 = v9_2; + this.I$1 = v8_2; + this.I$2 = v7_0; + this.I$3 = v2_9; + this.label = 2; + v0_5 = qbsg.top.ble.BleSession.access$executeWrite(v12_0, v0_4, v15_1, this); + v4_1 = v17; + if (v0_5 == v4_1) { + return v4_1; + } else { + int v5_3 = v2_9; + v2_9 = v7_0; + if (((Boolean) v0_5).booleanValue()) { + v0_9 = v4_1; + v5_2 = v16_0; + int v6 = 0; + } else { + int v0_6 = qbsg.top.ble.BleSession.access$getTag$p(v12_0); + int v5_1 = (v5_3 + 1); + String v2_3 = new StringBuilder(); + v2_3.append("\u7b2c "); + v2_3.append(v5_1); + v2_3.append(" \u4e2a\u5206\u5305\u53d1\u9001\u5931\u8d25"); + android.util.Log.e(v0_6, v2_3.toString()); + v4_2 = 0; + } + } + } + } else { + v4_1 = v0_9; + return v4_1; + } + } else { + if (v2_8 == 1) { + v2_7 = this.I$0; + v7_1 = ((String) this.L$3); + v8_0 = ((qbsg.top.ble.BleSession) this.L$2); + v9_0 = ((java.util.List) this.L$1); + java.util.Iterator v10_2 = ((kotlinx.coroutines.sync.Mutex) this.L$0); + q6.y.Y(p19); + v14_0 = v10_2; + } else { + if (v2_8 != 2) { + throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine"); + } else { + v2_9 = this.I$3; + v7_0 = this.I$2; + v8_2 = this.I$1; + v9_2 = this.I$0; + v10_3 = ((java.util.Iterator) this.L$4); + v11_0 = ((String) this.L$3); + v12_0 = ((qbsg.top.ble.BleSession) this.L$2); + v13_0 = ((java.util.List) this.L$1); + v14_0 = ((kotlinx.coroutines.sync.Mutex) this.L$0); + try { + q6.y.Y(p19); + v4_1 = v0_9; + v16_0 = 1; + v0_5 = p19; + } catch (int v0_7) { + String v2_5 = 0; + v14_0.unlock(v2_5); + throw v0_7; + } + } + } + } + int v0_8 = kotlin.coroutines.jvm.internal.Boxing.boxBoolean(v4_2); + v14_0.unlock(0); + return v0_8; + } +} diff --git a/analysis/apk/decomp/qbsg_top_ble_BleSession$sendEpdBytes$2.java b/analysis/apk/decomp/qbsg_top_ble_BleSession$sendEpdBytes$2.java new file mode 100644 index 0000000..5f25d97 --- /dev/null +++ b/analysis/apk/decomp/qbsg_top_ble_BleSession$sendEpdBytes$2.java @@ -0,0 +1,113 @@ +package qbsg.top.ble; +final class BleSession$sendEpdBytes$2 extends kotlin.coroutines.jvm.internal.SuspendLambda implements a7.e { + final synthetic android.bluetooth.BluetoothGattCharacteristic $char; + final synthetic byte[] $data; + int I$0; + int I$1; + Object L$0; + Object L$1; + Object L$2; + Object L$3; + int label; + final synthetic qbsg.top.ble.BleSession this$0; + + public BleSession$sendEpdBytes$2(qbsg.top.ble.BleSession p1, android.bluetooth.BluetoothGattCharacteristic p2, byte[] p3, kotlin.coroutines.Continuation p4) + { + this.this$0 = p1; + this.$char = p2; + this.$data = p3; + super(2, p4); + return; + } + + public final kotlin.coroutines.Continuation create(Object p4, kotlin.coroutines.Continuation p5) + { + return new qbsg.top.ble.BleSession$sendEpdBytes$2(this.this$0, this.$char, this.$data, p5); + } + + public bridge synthetic Object invoke(Object p1, Object p2) + { + return this.invoke(((kotlinx.coroutines.CoroutineScope) p1), ((kotlin.coroutines.Continuation) p2)); + } + + public final Object invoke(kotlinx.coroutines.CoroutineScope p1, kotlin.coroutines.Continuation p2) + { + return ((qbsg.top.ble.BleSession$sendEpdBytes$2) this.create(p1, p2)).invokeSuspend(p6.a0.a); + } + + public final Object invokeSuspend(Object p11) + { + Boolean v11_1; + qbsg.top.ble.BleSession v7_0; + Throwable v0_2; + int v4_1; + android.bluetooth.BluetoothGattCharacteristic v6_0; + Object v1_2; + Throwable v0_0 = kotlin.coroutines.intrinsics.IntrinsicsKt.getCOROUTINE_SUSPENDED(); + Object v1_1 = this.label; + try { + if (v1_1 == null) { + q6.y.Y(p11); + v11_1 = qbsg.top.ble.BleSession.access$getWriteMutex$p(this.this$0); + v7_0 = this.this$0; + v6_0 = this.$char; + Object v1_0 = this.$data; + this.L$0 = v11_1; + this.L$1 = v7_0; + this.L$2 = v6_0; + this.L$3 = v1_0; + this.I$0 = 0; + this.label = 1; + if (v11_1.lock(0, this) != v0_0) { + v4_1 = v1_0; + v1_2 = 0; + try { + this.L$0 = v11_1; + this.L$1 = 0; + this.L$2 = 0; + this.L$3 = 0; + this.I$0 = v1_2; + this.I$1 = 0; + this.label = 2; + Object v1_3 = qbsg.top.ble.BleSession.access$executeWrite(v7_0, v6_0, v4_1, this); + } catch (Throwable v0_1) { + v0_2 = v11_1; + Boolean v11_2 = v0_1; + } + if (v1_3 != v0_0) { + v0_2 = v11_1; + p11 = v1_3; + Boolean v11_7 = kotlin.coroutines.jvm.internal.Boxing.boxBoolean(((Boolean) p11).booleanValue()); + v0_2.unlock(0); + return v11_7; + } else { + return v0_0; + } + } + } else { + if (v1_1 == 1) { + v1_2 = this.I$0; + v4_1 = ((byte[]) this.L$3); + v6_0 = ((android.bluetooth.BluetoothGattCharacteristic) this.L$2); + v7_0 = ((qbsg.top.ble.BleSession) this.L$1); + kotlinx.coroutines.sync.Mutex v8_1 = ((kotlinx.coroutines.sync.Mutex) this.L$0); + q6.y.Y(p11); + v11_1 = v8_1; + } else { + if (v1_1 != 2) { + throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine"); + } else { + v0_2 = ((kotlinx.coroutines.sync.Mutex) this.L$0); + q6.y.Y(p11); + v11_7 = kotlin.coroutines.jvm.internal.Boxing.boxBoolean(((Boolean) p11).booleanValue()); + v0_2.unlock(0); + return v11_7; + } + } + } + } catch (Boolean v11_2) { + } + v0_2.unlock(0); + throw v11_2; + } +} diff --git a/src/ppclock/cli.py b/src/ppclock/cli.py index ab85501..9de2446 100644 --- a/src/ppclock/cli.py +++ b/src/ppclock/cli.py @@ -161,83 +161,114 @@ def build_parser() -> argparse.ArgumentParser: sp.add_argument("hex") sp.add_argument("--channel", default="rxtx", choices=["rxtx", "epd"]) sp.add_argument("--no-response", action="store_true") + + sp = sub.add_parser("batch", help="JSONL 批量执行(单次连接,agent 编排)") + sp.add_argument("file", help="JSONL 文件,每行 {\"argv\": [...]};'-' 读 stdin") + sp.add_argument("--stop-on-error", action="store_true") return p -async def _dispatch(args) -> dict | None: +async def _run_command(args, t): + """在已有连接上执行单条命令(body 供 batch 复用)。""" + c = args.cmd + if c == "time": + dt = (datetime.datetime.fromisoformat(args.datetime) + if args.datetime else None) + await C.set_time(t, dt) + return {"set": (dt or datetime.datetime.now()).isoformat()} + if c == "mode": + await C.set_mode(t, args.name) + return {"mode": args.name} + if c == "toggle": + await C.toggle(t, args.name) + return {"toggle": args.name} + if c == "clear": + await C.clear(t) + return None + if c == "image": + return await _cmd_image(args, t) + if c == "template": + return await _cmd_template(args, t) + if c == "countdown": + if args.off: + await C.countdown_off(t) + return {"countdown": "off"} + if args.show_calendar: + if not args.prefix: + raise ValueError("--show-calendar 需要 --prefix 提供日历文字") + await C.countdown_set(t, None, args.mode, prefix=args.prefix, + show_calendar=True) + return {"calendar_text": args.prefix} + if not args.date: + raise ValueError("缺少日期(YYYY-MM-DD)或 --off") + d = datetime.date.fromisoformat(args.date) + await C.countdown_set(t, d, args.mode, prefix=args.prefix) + return {"date": d.isoformat(), "mode": args.mode, + "prefix": args.prefix} + if c == "sleep": + on = args.on or not args.off + await C.sleep(t, on, args.start, args.end) + return {"on": on, "start": args.start, "end": args.end} + if c == "parking": + await C.parking(t, args.number) + return {"number": args.number} + if c == "rotation": + await C.rotation(t, count=args.count, interval=args.interval, unit=args.unit) + return {"count": args.count, "interval": args.interval, "unit": args.unit} + if c == "lut": + await C.lut(t, args.value) + return {"lut": hex(args.value)} + if c == "activate": + if args.show_id: + return {"device_id": await C.get_device_id(t)} + if not args.code: + raise ValueError("缺少激活码或 --show-id") + await C.activate(t, args.code) + return None + if c == "wifi": + await C.wifi(t, args.ssid, args.password, args.city) + return None + if c == "raw": + data = bytes.fromhex(args.hex) + if args.channel == "epd": + await t.write_epd(data, response=not args.no_response) + else: + await t.write_rxtx(data, response=not args.no_response) + return {"sent": len(data)} + raise ValueError(f"未知命令 {c}") + + +async def _run_batch(args) -> list: + lines = (sys.stdin.read() if args.file == "-" + else open(args.file, encoding="utf-8").read()) + results = [] + parser = build_parser() + async with _make_transport(args) as t: + for lineno, line in enumerate(lines.splitlines(), 1): + line = line.strip() + if not line: + continue + try: + item = json.loads(line) + argv = item["argv"] if isinstance(item, dict) else item + sub_args = parser.parse_args(argv) + data = await _run_command(sub_args, t) + results.append({"line": lineno, "ok": True, "cmd": sub_args.cmd, + "data": data}) + except Exception as e: + results.append({"line": lineno, "ok": False, "error": str(e)}) + if args.stop_on_error: + break + return results + + +async def _dispatch(args) -> dict | list | None: if args.cmd == "scan": from .transport import scan return {"devices": await scan(timeout=args.timeout)} - - async def body(t): - c = args.cmd - if c == "time": - dt = (datetime.datetime.fromisoformat(args.datetime) - if args.datetime else None) - await C.set_time(t, dt) - return {"set": (dt or datetime.datetime.now()).isoformat()} - if c == "mode": - await C.set_mode(t, args.name) - return {"mode": args.name} - if c == "toggle": - await C.toggle(t, args.name) - return {"toggle": args.name} - if c == "clear": - await C.clear(t) - return None - if c == "image": - return await _cmd_image(args, t) - if c == "template": - return await _cmd_template(args, t) - if c == "countdown": - if args.off: - await C.countdown_off(t) - return {"countdown": "off"} - if args.show_calendar: - if not args.prefix: - raise ValueError("--show-calendar 需要 --prefix 提供日历文字") - await C.countdown_set(t, None, args.mode, prefix=args.prefix, - show_calendar=True) - return {"calendar_text": args.prefix} - if not args.date: - raise ValueError("缺少日期(YYYY-MM-DD)或 --off") - d = datetime.date.fromisoformat(args.date) - await C.countdown_set(t, d, args.mode, prefix=args.prefix) - return {"date": d.isoformat(), "mode": args.mode, - "prefix": args.prefix} - if c == "sleep": - on = args.on or not args.off - await C.sleep(t, on, args.start, args.end) - return {"on": on, "start": args.start, "end": args.end} - if c == "parking": - await C.parking(t, args.number) - return {"number": args.number} - if c == "rotation": - await C.rotation(t, count=args.count, interval=args.interval, unit=args.unit) - return {"count": args.count, "interval": args.interval, "unit": args.unit} - if c == "lut": - await C.lut(t, args.value) - return {"lut": hex(args.value)} - if c == "activate": - if args.show_id: - return {"device_id": await C.get_device_id(t)} - if not args.code: - raise ValueError("缺少激活码或 --show-id") - await C.activate(t, args.code) - return None - if c == "wifi": - await C.wifi(t, args.ssid, args.password, args.city) - return None - if c == "raw": - data = bytes.fromhex(args.hex) - if args.channel == "epd": - await t.write_epd(data, response=not args.no_response) - else: - await t.write_rxtx(data, response=not args.no_response) - return {"sent": len(data)} - raise ValueError(f"未知命令 {c}") - - return await _run_with_transport(args, body) + if args.cmd == "batch": + return {"results": await _run_batch(args)} + return await _run_with_transport(args, lambda t: _run_command(args, t)) def main(argv=None) -> int: diff --git a/tests/test_cli.py b/tests/test_cli.py index 66e5796..27d8052 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -165,3 +165,29 @@ class TestCommands: assert code == 0 t = FakeTransport.instances[0] assert len(t.epd_writes) == 63 + 63 + 1 # 默认三色双平面 + + +class TestBatch: + def test_batch_jsonl(self, capsys, tmp_path): + f = tmp_path / "job.jsonl" + f.write_text( + '{"argv": ["mode", "clock1"]}\n' + '{"argv": ["parking", "12"]}\n' + '{"argv": ["countdown"]}\n' # 故意错误行 + ) + code, out = run_cli(capsys, ["--json", "batch", str(f)]) + assert code == 0 + results = out["data"]["results"] + assert [r["ok"] for r in results] == [True, True, False] + # 单连接:只有1个 transport 实例 + assert len(FakeTransport.instances) == 1 + + def test_batch_stop_on_error(self, capsys, tmp_path): + f = tmp_path / "job.jsonl" + f.write_text( + '{"argv": ["countdown"]}\n' + '{"argv": ["mode", "clock1"]}\n' + ) + code, out = run_cli(capsys, ["--json", "batch", str(f), "--stop-on-error"]) + results = out["data"]["results"] + assert len(results) == 1 and results[0]["ok"] is False