Package: tilt-pitch
Pitch (Tilt Hydrometer tool)Pitch 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.WhyThe 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.FeaturesThe following features are implemented, planned, or will be investigated in the future:Track multiple Tilts at onceCalibrate Tilt readings with known good valuesPrometheus MetricsTilt status log file (JSON)InfluxDB 1.0 and 2.0 MetricsMultiple logging and metric sources simultaneouslyWebhooks for supporting generic integrations (similar to Tilt's Cloud Logging feature)Gravity, original gravity, ABV, temperature and apparent attenuationCustom Beer/brew names (e.g. purple tilt = Pumpkin Ale)Brewing Cloud Services (Brewfather, Brewer's Friend, Grainfather, more can be requested!)Google Sheets (using any Google Drive)InstallationPitch will only work on Linux, with libbluetooth-dev installed.See examples/install/prereq.shfor
an example of how to do this using apt-get (Ubuntu, Raspberry Pi, etc).After setting up prereqs install using:pip3 install tilt-pitchPitch can be run using:python3 -m pitchConfigurationCustom configurations can be used by creating a filepitch.jsonin the working directory you are running Pitch from.OptionPurposeDefaultExamplequeue_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.3Example configqueue_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).1Example configtemp_range_min(int)Minimum temperature (Fahrenheit) for Pitch to consider a Tilt broadcast to be valid.32No example yet (PRs welcome!)temp_range_max(int)Maximum temperature (Fahrenheit) for Pitch to consider a Tilt broadcast to be valid.212No example yet (PRs welcome!)gravity_range_min(int)Minimum gravity for Pitch to consider a Tilt broadcast to be valid.0.7No example yet (PRs welcome!)gravity_range_max(int)Maximum gravity for Pitch to consider a Tilt broadcast to be valid.1.4No example yet (PRs welcome!)webhook_urls(array)Adds webhook URLs for Tilt status updatesNone/emptyExample configwebhook_limit_rate(int)Number of webhooks to fire for the limit period (per URL)1Example configwebhook_limit_period(int)Period for rate limiting (in seconds)1Example configlog_file_path(str)Path to file for JSON event loggingpitch_log.jsonNo example yet (PRs welcome!)log_file_max_mb(int)Max JSON log file size in megabytes10No example yet (PRs welcome!)prometheus_enabled(bool)Enable/Disable Prometheus metricstrueNo example yet (PRs welcome!)prometheus_port(int)Port number for Prometheus Metrics8000No example yet (PRs welcome!)influxdb_hostname(str)Hostname for InfluxDB databaseNone/emptyNo example yet (PRs welcome!)influxdb_port(int)Port for InfluxDB databaseNone/emptyNo example yet (PRs welcome!)influxdb_database(str)Name of InfluxDB databaseNone/emptyNo example yet (PRs welcome!)influxdb_username(str)Username for InfluxDBNone/emptyNo example yet (PRs welcome!)influxdb_password(str)Password for InfluxDBNone/emptyNo example yet (PRs welcome!)influxdb_batch_size(int)Number of events to batch.  Data is not saved to InfluxDB until this threshold is met10No example yet (PRs welcome!)influxdb2_url(str)URL of InfluxDB 2.0 databaseNone/emptyhttp://localhost:8086influxdb2_token(str)Token for writing to InfluxDB 2.0None/emptya base64 encoded stringinfluxdb2_org(str)Org for InfluxDB 2.0 databaseNone/emptyorg_nameinfluxdb2_bucket(str)Bucket to write data to in InfluxDB 2.0None/emptybucket_nameinfluxdb_timeout_seconds(int)Timeout of InfluxDB reads/writes5No example yet (PRs welcome!)brewfather_custom_stream_url(str)URL of Brewfather Custom StreamNone/emptyNo example yet (PRs welcome!)grainfather_custom_stream_urls(dict)Dict of color (key) and URLs (value)None/emptyExample configgrainfather_temp_unit(str)Temperature unitForCfor GrainfatherFExample configbrewersfriend_api_key(str)API Key for Brewer's FriendNone/emptyNo example yet (PRs welcome!)taplistio_url(str)URL of Taplist.io Tilt reporting webhookNone/emptyNo exampleazure_iot_hub_connectionstring(str)Azure IoT Hub Device Connection StringNone/emptyExample configazure_iot_hub_limit_rate(int)Rate limit according to selected IoT Hub tier.8000Example configazure_iot_hub_limit_period(int)Period during which to observe rate limit, defaults to one day.86400Example config{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!){color}_original_gravity(float)Original gravity of the beer, where {color} is the color of the Tilt (purple, red, etc)None/emptyNo example yet (PRs welcome!){color}_temp_offset(int)Temperature offset to calibrate Tilt temperatures with a secondary readingSee Calibration0No example yet (PRs welcome!){color}_gravity_offset(float)Gravity offset to calibrate Tilt temperatures with a secondary readingSee Calibration0No example yet (PRs welcome!)Rate Limiting and BatchingA single Tilt can emit several events per second.  To avoid overloading integrations with data events are queued with a max queue size set via thequeue_sizeconfiguration 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
providers have handled the event.  Additionally some providers may implement their own queueing or rate limiting.  InfluxDB for example waits until a certain
queue size is met before sending a batch of events, and the Brewfather and Grainfather integrations will only send updates every fifteen minutes.Refer to the above configuration and the integration list below for details on how this works for different integrations.CalibrationYou 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:pitch --calibrate={color} --actual-temp=70 --actual-gravity=1.060Pitch 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 thepitch.jsonconfig file to calibrate the Tilt.  Recommendations will be positive when a Tilt is reading low, but negative when a Tilt is reading high.Example output:pitch --calibrate=purple --actual-gravity=1.070 --actual-temp=50
purple: gravity=1.035, gravity_offset=0.03500000000000014; temp_f=70, temp_offset=-20
purple: gravity=1.035, gravity_offset=0.03500000000000014; temp_f=70, temp_offset=-20
purple: gravity=1.035, gravity_offset=0.03500000000000014; temp_f=70, temp_offset=-20Running without a Tilt or on Mac/WindowsIf you want to run Tilt on a non-linux system, for development, or without a Tilt you can use the--simulate-beaconsflag to create fake
beacon events instead of scanning for Tilt events via Bluetooth.python3 -m pitch --simulate-beaconsIntegrationsPrometheusInfluxDbWebhookJSON Log FileBrewfatherBrewer's FriendGrainfatherTaplist.ioAzure IoT HubDon't see one you want, send a PR implementingCloudProviderBasePrometheus MetricsPrometheus metrics are hosted on port 8000 by default.  No rate limiting or batching is used for Prometheus.For each Tilt the followed Prometheus metrics are created:# HELP pitch_beacons_received_total Number of beacons received
# TYPE pitch_beacons_received_total counter
pitch_beacons_received_total{name="Pumpkin Ale", color="purple"} 3321.0

# HELP pitch_temperature_fahrenheit Temperature in fahrenheit
# TYPE pitch_temperature_fahrenheit gauge
pitch_temperature_fahrenheit{name="Pumpkin Ale", color="purple"} 69.0

# HELP pitch_temperature_celcius Temperature in celcius
# TYPE pitch_temperature_celcius gauge
pitch_temperature_celcius{name="Pumpkin Ale", color="purple"} 21.0

# HELP pitch_gravity Gravity of the beer
# TYPE pitch_gravity gauge
pitch_gravity{name="Pumpkin Ale", color="purple"} 1.035

# HELP pitch_alcohol_by_volume ABV of the beer
# TYPE pitch_alcohol_by_volume gauge
pitch_alcohol_by_volume{name="Pumpkin Ale", color="purple"} 5.63

# HELP pitch_apparent_attenuation Apparent attenuation of the beer
# TYPE pitch_apparent_attenuation gauge
pitch_apparent_attenuation{name="Pumpkin Ale", color="purple"} 32.32WebhookUnlimited webhooks URLs can be configured using the config optionwebhook_urls.  Webhooks are rate limited per URL and per Tilt, the rate limit is configurable.Webhooks are sent as HTTP POST with the following json payload:{
    "name": "Pumpkin Ale",
    "color": "purple",
    "temp_fahrenheit": 69,
    "temp_celsius": 21,
    "gravity": 1.035,
    "alcohol_by_volume": 5.63,
    "apparent_attenuation": 32.32
}JSON Log FileTilt status broadcast events can be logged to a json file using the config optionlog_file_path.  Each event is a newline.  Example file:{"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}
{"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}
{"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}
{"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}
{"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}
{"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}InfluxDB MetricsMetrics can be sent to an InfluxDB database.  SeeConfiguration sectionfor setting this up.  Pitch does not create the database
so 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
does not take color into account, so a batch of 50 purple events works the same as 25 purple and 25 red.Each beacon event from a Tilt will create a measurement like this:{"measurement":"tilt","tags":{"name":"Pumpkin Ale","color":"purple"},"fields":{"temp_fahrenheit":70,"temp_celsius":21,"gravity":1.035,"alcohol_by_volume":5.63,"apparent_attenuation":32.32}}and can be queried with something like:SELECTmean("gravity")AS"mean_gravity"FROM"pitch"."autogen"."tilt"WHEREtime>:dashboardTime:ANDtime<:upperDashboardTime:AND"name"='Pumpkin Ale'GROUPBYtime(:interval:)FILL(previous)InfluxDB 2.0 MetricsMetrics can be sent to an InfluxDB 2.0 database. SeeConfiguration sectionfor details on setting it up.  Pitch does not create the bucket.
This integration uses the same batching logic, output format, and configuration as the 1.0 integration above.Shared configuration values:influxdb_timeoutinfluxdb_batch_sizeBrewfatherTilt data can be logged to Brewfather using their Custom Log Stream feature.  SeeConfiguration sectionfor setting this up in the Pitch config.  Brewfather
only allows logging data every fifteen minutes per Tilt which Pitch adheres to.  Devices will show asPitchTilt{color}.To setup login into Brewfather > Settings > PowerUps > Enable Custom Stream > Copy the URL into your Pitch configGrainfatherTilt data can be logged to Grainfather using their Custom Fermenation Device feature.  SeeConfiguration sectionfor 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.To setup login into Grainfather > My Equipment > Add Fermenation Device > Set the name and save > Press the "i" (info) button next to the device > Copy the URL into pitch.configBrewer's FriendTilt data can be logged to Brewer's Friend using their Custom App Stream feature.  SeeConfiguration sectionfor setting this up in the Pitch config.  Brewer's Friend
only allows logging data every fifteen minutes per Tilt which Pitch adheres to.  Devices will show asPitch-Tilt-{color}as custom devices (they will not appear as Tilts).To setup login into Brewer's Friend > Profile > Integrations > Copy Api KeyTaplist.ioTilt data can be logged toTaplist.iousing the Tilt Integration feature.To setup, log into Taplist.io and visitAccount>Integrations>Tilt Hydrometer. Copy theWebhook URLvalue into your Pitch config astaplistio_url.Azure IoT HubTilt data can be logged as IoT telemetry to Azure IoT hub and processed
by a variety of services like Event Hubs, Stream Analytics and Power BI.To set up, follow the instructions atMicrosoft Learnto configure the IoT hub and create a new device to receive your Tilt's measurements.ExamplesSee the examples directory for:InfluxDB Grafana DashboardRunning Pitch as a systemd servicepitch.json configuration fileOtherBuy me a coffee (beer)If you like Pitch, feel free to coffee (or a beer) here:https://www.buymeacoffee.com/linjmeyerNameIt'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.