{"id":48600795,"url":"https://github.com/stahnma/therm-pro","last_synced_at":"2026-04-08T22:02:09.930Z","repository":{"id":347712472,"uuid":"1194996862","full_name":"stahnma/therm-pro","owner":"stahnma","description":"Forward Temperatures from a Therm-Pro TP-25 over HTTP. Alert via Webhook.","archived":false,"fork":false,"pushed_at":"2026-03-29T05:37:22.000Z","size":126,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T08:25:55.592Z","etag":null,"topics":["golang","iot","meat","slack","smoking","smoking-swines","webhook"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stahnma.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T04:33:09.000Z","updated_at":"2026-03-29T05:37:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stahnma/therm-pro","commit_stats":null,"previous_names":["stahnma/therm-pro"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/stahnma/therm-pro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stahnma%2Ftherm-pro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stahnma%2Ftherm-pro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stahnma%2Ftherm-pro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stahnma%2Ftherm-pro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stahnma","download_url":"https://codeload.github.com/stahnma/therm-pro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stahnma%2Ftherm-pro/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31575755,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","iot","meat","slack","smoking","smoking-swines","webhook"],"created_at":"2026-04-08T22:02:07.304Z","updated_at":"2026-04-08T22:02:09.917Z","avatar_url":"https://github.com/stahnma.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Therm-Pro\n\nRemote BBQ temperature monitoring system for the ThermPro TP25. An ESP32 connects to the TP25 over Bluetooth LE and relays temperature data over WiFi to a Go server, which provides a real-time web dashboard and Slack alerts.\n\n```\nTP25 --BLE--\u003e ESP32 --HTTP/WiFi--\u003e Go Server --WebSocket--\u003e Browser\n                                       |\n                                       +--Webhook--\u003e Slack\n```\n\n![Therm-Pro Dashboard](docs/img/screenshot.png)\n\n## The ThermoPro TP25\n\nThe [ThermoPro TP25](https://buythermopro.com/products/tp25-wireless-leave-in-meat-thermometer) ([Amazon](https://www.amazon.com/dp/B09MJ1H5JY)) is a wireless Bluetooth leave-in meat thermometer that supports up to 4 probes. It transmits temperature readings over Bluetooth Low Energy (BLE), which makes it possible to bypass the official phone app and read data directly with an ESP32.\n\n![ThermoPro TP25](docs/img/TP25-image.png)\n\n## Features\n\n- **4 probe support** -- pit temp + 3 meat probes, all tracked independently\n- **Real-time web dashboard** -- mobile-friendly with dark/light theme toggle, Fahrenheit/Celsius toggle, per-probe color coding (silver, blue, black, gold), live-updating probe cards and time-series chart\n- **Slack alerts** -- notifications when target temps are hit or pit temp drifts out of range\n- **Alert hysteresis** -- alerts fire once, reset after 3 degrees F, rate-limited to avoid spam\n- **OTA firmware updates** -- upload new ESP32 firmware via the server, ESP32 pulls it on boot\n- **Session persistence** -- cook data survives server restarts, manual reset between cooks\n- **Consul service discovery** -- server auto-registers with local Consul agent; ESP32 finds the server via DNS (`tp25.service.dc1.consul`)\n- **Graceful shutdown** -- server deregisters from Consul and drains connections on SIGINT/SIGTERM\n\n## What You Need\n\n- **ESP32 dev board** -- any ESP32 with WiFi and BLE (e.g., ESP32-DevKitC)\n- **ThermPro TP25** -- the Bluetooth BBQ thermometer\n- **A machine to run the server** -- Linux, macOS, or anything that runs Go binaries (a Raspberry Pi works great)\n\n## Quick Start\n\n### 1. Build the Server\n\nYou'll need [Go 1.21+](https://go.dev/dl/) and GNU Make. If you use [Flox](https://flox.dev), `flox activate` provides all dependencies automatically.\n\n```bash\ngit clone https://github.com/stahnma/therm-pro.git\ncd therm-pro\nmake build\n```\n\nCross-compile for a different target (e.g., Raspberry Pi):\n\n```bash\nGOOS=linux GOARCH=arm64 make build    # ARM64 (Raspberry Pi 4, etc.)\nGOOS=linux GOARCH=amd64 make build    # x86_64\n```\n\n### 2. Run the Server\n\n```bash\n./bin/therm-pro-server\n```\n\nThe server listens on port 8088 by default and stores session data in `~/.therm-pro/session.json`.\n\n**Configuration** is loaded in layers (each overrides the previous):\n\n1. Built-in defaults\n2. `~/.therm-pro/config.yaml` (optional)\n3. `~/.therm-pro/.env` (optional)\n4. Environment variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PORT` | `8088` | HTTP server port |\n| `THERM_PRO_REGISTRATION_PIN` | _(empty)_ | PIN required to register a new passkey (empty = registration disabled) |\n| `THERM_PRO_WEBAUTHN_ORIGIN` | `http://localhost:8088` | WebAuthn origin URL (set to your public URL for passkey auth; domain is derived automatically) |\n| `THERM_PRO_LOG_LEVEL` | `info` | Log verbosity: `debug`, `info`, `warn`, `error` |\n| `THERM_PRO_SLACK_WEBHOOK` | _(empty)_ | Slack incoming webhook URL for alerts |\n| `THERM_PRO_SLACK_SIGNING_SECRET` | _(empty)_ | Slack app signing secret (for `/tp25` slash command) |\n| `THERM_PRO_SLACK_BOT_TOKEN` | _(empty)_ | Slack bot token (for `/tp25` slash command) |\n\nExample `~/.therm-pro/config.yaml`:\n\n```yaml\nport: 8088\nregistration_pin: \"1234\"\nwebauthn_origin: \"http://localhost:8088\"\nlog_level: \"info\"\n\nslack:\n  webhook: \"\"\n  signing_secret: \"\"\n  bot_token: \"\"\n```\n\n**Access control:** Unauthenticated users see a read-only dashboard. Authenticate with a [passkey](#passkey-authentication) for full read/write access. See [Access Control](#access-control) below.\n\nThe server automatically registers itself with the local Consul agent (`localhost:8500`) on startup. If Consul isn't running, the server logs a warning and operates normally.\n\n### 3. Flash the ESP32\n\nSet your WiFi credentials and build/flash the firmware. You'll need [PlatformIO](https://docs.platformio.org/en/latest/core/installation.html) (or use `flox activate` which provides it).\n\n```bash\nexport ESP32_WIFI_SSID=\"your-wifi-name\"\nexport ESP32_WIFI_PASS=\"your-wifi-password\"\n\n# Optional: override if not using Consul DNS\n# export ESP32_SERVER_URL=\"http://192.168.1.100:8088\"\n\n# Generate config, build, and flash in one step (connect ESP32 via USB first)\nmake esp32-flash\n```\n\nIf you have Consul DNS forwarding set up, the default server URL of `http://tp25.service.dc1.consul:8088` resolves automatically. Otherwise, set `ESP32_SERVER_URL` to the server's LAN IP.\n\nThe generated `esp32/src/config.h` is gitignored. A reference template is at `esp32/src/config.h.example`.\n\n### 4. Open the Dashboard\n\nOpen `http://\u003cserver-ip\u003e:8088` in a browser. You should see 4 probe cards updating in real time once the ESP32 connects to the TP25.\n\n## Using Therm-Pro\n\n### Setting Up a Cook\n\n1. Turn on your TP25 and insert probes\n2. Power on the ESP32 -- it will auto-connect to the TP25 (LED blinks while scanning, solid when connected)\n3. Open the dashboard on your phone or laptop\n4. Tap each probe card to set a label (e.g., \"Pit\", \"Brisket\") and alert thresholds\n5. Cook!\n\n### Alert Types\n\n| Alert | Use Case | Example |\n|-------|----------|---------|\n| **Target temp** | Meat is done | Brisket probe hits 203 F |\n| **High temp** | Pit running hot | Pit temp exceeds 275 F |\n| **Low temp** | Pit running cold / fire dying | Pit temp drops below 225 F |\n\nAlerts fire once when the threshold is crossed, then reset after the temperature moves 3 degrees F past the threshold (hysteresis). Minimum 60 seconds between repeated alerts for the same probe.\n\n### Resetting Between Cooks\n\nClick the \"Reset Cook\" button on the dashboard to clear all temperature history. Probe labels and alert configurations are preserved.\n\n### ESP32 LED Status\n\n| LED State | Meaning |\n|-----------|---------|\n| Blinking | Connecting to WiFi or scanning for TP25 |\n| Solid on | Connected to TP25 and sending data |\n| Off | BLE disconnected, attempting reconnect |\n\n### OTA Firmware Updates\n\nAfter the initial USB flash, you can update the ESP32 over WiFi:\n\n1. Make your code changes in `esp32/src/`\n2. Bump the version and rebuild:\n   ```bash\n   export ESP32_FIRMWARE_VERSION=2\n   make esp32-build\n   ```\n3. Upload to the server:\n   ```bash\n   make esp32-upload\n   ```\n4. Reboot the ESP32 (power cycle or reset button) -- it checks for updates on boot and will self-flash\n\n### Slack Webhook Setup (Push Alerts)\n\n1. Go to [Slack API: Incoming Webhooks](https://api.slack.com/messaging/webhooks)\n2. Create a new app (or use an existing one)\n3. Enable Incoming Webhooks\n4. Add a new webhook to a channel of your choice\n5. Copy the webhook URL and set it as `THERM_PRO_SLACK_WEBHOOK`\n\nAlert messages include the alert details and current temps for all 4 probes.\n\n### Slack `/tp25` Slash Command (Pull Status)\n\nType `/tp25` in any Slack channel to get the current cook status: probe temperatures, battery level, and a temperature history chart as a PNG image.\n\n**Setup:**\n\n1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App \u003e From a manifest**\n2. Select your workspace, then paste the contents of [`contrib/slack-app-manifest.json`](contrib/slack-app-manifest.json)\n3. Replace `REPLACE_WITH_YOUR_DOMAIN` in the slash command URL with your actual domain\n4. Install the app to your workspace\n5. Set environment variables:\n   - `THERM_PRO_SLACK_SIGNING_SECRET` -- from **Basic Information \u003e App Credentials \u003e Signing Secret**\n   - `THERM_PRO_SLACK_BOT_TOKEN` -- from **OAuth \u0026 Permissions \u003e Bot User OAuth Token** (starts with `xoxb-`)\n\n**Network access:** Slack needs to reach your server over HTTPS. If the server is on a home network, use [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) to expose the `/slack/command` endpoint without port forwarding:\n\n```bash\n# Install cloudflared and authenticate\ncloudflared tunnel login\ncloudflared tunnel create tp25\n\n# Route traffic to your server\ncloudflared tunnel route dns tp25 tp25.yourdomain.com\n\n# Run the tunnel (or install as a systemd service)\ncloudflared tunnel --url http://localhost:8088 run tp25\n```\n\nFor development/testing, you can use [ngrok](https://ngrok.com/) instead: `ngrok http 8088`.\n\n### Access Control\n\nThe dashboard has two access tiers:\n\n| Tier | Who | Access |\n|------|-----|--------|\n| **Authenticated** | Users with a registered passkey | Full read/write via session cookie |\n| **Unauthenticated** | Everyone else | Read-only — can view temps, chart, battery |\n\nProtected actions (reset cook, set alerts, upload firmware) require a valid passkey session.\n\n### Passkey Authentication\n\nPasskeys let you authenticate using 1Password, Face ID, or any FIDO2 authenticator.\n\n**Register a passkey:**\n\n1. Set `registration_pin` in your config (or `THERM_PRO_REGISTRATION_PIN` env var)\n2. Open the dashboard and click \"Register Passkey\" in the header\n3. Enter the PIN when prompted\n4. Follow the browser/authenticator prompt\n\nRegistration is disabled when no PIN is configured.\n\n**Sign in:**\n\n1. Open the dashboard from anywhere\n2. Click \"Sign In\" in the header\n3. Your authenticator (1Password, etc.) handles the rest\n4. Session lasts 24 hours\n\n**Production setup:** When running behind a reverse proxy (e.g., Cloudflare Tunnel), set `THERM_PRO_WEBAUTHN_ORIGIN` to your public URL (the domain is derived automatically):\n\n```bash\nTHERM_PRO_WEBAUTHN_ORIGIN=https://tp25.yourdomain.com\n```\n\n### Network Setup\n\nThe server runs on your local network. The ESP32 and your phone/laptop need to be on the same network (or have routes to the server).\n\n**Accessing from outside your network:** Set up port forwarding on your router to forward an external port to `\u003cserver-ip\u003e:8088`. The specifics depend on your router.\n\n## Installation\n\nBuild and install as a systemd service:\n\n    make build\n    sudo ./bin/therm-pro-server install\n\nThis will:\n- Copy the binary to `/usr/local/bin/therm-pro-server`\n- Create a `therm-pro` system user\n- Create `/var/lib/therm-pro` data directory\n- Install and enable a systemd unit\n\nTo install to a different prefix (e.g. `/usr` or `/opt`):\n\n    sudo ./bin/therm-pro-server install --prefix=/usr\n\nStart the service:\n\n    sudo systemctl start therm-pro-server\n\nPreview without making changes:\n\n    ./bin/therm-pro-server install --dry-run\n\n## Running as a systemd Service (Manual)\n\nA systemd unit file is provided in `contrib/therm-pro-server.service`. To install manually:\n\n```bash\n# Create a dedicated user\nsudo useradd -r -s /sbin/nologin -d /var/lib/therm-pro therm-pro\n\n# Copy the binary\nsudo cp bin/therm-pro-server /usr/local/bin/\n\n# Install the unit file\nsudo cp contrib/therm-pro-server.service /etc/systemd/system/\n\n# (Optional) Set the Slack webhook\nsudo systemctl edit therm-pro-server\n# Add:\n#   [Service]\n#   Environment=THERM_PRO_SLACK_WEBHOOK=https://hooks.slack.com/services/T.../B.../...\n\n# Enable and start\nsudo systemctl daemon-reload\nsudo systemctl enable --now therm-pro-server\n\n# Check status\nsudo systemctl status therm-pro-server\njournalctl -u therm-pro-server -f\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eDiagnostics\u003c/summary\u003e\n\nThe `/diagnostics` endpoint provides a full connectivity health check across the system. Access it from the \"Diagnostics\" link in the dashboard nav bar or via curl:\n\n```bash\ncurl -s http://localhost:8088/diagnostics | jq .\n```\n\nExample response:\n\n```json\n{\n  \"status\": \"ok\",\n  \"server_firmware_version\": 3,\n  \"consul\": {\n    \"registered\": true,\n    \"service_id\": \"tp25-myhost\",\n    \"service_url\": \"http://192.168.1.100:8088\",\n    \"health_url\": \"http://192.168.1.100:8088/healthz\",\n    \"healthy\": true\n  },\n  \"esp32\": {\n    \"status\": \"ok\",\n    \"ip\": \"192.168.1.50:54321\",\n    \"firmware_version\": 3,\n    \"ble_connected\": true,\n    \"last_seen\": \"2025-07-04T14:30:00Z\",\n    \"data_age\": \"3s\",\n    \"data_age_seconds\": 3\n  }\n}\n```\n\nThe top-level `status` is `\"ok\"` when everything is healthy, or `\"degraded\"` when any component has an issue.\n\n| Problem | What you'll see |\n|---------|-----------------|\n| Consul not running or registration failed | `consul.healthy: false` with an error message |\n| ESP32 has never connected | `esp32.status: \"no data received\"` |\n| ESP32 stopped sending data (\u003e30s) | `esp32.status: \"stale\"` with `data_age` showing how long |\n| ESP32 can't find the TP25 | `esp32.ble_connected: false`, `esp32.status: \"ble_disconnected\"` |\n| Firmware version mismatch | Compare `server_firmware_version` vs `esp32.firmware_version` |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eLogging\u003c/summary\u003e\n\nThe server uses structured logging (`log/slog`) written to stderr. Set `THERM_PRO_LOG_LEVEL` to control verbosity:\n\n```bash\n# Debug logging — shows HTTP requests, session validation, network checks\nTHERM_PRO_LOG_LEVEL=debug ./bin/therm-pro-server\n\n# Default — server lifecycle, auth events, alerts\nTHERM_PRO_LOG_LEVEL=info ./bin/therm-pro-server\n\n# Quiet — only warnings and errors\nTHERM_PRO_LOG_LEVEL=warn ./bin/therm-pro-server\n```\n\nDebug mode is especially useful for diagnosing WebAuthn passkey failures through Cloudflare tunnels — it logs each step of the login/registration ceremony with remote address and error details.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eTroubleshooting\u003c/summary\u003e\n\n#### ESP32 won't connect to TP25\n- Make sure the TP25 is powered on and not connected to another device (phone app, etc.)\n- The TP25 advertises as \"Thermopro\" -- check serial monitor output for scan results\n- Try power cycling both the TP25 and ESP32\n\n#### ESP32 can't reach the server\n- Verify WiFi credentials in `config.h`\n- If using Consul DNS, verify `tp25.service.dc1.consul` resolves: `dig tp25.service.dc1.consul`\n- If not using Consul, check that `ESP32_SERVER_URL` matches the server's LAN IP\n- Ensure the ESP32 and server are on the same network\n- Check serial monitor for connection errors\n\n#### ESP32 flashing fails\n- Make sure you're using `make esp32-flash` (uses espflash) rather than `pio run -t upload` (uses pyserial, which has issues under nix)\n- If espflash can't find the port, try `espflash flash --port /dev/cu.usbserial-XXXXX esp32/.pio/build/esp32/firmware.elf`\n- Run `espflash list-ports` to see available serial ports\n\n#### Dashboard not updating\n- Check that the ESP32 is connected (solid LED)\n- Open browser dev tools and check the WebSocket connection to `/api/ws`\n- Verify data is arriving: `curl http://localhost:8088/api/session`\n\n\u003c/details\u003e\n\n## Development\n\nSee [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer documentation including the dev environment setup, project structure, API reference, testing, and simulating the ESP32 without hardware.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstahnma%2Ftherm-pro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstahnma%2Ftherm-pro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstahnma%2Ftherm-pro/lists"}