CI / check (push) Successful in 1m30s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
100 lines
3.9 KiB
Markdown
100 lines
3.9 KiB
Markdown
# presence
|
|
|
|
BLE proximity events for a kiosk screen, in the spirit of swayidle: you give
|
|
it commands to run when someone comes near and when nobody has been near for a
|
|
while. It listens to Bluetooth LE advertisements through BlueZ and treats their
|
|
signal strength as a rough distance.
|
|
|
|
```
|
|
exec presence --rssi -58 \
|
|
present 'swaymsg "output * power on"' \
|
|
absent 600 'swaymsg "output * power off"'
|
|
```
|
|
|
|
## How it decides
|
|
|
|
Every advertisement BlueZ hears is one RSSI sample for that device. A device
|
|
is **near** when at least `--hits` of its last `--window` samples are at or
|
|
above `--rssi` (defaults: 3 of 5 at -58 dBm). Samples older than `--stale`
|
|
seconds are forgotten, so a phone that stops advertising stops counting.
|
|
|
|
Someone is **present** while any device is near. `present` commands run when
|
|
that becomes true. Each `absent <seconds> <command>` runs once, after nobody
|
|
has been near for that many seconds. `present` fires again only after at
|
|
least one `absent` has fired, so a phone flickering around the threshold does
|
|
not run it over and over. With no `absent` events, `present` fires on every
|
|
arrival.
|
|
|
|
## What it cannot do
|
|
|
|
RSSI is a poor ruler. The same phone reads 15 dB apart in a hand and in a back
|
|
pocket, bodies in between swing it by 10 dB, and phone models differ in
|
|
transmit power. A phone held out at 4 m and one pocketed at 1.5 m can read the
|
|
same. Not every phone advertises: iPhones do constantly, many Androids only
|
|
while their screen is on. Departure is only visible as silence, hence the
|
|
timeout.
|
|
|
|
Treat it as "someone with a phone is around", with a threshold you tune on
|
|
site. For a real 2 m boundary with millisecond response use a 24 GHz mmWave
|
|
module (HLK-LD2410) instead; this tool exists to try the cheap thing first.
|
|
|
|
Addresses are randomized by modern phones and are never written anywhere by
|
|
`presence`. It keeps a few seconds of samples in memory and counts. If you
|
|
log `--calibrate` output, you are the one storing identifiers; do not keep it.
|
|
|
|
## Calibrating
|
|
|
|
Run it at the installed screen, with the display mounted where it will hang,
|
|
because the wall and the panel change the readings:
|
|
|
|
```
|
|
presence --calibrate
|
|
```
|
|
|
|
It prints every advertisement as `seconds address rssi NEAR name` and runs
|
|
nothing. Stand where a visitor stands with your phone in a pocket, then at 4 m,
|
|
then walk past. Pick `--rssi` from what you see; the default -58 is a starting
|
|
point, not a measurement. `--ignore <addr>` drops the staff phones and the
|
|
kiosk's own peripherals once you know their addresses.
|
|
|
|
## Options
|
|
|
|
```
|
|
--rssi <dBm> sample counts as near at or above this (default -58)
|
|
--window <n> samples kept per device (default 5)
|
|
--hits <n> near samples out of the window that make a device near (default 3)
|
|
--stale <secs> forget samples older than this (default 15)
|
|
--adapter <hciN> which adapter (default: the first)
|
|
--ignore <addr> repeatable; addresses to skip
|
|
--calibrate print advertisements, run nothing
|
|
-w, --wait wait for each command to finish, like swayidle -w
|
|
```
|
|
|
|
Commands run through `sh -c` as the user who started `presence`, which in a
|
|
sway config is the session user, so `swaymsg` finds the right socket.
|
|
|
|
## Requirements
|
|
|
|
`bluetoothd` must be running (`systemctl enable --now bluetooth`). BlueZ's
|
|
default D-Bus policy lets any user start discovery, so no root is needed. On a
|
|
Raspberry Pi 3 the BLE controller shares the antenna with wifi; scanning costs
|
|
wifi throughput, which is one more reason to wire the kiosk.
|
|
|
|
## Install
|
|
|
|
From the `[uhhm]` Arch registry on x86_64 and aarch64:
|
|
|
|
```
|
|
sudo pacman -S presence
|
|
```
|
|
|
|
Or build it: `cargo build --release`, or `makepkg` at the repo root.
|
|
|
|
## Release
|
|
|
|
Bump `version` in `Cargo.toml` and `pkgver` in `PKGBUILD` to the same string,
|
|
commit, tag `vX.Y.Z`. CI refuses a tag that disagrees with either. Gitea
|
|
Actions then builds the package natively on both arches and uploads it to the
|
|
registry, authenticated with the uhhm org's `REGISTRY_TOKEN` action secret,
|
|
which every repo in the org inherits.
|