A swayidle-shaped daemon: `present <cmd>` runs when a Bluetooth LE device comes within an RSSI threshold, `absent <secs> <cmd>` once nobody has for that long. Near means 3 of the last 5 advertisements at or above -58 dBm by default; --calibrate prints the raw stream so the threshold is picked at the installed screen, not guessed. Nothing is stored: a few seconds of samples in memory, counted. Packaged for the [uhhm] registry from the checkout itself, built natively per arch on tag by Gitea Actions, with the version checked across Cargo.toml, PKGBUILD and the tag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
commit
8fe75daad6
@@ -0,0 +1,99 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user