第10章 智能厨房:从代码到灶台的具身智能

核心隐喻:如果说前面的章节是在虚拟世界里写菜谱,这一章我们要把代码烧进真实的灶台。智能厨房不是"给锅装个WiFi",而是构建一个能感知、决策、执行的烹饪Agent系统——从感知食材状态,到推理烹饪参数,到控制火候调味,完成从数字世界到物理世界的闭环。


10.1 开篇:从虚拟世界到物理世界

10.1.1 一个程序员的厨房幻想

2023年底,我在厨房做一道"糖醋排骨"。按照精确的配方:排骨500克、白糖50克、醋30毫升、生抽20毫升。一切完美,直到最后收汁时接了个电话,回来时糖浆已经焦化发苦。

那一刻我意识到:再完美的菜谱,也无法对抗人类注意力的脆弱。

这让我开始思考一个问题:如果我们能把程序员的思维——精确控制、状态监控、异常处理、自动重试——应用到真实的烹饪过程中,会发生什么?

答案是:智能厨房

10.1.2 什么是"具身智能"

“具身智能”(Embodied AI)是AI领域的前沿概念,指的是AI不仅存在于数字世界,还能通过物理身体(机器人、传感器、执行器)与真实世界交互。

在厨房场景中:

维度 传统厨房 智能厨房
感知 人眼观察颜色、鼻子闻气味 摄像头+气体传感器+温度探针
决策 厨师经验和直觉 AI模型推理最佳参数
执行 人手控制火候、翻炒 电机驱动搅拌、电磁阀控制燃气
反馈 尝一口判断味道 传感器实时监测pH值、糖度、咸度
学习 做100次才能掌握 每次烹饪数据自动优化模型

这不是科幻。2024年,中国市场上已经有成熟的智能烹饪设备:

  • 捷赛DW30:家用自动炒菜机,内置3000+菜谱,自动控温翻炒
  • Liffo:商用智能炒菜机器人,3分钟出一道菜,连锁餐厅标配
  • 熙香智慧餐厅:全自动化厨房,从切配到出餐无人化
  • 万得厨:端到端烹饪大模型,让机器人"学会"做中餐

本章将从硬件、软件、AI三个层面,系统拆解智能厨房的技术栈。作为程序员转型的美食博主,你不仅能用这些工具提升效率,更能从技术视角创作独特的内容——这是传统美食博主无法触及的领域。


10.2 智能设备全景:从家用到商用的技术谱系

10.2.1 家用智能设备

捷赛DW30:程序员的"第一台智能锅"

捷赛DW30是我购买的第一台智能炒菜机。作为一个程序员,我首先关注的不是它的菜谱数量,而是它的控制逻辑

温度<180°C

温度>=180°C

温度波动

需要翻炒

需要调味

时间到

用户选择菜谱

设备加载参数

预热阶段

大功率加热

投料提示

烹饪阶段

PID控温

电机驱动搅拌

自动投料泵

出锅提示

技术参数对比

参数 捷赛DW30 传统炒锅 程序员视角
控温精度 ±5°C ±30°C(人手) 闭环控制 vs 开环控制
翻炒频率 恒定30rpm 不稳定 确定性 vs 随机性
调味精度 ±1ml ±5ml(手感) 量化 vs 模糊
菜谱复现率 >95% <60% 可重复性
学习曲线 选菜谱→按开始 练习3-6个月 即插即用 vs 长周期训练

实际体验:用DW30做"宫保鸡丁",连续做了5次,口感一致性惊人。但局限性也很明显——它无法处理"少许盐"这种模糊指令,也无法根据食材的新鲜度动态调整烹饪时间。这正是AI可以发挥作用的地方。

其他家用设备
设备 核心能力 价格区间 适用场景
美善品TM6 12种烹饪模式、精确控温、称重 ¥12,000-15,000 西餐、烘焙
小米智能电饭煲 IH加热、APP控制 ¥500-1,500 米饭、粥
Anova Precision Cooker 低温慢煮、0.1°C精度 ¥1,500-2,500 牛排、鸡胸
June Oven 摄像头识别食材、自动调温 $1,200 烤箱料理

10.2.2 商用智能设备

Liffo:连锁餐厅的"代码厨师"

Liffo是我在调研商用厨房时接触最多的品牌。它的商业模式很有趣:卖设备+卖菜谱订阅

技术架构

// types/liffo-device.ts — Liffo设备数据模型

interface LiffoDevice {
  deviceId: string;
  model: 'standard' | 'pro' | 'enterprise';
  firmware: string;
  
  // 硬件能力
  capabilities: {
    maxTemperature: number;      // 最高温度 (°C)
    heatingPower: number;        // 加热功率 (W)
    stirringSpeed: number[];     // 搅拌速度档位 [rpm]
    seasoningPumps: number;      // 调味料泵数量
    weightSensor: boolean;       // 是否有称重传感器
  };
  
  // 运行状态
  status: {
    state: 'idle' | 'preheating' | 'cooking' | 'cleaning' | 'error';
    currentTemperature: number;
    currentRecipe?: string;
    progress: number;            // 0-100%
    estimatedCompletionTime?: string;
  };
  
  // 烹饪历史
  cookingHistory: Array<{
    recipeId: string;
    timestamp: string;
    duration: number;
    successRate: number;         // 用户评分
    adjustments: Record<string, number>;  // 实际调整的参数
  }>;
}

interface RecipeSubscription {
  recipeId: string;
  name: string;
  cuisine: string;
  difficulty: 'easy' | 'medium' | 'hard';
  estimatedTime: number;         // 分钟
  servings: number;
  
  // 烹饪参数(精确到每一步)
  steps: Array<{
    order: number;
    action: 'heat' | 'stir' | 'add_ingredient' | 'add_seasoning' | 'wait';
    parameters: {
      temperature?: number;
      duration?: number;
      speed?: number;
      ingredient?: { name: string; amount: number; unit: string };
    };
    condition?: string;          // 触发条件(如"温度达到180°C")
  }>;
  
  // 设备适配
  compatibleDevices: string[];
  lastUpdated: string;
}

实际案例:某连锁快餐品牌使用Liffo后,出餐时间从平均8分钟降至3分钟,厨师培训时间从3个月降至1周。更重要的是,口味一致性从70%提升到95%。

熙香智慧餐厅:全自动化厨房

熙香是更激进的方案——整个厨房无人化。我参观了他们在上海的一家门店:

工作流程

  1. 订单接收:系统接收外卖/堂食订单
  2. 食材调度:AGV小车将预切配的食材送到对应工位
  3. 自动烹饪:机械臂抓取食材,投入智能锅
  4. 智能烹饪:设备按预设程序执行,传感器实时监控
  5. 自动出餐:成品装盘,传送到出餐口
  6. 自动清洗:设备自清洁,准备下一单

关键技术

  • 计算机视觉:识别食材种类和状态(是否切好、是否新鲜)
  • 机械臂控制:精确的抓取和投放(精度±2mm)
  • 多设备调度:8口锅同时工作,避免瓶颈
  • 异常处理:设备故障时自动切换备用方案

10.2.3 设备对比矩阵

维度 家用(捷赛DW30) 商用(Liffo) 全自动(熙香)
价格 ¥3,000-5,000 ¥30,000-80,000 ¥500,000+
自动化程度 半自动(需投料) 全自动(单设备) 全自动(整厨房)
菜谱数量 3000+ 5000+(订阅制) 定制化
控温精度 ±5°C ±2°C ±1°C
连接性 WiFi + APP 以太网 + 云平台 工业以太网
数据采集 基础(温度、时间) 详细(多传感器) 全量(视觉+传感器)
AI能力 菜谱推荐 参数自适应 端到端优化
适用场景 家庭、美食博主 连锁餐厅、食堂 大型餐饮、中央厨房

10.3 端到端烹饪大模型:让机器"学会"做菜

10.3.1 万得厨的技术突破

2024年,万得厨发布了业界首个端到端烹饪大模型。这不是简单的"菜谱数据库+控制程序",而是一个能从视觉输入直接生成控制指令的AI系统。

核心架构

反馈层

控制层

推理层

感知层

状态反馈

状态反馈

状态反馈

RGB摄像头

视觉编码器
ViT-L/14

红外热成像

温度探针阵列

传感器融合

气体传感器

多模态大模型
70B参数

菜谱知识库
RAG

历史烹饪数据
微调

动作解码器

火力控制
0-100%

搅拌速度
0-60rpm

调味料投放
精度±0.5g

时间控制
毫秒级

执行器

10.3.2 传感器阵列设计

万得厨的传感器配置是行业标杆:

传感器类型 型号/规格 用途 采样频率
RGB摄像头 Sony IMX477, 12MP 食材识别、颜色监测 30fps
红外热成像 FLIR Lepton 3.5 表面温度分布 9fps
温度探针 PT100, ±0.1°C 食材内部温度 10Hz
称重传感器 HX711, ±1g 食材重量、液体体积 5Hz
气体传感器 MQ系列(MQ-3, MQ-7, MQ-135) 酒精、CO、气味 1Hz
pH传感器 Atlas Scientific 酸碱度(汤、酱汁) 0.5Hz
湿度传感器 DHT22 蒸汽、环境湿度 2Hz

10.3.3 Python 传感器数据融合

# smart_kitchen/sensor_fusion.py — 多传感器数据融合

import numpy as np
from dataclasses import dataclass
from typing import Optional
from collections import deque
import time


@dataclass
class SensorReading:
    """传感器读数"""
    timestamp: float
    sensor_type: str
    value: float
    unit: str
    confidence: float  # 0-1


@dataclass
class CookingState:
    """烹饪状态估计"""
    phase: str                    # 'preheating' | 'cooking' | 'finishing'
    ingredient_state: str         # 'raw' | 'half_cooked' | 'cooked' | 'burned'
    surface_temperature: float    # 表面温度
    internal_temperature: float   # 内部温度
    moisture_level: float         # 水分含量估计
    aroma_intensity: float        # 香气强度
    estimated_completion: float   # 预计完成百分比
    alerts: list[str]             # 告警信息


class SensorFusionEngine:
    """多传感器数据融合引擎"""
    
    def __init__(self, window_size: int = 10):
        self.window_size = window_size
        self.buffers: dict[str, deque] = {
            'rgb': deque(maxlen=window_size),
            'thermal': deque(maxlen=window_size),
            'temperature': deque(maxlen=window_size),
            'weight': deque(maxlen=window_size),
            'gas': deque(maxlen=window_size),
        }
    
    def add_reading(self, reading: SensorReading):
        """添加传感器读数"""
        if reading.sensor_type in self.buffers:
            self.buffers[reading.sensor_type].append(reading)
    
    def estimate_state(self) -> CookingState:
        """融合多传感器数据,估计当前烹饪状态"""
        # 提取各传感器的最新值
        temp_surface = self._get_latest('thermal', default=25.0)
        temp_internal = self._get_latest('temperature', default=25.0)
        weight = self._get_latest('weight', default=0.0)
        gas_alcohol = self._get_gas_reading('MQ-3')
        gas_co = self._get_gas_reading('MQ-7')
        
        # 计算水分含量(基于重量变化率)
        weight_history = list(self.buffers['weight'])
        if len(weight_history) >= 2:
            weight_change = (weight_history[-1].value - weight_history[0].value) / weight_history[0].value
            moisture = max(0, 1 + weight_change)  # 简化模型
        else:
            moisture = 1.0
        
        # 估计食材状态
        ingredient_state = self._estimate_ingredient_state(
            temp_surface, temp_internal, moisture
        )
        
        # 估计烹饪阶段
        phase = self._estimate_phase(temp_surface, temp_internal)
        
        # 香气强度(基于气体传感器)
        aroma = self._estimate_aroma(gas_alcohol, gas_co)
        
        # 完成度估计
        completion = self._estimate_completion(
            temp_internal, ingredient_state, phase
        )
        
        # 告警检测
        alerts = self._check_alerts(temp_surface, temp_internal, gas_co)
        
        return CookingState(
            phase=phase,
            ingredient_state=ingredient_state,
            surface_temperature=temp_surface,
            internal_temperature=temp_internal,
            moisture_level=moisture,
            aroma_intensity=aroma,
            estimated_completion=completion,
            alerts=alerts,
        )
    
    def _get_latest(self, sensor_type: str, default: float = 0.0) -> float:
        """获取传感器最新读数(加权平均)"""
        buffer = self.buffers.get(sensor_type, deque())
        if not buffer:
            return default
        
        # 时间加权(越新权重越高)
        now = time.time()
        weights = []
        values = []
        for reading in buffer:
            age = now - reading.timestamp
            weight = np.exp(-age / 5.0)  # 5秒衰减
            weights.append(weight * reading.confidence)
            values.append(reading.value)
        
        if not weights:
            return default
        
        return np.average(values, weights=weights)
    
    def _get_gas_reading(self, sensor_model: str) -> float:
        """获取特定气体传感器读数"""
        for reading in reversed(list(self.buffers['gas'])):
            if sensor_model in reading.unit:
                return reading.value
        return 0.0
    
    def _estimate_ingredient_state(self, 
                                   temp_surface: float, 
                                   temp_internal: float,
                                   moisture: float) -> str:
        """估计食材状态"""
        if temp_surface > 250:
            return 'burned'
        elif temp_internal > 75 and moisture < 0.3:
            return 'cooked'
        elif temp_internal > 60:
            return 'half_cooked'
        else:
            return 'raw'
    
    def _estimate_phase(self, temp_surface: float, temp_internal: float) -> str:
        """估计烹饪阶段"""
        if temp_surface < 100:
            return 'preheating'
        elif temp_internal < 60:
            return 'cooking'
        else:
            return 'finishing'
    
    def _estimate_aroma(self, alcohol: float, co: float) -> float:
        """估计香气强度(简化模型)"""
        # 酒精和CO浓度与美拉德反应相关
        return min(1.0, (alcohol * 0.6 + co * 0.4) / 100)
    
    def _estimate_completion(self, 
                             temp_internal: float, 
                             state: str,
                             phase: str) -> float:
        """估计完成百分比"""
        if state == 'burned':
            return 1.0
        elif state == 'cooked':
            return 0.95
        elif state == 'half_cooked':
            return 0.6
        elif phase == 'preheating':
            return 0.1
        else:
            # 基于温度的线性估计
            return min(0.5, temp_internal / 150)
    
    def _check_alerts(self, 
                      temp_surface: float, 
                      temp_internal: float,
                      gas_co: float) -> list[str]:
        """检查告警条件"""
        alerts = []
        
        if temp_surface > 280:
            alerts.append(f"⚠️ 表面温度过高 ({temp_surface:.0f}°C),可能烧焦")
        
        if temp_internal > 100 and temp_surface < 150:
            alerts.append("⚠️ 内部温度异常,可能传感器故障")
        
        if gas_co > 50:
            alerts.append(f"⚠️ CO浓度过高 ({gas_co:.1f}ppm),请检查通风")
        
        return alerts

10.3.4 烹饪动作生成模型

万得厨的核心创新是从感知直接到控制,不需要人工编写菜谱:

// smart_kitchen/cooking_policy.ts — 烹饪策略生成

interface CookingObservation {
  // 视觉特征(来自ViT编码器)
  visualEmbedding: number[];       // 512维向量
  
  // 传感器特征
  temperatures: {
    surface: number;
    internal: number;
    ambient: number;
  };
  weight: number;
  moisture: number;
  aroma: number;
  
  // 时间特征
  elapsedSeconds: number;
  phase: string;
}

interface CookingAction {
  // 控制指令
  heatingPower: number;      // 0-100%
  stirringSpeed: number;     // 0-60 rpm
  seasoningDispense?: {
    type: string;            // 'salt' | 'sugar' | 'soy_sauce' | ...
    amount: number;          // 克
  };
  waitSeconds?: number;      // 等待时间
  
  // 解释(用于调试和用户理解)
  reasoning: string;
}

/**
 * 烹饪策略网络(简化版)
 * 实际模型是70B参数的Transformer
 */
class CookingPolicyNetwork {
  private model: any;  // TensorFlow.js 或 PyTorch 模型
  
  constructor(modelPath: string) {
    // 加载预训练模型
    this.model = this.loadModel(modelPath);
  }
  
  /**
   * 根据当前观察生成下一步动作
   */
  async getAction(observation: CookingObservation): Promise<CookingAction> {
    // 特征拼接
    const features = [
      ...observation.visualEmbedding,
      observation.temperatures.surface,
      observation.temperatures.internal,
      observation.temperatures.ambient,
      observation.weight,
      observation.moisture,
      observation.aroma,
      observation.elapsedSeconds / 60,  // 归一化到分钟
      this.phaseToIndex(observation.phase),
    ];
    
    // 模型推理
    const actionVector = await this.model.predict([features]);
    
    // 解码动作
    return {
      heatingPower: this.sigmoid(actionVector[0]) * 100,
      stirringSpeed: this.sigmoid(actionVector[1]) * 60,
      seasoningDispense: actionVector[2] > 0.5 ? {
        type: this.indexToSeasoning(actionVector[3]),
        amount: Math.abs(actionVector[4]) * 10,  // 最多10g
      } : undefined,
      waitSeconds: actionVector[5] > 0.5 ? Math.abs(actionVector[6]) * 30 : undefined,
      reasoning: this.generateReasoning(observation, actionVector),
    };
  }
  
  private sigmoid(x: number): number {
    return 1 / (1 + Math.exp(-x));
  }
  
  private phaseToIndex(phase: string): number {
    const phases = ['preheating', 'cooking', 'finishing'];
    return phases.indexOf(phase);
  }
  
  private indexToSeasoning(index: number): string {
    const seasonings = ['salt', 'sugar', 'soy_sauce', 'vinegar', 'oil'];
    return seasonings[Math.round(index) % seasonings.length];
  }
  
  private generateReasoning(obs: CookingObservation, action: number[]): string {
    // 生成人类可读的推理解释
    const reasons: string[] = [];
    
    if (obs.temperatures.surface < 150) {
      reasons.push("表面温度较低,增加火力");
    }
    
    if (obs.moisture > 0.7) {
      reasons.push("水分充足,继续翻炒");
    }
    
    if (action[2] > 0.5) {
      reasons.push(`检测到需要调味,投放${this.indexToSeasoning(action[3])}`);
    }
    
    return reasons.join(";") || "维持当前状态";
  }
  
  private loadModel(path: string): any {
    // 实际实现:加载 TensorFlow.js 或 ONNX 模型
    return {
      predict: async (input: number[][]) => {
        // 模拟推理(实际使用预训练模型)
        return [0.7, 0.5, 0.3, 2.0, 5.0, 0.0, 0.0];
      }
    };
  }
}

10.4 厨房IoT设计:构建你自己的智能厨房

10.4.1 为什么需要IoT

即使不买昂贵的商用设备,程序员也可以用IoT技术打造智能厨房。核心目标:

  1. 远程监控:在客厅看电视时,手机提醒"汤快好了"
  2. 数据采集:记录每次烹饪的参数,分析成功模式
  3. 自动化:定时启动电饭煲、预热烤箱
  4. 安全告警:烟雾、燃气泄漏、干烧检测

10.4.2 MQTT协议:厨房IoT的通信标准

MQTT(Message Queuing Telemetry Transport)是IoT领域的标准协议,轻量、可靠、适合低带宽环境。

Topic 设计

kitchen/
├── sensor/
│   ├── temperature/{device_id}    # 温度传感器
│   ├── humidity/{device_id}       # 湿度传感器
│   ├── smoke/{device_id}          # 烟雾传感器
│   ├── gas/{device_id}            # 燃气传感器
│   └── weight/{device_id}         # 称重传感器
├── actuator/
│   ├── stove/{device_id}/power    # 灶具开关
│   ├── stove/{device_id}/level    # 火力等级
│   ├── oven/{device_id}/temp      # 烤箱温度
│   └── fan/{device_id}/speed      # 抽油烟机风速
├── alert/
│   ├── smoke                      # 烟雾告警
│   ├── gas_leak                   # 燃气泄漏
│   └── dry_burn                   # 干烧告警
└── status/
    └── {device_id}                # 设备在线状态

10.4.3 传感器选型指南

传感器 型号推荐 价格 接口 精度 用途
温湿度 DHT22 / SHT30 ¥15-30 I2C ±0.3°C 环境监控
温度探针 MAX31855 + K型热电偶 ¥40-80 SPI ±0.25°C 食材内部温度
烟雾 MQ-2 ¥10-20 模拟 定性 烟雾检测
燃气 MQ-5 ¥15-25 模拟 定性 天然气/液化气
称重 HX711 + 5kg传感器 ¥30-50 数字 ±1g 食材称重
红外测温 MLX90614 ¥60-100 I2C ±0.5°C 表面温度
摄像头 ESP32-CAM ¥40-80 WiFi - 视觉监控

10.4.4 Python IoT网关实现

# smart_kitchen/iot_gateway.py — 厨房IoT网关

import paho.mqtt.client as mqtt
import json
import time
import logging
from dataclasses import dataclass, asdict
from typing import Callable, Optional
from datetime import datetime
import sqlite3

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@dataclass
class SensorEvent:
    """传感器事件"""
    device_id: str
    sensor_type: str
    value: float
    unit: str
    timestamp: str
    metadata: dict = None


@dataclass
class AlertEvent:
    """告警事件"""
    alert_type: str
    severity: 'info' | 'warning' | 'critical'
    message: str
    device_id: Optional[str]
    timestamp: str
    acknowledged: bool = False


class KitchenIoTGateway:
    """厨房IoT网关 — 连接所有传感器和执行器"""
    
    def __init__(self, 
                 mqtt_broker: str = "localhost",
                 mqtt_port: int = 1883,
                 db_path: str = "data/kitchen_iot.db"):
        self.mqtt_broker = mqtt_broker
        self.mqtt_port = mqtt_port
        self.db_path = db_path
        
        self.client = mqtt.Client()
        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message
        
        self.alert_handlers: list[Callable[[AlertEvent], None]] = []
        self.sensor_handlers: list[Callable[[SensorEvent], None]] = []
        
        self._init_database()
    
    def _init_database(self):
        """初始化数据库"""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS sensor_data (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    device_id TEXT,
                    sensor_type TEXT,
                    value REAL,
                    unit TEXT,
                    timestamp TEXT,
                    metadata TEXT
                )
            """)
            conn.execute("""
                CREATE TABLE IF NOT EXISTS alerts (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    alert_type TEXT,
                    severity TEXT,
                    message TEXT,
                    device_id TEXT,
                    timestamp TEXT,
                    acknowledged INTEGER DEFAULT 0
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_sensor_time 
                ON sensor_data(timestamp)
            """)
    
    def start(self):
        """启动网关"""
        self.client.connect(self.mqtt_broker, self.mqtt_port, 60)
        self.client.loop_start()
        logger.info(f"IoT Gateway started, connecting to {self.mqtt_broker}:{self.mqtt_port}")
    
    def stop(self):
        """停止网关"""
        self.client.loop_stop()
        self.client.disconnect()
        logger.info("IoT Gateway stopped")
    
    def _on_connect(self, client, userdata, flags, rc):
        """MQTT连接回调"""
        if rc == 0:
            logger.info("Connected to MQTT broker")
            # 订阅所有传感器主题
            client.subscribe("kitchen/sensor/#")
            client.subscribe("kitchen/alert/#")
            client.subscribe("kitchen/status/#")
        else:
            logger.error(f"Connection failed with code {rc}")
    
    def _on_message(self, client, userdata, msg):
        """MQTT消息回调"""
        try:
            topic = msg.topic
            payload = json.loads(msg.payload.decode())
            
            if topic.startswith("kitchen/sensor/"):
                self._handle_sensor_message(topic, payload)
            elif topic.startswith("kitchen/alert/"):
                self._handle_alert_message(topic, payload)
            elif topic.startswith("kitchen/status/"):
                self._handle_status_message(topic, payload)
        
        except Exception as e:
            logger.error(f"Error processing message: {e}")
    
    def _handle_sensor_message(self, topic: str, payload: dict):
        """处理传感器数据"""
        # 从主题解析设备ID和传感器类型
        # kitchen/sensor/temperature/stove_01
        parts = topic.split('/')
        sensor_type = parts[2]
        device_id = parts[3] if len(parts) > 3 else "unknown"
        
        event = SensorEvent(
            device_id=device_id,
            sensor_type=sensor_type,
            value=float(payload.get('value', 0)),
            unit=payload.get('unit', ''),
            timestamp=datetime.now().isoformat(),
            metadata=payload.get('metadata', {}),
        )
        
        # 存储数据
        self._store_sensor_data(event)
        
        # 触发处理器
        for handler in self.sensor_handlers:
            handler(event)
        
        # 检查告警条件
        self._check_alerts(event)
    
    def _handle_alert_message(self, topic: str, payload: dict):
        """处理告警消息"""
        alert_type = topic.split('/')[-1]
        
        alert = AlertEvent(
            alert_type=alert_type,
            severity=payload.get('severity', 'warning'),
            message=payload.get('message', ''),
            device_id=payload.get('device_id'),
            timestamp=datetime.now().isoformat(),
        )
        
        # 存储告警
        self._store_alert(alert)
        
        # 触发告警处理器
        for handler in self.alert_handlers:
            handler(alert)
    
    def _handle_status_message(self, topic: str, payload: dict):
        """处理设备状态"""
        device_id = topic.split('/')[-1]
        logger.info(f"Device {device_id} status: {payload}")
    
    def _store_sensor_data(self, event: SensorEvent):
        """存储传感器数据"""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT INTO sensor_data 
                (device_id, sensor_type, value, unit, timestamp, metadata)
                VALUES (?, ?, ?, ?, ?, ?)
            """, (
                event.device_id,
                event.sensor_type,
                event.value,
                event.unit,
                event.timestamp,
                json.dumps(event.metadata),
            ))
    
    def _store_alert(self, alert: AlertEvent):
        """存储告警"""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT INTO alerts 
                (alert_type, severity, message, device_id, timestamp)
                VALUES (?, ?, ?, ?, ?)
            """, (
                alert.alert_type,
                alert.severity,
                alert.message,
                alert.device_id,
                alert.timestamp,
            ))
    
    def _check_alerts(self, event: SensorEvent):
        """检查告警条件"""
        alerts = []
        
        # 烟雾告警
        if event.sensor_type == 'smoke' and event.value > 300:
            alerts.append(AlertEvent(
                alert_type='smoke',
                severity='critical',
                message=f"烟雾浓度过高 ({event.value:.0f}ppm)",
                device_id=event.device_id,
                timestamp=datetime.now().isoformat(),
            ))
        
        # 温度告警
        if event.sensor_type == 'temperature':
            if event.value > 300:
                alerts.append(AlertEvent(
                    alert_type='overheat',
                    severity='critical',
                    message=f"温度过高 ({event.value:.1f}°C),可能干烧",
                    device_id=event.device_id,
                    timestamp=datetime.now().isoformat(),
                ))
        
        # 触发告警
        for alert in alerts:
            self._store_alert(alert)
            for handler in self.alert_handlers:
                handler(alert)
    
    def on_alert(self, handler: Callable[[AlertEvent], None]):
        """注册告警处理器"""
        self.alert_handlers.append(handler)
    
    def on_sensor(self, handler: Callable[[SensorEvent], None]):
        """注册传感器数据处理器"""
        self.sensor_handlers.append(handler)
    
    def publish_command(self, device_id: str, command: dict):
        """发送控制指令"""
        topic = f"kitchen/actuator/{device_id}"
        payload = json.dumps(command)
        self.client.publish(topic, payload)
        logger.info(f"Published command to {device_id}: {command}")


# 使用示例
if __name__ == "__main__":
    gateway = KitchenIoTGateway()
    
    # 注册告警处理器
    def handle_alert(alert: AlertEvent):
        print(f"🚨 ALERT [{alert.severity}]: {alert.message}")
        # 这里可以发送邮件、短信、APP推送
    
    gateway.on_alert(handle_alert)
    
    # 注册数据处理器
    def handle_sensor(event: SensorEvent):
        print(f"📊 Sensor: {event.sensor_type} = {event.value} {event.unit}")
    
    gateway.on_sensor(handle_sensor)
    
    # 启动网关
    gateway.start()
    
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        gateway.stop()

10.4.5 ESP32 传感器节点固件

// firmware/esp32_temperature_node.ino — ESP32温度传感器节点

#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_MAX31855.h>
#include <ArduinoJson.h>

// WiFi配置
const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";

// MQTT配置
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 1883;

// 传感器引脚
#define MAX31855_CS   5
#define MAX31855_DO   19
#define MAX31855_CLK  18

Adafruit_MAX31855 thermocouple(MAX31855_CLK, MAX31855_CS, MAX31855_DO);
WiFiClient espClient;
PubSubClient mqttClient(espClient);

const char* device_id = "stove_01";
unsigned long lastSend = 0;
const int sendInterval = 2000;  // 2秒发送一次

void setup() {
  Serial.begin(115200);
  
  // 连接WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");
  
  // 连接MQTT
  mqttClient.setServer(mqtt_server, mqtt_port);
  
  // 初始化传感器
  Serial.println("MAX31855 test");
  delay(500);
}

void loop() {
  if (!mqttClient.connected()) {
    reconnect();
  }
  mqttClient.loop();
  
  unsigned long now = millis();
  if (now - lastSend > sendInterval) {
    lastSend = now;
    sendTemperature();
  }
}

void sendTemperature() {
  double temp = thermocouple.readCelsius();
  
  if (isnan(temp)) {
    Serial.println("Something wrong with thermocouple!");
    return;
  }
  
  // 构造JSON
  StaticJsonDocument<200> doc;
  doc["value"] = temp;
  doc["unit"] = "°C";
  doc["timestamp"] = millis() / 1000;
  
  char payload[200];
  serializeJson(doc, payload);
  
  // 发布到MQTT
  String topic = "kitchen/sensor/temperature/" + String(device_id);
  mqttClient.publish(topic.c_str(), payload);
  
  Serial.printf("Temperature: %.2f °C\n", temp);
}

void reconnect() {
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    
    if (mqttClient.connect(clientId.c_str())) {
      Serial.println("connected");
      
      // 发布在线状态
      String statusTopic = "kitchen/status/" + String(device_id);
      mqttClient.publish(statusTopic.c_str(), "online");
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

10.5 AI烹饪辅助:你的私人AI厨师顾问

10.5.1 实时烹饪指导

想象一下:你正在做一道复杂的菜(比如法式焦糖布丁),旁边有一个AI助手,通过摄像头看着你的操作,实时给出建议:

  • “糖浆颜色偏浅,建议再加热30秒”
  • “搅拌速度太快,会引入过多气泡”
  • “烤箱预热完成,可以放入模具了”

技术实现

# smart_kitchen/ai_coach.py — AI烹饪教练

import cv2
import numpy as np
import anthropic
from dataclasses import dataclass
from typing import Optional
import base64


@dataclass
class CookingFrame:
    """单帧烹饪状态"""
    image_b64: str          # base64编码的图像
    timestamp: float
    temperature: Optional[float]
    elapsed_seconds: int
    current_step: int
    recipe_name: str


class AICookingCoach:
    """AI烹饪教练 — 实时视觉指导"""
    
    def __init__(self, api_key: str):
        self.client = anthropic.Anthropic(api_key=api_key)
        self.conversation_history = []
    
    def analyze_frame(self, frame: CookingFrame, 
                      recipe_context: str) -> str:
        """分析当前帧,给出建议"""
        
        prompt = f"""你是一位经验丰富的中餐厨师,正在指导用户做"{frame.recipe_name}"。

## 当前状态
- 已用时: {frame.elapsed_seconds}秒
- 当前步骤: 第{frame.current_step}步
- 温度: {frame.temperature or '未知'}°C

## 菜谱参考
{recipe_context}

## 任务
1. 描述你看到的状态(食材颜色、质地、气泡等)
2. 判断当前进展是否正常
3. 给出下一步建议(如果有)
4. 指出潜在问题(如果有)

请用简洁的中文回答,不超过100字。
"""
        
        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=300,
            messages=[{
                "role": "user",
                "content": [
                    {
                        "type": "image",
                        "source": {
                            "type": "base64",
                            "media_type": "image/jpeg",
                            "data": frame.image_b64,
                        },
                    },
                    {
                        "type": "text",
                        "text": prompt,
                    },
                ],
            }],
        )
        
        advice = response.content[0].text
        self.conversation_history.append({
            'timestamp': frame.timestamp,
            'advice': advice,
        })
        
        return advice
    
    def get_summary(self) -> str:
        """生成烹饪过程总结"""
        if not self.conversation_history:
            return "尚无记录"
        
        advices = [h['advice'] for h in self.conversation_history]
        
        return f"""
## 烹饪过程总结
共给出 {len(advices)} 条建议:

{chr(10).join(f"- {a}" for a in advices[:5])}
...
"""

10.5.2 数字人厨师

2024年,数字人技术已经相当成熟。你可以创建一个虚拟厨师形象,在直播或视频中担任"主持人":

技术栈

  • 形象生成:Midjourney / Stable Diffusion 生成角色设计
  • 语音合成:Azure TTS / 讯飞 TTS 生成语音
  • 唇形同步:SadTalker / Wav2Lip 实现口型与语音同步
  • 动作生成:Mixamo 动画库 + 动作捕捉

应用场景

  1. 美食直播:数字人主持,真人幕后操作
  2. 教学视频:数字人讲解理论知识,真人演示实操
  3. 品牌代言:创建虚拟美食博主IP
// smart_kitchen/digital_chef.ts — 数字人厨师控制器

interface DigitalChefConfig {
  name: string;
  avatar: string;           // 形象URL
  voiceModel: string;       // 语音模型
  personality: string;      // 人设描述
  expertise: string[];      // 擅长菜系
}

interface ScriptLine {
  text: string;
  emotion: 'neutral' | 'happy' | 'surprised' | 'serious';
  gesture?: string;         // 手势动作
  duration?: number;        // 预计时长(秒)
}

class DigitalChefController {
  private config: DigitalChefConfig;
  private script: ScriptLine[] = [];
  
  constructor(config: DigitalChefConfig) {
    this.config = config;
  }
  
  /** 生成烹饪教学脚本 */
  generateScript(recipeName: string, steps: string[]): ScriptLine[] {
    const script: ScriptLine[] = [];
    
    // 开场白
    script.push({
      text: `大家好,我是${this.config.name}!今天教大家做${recipeName}`,
      emotion: 'happy',
      gesture: 'wave',
      duration: 5,
    });
    
    // 步骤讲解
    for (let i = 0; i < steps.length; i++) {
      script.push({
        text: `${i + 1}步:${steps[i]}`,
        emotion: 'neutral',
        gesture: 'point',
        duration: 8,
      });
      
      // 关键步骤加入提示
      if (i === Math.floor(steps.length / 2)) {
        script.push({
          text: "这里是关键步骤,大家注意看细节!",
          emotion: 'serious',
          gesture: 'emphasize',
          duration: 3,
        });
      }
    }
    
    // 结束语
    script.push({
      text: `${recipeName}完成了!记得点赞关注,下期见!`,
      emotion: 'happy',
      gesture: 'thumbs_up',
      duration: 5,
    });
    
    this.script = script;
    return script;
  }
  
  /** 生成数字人视频(调用API) */
  async renderVideo(outputPath: string): Promise<void> {
    // 实际实现:调用 SadTalker / D-ID API
    console.log(`Rendering video to ${outputPath}`);
    
    for (const line of this.script) {
      // 1. 生成语音(TTS)
      const audioPath = await this.generateSpeech(line.text);
      
      // 2. 生成视频(唇形同步)
      const videoSegment = await this.generateVideoSegment(audioPath, line);
      
      // 3. 拼接视频
      await this.appendVideo(outputPath, videoSegment);
    }
  }
  
  private async generateSpeech(text: string): Promise<string> {
    // 调用TTS API
    return "/tmp/speech.mp3";
  }
  
  private async generateVideoSegment(audioPath: string, line: ScriptLine): Promise<string> {
    // 调用数字人生成API
    return "/tmp/segment.mp4";
  }
  
  private async appendVideo(outputPath: string, segment: string): Promise<void> {
    // 使用FFmpeg拼接视频
  }
}

10.6 烹饪机器人:从实验室到厨房

10.6.1 索尼AI的烹饪机器人项目

2023年,索尼AI发布了他们的烹饪机器人原型,目标是从零开始制作一道完整的菜

技术挑战

挑战 难度 解决方案
食材识别 多摄像头 + 深度学习(YOLO v8)
精细操作 极高 6轴机械臂 + 力反馈传感器
液体处理 视觉引导 + 流量控制
火候控制 红外测温 + PID控制
异常处理 极高 强化学习 + 人类示范学习

关键突破

  • 食材抓取:通过视觉识别食材形状和位置,机械臂自适应抓取
  • 刀工:高速摄像头 + 力控制,实现切丝、切片、切块
  • 翻炒:模仿人类手腕动作,避免食材破碎

10.6.2 Cook-Gen:生成式烹饪动作规划

Cook-Gen是学术界的前沿工作,用生成式AI规划烹饪动作序列:

核心思想

  • 输入:菜谱文本 + 初始状态(食材、工具)
  • 输出:动作序列(每个动作包含:类型、参数、时长)
  • 模型:基于GPT-4微调的动作生成模型

示例

输入: "炒鸡蛋"
初始状态: {鸡蛋2个, 油, 盐, 碗, 筷子, 炒锅}

输出动作序列:
1. crack_egg(egg_1, bowl_1) -> 2秒
2. crack_egg(egg_2, bowl_1) -> 2秒
3. add_salt(bowl_1, 2g) -> 1秒
4. whisk(bowl_1, chopsticks_1, 10秒) -> 10秒
5. heat(stove_1, pan_1, medium, 30秒) -> 30秒
6. pour_oil(pan_1, 10ml) -> 2秒
7. pour(bowl_1, pan_1) -> 3秒
8. stir_fry(pan_1, spatula_1, 20秒) -> 20秒
9. plate(pan_1, plate_1) -> 5秒

总耗时: 75秒

10.7 DIY智能厨房:程序员的实战方案

10.7.1 树莓派方案

对于预算有限的程序员,树莓派是绝佳的起点:

硬件清单

组件 型号 价格 用途
主控 树莓派4B (4GB) ¥400 运行网关和AI模型
摄像头 树莓派摄像头V2 ¥150 视觉监控
温度传感器 MAX31855 + K型探针 ¥80 食材温度
显示屏 7寸触摸屏 ¥300 本地交互
扬声器 USB小音箱 ¥50 语音提示
外壳 3D打印防水外壳 ¥100 保护电路
总计 - ¥1,080 -

10.7.2 Home Assistant集成

Home Assistant是开源的智能家居平台,可以统一管理厨房设备:

# homeassistant/configuration.yml — Home Assistant配置

# MQTT集成
mqtt:
  broker: localhost
  port: 1883

# 温度传感器
sensor:
  - platform: mqtt
    name: "Stove Temperature"
    state_topic: "kitchen/sensor/temperature/stove_01"
    value_template: "{{ value_json.value }}"
    unit_of_measurement: "°C"
    
  - platform: mqtt
    name: "Kitchen Humidity"
    state_topic: "kitchen/sensor/humidity/env_01"
    value_template: "{{ value_json.value }}"
    unit_of_measurement: "%"

# 烟雾告警
binary_sensor:
  - platform: mqtt
    name: "Smoke Detector"
    state_topic: "kitchen/sensor/smoke/detector_01"
    value_template: "{{ 'ON' if value_json.value > 300 else 'OFF' }}"
    device_class: smoke

# 自动化:温度过高告警
automation:
  - alias: "Stove Overheat Alert"
    trigger:
      platform: numeric_state
      entity_id: sensor.stove_temperature
      above: 280
    action:
      - service: notify.mobile_app
        data:
          title: "⚠️ 灶具温度过高"
          message: "当前温度 {{ states('sensor.stove_temperature') }}°C,请检查!"
      - service: tts.speak
        data:
          entity_id: media_player.kitchen_speaker
          message: "警告,灶具温度过高,请注意安全"

# 自动化:烹饪完成提醒
  - alias: "Cooking Complete"
    trigger:
      platform: state
      entity_id: input_boolean.cooking_mode
      to: "off"
    action:
      - service: notify.mobile_app
        data:
          title: "✅ 烹饪完成"
          message: "菜做好了,记得关火!"

10.7.3 数据记录与分析

# smart_kitchen/cooking_logger.py — 烹饪数据记录器

import sqlite3
import json
from dataclasses import dataclass, asdict
from datetime import datetime
from typing import Optional


@dataclass
class CookingSession:
    """单次烹饪记录"""
    session_id: str
    recipe_name: str
    start_time: str
    end_time: Optional[str]
    duration_seconds: Optional[int]
    
    # 参数记录
    ingredients: list[dict]      # [{name, amount, unit}]
    temperature_profile: list[dict]  # [{timestamp, value}]
    actions: list[dict]          # [{timestamp, action, params}]
    
    # 结果评估
    success_rating: Optional[int]  # 1-5
    taste_notes: Optional[str]
    improvements: Optional[str]


class CookingLogger:
    """烹饪数据记录器"""
    
    def __init__(self, db_path: str = "data/cooking_sessions.db"):
        self.db_path = db_path
        self.current_session: Optional[CookingSession] = None
        self._init_database()
    
    def _init_database(self):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS cooking_sessions (
                    session_id TEXT PRIMARY KEY,
                    recipe_name TEXT,
                    start_time TEXT,
                    end_time TEXT,
                    duration_seconds INTEGER,
                    ingredients TEXT,
                    temperature_profile TEXT,
                    actions TEXT,
                    success_rating INTEGER,
                    taste_notes TEXT,
                    improvements TEXT
                )
            """)
    
    def start_session(self, recipe_name: str, ingredients: list[dict]) -> str:
        """开始烹饪记录"""
        import uuid
        session_id = str(uuid.uuid4())[:8]
        
        self.current_session = CookingSession(
            session_id=session_id,
            recipe_name=recipe_name,
            start_time=datetime.now().isoformat(),
            end_time=None,
            duration_seconds=None,
            ingredients=ingredients,
            temperature_profile=[],
            actions=[],
            success_rating=None,
            taste_notes=None,
            improvements=None,
        )
        
        return session_id
    
    def log_temperature(self, timestamp: float, value: float):
        """记录温度"""
        if self.current_session:
            self.current_session.temperature_profile.append({
                'timestamp': timestamp,
                'value': value,
            })
    
    def log_action(self, action: str, params: dict = None):
        """记录操作"""
        if self.current_session:
            self.current_session.actions.append({
                'timestamp': datetime.now().isoformat(),
                'action': action,
                'params': params or {},
            })
    
    def end_session(self, rating: int, notes: str = "", improvements: str = ""):
        """结束烹饪记录"""
        if not self.current_session:
            return
        
        self.current_session.end_time = datetime.now().isoformat()
        self.current_session.duration_seconds = int(
            (datetime.fromisoformat(self.current_session.end_time) -
             datetime.fromisoformat(self.current_session.start_time)).total_seconds()
        )
        self.current_session.success_rating = rating
        self.current_session.taste_notes = notes
        self.current_session.improvements = improvements
        
        # 存储到数据库
        self._save_session(self.current_session)
        self.current_session = None
    
    def _save_session(self, session: CookingSession):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                INSERT INTO cooking_sessions VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                session.session_id,
                session.recipe_name,
                session.start_time,
                session.end_time,
                session.duration_seconds,
                json.dumps(session.ingredients, ensure_ascii=False),
                json.dumps(session.temperature_profile),
                json.dumps(session.actions, ensure_ascii=False),
                session.success_rating,
                session.taste_notes,
                session.improvements,
            ))
    
    def analyze_success_patterns(self, recipe_name: str) -> str:
        """分析成功模式"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.execute("""
                SELECT * FROM cooking_sessions 
                WHERE recipe_name = ? AND success_rating >= 4
                ORDER BY start_time DESC LIMIT 10
            """, (recipe_name,))
            
            successful = cursor.fetchall()
            
            if not successful:
                return "暂无高分记录"
            
            # 分析共同特征
            avg_duration = sum(s[4] for s in successful) / len(successful)
            
            return f"""
## {recipe_name} 成功模式分析

基于 {len(successful)} 次高分记录:

- **平均用时**: {avg_duration:.0f}秒
- **最佳实践**: {successful[0][9] or '无记录'}
- **改进建议**: {successful[0][10] or '无记录'}
"""

10.8 分子料理与AI:当科学遇见美食

10.8.1 风味配对科学

分子料理的核心思想是:用科学方法理解风味

风味配对理论:如果两种食材共享大量挥发性化合物,它们很可能搭配得很好。

经典案例

  • 草莓 + 罗勒:共享芳樟醇
  • 巧克力 + 蓝纹奶酪:共享吡嗪类化合物
  • 西瓜 + 羊奶酪:共享醛类

10.8.2 IBM Chef Watson

IBM在2015年推出了Chef Watson,用AI生成创新菜谱:

技术原理

  1. 食材数据库:包含数千种食材的化学成分
  2. 风味配对算法:基于共享化合物的相似度计算
  3. 菜谱生成:结合地域菜系、烹饪技法、营养平衡

实际效果:Chef Watson生成了很多"反直觉但好吃"的组合,比如:

  • 比利时巧克力 + 墨西哥辣椒
  • 苹果 + 猪肉 + 迷迭香(经典组合的科学验证)

10.8.3 Python 风味配对工具

# smart_kitchen/flavor_pairing.py — AI风味配对

import numpy as np
from dataclasses import dataclass
from typing import Optional
from collections import defaultdict


@dataclass
class Ingredient:
    """食材"""
    name: str
    category: str            # 'meat' | 'vegetable' | 'spice' | ...
    compounds: list[str]     # 挥发性化合物列表
    flavor_profile: dict     # {'sweet': 0.3, 'sour': 0.1, ...}
    texture: str             # 'crunchy' | 'soft' | 'chewy'


class FlavorPairingEngine:
    """风味配对引擎"""
    
    FLAVOR_DIMENSIONS = ['sweet', 'sour', 'bitter', 'salty', 'umami', 'spicy']
    
    def __init__(self):
        self.ingredients: dict[str, Ingredient] = {}
        self.compound_index = defaultdict(list)  # 化合物 -> 食材列表
    
    def add_ingredient(self, ingredient: Ingredient):
        """添加食材"""
        self.ingredients[ingredient.name] = ingredient
        for compound in ingredient.compounds:
            self.compound_index[compound].append(ingredient.name)
    
    def calculate_similarity(self, ing1: str, ing2: str) -> float:
        """计算两种食材的相似度"""
        if ing1 not in self.ingredients or ing2 not in self.ingredients:
            return 0.0
        
        i1 = self.ingredients[ing1]
        i2 = self.ingredients[ing2]
        
        # 化合物重叠度
        common_compounds = set(i1.compounds) & set(i2.compounds)
        compound_score = len(common_compounds) / max(len(i1.compounds), len(i2.compounds), 1)
        
        # 风味轮廓相似度(余弦相似度)
        v1 = np.array([i1.flavor_profile.get(dim, 0) for dim in self.FLAVOR_DIMENSIONS])
        v2 = np.array([i2.flavor_profile.get(dim, 0) for dim in self.FLAVOR_DIMENSIONS])
        
        if np.linalg.norm(v1) == 0 or np.linalg.norm(v2) == 0:
            flavor_score = 0.0
        else:
            flavor_score = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
        
        # 综合得分(化合物60% + 风味40%)
        return compound_score * 0.6 + flavor_score * 0.4
    
    def find_pairings(self, ingredient: str, top_n: int = 5) -> list[tuple[str, float]]:
        """查找最佳搭配"""
        if ingredient not in self.ingredients:
            return []
        
        scores = []
        for other in self.ingredients:
            if other != ingredient:
                score = self.calculate_similarity(ingredient, other)
                scores.append((other, score))
        
        return sorted(scores, key=lambda x: x[1], reverse=True)[:top_n]
    
    def generate_recipe_idea(self, main_ingredients: list[str], 
                             cuisine: str = "fusion") -> str:
        """生成创新菜谱想法"""
        # 找出与所有主食材都搭配的辅料
        candidates = defaultdict(int)
        for main in main_ingredients:
            pairings = self.find_pairings(main, top_n=20)
            for name, score in pairings:
                if name not in main_ingredients:
                    candidates[name] += score
        
        # 排序
        best_additions = sorted(candidates.items(), key=lambda x: x[1], reverse=True)[:5]
        
        recipe_idea = f"""
## 🍳 创新菜谱建议

**主料**: {', '.join(main_ingredients)}
**推荐辅料**: {', '.join(name for name, _ in best_additions)}
**菜系风格**: {cuisine}

**创意说明**:
这些食材共享多种挥发性化合物,理论上风味和谐。
建议尝试:{best_additions[0][0]} 作为桥梁食材,连接主料的风味。
"""
        return recipe_idea


# 使用示例
engine = FlavorPairingEngine()

# 添加食材数据(简化版)
engine.add_ingredient(Ingredient(
    name="番茄",
    category="vegetable",
    compounds=["linalool", "geranial", "neral"],
    flavor_profile={"sweet": 0.3, "sour": 0.6, "umami": 0.4},
    texture="soft",
))

engine.add_ingredient(Ingredient(
    name="罗勒",
    category="herb",
    compounds=["linalool", "estragole", "eugenol"],
    flavor_profile={"sweet": 0.2, "bitter": 0.3, "spicy": 0.2},
    texture="soft",
))

# 查找搭配
pairings = engine.find_pairings("番茄", top_n=3)
print(f"番茄的最佳搭配: {pairings}")

10.9 食品安全:智能厨房的底线

10.9.1 关键控制点

智能厨房必须内置食品安全监控:

风险 检测方式 告警阈值 应对措施
细菌滋生 温度+时间 4-60°C超过2小时 自动提醒冷藏或加热
交叉污染 视觉识别 生熟混放 语音警告
过敏原 用户配置 检测到过敏食材 阻止使用并提示
燃气泄漏 MQ-5传感器 >100ppm 关闭电磁阀+通风
干烧 温度异常 >300°C且无食材 自动关火

10.9.2 HACCP自动化

HACCP(危害分析与关键控制点)是食品安全的国际标准。智能厨房可以自动化HACCP流程:

# smart_kitchen/food_safety.py — 食品安全监控

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional


@dataclass
class FoodItem:
    """食材追踪"""
    item_id: str
    name: str
    storage_location: str      # 'fridge' | 'freezer' | 'pantry'
    purchase_date: str
    expiry_date: Optional[str]
    temperature_history: list[float]
    allergens: list[str]       # ['peanut', 'dairy', 'gluten', ...]


class FoodSafetyMonitor:
    """食品安全监控器"""
    
    DANGER_ZONE = (4, 60)  # 危险温度区间 (°C)
    MAX_TIME_IN_DANGER = 2  # 小时
    
    def __init__(self):
        self.food_items: dict[str, FoodItem] = {}
        self.user_allergens: list[str] = []  # 用户过敏原配置
    
    def check_temperature_exposure(self, item: FoodItem) -> dict:
        """检查食材是否在危险温度区间暴露过久"""
        if not item.temperature_history:
            return {"status": "unknown"}
        
        # 计算在危险区间的累计时间
        time_in_danger = 0
        for i in range(1, len(item.temperature_history)):
            temp = item.temperature_history[i]
            if self.DANGER_ZONE[0] < temp < self.DANGER_ZONE[1]:
                time_in_danger += 0.5  # 假设每0.5小时记录一次
        
        if time_in_danger > self.MAX_TIME_IN_DANGER:
            return {
                "status": "warning",
                "message": f"{item.name}在危险温度区间暴露{time_in_danger:.1f}小时,建议丢弃",
                "risk_level": "high",
            }
        else:
            return {
                "status": "safe",
                "message": f"安全(暴露{time_in_danger:.1f}小时)",
                "risk_level": "low",
            }
    
    def check_allergen_conflict(self, recipe_ingredients: list[str]) -> list[str]:
        """检查菜谱是否包含过敏原"""
        conflicts = []
        for ing_name in recipe_ingredients:
            item = self.food_items.get(ing_name)
            if item:
                for allergen in item.allergens:
                    if allergen in self.user_allergens:
                        conflicts.append(f"⚠️ {item.name} 含有过敏原: {allergen}")
        return conflicts
    
    def check_expiry(self, item: FoodItem) -> dict:
        """检查保质期"""
        if not item.expiry_date:
            return {"status": "unknown"}
        
        expiry = datetime.fromisoformat(item.expiry_date)
        now = datetime.now()
        days_left = (expiry - now).days
        
        if days_left < 0:
            return {
                "status": "expired",
                "message": f"⚠️ {item.name} 已过期 {abs(days_left)} 天",
                "action": "discard",
            }
        elif days_left <= 3:
            return {
                "status": "expiring_soon",
                "message": f"⚠️ {item.name} {days_left} 天后过期",
                "action": "use_soon",
            }
        else:
            return {
                "status": "fresh",
                "message": f"✓ {item.name} 还有 {days_left} 天",
                "action": "none",
            }

10.10 总结:从代码到灶台的最后一公里

10.10.1 核心收获

本章我们探索了智能厨房的完整技术栈:

层次 技术 应用
感知 摄像头、温度探针、气体传感器 实时监控烹饪状态
推理 多模态大模型、传感器融合 判断食材状态、预测完成时间
控制 PID控温、机械臂、电磁阀 精确执行烹饪参数
学习 强化学习、历史数据分析 持续优化烹饪策略
安全 HACCP、过敏原检测、燃气监控 保障食品安全

10.10.2 程序员做智能厨房的三个优势

  1. 系统集成能力:把传感器、执行器、AI模型整合成一个闭环系统
  2. 数据驱动思维:用数据量化"少许盐",用模型优化"火候"
  3. 开源精神:分享代码、方案、经验,推动整个行业发展

10.10.3 美食博主的独特内容机会

作为懂技术的博主,你可以创作传统美食博主无法触及的内容:

  • 拆解智能设备:从代码视角分析"自动炒菜机"的控制逻辑
  • DIY智能厨房:记录用树莓派搭建厨房IoT的过程
  • AI烹饪实验:让AI生成菜谱,真人执行并评测
  • 数据对比:同一道菜,人工 vs 机器,盲测评分

这些内容既有技术深度,又有美食温度,是真正的差异化竞争力。

本章代码仓库: github.com/programmer-foodie/smart-kitchen
硬件清单与购买链接: smart-kitchen-bom.vercel.app


代码可以完美,但美食需要不完美的人间烟火。—— 一个在厨房写代码的美食博主

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐