# 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 ` 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 ` drops the staff phones and the kiosk's own peripherals once you know their addresses. ## Options ``` --rssi sample counts as near at or above this (default -58) --window samples kept per device (default 5) --hits near samples out of the window that make a device near (default 3) --stale forget samples older than this (default 15) --adapter which adapter (default: the first) --ignore 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 ``` cargo release patch --execute # or minor / major ``` cargo-release bumps `Cargo.toml`, mirrors the version into `PKGBUILD` with `pkgrel=1` (see `release.toml`), commits, tags `vX.Y.Z` and pushes. CI refuses a tag that disagrees with either file, so edit neither by hand. 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.