Terminal utility for reading a Bluetooth LE pressure/force sensor module and visualizing the live value as a pressure bar.
This works with Bluetooth LE pressure/force sensors that expose a readable or notifiable GATT characteristic. Many simple firmwares return ASCII frames like:
115.01,0,0,0
The reader uses the first numeric field as value and keeps the full frame in the CSV raw column.
If you do not know the BLE address or advertised name yet, run one of the launchers without DEVICE_ADDRESS or DEVICE_NAME. It will install dependencies, scan nearby BLE devices, then print the target configuration to use.
Linux/macOS:
cd /home/krish/bluetooth-pressure-sensor
DEVICE_ADDRESS=AA:BB:CC:DD:EE:FF ./run_pressure.shWindows PowerShell:
cd path\to\bluetooth-pressure-sensor
$env:DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"
.\run_pressure.ps1The script:
- creates
.venvif needed - installs Python requirements
- reconnects the configured paired device if possible on Linux
- polls or subscribes to the selected BLE characteristic
- renders a live terminal pressure bar
- logs readings to
readings.csv
Press Ctrl-C to stop.
The launchers do not include a default device address. Set either DEVICE_ADDRESS or DEVICE_NAME before running.
Set CHARACTERISTIC_UUID only when you know which GATT characteristic should be used. If it is omitted, the reader skips standard GAP/GATT system characteristics, chooses a Nordic UART TX characteristic when present, then prefers readable data characteristics for read polling.
The launchers default to read polling because some pressure sensor firmwares advertise notifications but do not emit samples. For firmware that streams notifications, run the Python CLI with --notify.
The standard 00002a05-0000-1000-8000-00805f9b34fb Service Changed characteristic is not sensor data. If your firmware uses a known custom characteristic, pin it explicitly:
DEVICE_ADDRESS=AA:BB:CC:DD:EE:FF CHARACTERISTIC_UUID=00000002-0000-1000-8000-00805f9b34fb ./run_pressure.sh$env:DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"
$env:CHARACTERISTIC_UUID = "00000002-0000-1000-8000-00805f9b34fb"
.\run_pressure.ps1All platforms need:
- Python 3.10+
- Bluetooth adapter
Linux also needs:
- BlueZ tools, especially
bluetoothctl - GitHub CLI
ghonly if publishing changes
On Ubuntu/Debian:
sudo apt update
sudo apt install python3 python3-venv bluetooth bluezEnable Bluetooth:
sudo systemctl enable --now bluetooth
bluetoothctl showThe controller should show Powered: yes.
On Windows:
- Install Python from https://www.python.org/ or the Microsoft Store
- Pair the BLE module in Windows Settings before running the reader
- Run PowerShell from this project directory
If script execution is blocked by local policy, run:
powershell -ExecutionPolicy Bypass -File .\run_pressure.ps1Start bluetoothctl:
bluetoothctlInside the prompt:
power on
agent on
default-agent
scan on
Wait for the module to appear. It may show a friendly name or only a MAC-like address. Then:
pair AA:BB:CC:DD:EE:FF
trust AA:BB:CC:DD:EE:FF
connect AA:BB:CC:DD:EE:FF
info AA:BB:CC:DD:EE:FF
quit
On a different PC the address may change, especially for BLE random-address devices. If the address does not work, scan again:
source .venv/bin/activate
python pressure_ble_reader.py scanThen inspect the candidate device:
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --list-characteristics --duration 5Use the characteristic that has read, notify, or indicate.
Open Windows Settings, then go to Bluetooth & devices and pair the module. After pairing, scan from this project:
.\.venv\Scripts\python.exe pressure_ble_reader.py scanIf .venv does not exist yet, let one of the PowerShell launchers create it first:
.\run_pressure.ps1Then inspect the candidate device:
.\.venv\Scripts\python.exe pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --list-characteristics --duration 5On Windows, Bleak uses the Windows Bluetooth stack directly. The Linux-only bluetoothctl reconnect fallback is skipped, so the device must be discoverable or already known to Windows.
run_pressure.sh and run_pressure.ps1 do this automatically, but the manual setup is:
Linux/macOS:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txtWindows PowerShell:
py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txtLinux/macOS:
DEVICE_ADDRESS=AA:BB:CC:DD:EE:FF ./run_pressure.shWindows PowerShell:
$env:DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"
.\run_pressure.ps1Override settings with environment variables:
Linux/macOS:
DEVICE_NAME=MySensor MIN_VALUE=28 MAX_VALUE=1728 INTERVAL=0.1 CSV_FILE=readings.csv ./run_pressure.shWindows PowerShell:
$env:DEVICE_NAME = "MySensor"
$env:MIN_VALUE = "28"
$env:MAX_VALUE = "1728"
$env:INTERVAL = "0.1"
$env:CSV_FILE = "readings.csv"
.\run_pressure.ps1Equivalent direct command:
python pressure_ble_reader.py read \
--address AA:BB:CC:DD:EE:FF \
--prefer-read \
--interval 0.25 \
--decode auto \
--visualize \
--min 28 \
--max 1728 \
--csv readings.csvStart a WebSocket server for browser, Node, Python, or other clients:
Linux/macOS:
DEVICE_ADDRESS=AA:BB:CC:DD:EE:FF ./run_server.shWindows PowerShell:
$env:DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"
.\run_server.ps1Default server:
ws://0.0.0.0:8765
From the same PC, clients should connect to:
ws://localhost:8765
From another machine on the LAN, replace localhost with this PC's LAN IP address.
Each message is JSON:
{
"timestamp": "2026-07-20T08:56:28.218+00:00",
"value": 29.48,
"raw": "29.48,0,0,0",
"percent": 0.09,
"min": 28.0,
"max": 1728.0
}Fields:
value: first numeric field decoded from the BLE frameraw: complete raw BLE frame as printable text or hexpercent: clamped0..100display percentage usingMIN_VALUEandMAX_VALUEmin/max: calibration bounds used by the server
Override server settings:
Linux/macOS:
DEVICE_NAME=MySensor HOST=127.0.0.1 PORT=9000 MIN_VALUE=30 MAX_VALUE=2500 INTERVAL=0.1 ./run_server.shWindows PowerShell:
$env:DEVICE_NAME = "MySensor"
$env:HOST = "127.0.0.1"
$env:PORT = "9000"
$env:MIN_VALUE = "30"
$env:MAX_VALUE = "2500"
$env:INTERVAL = "0.1"
.\run_server.ps1For other firmware that actually streams notifications, run pressure_ws_server.py with --notify instead of the default read-polling mode.
Print samples on the server while clients receive them:
Linux/macOS:
DEVICE_ADDRESS=AA:BB:CC:DD:EE:FF PRINT_SAMPLES=1 ./run_server.shWindows PowerShell:
$env:DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"
$env:PRINT_SAMPLES = "1"
.\run_server.ps1Minimal Python client:
source .venv/bin/activate
python - <<'PY'
import asyncio
import websockets
async def main():
async with websockets.connect("ws://localhost:8765") as ws:
while True:
print(await ws.recv())
asyncio.run(main())
PYThe terminal percentage is based on recent local readings in readings.csv.
Observed stats:
All samples: min=10.57, median=27.95, p95=1463.74, max=5937.06
Recent tail: min=26.73, median=27.95, p95=42.24, max=1728.23
The current display calibration is:
0% = 28 raw units, roughly the resting baseline
100% = 1728 raw units, the largest recent pressure event
Values below baseline clamp to 0%. Values above 1728 clamp to 100%. This is a relative pressure indicator, not a calibrated force/pressure unit.
To recalibrate after collecting better samples:
MIN_VALUE=<resting_value> MAX_VALUE=<pressed_value> ./run_pressure.shFor example, if idle is around 30 and your desired full-scale press is 2500:
MIN_VALUE=30 MAX_VALUE=2500 ./run_pressure.shScan:
python pressure_ble_reader.py scanRead by address:
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FFRead by advertised name:
python pressure_ble_reader.py read --name MySensorList characteristics:
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --list-characteristics --duration 5Decode formats:
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --decode auto
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --decode u16le
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --decode f32le
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --decode rawhexApply simple numeric scaling:
python pressure_ble_reader.py read --address AA:BB:CC:DD:EE:FF --scale 0.001 --offset 0The FSR402 is a force-sensing resistor. Its resistance decreases as force increases, but raw readings depend on the voltage divider, firmware, contact area, and mechanical setup. Treat the live percent as a practical relative pressure display unless you calibrate with known loads.