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-scripts gives you the BlueZ example-advertisement and example-gatt-server test scripts). Kali Linuxkernel.googlesource.com

Plug in your TP-Link UB500 and verify the kernel detected it

dmesg | tail -n 30
lsusb

Check 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. The select command 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: select only affects that bluetoothctl session; include it in scripts that configure advertising. Ask UbuntuArch Manual Pages

If you want to persistently prefer the dongle, the simplest safe options are:

  • unplug/disable the built-in adapter (via rfkill or unloading kernel module) or

  • start 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.py with 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’s manufacturer command 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. bluetoothctl is easiest for testing/manufacturer-limited payloads. Arch Manual PagesStack Overflow

  • If bluetoothctl returns “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-advertisement and test/example-gatt-server scripts. 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-server

If the path to bluetoothd differs 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.com

If 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:

    • E3B40001 Event Summary (READ/NOTIFY)

    • E3B40002 Event Detail (READ)

    • E3B40003 ACK (WRITE)

    • E3B40004 Forklift Info (READ)

       

Use example-gatt-server as a template: edit the service/characteristic UUIDs and the JSON returned by read handlers. Example: when the Android app connects and reads E3B40001, return summary JSON; when it reads E3B40002 return full JSON string; when it writes E3B40003, print/log the ACK and persist if required. The BlueZ example shows this DBus pattern (Python + dbus)


7) Running everything & testing

  1. Start bluetoothd (or stop systemd bluetooth and start local debug bluetoothd -n -E -d). MARC

  2. Select your adapter in bluetoothctl (or include select in script). Ask Ubuntu

  3. Start example-gatt-server (edited to your UUIDs) or your DBus GATT server script.

  4. Start advertising payload (either via the Python forklift_adv.py script that uses bluetoothctl manufacturer or via example-advertisement with your bytes).

  5. 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) or sudo hcidump -a to observe adverts & connections. 

  6. 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

  • bluetoothctl manufacturer data is easiest for quick tests but is limited; for exact 31-byte payloads and control of advertising intervals, use BlueZ DBus API or example-advertisement. Arch Manual Pagesscribles.net

  • Running your own bluetoothd for development is normal — be careful to restart the system bluetooth service 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

Powered by Blogger.