step-by-step procedure you can run on Ubuntu with your TP-Link UB500 (BLE 5.3)
2) Prerequisites & installs (Ubuntu)
Open a terminal and run:
sudo apt update
sudo apt install -y bluez bluez-tools bluetooth libbluetooth-dev python3-pip git \
bluez-test-scripts dbus-python libdbus-1-dev libglib2.0-dev
pip3 install dbus-next(installing
bluez-test-scriptsgives you the BlueZexample-advertisementandexample-gatt-servertest scripts). Kali Linuxkernel.googlesource.comPlug in your TP-Link UB500 and verify the kernel detected it
dmesg | tail -n 30
lsusbCheck BlueZ sees adapters:
bluetoothctl list
# or
hciconfig -a
3) Make the TP-Link the active controller (per session)
If you have multiple adapters, list them and select the TP-Link’s controller address in
bluetoothctl. Theselectcommand sets the default controller for the session. Example:
# interactive:
sudo bluetoothctl
[bluetooth]# list
Controller XX:YY:ZZ:AA:BB:CC my-host [default] # see controllers
[bluetooth]# select AA:BB:CC:DD:EE:FF # set TP-Link as default for this session
[bluetooth]# power on
[bluetooth]# discoverable on
[bluetooth]# pairable on
You can also script it:
sudo bluetoothctl <<< $'select AA:BB:CC:DD:EE:FF\npower on\ndiscoverable on\n'
Note:
selectonly affects thatbluetoothctlsession; include it in scripts that configure advertising. Ask UbuntuArch Manual PagesIf you want to persistently prefer the dongle, the simplest safe options are:
unplug/disable the built-in adapter (via
rfkillor unloading kernel module) orstart scripts that always run
bluetoothctl select <TP-LINK_MAC>before advertising. (Persistent udev/kernel changes are possible but riskier.)
4) Build the advertising payload (Python: exact format + HMAC)
Create a file
forklift_adv.pywith this content (script explains itself). This script:
constructs the payload fields per your doc,
creates rotating token (simple example: random or time-based),
computes HMAC-SHA256 and truncates to 8 bytes,
writes manufacturer advertise data via
bluetoothctl(manufacturer data limited to 25 bytes in bluetoothctl; we’ll pack payload into service data or manufacturer data as needed — see notes below).
Save the file as
forklift_adv.py:
#!/usr/bin/env python3
# forklift_adv.py
# Usage: sudo python3 forklift_adv.py --adapter-mac AA:BB:CC:DD:EE:FF --forklift-id 0x12345678 --seq 1
import time, hmac, hashlib, struct, os, argparse, subprocess, binascii
# secret for HMAC (must match SafeView+ test secret or use known test key)
SECRET_KEY = b"super_secret_key_for_testing"
def to_hex_pairs(b: bytes):
return " ".join(f"{x:02x}" for x in b)
def build_payload(company_id=0xFFFF, proto=1, evt_type=1, severity=2,
forklift_hash=0x12345678, seq=1, rot_token=None, secret=SECRET_KEY):
ts = int(time.time())
header = struct.pack("<HBBB", company_id, proto, evt_type, severity) # 2 +1 +1 +1 =5
fl_hash = struct.pack("<I", forklift_hash) # 4
seq_b = struct.pack("<H", seq) # 2
ts_b = struct.pack("<I", ts) # 4
if rot_token is None:
# Simple rot token: 4 bytes from time or random (you may prefer deterministic)
rot_token = struct.pack("<I", int(ts/30))[:4] # example: 30-sec epoch slice (trim to 4 bytes)
sig = hmac.new(secret, header + fl_hash + seq_b + ts_b + rot_token, hashlib.sha256).digest()[:8]
payload = header + fl_hash + seq_b + ts_b + rot_token + sig
return payload
def set_advertisement_via_bluetoothctl(adapter_mac, payload_bytes):
# bluetoothctl manufacturer expects hex bytes; manufacturer can be used to store up to 25 bytes
# Compose manufacturer id + data. Use 0xFFFF as test company ID or your assigned company ID.
manu_id = 0xffff
hexpairs = to_hex_pairs(payload_bytes)
cmd = f"select {adapter_mac}\npower on\nadvertise off\nmanufacturer {manu_id} {hexpairs}\nadvertise on\n"
print("Running bluetoothctl commands:\n", cmd)
subprocess.run(["sudo", "bluetoothctl"], input=cmd.encode())
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--adapter-mac", required=True, help="Bluetooth controller MAC (from bluetoothctl list)")
parser.add_argument("--forklift-id", type=lambda x: int(x,0), default=0x12345678)
parser.add_argument("--seq", type=int, default=1)
args = parser.parse_args()
payload = build_payload(forklift_hash=args.forklift_id, seq=args.seq)
print("payload (len={}): {}".format(len(payload), payload.hex()))
set_advertisement_via_bluetoothctl(args.adapter_mac, payload)Run:
sudo python3 forklift_adv.py --adapter-mac AA:BB:CC:DD:EE:FF --forklift-id 0x12345678 --seq 42
Notes & caveats:
bluetoothctl’smanufacturercommand limits you to 25 bytes of manufacturer data; your full structure may be 31 bytes. You can split into manufacturer data + service data or use BlueZ DBus API to set full 31-byte advertising payload.bluetoothctlis easiest for testing/manufacturer-limited payloads. Arch Manual PagesStack OverflowIf
bluetoothctlreturns “Failed to register advertisement” reduce data size or use BlueZ example-advertisement (next section).
5) Alternative: use BlueZ example-advertisement & example-gatt-server (recommended for exact payload/GATT)
BlueZ includes
test/example-advertisementandtest/example-gatt-serverscripts. These let you:
make arbitrary advertisement (service data / manufacturer data),
provide a DBus GATT server (service + characteristics) which Android can connect to and read/write.
Get & run them:
# clone BlueZ source and run test scripts (no installation necessary)
git clone https://git.kernel.org/pub/scm/bluetooth/bluez.git
cd bluez/test
# Stop system bluetoothd to use the test daemon (optional but recommended for testing)
sudo systemctl stop bluetooth
# Start your own bluetoothd (foreground, with debug/experimental if needed)
sudo /usr/libexec/bluetooth/bluetoothd -n -E -d & # path may be /usr/sbin/bluetoothd depending on distro
# In another terminal, run example-advertisement
sudo python3 example-advertisement
# Run example GATT server
sudo python3 example-gatt-serverIf the path to
bluetoothddiffers on Ubuntu, use the package location (on Debian/Ubuntu it is usually/usr/sbin/bluetoothd). The example scripts are already set up to register a service/characteristic via BlueZ DBus. scribles.netkernel.googlesource.comIf you need your exact custom payload bytes in
example-advertisement, edit that script to place your service data or manufacturer bytes into the advertisement structure.
6) Expose the GATT characteristics SafeView expects
Per the handover doc you need these UUIDs:
Service:
E3B40000-...(use the exact full UUID you decide)Characteristics:
E3B40001Event Summary (READ/NOTIFY)
E3B40002Event Detail (READ)
E3B40003ACK (WRITE)
E3B40004Forklift Info (READ)
Use
example-gatt-serveras a template: edit the service/characteristic UUIDs and the JSON returned by read handlers. Example: when the Android app connects and readsE3B40001, return summary JSON; when it readsE3B40002return full JSON string; when it writesE3B40003, print/log the ACK and persist if required. The BlueZ example shows this DBus pattern (Python + dbus)
7) Running everything & testing
Start
bluetoothd(or stop systemd bluetooth and start local debugbluetoothd -n -E -d). MARCSelect your adapter in
bluetoothctl(or includeselectin script). Ask UbuntuStart
example-gatt-server(edited to your UUIDs) or your DBus GATT server script.Start advertising payload (either via the Python
forklift_adv.pyscript that usesbluetoothctl manufactureror viaexample-advertisementwith your bytes).On Android (SafeView+), scan — the app should detect the advertisement, verify the HMAC (if you used the same secret), and then connect to the GATT service to read summary/detail and write ACK. If it doesn’t connect, check logs with:
sudo btmon(BlueZ monitor) orsudo hcidump -ato observe adverts & connections.For multi-forklift testing, run multiple advertisement instances (each with different forklift_hash and seq). For heavy tests use multiple Linux machines or scripts that schedule adverts at different times.
9) Safety & limits
bluetoothctlmanufacturer data is easiest for quick tests but is limited; for exact 31-byte payloads and control of advertising intervals, use BlueZ DBus API orexample-advertisement. Arch Manual Pagesscribles.netRunning your own
bluetoothdfor development is normal — be careful to restart the systembluetoothservice when you are done:
sudo pkill bluetoothd
sudo systemctl start bluetooth
https://docs.google.com/document/d/1eHswVH-lf3DrOYpYtx1yqeToBoXZJ8nr7M_oQqezqj4/edit?usp=sharing
https://chatgpt.com/share/68b9b9a1-58ec-8000-a48b-563dd1607570
No comments