bbq_analyze_device_reading
Analyzes temperature readings from ThermoWorks BBQ devices to monitor cook progress, detect stalls, and provide guidance based on protein type and target temperature.
Instructions
Analyze temperature readings from a ThermoWorks device (Signals, Smoke, BlueDOT).
Simulates integration with ThermoWorks Cloud to provide analysis of multi-probe readings.
Args:
device_type: Type of ThermoWorks device ('Signals', 'Smoke', 'BlueDOT')
probe_readings: Array of probe readings with {probe_id, name, temperature}
protein_type: Type of protein being cooked (optional)
target_temp: Target temperature (optional)
response_format: 'markdown' or 'json'
Examples:
"Signals reading: Probe 1 at 165°F, Ambient at 250°F"
"Smoke shows 180°F on the meat probe"
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| device_type | Yes | Type of ThermoWorks device | |
| probe_readings | Yes | Temperature readings from each probe | |
| protein_type | No | Type of protein being cooked (if known) | |
| target_temp | No | Target temperature set on device | |
| response_format | No | Output format | markdown |
Implementation Reference
- src/index.ts:850-904 (handler)The inline asynchronous handler function that implements the core logic for the bbq_analyze_device_reading tool. It identifies the meat probe from readings, performs temperature analysis if provided, and outputs formatted markdown or JSON.async (params: SimulateDeviceReadingInput) => { try { // Find the meat probe (not ambient) const meatProbe = params.probe_readings.find( (p) => !p.probe_id.toLowerCase().includes("ambient") && !p.name?.toLowerCase().includes("ambient") ); let analysis: ReturnType<typeof analyzeTemperature> | undefined; if (meatProbe && params.protein_type && params.target_temp) { analysis = analyzeTemperature( meatProbe.temperature, params.target_temp, params.protein_type ); } if (params.response_format === "json") { const output = { deviceType: params.device_type, probeReadings: params.probe_readings, proteinType: params.protein_type, targetTemp: params.target_temp, analysis: analysis ? { currentTemp: analysis.currentTemp, targetTemp: analysis.targetTemp, percentComplete: analysis.percentComplete, tempDelta: analysis.tempDelta, recommendations: analysis.recommendations, } : null, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } const markdown = formatDeviceReadingMarkdown( params.device_type, params.probe_readings, analysis ); return { content: [{ type: "text", text: markdown }], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error occurred"; return { isError: true, content: [{ type: "text", text: `Error analyzing device reading: ${message}` }], }; } }
- src/schemas/index.ts:233-253 (schema)Zod schema defining the input parameters for the bbq_analyze_device_reading tool, including device_type, probe_readings array, optional protein_type and target_temp.export const SimulateDeviceReadingSchema = z .object({ device_type: z.enum(["Signals", "Smoke", "BlueDOT"]).describe("Type of ThermoWorks device"), probe_readings: z .array( z.object({ probe_id: z.string().describe("Probe identifier (e.g., 'probe1', 'ambient')"), name: z.string().optional().describe("Custom name for this probe"), temperature: z.number().describe("Temperature reading"), }) ) .min(1) .max(4) .describe("Temperature readings from each probe"), protein_type: ProteinTypeSchema.optional().describe("Type of protein being cooked (if known)"), target_temp: z.number().optional().describe("Target temperature set on device"), response_format: ResponseFormatSchema.describe("Output format"), }) .strict(); export type SimulateDeviceReadingInput = z.infer<typeof SimulateDeviceReadingSchema>;
- src/index.ts:824-905 (registration)The server.registerTool call that registers the bbq_analyze_device_reading tool, providing title, description, input schema, annotations, and references the handler function.server.registerTool( "bbq_analyze_device_reading", { title: "Analyze ThermoWorks Device Reading", description: `Analyze temperature readings from a ThermoWorks device (Signals, Smoke, BlueDOT). Simulates integration with ThermoWorks Cloud to provide analysis of multi-probe readings. Args: - device_type: Type of ThermoWorks device ('Signals', 'Smoke', 'BlueDOT') - probe_readings: Array of probe readings with {probe_id, name, temperature} - protein_type: Type of protein being cooked (optional) - target_temp: Target temperature (optional) - response_format: 'markdown' or 'json' Examples: - "Signals reading: Probe 1 at 165°F, Ambient at 250°F" - "Smoke shows 180°F on the meat probe"`, inputSchema: SimulateDeviceReadingSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (params: SimulateDeviceReadingInput) => { try { // Find the meat probe (not ambient) const meatProbe = params.probe_readings.find( (p) => !p.probe_id.toLowerCase().includes("ambient") && !p.name?.toLowerCase().includes("ambient") ); let analysis: ReturnType<typeof analyzeTemperature> | undefined; if (meatProbe && params.protein_type && params.target_temp) { analysis = analyzeTemperature( meatProbe.temperature, params.target_temp, params.protein_type ); } if (params.response_format === "json") { const output = { deviceType: params.device_type, probeReadings: params.probe_readings, proteinType: params.protein_type, targetTemp: params.target_temp, analysis: analysis ? { currentTemp: analysis.currentTemp, targetTemp: analysis.targetTemp, percentComplete: analysis.percentComplete, tempDelta: analysis.tempDelta, recommendations: analysis.recommendations, } : null, }; return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } const markdown = formatDeviceReadingMarkdown( params.device_type, params.probe_readings, analysis ); return { content: [{ type: "text", text: markdown }], }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error occurred"; return { isError: true, content: [{ type: "text", text: `Error analyzing device reading: ${message}` }], }; } } );
- src/services/cooking.ts:175-283 (helper)Helper function analyzeTemperature used within the tool handler to perform detailed temperature progress analysis, stall detection, and generate recommendations.export function analyzeTemperature( currentTemp: number, targetTemp: number, proteinType: ProteinType, cookMethod?: CookMethod, cookStartTime?: Date, previousReadings?: Array<{ temp: number; timestamp: Date }> ): TemperatureAnalysis { const profile = getProteinProfile(proteinType); const tempDelta = targetTemp - currentTemp; const startingTemp = 40; // Assume refrigerator temp as starting point const totalTempRange = targetTemp - startingTemp; const tempProgress = currentTemp - startingTemp; const percentComplete = Math.min(100, Math.max(0, (tempProgress / totalTempRange) * 100)); // Determine trend from previous readings let trend: "rising" | "falling" | "stable" | "stalled" = "stable"; let trendRatePerHour = 0; if (previousReadings && previousReadings.length >= 2) { const recentReadings = previousReadings.slice(-5); // Look at last 5 readings const firstReading = recentReadings[0]; const lastReading = recentReadings[recentReadings.length - 1]; const tempChange = lastReading.temp - firstReading.temp; const timeChange = (lastReading.timestamp.getTime() - firstReading.timestamp.getTime()) / (1000 * 60 * 60); // hours if (timeChange > 0) { trendRatePerHour = tempChange / timeChange; if (Math.abs(trendRatePerHour) < 2) { // Less than 2°F/hour trend = profile.stallRange && currentTemp >= profile.stallRange.start && currentTemp <= profile.stallRange.end ? "stalled" : "stable"; } else if (trendRatePerHour > 0) { trend = "rising"; } else { trend = "falling"; } } } // Check if in stall zone const inStallZone = profile.stallRange !== undefined && currentTemp >= profile.stallRange.start && currentTemp <= profile.stallRange.end; // Estimate time remaining let estimatedMinutesRemaining: number | null = null; if (trendRatePerHour > 0 && trend === "rising") { const hoursRemaining = tempDelta / trendRatePerHour; estimatedMinutesRemaining = Math.round(hoursRemaining * 60); } else if (trend === "stalled") { // During stall, estimate based on typical stall duration estimatedMinutesRemaining = null; // Can't reliably estimate during stall } // Generate recommendations const recommendations: string[] = []; if (inStallZone && trend === "stalled") { recommendations.push("🛑 You're in the stall zone! Temperature may plateau for 2-4 hours."); recommendations.push( "💡 Consider wrapping in butcher paper or foil (Texas crutch) to push through faster." ); } else if (inStallZone && trend === "rising") { recommendations.push("📈 Temperature is rising through the stall zone - looking good!"); } if (tempDelta <= profile.carryoverDegrees + 5) { recommendations.push( `🎯 Getting close! Consider pulling at ${targetTemp - profile.carryoverDegrees}°F to account for carryover.` ); } if (tempDelta <= 0) { recommendations.push("✅ Target temperature reached! Time to rest."); if (profile.requiresRest) { recommendations.push(`⏰ Rest for ${profile.restTimeMinutes} minutes before slicing.`); } } if (trend === "falling") { recommendations.push("⚠️ Temperature is dropping - check your heat source!"); if (cookMethod?.includes("smoke")) { recommendations.push("🔥 You may need to add more fuel or adjust airflow."); } } if (percentComplete < 25 && previousReadings && previousReadings.length > 0) { recommendations.push("🕐 Still in early stages - patience is key!"); } return { currentTemp, targetTemp, tempDelta, percentComplete: Math.round(percentComplete * 10) / 10, trend, trendRatePerHour: Math.round(trendRatePerHour * 10) / 10, estimatedMinutesRemaining, inStallZone, recommendations, }; }