{"id":15286502,"url":"https://github.com/linjmeyer/tilt-pitch","last_synced_at":"2025-08-20T18:32:31.656Z","repository":{"id":48056784,"uuid":"293991014","full_name":"linjmeyer/tilt-pitch","owner":"linjmeyer","description":"Simple replacement for the Tilt Hydrometer mobile apps and TiltPi with lots of features","archived":false,"fork":false,"pushed_at":"2024-05-13T14:41:13.000Z","size":540,"stargazers_count":51,"open_issues_count":4,"forks_count":16,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-12-07T09:50:28.651Z","etag":null,"topics":["beer","brewing","grafana","hacktoberfest","homebrew","hydrometer","influxdb","pitch","prometheus","python","tilt","tilt-hydrometer","webhook"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/linjmeyer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"custom":["https://www.buymeacoffee.com/linjmeyer"],"github":["linjmeyer"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null}},"created_at":"2020-09-09T03:20:11.000Z","updated_at":"2024-11-05T06:28:32.000Z","dependencies_parsed_at":"2022-08-12T17:40:27.649Z","dependency_job_id":"68fd796b-8748-4455-8299-159121a013e6","html_url":"https://github.com/linjmeyer/tilt-pitch","commit_stats":{"total_commits":107,"total_committers":7,"mean_commits":"15.285714285714286","dds":"0.16822429906542058","last_synced_commit":"d9f41e51a797ee645eba4e95c690d4254923e344"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjmeyer%2Ftilt-pitch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjmeyer%2Ftilt-pitch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjmeyer%2Ftilt-pitch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjmeyer%2Ftilt-pitch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/linjmeyer","download_url":"https://codeload.github.com/linjmeyer/tilt-pitch/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230445926,"owners_count":18227060,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["beer","brewing","grafana","hacktoberfest","homebrew","hydrometer","influxdb","pitch","prometheus","python","tilt","tilt-hydrometer","webhook"],"created_at":"2024-09-30T15:15:12.884Z","updated_at":"2024-12-19T14:06:56.014Z","avatar_url":"https://github.com/linjmeyer.png","language":"Python","funding_links":["https://www.buymeacoffee.com/linjmeyer","https://github.com/sponsors/linjmeyer"],"categories":[],"sub_categories":[],"readme":"# Pitch (Tilt Hydrometer tool)\n\n![](beer-icon.png \"Beer Icon\")\n\nPitch is an unofficial replacement for Tilt Hydrometer mobile apps and TiltPi software.  Tilt hardware is required.  It is designed to be easy to use and integrated with other tools like Promethues and InfluxDB for metrics, or any generic third party source using webhooks.\n\n![](misc/grafana_example_dashboard.png \"Grafana Example Dashboard\")\n\n# Why\n\nThe Tilt hardware is impressive, but the mobile apps and TiltPi are confusing and buggy.  This project aims to provide a better more reliable solution, but is focused on more tech-savvy brewers than the official Tilt projects.  \n\n# Features\n\nThe following features are implemented, planned, or will be investigated in the future:\n\n* [x] Track multiple Tilts at once\n* [x] Calibrate Tilt readings with known good values\n* [x] Prometheus Metrics\n* [x] Tilt status log file (JSON)\n* [X] InfluxDB 1.0 and 2.0 Metrics\n* [X] Multiple logging and metric sources simultaneously\n* [X] Webhooks for supporting generic integrations (similar to Tilt's Cloud Logging feature)\n* [X] Gravity, original gravity, ABV, temperature and apparent attenuation\n* [X] Custom Beer/brew names (e.g. purple tilt = Pumpkin Ale)\n* [X] Brewing Cloud Services (Brewfather, Brewer's Friend, Grainfather, more can be requested!)\n* [ ] Google Sheets (using any Google Drive)\n\n# Installation\n\nPitch will only work on Linux, with libbluetooth-dev installed.  [See examples/install/prereq.sh](https://github.com/linjmeyer/tilt-pitch/blob/master/examples/install/prereqs.sh) for\nan example of how to do this using apt-get (Ubuntu, Raspberry Pi, etc).  \n\nAfter setting up prereqs install using: `pip3 install tilt-pitch`\nPitch can be run using: `python3 -m pitch`\n\n## Configuration\n\nCustom configurations can be used by creating a file `pitch.json` in the working directory you are running Pitch from.\n\n| Option                       | Purpose                      | Default               | Example               |\n| ---------------------------- | ---------------------------- | --------------------- | --------------------- |\n| `queue_size` (int) | Max queue size for all Tilt event broadcasts.  Events are removed from the queue once all enabled providers have handled the event.  New events are dropped when the queue is maxed.  | `3` | [Example config](examples/queue/pitch.json) |\n| `queue_empty_sleep_seconds` (int) | Time in seconds Pitch will sleep when the queue reaches 0. The higher the value the less CPU time Pitch uses.  Can be 0 or negative (this disables sleep and Pitch will always run). | `1` | [Example config](examples/queue/pitch.json) |\n| `temp_range_min` (int) | Minimum temperature (Fahrenheit) for Pitch to consider a Tilt broadcast to be valid. | `32` | No example yet (PRs welcome!) |\n| `temp_range_max` (int) | Maximum temperature (Fahrenheit) for Pitch to consider a Tilt broadcast to be valid. | `212` | No example yet (PRs welcome!) |\n| `gravity_range_min` (int) | Minimum gravity for Pitch to consider a Tilt broadcast to be valid. | `0.7` | No example yet (PRs welcome!) |\n| `gravity_range_max` (int) | Maximum gravity for Pitch to consider a Tilt broadcast to be valid. | `1.4` | No example yet (PRs welcome!) |\n| `webhook_urls` (array) | Adds webhook URLs for Tilt status updates | None/empty | [Example config](examples/webhook/pitch.json) |\n| `webhook_limit_rate` (int) | Number of webhooks to fire for the limit period (per URL) | 1 | [Example config](examples/webhook/pitch.json) |\n| `webhook_limit_period` (int) | Period for rate limiting (in seconds) | 1 | [Example config](examples/webhook/pitch.json) |\n| `log_file_path` (str) | Path to file for JSON event logging | `pitch_log.json` | No example yet (PRs welcome!) |\n| `log_file_max_mb` (int) | Max JSON log file size in megabytes | `10` | No example yet (PRs welcome!) |\n| `prometheus_enabled` (bool) | Enable/Disable Prometheus metrics | `true` | No example yet (PRs welcome!) |\n| `prometheus_port` (int) | Port number for Prometheus Metrics | `8000` | No example yet (PRs welcome!) |\n| `influxdb_hostname` (str) | Hostname for InfluxDB database | None/empty | No example yet (PRs welcome!) |\n| `influxdb_port` (int) | Port for InfluxDB database | None/empty | No example yet (PRs welcome!) |\n| `influxdb_database` (str) | Name of InfluxDB database | None/empty | No example yet (PRs welcome!) |\n| `influxdb_username` (str) | Username for InfluxDB | None/empty | No example yet (PRs welcome!) |\n| `influxdb_password` (str) | Password for InfluxDB | None/empty | No example yet (PRs welcome!) |\n| `influxdb_batch_size` (int) | Number of events to batch.  Data is not saved to InfluxDB until this threshold is met | `10` | No example yet (PRs welcome!) |\n| `influxdb2_url` (str) | URL of InfluxDB 2.0 database | None/empty | `http://localhost:8086` |\n| `influxdb2_token` (str) | Token for writing to InfluxDB 2.0 | None/empty | a base64 encoded string |\n| `influxdb2_org` (str) | Org for InfluxDB 2.0 database | None/empty | `org_name` |\n| `influxdb2_bucket` (str) | Bucket to write data to in InfluxDB 2.0 | None/empty | `bucket_name`\n| `influxdb_timeout_seconds` (int) | Timeout of InfluxDB reads/writes | `5` | No example yet (PRs welcome!) |\n| `brewfather_custom_stream_url` (str) | URL of Brewfather Custom Stream | None/empty | No example yet (PRs welcome!) |\n| `grainfather_custom_stream_urls` (dict) | Dict of color (key) and URLs (value) | None/empty | [Example config](examples/grainfather/pitch.json) |\n| `grainfather_temp_unit` (str) | Temperature unit `F` or `C` for Grainfather | `F` |  [Example config](examples/grainfather/pitch.json) |\n| `brewersfriend_api_key` (str) | API Key for Brewer's Friend | None/empty | No example yet (PRs welcome!) |\n| `taplistio_url` (str) | URL of Taplist.io Tilt reporting webhook | None/empty | No example |\n| `azure_iot_hub_connectionstring` (str) | Azure IoT Hub Device Connection String | None/empty | [Example config](examples/azure_iot/readme.md) |\n| `azure_iot_hub_limit_rate` (int) | Rate limit according to selected IoT Hub tier. | 8000 | [Example config](examples/azure_iot/pitch.json) |\n| `azure_iot_hub_limit_period` (int) | Period during which to observe rate limit, defaults to one day. | 86400 | [Example config](examples/azure_iot/pitch.json) |\n| `{color}_name` (str) | Name of your brew, where {color} is the color of the Tilt (purple, red, etc) | Color (e.g. purple, red, etc) | No example yet (PRs welcome!) |\n| `{color}_original_gravity` (float) | Original gravity of the beer, where {color} is the color of the Tilt (purple, red, etc) | None/empty | No example yet (PRs welcome!) |\n| `{color}_temp_offset` (int) | Temperature offset to calibrate Tilt temperatures with a secondary reading [See Calibration](#Calibration) | 0 | No example yet (PRs welcome!) |\n| `{color}_gravity_offset` (float) | Gravity offset to calibrate Tilt temperatures with a secondary reading [See Calibration](#Calibration)  | 0 | No example yet (PRs welcome!) |\n\n## Rate Limiting and Batching\n\nA single Tilt can emit several events per second.  To avoid overloading integrations with data events are queued with a max queue size set via the `queue_size`\nconfiguration parameter.  If new events are broadcast from a Tilt and the queue is full, they are ignored.  Events are removed from the queue once all enabled\nproviders have handled the event.  Additionally some providers may implement their own queueing or rate limiting.  InfluxDB for example waits until a certain\nqueue size is met before sending a batch of events, and the Brewfather and Grainfather integrations will only send updates every fifteen minutes.\n\nRefer to the above configuration and the integration list below for details on how this works for different integrations.\n\n## Calibration\n\nYou can calibrate temperature and gravity for each Tilt by color.  To do this stop Pitch if it is running in the background, then run the following command:\n\n`pitch --calibrate={color} --actual-temp=70 --actual-gravity=1.060`\n\nPitch will run for 5 seconds, and log any readings from the color given along with recommended offsets to gravity and temperature.  These can be put in the `pitch.json`\nconfig file to calibrate the Tilt.  Recommendations will be positive when a Tilt is reading low, but negative when a Tilt is reading high.\n\nExample output:\n\n```\npitch --calibrate=purple --actual-gravity=1.070 --actual-temp=50\npurple: gravity=1.035, gravity_offset=0.03500000000000014; temp_f=70, temp_offset=-20\npurple: gravity=1.035, gravity_offset=0.03500000000000014; temp_f=70, temp_offset=-20\npurple: gravity=1.035, gravity_offset=0.03500000000000014; temp_f=70, temp_offset=-20\n```\n\n\n## Running without a Tilt or on Mac/Windows\n\nIf you want to run Tilt on a non-linux system, for development, or without a Tilt you can use the `--simulate-beacons` flag to create fake\nbeacon events instead of scanning for Tilt events via Bluetooth.  \n\n`python3 -m pitch --simulate-beacons`\n\n# Integrations\n\n* [Prometheus](#Prometheus-Metrics)\n* [InfluxDb](#InfluxDB-Metrics)\n* [Webhook](#Webhook)\n* [JSON Log File](#JSON-Log-File)\n* [Brewfather](#Brewfather)\n* [Brewer's Friend](#BrewersFriend)\n* [Grainfather](#Grainfather)\n* [Taplist.io](#taplistio)\n* [Azure IoT Hub](#Azure-IoT-Hub)\n\nDon't see one you want, send a PR implementing [CloudProviderBase](https://github.com/linjmeyer/tilt-pitch/blob/master/pitch/abstractions/cloud_provider.py)\n\n## Prometheus Metrics\n\nPrometheus metrics are hosted on port 8000 by default.  No rate limiting or batching is used for Prometheus.  \n\nFor each Tilt the followed Prometheus metrics are created:\n\n```\n# HELP pitch_beacons_received_total Number of beacons received\n# TYPE pitch_beacons_received_total counter\npitch_beacons_received_total{name=\"Pumpkin Ale\", color=\"purple\"} 3321.0\n\n# HELP pitch_temperature_fahrenheit Temperature in fahrenheit\n# TYPE pitch_temperature_fahrenheit gauge\npitch_temperature_fahrenheit{name=\"Pumpkin Ale\", color=\"purple\"} 69.0\n\n# HELP pitch_temperature_celcius Temperature in celcius\n# TYPE pitch_temperature_celcius gauge\npitch_temperature_celcius{name=\"Pumpkin Ale\", color=\"purple\"} 21.0\n\n# HELP pitch_gravity Gravity of the beer\n# TYPE pitch_gravity gauge\npitch_gravity{name=\"Pumpkin Ale\", color=\"purple\"} 1.035\n\n# HELP pitch_alcohol_by_volume ABV of the beer\n# TYPE pitch_alcohol_by_volume gauge\npitch_alcohol_by_volume{name=\"Pumpkin Ale\", color=\"purple\"} 5.63\n\n# HELP pitch_apparent_attenuation Apparent attenuation of the beer\n# TYPE pitch_apparent_attenuation gauge\npitch_apparent_attenuation{name=\"Pumpkin Ale\", color=\"purple\"} 32.32\n```\n\n## Webhook\n\nUnlimited webhooks URLs can be configured using the config option `webhook_urls`.  Webhooks are rate limited per URL and per Tilt, the rate limit is configurable.\n\nWebhooks are sent as HTTP POST with the following json payload:\n\n```\n{\n    \"name\": \"Pumpkin Ale\",\n    \"color\": \"purple\",\n    \"temp_fahrenheit\": 69,\n    \"temp_celsius\": 21,\n    \"gravity\": 1.035,\n    \"alcohol_by_volume\": 5.63,\n    \"apparent_attenuation\": 32.32\n}\n```\n\n## JSON Log File\n\nTilt status broadcast events can be logged to a json file using the config option `log_file_path`.  Each event is a newline.  Example file:\n\n```\n{\"timestamp\": \"2020-09-11T02:15:30.525232\", \"name\": \"Pumpkin Ale\", \"color\": \"purple\", \"temp_fahrenheit\": 70, \"temp_celsius\": 21, \"gravity\": 0.997, \"alcohol_by_volume\": 5.63, \"apparent_attenuation\": 32.32}\n{\"timestamp\": \"2020-09-11T02:15:32.539619\", \"name\": \"Pumpkin Ale\", \"color\": \"purple\", \"temp_fahrenheit\": 70, \"temp_celsius\": 21, \"gravity\": 0.997, \"alcohol_by_volume\": 5.63, \"apparent_attenuation\": 32.32}\n{\"timestamp\": \"2020-09-11T02:15:33.545388\", \"name\": \"Pumpkin Ale\", \"color\": \"purple\", \"temp_fahrenheit\": 70, \"temp_celsius\": 21, \"gravity\": 0.997, \"alcohol_by_volume\": 5.63, \"apparent_attenuation\": 32.32}\n{\"timestamp\": \"2020-09-11T02:15:34.548556\", \"name\": \"Pumpkin Ale\", \"color\": \"purple\", \"temp_fahrenheit\": 70, \"temp_celsius\": 21, \"gravity\": 0.997, \"alcohol_by_volume\": 5.63, \"apparent_attenuation\": 32.32}\n{\"timestamp\": \"2020-09-11T02:15:35.557411\", \"name\": \"Pumpkin Ale\", \"color\": \"purple\", \"temp_fahrenheit\": 70, \"temp_celsius\": 21, \"gravity\": 0.997, \"alcohol_by_volume\": 5.63, \"apparent_attenuation\": 32.32}\n{\"timestamp\": \"2020-09-11T02:15:36.562158\", \"name\": \"Pumpkin Ale\", \"color\": \"purple\", \"temp_fahrenheit\": 70, \"temp_celsius\": 21, \"gravity\": 0.996, \"alcohol_by_volume\": 5.63, \"apparent_attenuation\": 32.32}\n```\n\n## InfluxDB Metrics\n\nMetrics can be sent to an InfluxDB database.  See [Configuration section](#Configuration) for setting this up.  Pitch does not create the database\nso it must be created before using Pitch.  Tilt events are sent to InfluxDB in batches, data is not sent until the batch size is reached.  The batch size\ndoes not take color into account, so a batch of 50 purple events works the same as 25 purple and 25 red.\n\nEach beacon event from a Tilt will create a measurement like this:\n\n```json\n{\n    \"measurement\": \"tilt\",\n    \"tags\": {\n        \"name\": \"Pumpkin Ale\",\n        \"color\": \"purple\"\n    },\n    \"fields\": {\n        \"temp_fahrenheit\": 70,\n        \"temp_celsius\": 21,\n        \"gravity\": 1.035,\n        \"alcohol_by_volume\": 5.63,\n        \"apparent_attenuation\": 32.32\n    }\n}\n```  \n\nand can be queried with something like:\n\n```sql\nSELECT mean(\"gravity\") AS \"mean_gravity\" FROM \"pitch\".\"autogen\".\"tilt\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: AND \"name\"='Pumpkin Ale' GROUP BY time(:interval:) FILL(previous)\n```\n\n## InfluxDB 2.0 Metrics\n\nMetrics can be sent to an InfluxDB 2.0 database. See [Configuration section](#Configuration) for details on setting it up.  Pitch does not create the bucket.\nThis integration uses the same batching logic, output format, and configuration as the 1.0 integration above.\n\nShared configuration values:\n- `influxdb_timeout`\n- `influxdb_batch_size`\n\n## Brewfather\n\nTilt data can be logged to Brewfather using their Custom Log Stream feature.  See [Configuration section](#Configuration) for setting this up in the Pitch config.  Brewfather\nonly allows logging data every fifteen minutes per Tilt which Pitch adheres to.  Devices will show as `PitchTilt{color}`.\n\nTo setup login into Brewfather \u003e Settings \u003e PowerUps \u003e Enable Custom Stream \u003e Copy the URL into your Pitch config\n\n![Configuring Brewfather Custom Stream URL](misc/brewfather_custom_stream.png)\n\n## Grainfather\n\nTilt data can be logged to Grainfather using their Custom Fermenation Device feature.  See [Configuration section](#Configuration) for setting this up in the Pitch config.  Grainfather only allows logging data ever fifteen minutes per Tilt which Pitch adheres to.  You must create a custom device per Tilt and save each URL into the Pitch config.\n\nTo setup login into Grainfather \u003e My Equipment \u003e Add Fermenation Device \u003e Set the name and save \u003e Press the \"i\" (info) button next to the device \u003e Copy the URL into pitch.config\n\n![Configuring Brewfather Custom Stream URL](misc/grainfather_custom_stream.png)\n\n## Brewer's Friend\n\nTilt data can be logged to Brewer's Friend using their Custom App Stream feature.  See [Configuration section](#Configuration) for setting this up in the Pitch config.  Brewer's Friend\nonly allows logging data every fifteen minutes per Tilt which Pitch adheres to.  Devices will show as `Pitch-Tilt-{color}` as custom devices (they will not appear as Tilts).\n\nTo setup login into Brewer's Friend \u003e Profile \u003e Integrations \u003e Copy Api Key\n\n![Configuring Brewfather Custom Stream URL](misc/brewersfriend_custom_stream.png)\n\n## Taplist.io\n\nTilt data can be logged to [Taplist.io](https://taplist.io/) using the Tilt Integration feature.\n\nTo setup, log into Taplist.io and visit _Account_ \u003e _Integrations_ \u003e _Tilt Hydrometer_. Copy the _Webhook URL_ value into your Pitch config as `taplistio_url`.\n\n## Azure IoT Hub\n\nTilt data can be logged as IoT telemetry to Azure IoT hub and processed\nby a variety of services like Event Hubs, Stream Analytics and Power BI.\n\nTo set up, follow the instructions at [Microsoft Learn](https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-create-through-portal)\nto configure the IoT hub and create a new device to receive your Tilt's measurements.\n\n# Examples\n\nSee the examples directory for:\n\n* InfluxDB Grafana Dashboard\n* Running Pitch as a systemd service\n* pitch.json configuration file\n\n# Other\n\n## Buy me a coffee (beer)\n\n![Buy me a coffee (beer)](misc/buy-me-a-coffee.png)\n\nIf you like Pitch, feel free to coffee (or a beer) here: https://www.buymeacoffee.com/linjmeyer\n\n## Name\n\nIt's an unofficial tradition to name tech projects using nautical terms.  Pitch is a term used to describe the tilting/movement of a ship at sea.  Given pitching is also a brewing term, it seemed like a good fit.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinjmeyer%2Ftilt-pitch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flinjmeyer%2Ftilt-pitch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinjmeyer%2Ftilt-pitch/lists"}