Emergency notification service for solo outdoor trips.
You (in the field, with a Meshtastic node)
│ LoRa mesh
▼
Base node (at home, on your local network)
│ TCP port 4403
▼
Host running Forest Alert
├── meshtastic_watcher.py → detects keywords + heartbeat
├── deadman.py → alerts if no sign of life
└── alerting.py → Signal + email in parallel
│
▼
Trusted contact
Emergency keywords: send HELP, SOS, URGENCE or MAYDAY from your node.
Dead man's switch: send CHECKIN every 4h. Silence = automatic alert.
The base node decrypts packets using the PSKs of its channels before forwarding them over TCP. The service therefore sees plaintext for every channel the base node has the key for. Channels without a known key are ignored (undecrypted packet = unknown portnum).
uv manages the Python environment and dependencies. Install it first:
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Or via pip / pipx
pipx install uvThen clone and sync:
cd ~/forest_alert
uv sync # creates .venv/ and installs all dependencies# Download from https://github.com/AsamK/signal-cli/releases
# Pick the build matching your host architecture.
tar xzf signal-cli-*.tar.gz
sudo mv signal-cli /usr/local/bin/
signal-cli --version# Link the host as a secondary device of your existing Signal account
signal-cli link -n "ForestAlert"
# → Scan the QR code in Signal > Settings > Linked Devices > Link New Device
# Test
signal-cli -a +33XXXXXXXXX send -m "Test from Forest Alert" +33YYYYYYYYYsudo nano /etc/systemd/system/signal-cli.service[Unit]
Description=signal-cli daemon
After=network-online.target
[Service]
Type=simple
User=pi
ExecStart=/usr/local/bin/signal-cli -a +33XXXXXXXXX daemon --http=127.0.0.1:8080
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now signal-clicp config.toml.example config.toml
nano config.toml
# → Fill in: [meshtastic] host, [trigger] my_node_id, [signal] contactsuv run python main.py
# Send "SOS" from the Meshtastic app → check Signal on the contact's phone
# Send "CHECKIN" → check "💓 Heartbeat received" in the logssudo cp forest_alert.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now forest_alert
# Live logs
journalctl -u forest_alert -f| Action | Message to send from your node |
|---|---|
| I am alive | CHECKIN |
| Emergency | SOS (or HELP, URGENCE, MAYDAY) |
Before every trip:
systemctl status signal-cli forest_alert→ bothactive- Send a test
SOS→ verify Signal rings on the contact's phone - Send
CHECKIN→ verify the logs - Share your planned itinerary with the contact
# Logs
journalctl -u forest_alert -f
# Check connection to the Meshtastic node
uv run python -c "
import meshtastic.tcp_interface
i = meshtastic.tcp_interface.TCPInterface('192.168.1.XXX')
print('Nodes:', list(i.nodes.keys()))
i.close()
"
# Test signal-cli daemon
curl -X POST http://127.0.0.1:8080/api/v1/rpc \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"version","id":"test"}'uv sync --group dev
uv run pytest -v- LoRa range: scout your route for dead zones before you leave
- Internet required: if the host loses connectivity, no alert goes out
- Power: a UPS is recommended on the base node and host