Files
presence/README.md
T

100 lines
3.9 KiB
Markdown
Raw Normal View History

# 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. The `REGISTRY_TOKEN` repo secret is a bl `write:package` token; org
repos do not inherit it.