1. 为什么家庭酿酒师最后都会走到自建管理工具这一步
1.1 配方越来越乱,批次对不上号
最开始玩精酿的时候,大部分人都是靠手机备忘录加点厨房电子秤走天下的。糖化时间设个闹钟,酒花分装在小袋子里,笔记里写一句“20g西楚,煮沸结束前5分钟”。前几批酒这么搞完全没问题,反正产量少、配方简单,无非就是一款小麦、一款IPA换着来。
但真正让人崩溃的节点,是你开始连续酿造、迭代配方的时候。我大概在第十批酒附近踩了坑:想复刻三个月前做的一款浑浊IPA,翻遍备忘录只找到“酒花用了不少,干投了两轮”,具体哪个品种多少克、干投在第几天、发酵温度多少,全部没有记录。更惨的是同一款配方的第二版和第三版之间完全没有版本标记,我甚至记不清到底是哪一版装出来的酒比较均衡。那一刻我才意识到,酿造管理不是“记不记”的问题,而是“怎么记、存在哪、怎么回溯”的问题。
BrewUI这个工具,本质上就是冲着这个场景去的。它不是一个菜谱App,也不是一个社交分享社区,而是一套围绕“配方-批次-测量-品鉴”四个核心对象组织的家庭酿造管理系统。你用它不是为了发朋友圈,而是为了让自己每一批酒都有完整的数字档案,任何一次操作都能追溯,任何一次配方改动都能被版本化保存下来。
1.2 市面工具的围城:纸笔、Excel和云端平台的各自边界
在决定自己搭一套BrewUI之前,我也把主流的记录方式都试过一遍,每种都有绕不开的短板。
纸笔记录最自由,想怎么画怎么画,但问题也很明显:不可搜索、不可统计、容易丢失。一本地酿笔记本写满之后,想找一条半年前的干投记录,基本靠翻页。如果你还要做效率分析、温度曲线回溯,纸质记录几乎帮不上忙。
Excel和Numbers是很多人进阶后的选择。表格确实能做结构化记录,糖化温度写一列、比重写一列、日期写一列,看起来挺专业。但用过两个月的人都知道,手工维护表格的时间成本高得吓人,尤其是发酵期每天要测两次比重、记一次温度,你得手动开电脑、打开表格、找到对应批次、填入单元格。手机上操作更是痛苦,一不小心就填错行。
面向消费者的云端酿造平台体验不错,配方计算器、IBU/SRM估算、社区分享这些功能都很完善。但它们最大的问题是数据主权:你的配方数据存在别人的服务器上,导出格式要么没有、要么是封闭的,想自己做跨批次分析几乎不可能。更关键的是,这类平台通常只侧重于配方设计和社区互动,对“这批酒在发酵罐里待了几天、温度变化曲线长什么样”这种过程数据支持得很弱。
桌面端的专业配方软件功能确实硬核,Brewhouse Efficiency、水处理计算、风格指南一应俱全,但它们是“设计工具”而非“追踪工具”。你可以在里面规划一款非常完美的世涛,却没法在它发酵期间每天把实际测量数据录进去生成趋势线。
1.3 BrewUI要解决的核心问题:过程的产物不该被丢掉
我自己定义BrewUI的产品逻辑时,只围绕三个核心问题展开。
第一,批次的生命周期是否完整可见?从计划酿造、糖化煮沸、入罐发酵、装瓶陈化到最终封档,每一步都该有时间戳、负责人(哪怕就是自己)和备注。
第二,测量数据能否自动沉淀、自动关联?发酵期是数据密度最高的阶段,温度、比重每几个小时就有变化,人工记录注定坚持不了多久。BrewUI需要能接收自动采集设备的推送,并把数据自动归到对应批次名下,生成可交互的曲线图。
第三,配方和批次之间是不是双向打通的关系?配方不是躺在列表里的一张纸,它应该能生成可以执行的批次;而每个批次执行过程中的实测数据,反过来要能为配方的下一次迭代提供依据。这才是BrewUI区别于“配方计算器”的本质差异。
一句话总结:BrewUI不是帮你“算”配方的,而是帮你“留住过程”的。
2. BrewUI的数据模型:先想清楚每一批酒的身份信息
2.1 Recipe、Batch、Measurement三层实体如何咬合
整个BrewUI的数据模型,我建议从三张核心表开始设计:Recipe、Batch、Measurement。
Recipe是配方模板,描述“我打算做什么样的酒”,麦芽投放量、酒花添加计划、酵母品种、目标OG、目标IBU这些都在这一层定义。Batch是一次真实执行,对应“我某一锅实际做了什么”,它引用一个Recipe,同时记录酿造日期、实际OG、实际装瓶量、状态流转等执行信息。Measurement则是批次下不断追加的测量点,每次比重、温度、pH数值都是一条记录,归属到具体Batch。
这三层关系简单直接:一个Recipe可以生成多个Batch,一个Batch可以拥有多条Measurement。往后再加一张TastingNote表,与Batch一一关联,存品鉴日期、外观、香气、口感、评分,就能把“设计-执行-结果”完整串起来。
这套模型的优势在于,Recipe和Batch可以不一样。你设计目标OG是1.060,实际测得1.058,数据都在系统里,后续可以轻松算出偏差率,而不会像传统表格那样把目标值和实际值搅在一起。这个设计看似是常识,但很多早期的酿造记录工具恰恰栽在这里。
2.2 麦芽、酒花、酵母的字段设计要面向未来计算
配方细节的字段设计需要想远一步:你现在可能只需要记录克数,但将来肯定会想要系统帮你估算色度和苦度。所以麦芽条目至少要包含重量、色度(Lovibond或EBC)、潜在出糖率三个字段;酒花条目至少要包含品种名称、重量、添加时间点(煮沸开始后多少分钟,或以“干投第3天”这样的相对标记)、Alpha酸百分比;酵母条目至少包含菌株编号、接种量、建议发酵温度区间。
之所以强调“面向未来计算”,是因为这些字段一旦在数据层定型,后面做配方克隆、风格筛选、IBU估算都直接躺在这上面。如果一开始图省事,酒花只存一个“克数”,等到你想按“美式IPA风格”筛选配方的时候,发现根本没有风格字段或者原料字段不完整,再回头补数据就非常痛苦。
辅料和水处理要不要存?我的建议是存,但作为可选的延伸字段即可。酿造水的钙镁离子浓度对出品有影响,但不是每次都必须精确量化,做成一个JSON扩展字段或者关联一张独立的WaterProfile表都行。关键是不要让非核心字段阻塞主流程录入。
2.3 测量数据的结构设计:自动采集与手工输入并存
测量数据是BrewUI最需要花心思设计的部分。发酵期间一个比重计可能每15分钟推送一条数据,一天就是96条,如果每条都纯粹手工录入,操作量大且不现实。所以Measurement表的写入来源需要区分manual与auto两种类型,前端标记清楚,后续数据分析时才能按需过滤。
每条测量数据至少包含测量时间戳、比重(SG)、温度(摄氏)、可选的pH值、可选的备注字段。这里有一个细节要提醒:时间戳一定要用UTC存储,展示时再按本地时区转换。这个规则非常关键,否则设备推送时间和你自己手机录入的时间会存在时区错位,后期画曲线的时候整条线都是歪的。这个问题我们后面第一章实战章节还会专门展开说,因为它在实际部署中真的太常见了。
3. 从零跑通一套酿造记录流程:糖化到装瓶的完整实操
3.1 创建配方页:把脑子里的啤酒翻译成结构化数据
第一次使用BrewUI时,我建议先创建一个配方,把酿造逻辑完整过一遍。假设要酿一款经典的美式IPA,目标OG 1.060、目标FG 1.012、IBU约60、色度约7 SRM,批次量19升。
在配方页里,麦芽列表我一般这样填:
- Pale Ale麦芽 4.5kg,色度3.5L,出糖率79%
- Munich麦芽 0.5kg,色度9L,出糖率75%
- Caramel/Crystal 40 麦芽 0.3kg,色度40L,出糖率74%
酒花列表填:
- Centennial 30g,AA 9.5%,煮沸60分钟(苦花)
- Cascade 30g,AA 6.2%,煮沸10分钟(香花)
- Citra 50g,AA 12%,干投,发酵第3天添加
酵母选US-05,接种量11g,发酵温度控制在18-20摄氏度。
录入界面的表单最好把每个字段都加一个“参考值”提示,比如IBU计算器在线实时估算、色度根据麦芽列表自动累加。这样你不是在单纯录数字,而是在搭积木的过程中直接看到设计方案的整体预估。BrewUI如果做得更贴心一点,还可以在保存配方时创建一个版本号,之后的每一次修改都自动生成新版本,但保留旧版本可供回滚查看。这个功能对后续迭代配方非常重要。
3.2 开启批次:让配方变成一锅真实的酒
配方保存之后,就可以正式“开工酿造”了。在BrewUI里,选择目标配方,点击“开始新批次”,系统自动生成一个Batch,默认状态是planned,同时把配方中的目标值全部复制到批次的预设字段里。班次状态建议设计成这样一个流转链:
planned(计划)→brewing(糖化煮沸中)→fermenting(发酵中)→conditioning(陈化/熟成)→bottled(已装瓶)→archived(封档)
实际操作中,我会在糖化开始时手动把状态从planned改成brewing,并在备注里记下糖化用水量、初始水温、糖化温度稳定在66摄氏度的时刻。煮沸结束、回旋沉淀完毕、麦汁冷却后,测量第一份比重数据,这就是OG。把OG值录入系统时要注意:一定要记录测量温度,因为麦汁温度高于校准温度时比重计读数是偏低的,如果不做温度补偿,OG记录会不准。
从这一锅开始,批量数据里必须同时保存“目标值”和“实际值”。目标OG是配方里的1.060,实际它们是1.058也没关系,关键是要保留偏差,后续效率分析全靠这个差值。
3.3 发酵期测量:OG到FG的每日追踪
入罐、接种酵母之后,Batch状态切换到fermenting。这之后才是BrewUI真正发挥价值的阶段,因为发酵期是一个动态变化的过程,尤其前72小时非常关键,你每隔一段时间就需要看一眼比重和温度。
纯手动录入的话,我建议每天至少记录两次:早上一次、晚上一次。每次记录包含温度、比重、观察到的现象(“蜂窝状泡沫很多”“液面有油花”)。系统会自动把两次记录连成曲线,之后你会非常直观地看到:主发酵阶段比重从1.058两天内猛降到1.030,随后降速放缓,一周后进入平缓期。这些曲线就是判断发酵状态的依据:连续两天比重不变,基本可以判定发酵结束。
如果接入了自动测量设备,这个阶段就彻底解放双手了。每隔15分钟一条数据自动推送到BrewUI,我能直接在手机上看到一条平滑的发酵衰减曲线,不用老是跑到发酵罐边上去看一眼。这里有一个实操经验要分享:自动测量的趋势值可以信,但最终判定FG是否稳定的那一刻,我建议还是手动用正规比重计或折光仪复测一下,因为浮子式电子比重计的绝对精度其实有限,后面还会有专门一节来讲这个坑。
3.4 装瓶、碳酸化与品鉴:批次闭环的最后一公里
发酵结束后,Batch状态切到conditioning。干投或者冷沉结束后就可以装瓶了。装瓶那天我在BrewUI里记录三个关键数据:装瓶量(升)、加糖方式(糖水剂量)、瓶数。加糖量关系到碳酸化后的气泡感,这个不能凭感觉,每升加多少克糖都有标准公式可以参考,记录到系统里能帮你复现「那一批气泡刚刚好」的状态。
装瓶完成、状态切到bottled,并不是这个批次的终点。真正有意思的部分发生在几周之后:开瓶品鉴。在BrewUI里为这个Batch添加一条品鉴记录,写下外观、香气、味道、口感、整体印象,然后给一个百分制评分。如果你同时品鉴了同款配方的两个批次,一个用了US-05,一个用了其他酵母,评分会直接成为配方迭代的支撑数据。
我自己的习惯是:品鉴之后如果觉得某次调整效果很好,就回到Recipe页面,把这个批次备注中提到的小改动正式合并进配方,新增一个版本。这样配方表就变成了一棵有迭代历史的树,哪个版本对应的成品评分最高,一目了然。
4. 为BrewUI接入自动测量设备:iSpindel与DS18B20的本地实践
4.1 比重计数据协议:从浮子倾斜到JSON推送
BrewUI最有价值的扩展,就是让发酵阶段的数据自动流入系统。目前家庭酿造圈使用最广泛的两种电子比重计,一个是iSpindel,一个是Tilt。
iSpindel是一个3D打印的浮子,内部装有加速度计、温度传感器和ESP8266 WiFi芯片,通过浮子倾斜角度换算出比重。它支持通过WiFi把测量数据以HTTP POST请求发送到指定服务器;Tilt则是通过蓝牙BLE广播数据,需要一台树莓派或手机做中转。
在BrewUI这边,实现iSpindel接入非常简单,只需要提供一个HTTP端点接收数据。后端我用FastAPI写了一个简单的接收接口,核心代码如下:
from fastapi import FastAPI, Request app = FastAPI() @app.post("/api/devices/spindel") async def receive_spindel(request: Request): data = await request.json() # data 示例: # {"name": "Spindel-FV1", "angle": 42.1, "temperature": 19.6, # "battery": 3.9, "gravity": 1.048} batch_id = find_batch_by_device(data["name"]) save_measurement(batch_id, data["gravity"], data["temperature"], "auto") return {"status": "ok"}注意这里的find_batch_by_device函数:每个比重计设备应该预先绑定到一个活跃批次上。比如发酵罐1里面的iSpindel要绑定到当前正在发酵的IPA批次。这个绑定关系通常是在Batch创建时手动指定,也可以在设备设置页面里维护一张设备-批次映射表。绑定逻辑虽然简单,但却是自动分流数据的关键,不然设备推上来的数据根本不知道该归到哪批酒的名下。
4.2 温度单独测:DS18B20与1-Wire总线
比重计虽然自带温度传感器,但我强烈建议发酵温度单独用一支高精度探头测。原因很简单:iSpindel是浮在液面上的,它测到的是发酵液上层温度,而发酵罐的酵母主发酵区往往在中下层,温度差异最大可以到一两摄氏度。另外电池电量下降会影响测量稳定性,温度数据波动也变得很可疑。
我自己的方案是用DS18B20防水探头加一块树莓派,通过1-Wire总线读取温度。树莓派上加一个4.7kΩ上拉电阻接在数据线上,系统就能自动识别设备:
# 加载内核模块 sudo modprobe w1-gpio sudo modprobe w1-therm # 查看挂载的设备 ls /sys/bus/w1/devices/ # 输出类似:28-0123456789ab之后直接读取设备文件里的温度值:
cat /sys/bus/w1/devices/28-0123456789ab/w1_slave输出的原始数据里有一行t=19.687,代表当前温度19.687摄氏度。BrewUI可以写一个定时任务,每5分钟读取一次并写入Measurement表,测量来源标记为auto。把温度探头伸进发酵罐前,记得先在沸水里煮几分钟做一下清洁消毒,否则你的消毒工作可能整个白费。
4.3 本地部署的架构选择:Docker Compose就够用
BrewUI这种自托管工具的部署方式,我推荐用Docker Compose把Web服务、数据库、MQTT Broker(如果走MQTT协议的话)编排在一起。下面是一个最简的编排文件结构:
version: "3.8" services: db: image: postgres:16 environment: POSTGRES_DB: brewui POSTGRES_USER: brew POSTGRES_PASSWORD: change_me volumes: - pgdata:/var/lib/postgresql/data web: image: brewui:1.0 ports: - "8080:8080" depends_on: - db数据库我选PostgreSQL而不是SQLite,是因为发酵数据会持续高频写入,而且后续要做跨批次聚合分析,PostgreSQL的窗口函数和扩展支持更顺手。SQLite当然也能跑,但等到测量数据超过几十万条时,查询体验会明显变差。
远程访问方面,我直接把服务暴露到公网的做法是坚决不推荐的。个人使用的系统,用组网工具把家里的服务器和手机、电脑建到一个虚拟局域网里就好了,不开放公网端口、不折腾反向代理,安全性高一个量级。这个方案对家庭部署来说是最省心的。
5. 实测翻车现场:BrewUI使用中最容易踩的几个坑
5.1 比重计校准失误,发酵曲线全线偏移
第一次把iSpindel接入BrewUI时,我犯了一个非常典型的错误:没有做两点校准,直接用了设备默认的换算参数。结果整个发酵周期里,iSpindel显示的比重一直比手动复测的数值低0.005左右,看起来1.055,实际手工测量是1.060。发酵结束后的曲线虽然形状正常,但所有数值整体偏移,导致我一度误判了发酵启动的时间。
排查思路是这样的:先用排除法,拿同一个液体用手动比重计和iSpindel同时测量,排除是温度补偿问题还是角度换算问题。确认是设备换算偏差后,再用两种已知密度的液体做两点校准:纯水对应1.000,蔗糖溶液对应比如1.050。把两个参考点的角度值和比重值输入设备固件里的公式,重新生成换算系数。这事之后我学到一个原则——任何自动测量设备在首次使用前必须做两点校准,并且每几个月要复核一次。
实际上,0.005的比重偏差说大不大,这在酿造上足以影响你对发酵进程的判断。如果拿着偏移的数据去计算酒精含量(ABV),结果也会跟着偏。所以自动测量数据的定位必须清晰:它主要用于观察趋势和相对变化,具体的绝对数值判定还是要以高性能的实验室级测量设备为准。
5.2 批次状态机卡在中间节点,又没有强制按钮
BrewUI早期版本里,批次状态只能按顺序流转,而且每个流转动作都要满足一定条件,比如从fermenting到conditioning,必须要有一条比重记录且两次测量间隔大于48小时。这个设计的初衷是防止误操作,但真正用起来会发现它很死板:有时候我明明已经装瓶了,却因为忘记在某个时间点录入测量数据,导致状态根本推不动。
结果就是:有一批真正已经装瓶装了一周的啤酒,在BrewUI里还挂着fermenting状态,后续想追加品鉴记录都找不到入口。这个问题的教训是——状态机的设计不要过分依赖自动触发的规则。后来我加了一个强制流转按钮override status,并记录强制操作的去重日志。系统可以提醒你“当前状态可能有误”,但绝不能替你锁死操作路径。毕竟在家庭酿造场景下,系统是为人服务的工具,不是约束人的制度。
5.3 时区问题:为什么发酵曲线和你的记忆对不上
这个坑埋得很深,直到我有一天在手机上翻看某一波的发酵曲线,发现时间轴上的峰值和我的记忆差了8个小时,才意识到前端展示时间和数据库存储时间发生了时区漂移。问题出在设备推送的HTTP请求里没有带时区信息,而后端在入库时直接原样存储了设备端的时间戳。
正确做法是:数据库统一用UTC时间存储,应用层在展示时再转换为浏览器所在时区的本地时间。如果设备端的时间戳本身可能是默认的协调世界时(UTC),而后端代码把字符串转成日期时又加了一次时区偏移,数据就会错位。排查这类问题时,可以先把数据库里的时间戳打印出来,看是不是预期值;然后再检查后端解析代码里有没有把字符串当成既带时区又不带时区的类型强行转换。这两个排查点基本能覆盖大多数时区错乱场景。
另外提一个家庭酿造相关的细节:发酵期的测量时间戳不仅影响曲线展示,还会影响“两次读数间隔”的计算,如果时区错乱,有些测量点会被判定为间隔不足48小时,干扰状态机判断。这就是为什么我坚持把时区规范写到数据库连接层配置里,而不是每次在应用代码中临时处理。
6. 把BrewUI变成工艺复盘工具:从记录到决策
6.1 糖化效率反向推算:设备稳定性的量化评估
记录足够多批次后,BrewUI的价值会从“记事本”升级成“仪表盘”。
我一直很看重一个指标:糖化效率。它代表麦芽中的可发酵糖实际萃取出多少,通常取决于设备设计和操作细节(碾碎度、温度控制、洗糟方式是否合理)。在BrewUI里,只要记录目标OG、实际OG、麦芽总克数和麦芽潜在出糖率,系统就能自动算出本次糖化效率。
计算公式可以简单记为:实际效率 = 实际可提取物总量 / 理论最大可提取物总量。举例说明:一批用了4.5kg Pale Ale麦芽,理论最大可提取物约为80%,那么理论可提取总量大约是3.6kg糖分。如果这批最终收集了19升比重1.060的麦汁,按比重和糖分的关系折算约1.42kg实际提取糖,那么糖化效率就是 1.42 / 3.6 ≈ 39%。
先别被这个数字绕晕,关键不是这个绝对值,而是趋势。同一套设备连续做几批之后,你会发现效率稳定在某个区间。如果你的效率稳定在72%到75%之间,那下次设计配方时,就可以直接按这个区间来预期投入量,不用再凭感觉猜达到目标OG需要多少麦芽了。
6.2 发酵曲线的异常识别:用手鼠标一眼揪出温度失控
当自动测量数据积累到一定程度,曲线本身就是一个诊断工具。
有一批Berliner Weisse在发酵到第二天时,温度曲线突然从19摄氏度飙升到22.5摄氏度,紧接着比重曲线在24小时内出现了一个异常快速的下降斜率。如果不是BrewUI里的温度曲线这样直观地显示出来,单看手写记录,我大概率会忽略这次“小波动”。
实际上这个波动意味着主发酵启动太剧烈了,酵母在快速繁殖阶段释放了大量热量,而我的温控设备反应太慢,导致发酵温度超出了预期范围。这种偏差在拉格等低温发酵风格里尤其容易产生不良风味(比如酯味过重、酒体过于辛辣)。看到曲线异常后再结合品鉴记录里的风味描述,就能把“温度失控”和“有异味”关联起来,下一次酿造时提前把温控设备降一度,或者更早开启主动制冷。
6.3 配方版本沉淀:让好喝的批次可以稳定复现
BrewUI里我最喜欢的操作是“从批次反推配方版本”。每批酒都记录了执行时具体的配方、设备、日期、品鉴评分、品鉴笔记。一段时间后,我筛出评分最高的几批酒,对比它们之间的差异,就能非常清晰地看出哪些改动是有效改进、哪些改动纯属多余。
比如我做过两款几乎一样的Session IPA,区别只在一处:A批次干投用量是每升5g,B批次每升8g。品鉴评分显示A批次在整体平衡度上明显高于B批次,原因是8g/L的干投带来了过于强烈的涩感。这个结论一旦沉淀下来,后续创建同类配方便有了依据,不会再纠结要不要“再多扔一把酒花进去”。
所以配方版本管理不是单纯的存档,而是把每次实验的结果固化下来,形成团队酿酒积累的一部分。即便是个人酿酒师,几十批次之后回看自己的配方演变史,也是一件非常有意思的事。
BrewUI这个项目的魅力,正在于它让“过程”不再被浪费。一个人喝的每一杯酒可能不会记得自己是哪个麦芽组合酿出来的,但BrewUI会记得,而这份记录,会让下一批酒变得更好。