This project was originally a fork of Tilt-Pitch. It is a remodelling of the work already done in that project. Tilt-Pitch is written in Python. This project aims to convert functionality to MicroPython.
The intention is to create a minimal hardware Bluetooth -> wifi bridge, this project has been developed using a Raspberry Pi Pico W. Requirements;
- Raspberry Pi Pico 2 W (RP2350, wifi and bluetooth)
- micro USB cable
- Thonny software
- UF2 release from this project
Since adding the option to use a display this project has had limited testing on the slightly older Pi Pico W (RP2040, wifi and bluetooth). Although it will run, the RP2040 microcontroller version of the Pico has less SRAM (264KB of SRAM vs 520KB) and flash storage (2MB of on-board flash memory vs. 4MB) than the newer RP2350. The price difference is minimal. UF2 files are available for both versions, using a Raspberry Pi Pico 2 W is recommended.
My personal interest is in getting this to work with the Grainfather system and website, then to get some averaging of values: the Tilt seems to transmit very regularly (as in every second), Grainfather allows logging every 15 minutes (which seems reasonable). Rather than log one potentially noisy value every 15 minutes, store the latest n minutes of data in a circular buffer, when a timer has elapsed do some normalisation and/or averaging on that data and log a single, averaged data point.
Below are some graphics, the first GIF shows the Pico W running with no display - the LED blinks every 3 seconds. The second image shows the addition of a Pico Display, and finally a demo showing the display enclosed in a 3d Printed case.
The following features are implemented, planned, or will be investigated in the future:
- Get a minimal demonstration working
- Get Grainfather provider working
- Tilt status data saved to log file (JSON)
- Enable averaging
- More robust WiFi check/reconnect - though more can be added in here
- Watchdog/restarts
- Error logging
- Calibrate Tilt readings with known good values
- Build Instructions
- UF2 release
- LCD display
- visual warning about low storage space
More detailed, step by step instructions are available, the below may suffice
Download the UF2 release (https://github.com/jef41/tilt-micro-bridge/releases) for your device - either Raspberry Pi Pico W or Raspberry Pi Pico 2 W.
Hold down the button on the Pico whilst plugging it into a USB port on your computer.
The device should appear as a mass storage device. Drag and drop the downloaded UF2 file onto the device. This file should take a few seconds to copy over. On completion the mass storage device will disappear. The green LED on the Pico should then light up.
Open Thonny, issue Ctrl-F2 (to stop and restart the connection). Thonny should now display a message about execution interrupt and the REPL prompt >>>. At this point you must edit the config.json file using Thonny.
The Thonny window should show some files on the device, at the bottom left of your screen. Double click the config.json file, add content according to the documentation below. Save this (Ctrl-S) file on the root of the Pico as config.json. These examples of configuration files might help as a starting point. The configuration section below details each option.
Perform aa soft reboot (Ctrl-D), the device will restart and you should see some text output from the deivce in the Thonny shell window. If this output looks OK and includes data from configured Tilt devices then the device is configured and may now be unplugged.
Once configured and in use, the device requires only USB power, it does not necessarily need to be connected to a computer.
Custom configurations can be used by creating a file config.json in the root directory on the Pico. Values in config.json will override any that are already in place, as shown below.
| Option | Purpose | Default | Example |
|---|---|---|---|
ssid (str) |
SSID for your wifi newtork | None | Example config |
password (str) |
password for your wifi newtork | None | Example config |
country_code (str) |
ISO 3166-1 alpha-2 character country code for wifi | None |
Example config |
debug_log (list) |
How many kb in each and how many debug backup files to keep | [20, 1] |
|
display_type (str) |
currently only option is "DISPLAY_PICO_DISPLAY" | None | Example config |
display_update_secs (float) |
how frequently to cycle content of display screen | 5 |
Example config |
lcd_gpio (dictionary) |
GPIO numbered pins for LCD display | {"cs": 17, "dc": 16, "sck": 18, "mosi": 19,"bl": 20} |
Example config |
lcd_backlight (float) |
brightness of LCD display | 0.7 |
Example config |
rgb_led_gpio (list) |
GPIO pins for RGB LED, for PICO_DISPLAY this is [6,7,8] | None | Example config |
rgb_brightness (float) |
brightness of RGB LED | 0.05 |
Example config |
default_averaging_period (int) |
Average data over this period of seconds, 0 = no averaging, use most recent value that is within log period. Value must be less than log period. This default will be used if no provider averaging period is present | 30 |
No example yet |
default_temp_unit (char) |
The deault temperature unit to display, valid values are either C or F. This default will be used if no provider averaging period is present |
C |
No example yet |
temp_range_min (int) |
Minimum temperature (Fahrenheit) for Pitch to consider a Tilt broadcast to be valid. | 32 |
Example config |
temp_range_max (int) |
Maximum temperature (Fahrenheit) for Pitch to consider a Tilt broadcast to be valid. | 212 |
Example config |
gravity_range_min (float) |
Minimum gravity for Pitch to consider a Tilt broadcast to be valid. | 0.7 |
Example config |
gravity_range_max (float) |
Maximum gravity for Pitch to consider a Tilt broadcast to be valid. | 1.4 |
Example config |
csv_log_period (int) |
log data at intervals of this many seconds | 60 |
Example config |
csv_bkp_count (int) |
Keep this number of older files | 4 |
Example config |
csv_log_tilt_colours (list) |
List of colours of Tilt devices to log to a CSV formatted file | None | Example config |
csv_log_averaging_period (int) |
Seconds of data to average over | default_averaging_period | Example config |
csv_log_temp_unit (str) |
Log temperatures in °C or °F | default_temp_unit | Example config |
grainfather_temp_unit (str) |
Temperature unit sent to Grainfather F or C |
C |
Example config |
grainfather_custom_stream_urls (dict) |
Dict of color (key) and URLs (value), seen as a Custom device on Grainfather site | None/empty | Example config |
grainfather_tilt_stream_urls (dict) |
Dict of color (key) and URLs (value), as above, but seen as a Tilt Device | None/empty | Example config |
grainfather_averaging_period (int) |
Average data over this period of seconds, 0 = no averaging, use most recent value that is within log period. Value must be less than log period. | default_averaging_period | Example config |
{colour}_name (str) |
Name of your brew, where {colour} is the color of the Tilt (purple, red, etc) | colour (e.g. purple, red, etc) | Example config |
{colour}_original_gravity (float) |
Original gravity of the beer, where {color} is the color of the Tilt (purple, red, etc) | None/empty | Example config |
{colour}_temp_offsets (list) |
Temperature calibration points See Calibration | None/empty | Example config |
{colour}_gravity_offsets (list) |
Gravity calibration points See Calibration | None/empty | Example config |
The broadcast temperature and gravity readings from the Tilt device may be adjusted by linear interpolation, using the same method as the Tilt2 App.
You may calibrate gravity for each Tilt by colour. At the moment, to apply and test calibration points you will need to run the device while connected to Thonny or other serial connection to observe the data. Alternatively set the config.json to use File CSV logging and run the device for a few minutes in each solution, then connect the device to Thonny and look in the CSV files for data. It is suggested to calibrate for temperature first - allowing 15 minutes for the temperature to stabilise. Then place the Tilt in known gravity solutions that are at a stable, room temperature, i.e. about 20°C. Gather all the temperature calibration points, then apply them to teh config.json, then repeat a similar process for the gravity calibration.
With the bridge running it will show uncalibrated values for the first hour, printed to the debug.log file and to a serial terminal as they are received.
Example output:
data: blue SG:1.0246 72.41°F
Once the value is stable, write down this uncalibrated value and repeat the process with the next solution.
The Tilt takes about 15 minutes to equilibrate with the temperature of a solution. You will need locations where you can maintain a solution at a stable temperature for at elast this amount of time. Insert the Tilt into a liquid at a stable temperature, wait for it to equilibrate then make a note of the Tilt reading and the solution temperature. Repeat as required at different temperature points. Once you have the required readings, enter the values into the config.json, using the colour of the Tilt, e.g.:
"blue_temp_offsets" : [ "C", [4.8,5.0], [49.3,50.0] ],
Note the first list entry identifies the units used for calibration - in this case celsius. In subsequent pairs, the first reading is that received from the Tilt, the second is the known temperature of the liquid.
Insert the Tilt into solutions of known gravity, e.g. 1.000, 1.060, 1.100, leaving the device to settle in each.
Add the uncalibrated values and their associated calibration points to the config file, using the correct colour code for the Tilt, e.g.:
"blue_gravity_offsets" : [ [1.005,1.000], [1.090,1.100], [1.060,1.060] ],
Note that for each pair, the first value is the (uncalibrated) reading from the debug messages, the second value is the calibration point.
The example above shows the Tilt was reading 1.005 in pure water.
As per the Tilt instructions it is suggested that you have at least 2 calibration points, 1.000 & 1.200. Futher points may be added as you see fit.
These calibration points are not stored on the Tilt, but in the Pico. This is also true of the Tilt2 App and the TiltPi setup. You can therefore alternatively use, say the Tilt2 App to view the uncalibrated readings.
If you want to run tilt-mico-bridge for development, or without a Tilt you can use the SIMULATE_BEACONS flag to create fake beacon events instead of scanning for Tilt events via Bluetooth. Edit main.py to set simulate_beacons to True or False. The default is False (i.e. listen for iBeacon Bluetooth transmissions). There is also a debug level that may be set. These are declared in the first few lines of main.py
DEBUG_LEVEL = logging.INFO
SIMULATE_BEACONS = False
The Pico board has an onboard LED. This is used to give a basic visual indication of the condition of the code. The table below should help to interpret the LED status;
| Condition | Appearance | Timing (on/off) milliseconds | Indication |
|---|---|---|---|
| STARTUP | solid ON | None | The Pico is in its initial startup state, loading variables etc. It should progress within 1 second to initiate a wifi connection |
| CONNECTING | fast blink (on-off ~ twice per second) | 10, 400 | Initial configuration loaded, in process of connecting to wifi |
| CONNECTED | 1Hz blink brief | 200, 800 | The Pico has connected to wifi. After 5 seconds it will progress from this state once a stable wifi connection has been established, or try to reconnect |
| NOT CONNECTED | 1Hz blink slow | 800, 200 | A wifi connection has not been established. If not requiring wifi (i.e. logging locally to file) this will not be a problem |
| RUNNING | blink once per 3 secs | 10, 3,000 | Once the startup routine has finished the LED should change to this status to indicate that the application is running and listenting for data from Tilt devices |
If the LED remains solidly lit this indicates that the Pico has encountered an error. It is most likely that either the config.json file is not present, or this file is invalid. In this situation, use Thonny to connect to the device, inspect the debug.log file and correct the issue.
During startup, if a LCD display is present, some information on progress (and errors) will be shown. Once starup has completed and the device is running, the display will normally report Tilt beacon data. If the wifi connection becomes disconnected the LED will revert to its NOT CONNECTED state and (if an LCD is present) the wifi connection status will be added to the display pages. The device will try to reconnect every 2 minutes, unless the password is detected as being incorrect, in which case the device will stop and cease to record Tilt data.
- Prometheus
- InfluxDb
- Webhook
- CSV Log File
- Brewfather
- Brewer's Friend
- Grainfather
- Taplist.io
- Azure IoT Hub
Don't see one you want, send a PR
Tilt status broadcast events can be logged to a .csv file using the config option csv_log_tilt_colours. Enter a list of Tilt colours to listen for, e.g. ["red'] to log only Red Tilt data to CSV. Example file:
2025-02-19 16:40:24, Simulated Tilt, Festbier logger added
2025-02-19 16:40:34, header, Simulated Tilt for Festbier:
timestamp, ABV (%), Apparent Attenuation (%), Temperature (°C), Specific Gravity
2025-02-19 16:40:34, 5.71, 77.04, 21.9, 1.0259
2025-02-19 16:41:04, 6.28, 85.10, 22.5, 1.0216
2025-02-19 16:41:34, 6.37, 86.41, 22.5, 1.0209
2025-02-19 16:42:04, 6.76, 92.03, 22.2, 1.0179
The data logged are;
- Timestamp
- Tilt colour
- Beer name
- ABV
- Apparent Attenuation
- Temperature (°C or °F as specified)
- SG
The log file name will be {colour}.csv. If beer name is included in the config file then the file will be named after the beer name.
If original gravity for the beer is not detailed in the config file then ABV and apparent attenuation will not be present.
When ABV is calculated, the calculation is the longer formula. This is more accurate at higher ABV values than the shorter formula (which is used by the Tilt2 App).
ABV = (OG – FG) * 131.25
ABV = (76.08 * (OG - FG) / (1.775 - OG)) * (FG / 0.794)
Note The Pico has limited flash storage, some of which is used for the program files. RP2350 & RP2040 devices are available with more flash storage, but if using CSV logging it is recommended to remove old files before starting a new logging session. Old files with the same name will be overwritten. See the CSV File examples for more detail.
Tilt data can be logged to Grainfather using their Custom Fermenation Device feature. See Configuration section for setting this up in the file config.json. Grainfather only allows logging data every fifteen minutes per Tilt, which micro-bridge adheres to. You must create a custom device per Tilt and save each URL into the micro-bridge config.
Tilt data can alternatively be logged to Grainfather using their Tilt Fermentation Device feature. The set up is the same as per the Custom device, the only difference being whether Grainfather displays your device as a Custom or a Tilt device.
Note that temperatures displayed on the Grainfather website will use the preference you have configured on their website. This means whether you configure micro-bridge to upload data in Farenheit or Centigrade, the temperature will be converted by the Grainfather website and displayed in your preference configured there. i.e. the Tilt hydrometer natively uses Farenheit, if you want to see temperature data displayed in Centigrade, then change your configuration on the Grainfather website.
To setup, first log in into Grainfather then go to the section My Equipment. Click Add Fermenation Device.
Select either the Custom or Tilt Wireless Hydrometer and Thermometer option. Set the name for a Custom device, or select the colour if you used the Tilt option. Save. Now click the "i" (info) button next to the device and copy this URL into pitch.config. See the Grainfather Provider examples for more detail.
At startup the device will look for a file named main.py on the root of the device. If not found, this file will be created and populated. It will then look for config.json, if not found a generic (but invalid) config.json will be created.
The software will then start from main.py and will look for and validate config.json, this must be located in the root folder of the file system on the device. If the configuration file is invalid the device will halt.
Once the configuration has loaded the Pico will look to see if wifi credentials have been specified. If they have been specified then the device will try to connect to the network. If no wifi credentials are present the device will disable all but the CSV file provider, then continue.
The Tilts and providers detailed in config.json will be provisioned (though if no wifi is present all but CSV file provider will be ignored).
The Pico will start to listen for Tilt devices using Bluetooth. As data is received it will be stored on a queue of data points.
At the specified upload intervals data will be retrieved from the queue. Averaging, calibration and conversion will then be applied as specified from the configuration, and a value stored or uploaded to the provider(s).
If the Pico is plugged in to a USB port on a computer you may use either Thonny, MicroPython's mpremote or a serial terminal (e.g. Putty) to observe messages from the Pico. In its default state received Tilt data will be displayed for the first hour - this is intended to help the calibration process.
In a normal running state the built in LED on the Pico board will blink approximately every 3 seconds to indicate that the device is operating correctly. If an LCD display is present and configured, the display will cycle between configured Tilt devices and a clock display.
The UF2 release contains all the necessary code, pre-compiled into .mpy and frozen (hidden) into the UF2. If you wish to develop/play/test things it is suggested that you manually copy the whole folder and contents /bridge/lib to the root of the Pico filesystem. This will result in reduced filespace for CSV files, but allows for development and testing.
It is worth noting that the UF2 release will automatically (re)create main.py if it is not present. To disable this autorun file, rename main.py to, for example tilt-micro-bridge.py then create a new main.py that contains somethign simple, for example:
print("new main, done.")
More information on drag and drop setup and links to standard releases are available on the Raspberry Pi website
If you like TiltMicroBridge, feel free to buy me a coffee (or a beer) here: https://www.buymeacoffee.com/jef41




