Document BullTron BLE commands
This commit is contained in:
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build BullTron/Daly-style BLE frames observed in the Android app."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
def crc16_modbus_swapped(hex_data: str) -> str:
|
||||
data = bytes.fromhex(hex_data)
|
||||
crc = 0xFFFF
|
||||
for byte in data:
|
||||
crc ^= byte
|
||||
for _ in range(8):
|
||||
if crc & 1:
|
||||
crc = (crc >> 1) ^ 0xA001
|
||||
else:
|
||||
crc >>= 1
|
||||
swapped = ((crc & 0xFF00) >> 8) | ((crc & 0x00FF) << 8)
|
||||
return f"{swapped:04X}"
|
||||
|
||||
|
||||
def read_frame(start: int, count: int) -> str:
|
||||
body = f"D203{start:04X}{count:04X}"
|
||||
return body + crc16_modbus_swapped(body)
|
||||
|
||||
|
||||
def write_single_frame(register: int, value: int) -> str:
|
||||
body = f"D206{register:04X}{value:04X}"
|
||||
return body + crc16_modbus_swapped(body)
|
||||
|
||||
|
||||
def write_multi_frame(start: int, count: int, payload_hex: str) -> str:
|
||||
payload = payload_hex.replace(" ", "").upper()
|
||||
body = f"D210{start:04X}{count:04X}{payload}"
|
||||
return body + crc16_modbus_swapped(body)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
p_read = sub.add_parser("read")
|
||||
p_read.add_argument("start", type=lambda x: int(x, 0))
|
||||
p_read.add_argument("count", type=lambda x: int(x, 0))
|
||||
|
||||
p_write = sub.add_parser("write")
|
||||
p_write.add_argument("register", type=lambda x: int(x, 0))
|
||||
p_write.add_argument("value", type=lambda x: int(x, 0))
|
||||
|
||||
p_multi = sub.add_parser("write-multi")
|
||||
p_multi.add_argument("start", type=lambda x: int(x, 0))
|
||||
p_multi.add_argument("count", type=lambda x: int(x, 0))
|
||||
p_multi.add_argument("payload_hex")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.cmd == "read":
|
||||
print(read_frame(args.start, args.count))
|
||||
elif args.cmd == "write":
|
||||
print(write_single_frame(args.register, args.value))
|
||||
elif args.cmd == "write-multi":
|
||||
print(write_multi_frame(args.start, args.count, args.payload_hex))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scan decompiled Android sources for BLE UUIDs and GATT-related calls."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
UUID_RE = re.compile(
|
||||
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
||||
)
|
||||
|
||||
GATT_TERMS = [
|
||||
"BluetoothGatt",
|
||||
"BluetoothGattCallback",
|
||||
"BluetoothGattCharacteristic",
|
||||
"BluetoothGattDescriptor",
|
||||
"BluetoothLeScanner",
|
||||
"startScan",
|
||||
"startLeScan",
|
||||
"connectGatt",
|
||||
"createBond",
|
||||
"discoverServices",
|
||||
"getService",
|
||||
"getCharacteristic",
|
||||
"setCharacteristicNotification",
|
||||
"writeCharacteristic",
|
||||
"readCharacteristic",
|
||||
"writeDescriptor",
|
||||
"onCharacteristicChanged",
|
||||
"onCharacteristicRead",
|
||||
"onCharacteristicWrite",
|
||||
]
|
||||
|
||||
|
||||
def interesting(path: Path) -> bool:
|
||||
return path.suffix.lower() in {".java", ".kt", ".xml", ".smali"}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("source", type=Path, help="JADX/apktool output directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
for path in sorted(p for p in args.source.rglob("*") if p.is_file() and interesting(p)):
|
||||
try:
|
||||
text = path.read_text(errors="ignore")
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
matches = sorted(set(UUID_RE.findall(text)))
|
||||
terms = [term for term in GATT_TERMS if term in text]
|
||||
if not matches and not terms:
|
||||
continue
|
||||
|
||||
print(f"## {path}")
|
||||
if matches:
|
||||
print("UUIDs:")
|
||||
for value in matches:
|
||||
print(f"- {value.lower()}")
|
||||
if terms:
|
||||
print("GATT terms:")
|
||||
for value in terms:
|
||||
print(f"- {value}")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user