Cookwala / SDK and scenarios / 015
The five presets of the home-page demo, from the SDK
The dry run on the home page uses five device presets. They are exported to examples/capabilities so the hub, the CLI and every SDK see the same devices; this scenario reproduces the demo for shakshuka.
Read first: /docs/QUICKSTART/ /docs/CORE/
Steps
The presets carry the vendor name "Cookwala demo presets"; the basic hob robot has one sensor, its camera.
getDevicesCounter arm: cuts under its camera, cracks the eggs; the person heats and sautes.
dryRunHob robot without thermometers: heating is a logged estimate.
dryRunHob robot with thermometers: heating is verified by the pan thermometer.
dryRunSmart oven: it lists cw.op.heat, so the dry run gives it the pan-heating step as an estimate and leaves the rest to the person. The dry run matches operations, not equipment; the recipe's equipment list is a separate check.
dryRunFryer robot: same as the hob robot for this dish, thirteen steps by the device.
dryRun
Outcome
You can reproduce any home-page result from code, and you have seen one limit of the dry run worth knowing: it checks operations and sensors, not whether the device owns the pan. Next: scenario 020 checks a temperature trace against an envelope.
Command line
cookwala dryrun examples/shakshuka.cookwala.json --device examples/capabilities/demo-arm.json --human-present
cookwala dryrun examples/shakshuka.cookwala.json --device examples/capabilities/demo-hob-robot-basic.json --human-present
cookwala dryrun examples/shakshuka.cookwala.json --device examples/capabilities/demo-hob-robot.json --human-present
cookwala dryrun examples/shakshuka.cookwala.json --device examples/capabilities/demo-oven.json --human-present
cookwala dryrun examples/shakshuka.cookwala.json --device examples/capabilities/demo-fryer-robot.json --human-presentRecorded output
Status: ok
[1] getDevices: {"devices": {"demo-arm": {"cookwala": "0.1.0", "actor": {"kind": "robot", "id": "demo:arm", "vendor": "Cookwala demo presets", "model": "Counter robot arm (cuts, cracks, mixes, moves)"}, "roles": ["executor"], "location": {"zone": "kitchen", "mobile": false}, "capabilities": {"ops": [{"op": "cw.op.cut"}, {"op": "cw.op.crack"}, {"op": "cw.op.transfer"}, {"op": "cw.op.mix"}, {"op": "cw.op.whisk"}, {"op": "cw.op.wash"}, {"op": "cw.op.drain"}, {"op": "cw.op.crush"}, {"op": "cw.op.grate"}, {"op": "cw.op.layer"}, {"op": "cw.op.garnish"}, {"op": "cw.op.form"}], "sensors": [{"sensor": "cw.sense.vision…
[2] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "human", "verifiedBy": "human"}, {"node": "n5", "op": "cw.op.saute", "by": "human", "verifiedBy": "human"}, {"node": "n6", "op": "cw.op.saute", "by": "human", "verifiedBy": "human"}, {"node": "n7", "op": "cw.op.simmer", "b…
[3] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "device", "verifiedBy": "model", "rung": "model"}, {"node": "n5", "op": "cw.op.saute", "by": "device", "verifiedBy": "model", "rung": "model"}, {"node": "n6", "op": "cw.op.saute", "by": "device", "verifiedBy": "model", "ru…
[4] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.pan_surface_temp"}, {"node": "n5", "op": "cw.op.saute", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.pan_surface_temp"}, {"node": "n6", "op": "cw.op.saute", …
[5] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.cut", "by": "human", "verifiedBy": "human"}, {"node": "n2", "op": "cw.op.cut", "by": "human", "verifiedBy": "human"}, {"node": "n3", "op": "cw.op.cut", "by": "human", "verifiedBy": "human"}, {"node": "n4", "op": "cw.op.heat", "by": "device", "verifiedBy": "model", "rung": "model"}, {"node": "n5", "op": "cw.op.saute", "by": "human", "verifiedBy": "human"}, {"node": "n6", "op": "cw.op.saute", "by": "human", "verifiedBy": "human"}, {"node": "n7", "op": "cw.op.simmer", "by": "human", "verifiedBy": "human"}, {"node": "n8", "op": "cw.op.seas…
[6] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.pan_surface_temp"}, {"node": "n5", "op": "cw.op.saute", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.pan_surface_temp"}, {"node": "n6", "op": "cw.op.saute", …Code
Run: python scenarios/out/015-browser-demo-presets/python.py
# Scenario 015: The five presets of the home-page demo, from the SDK
# The dry run on the home page uses five device presets. They are exported to examples/capabilities so the hub, the CLI and every SDK see the same devices; this scenario reproduces the demo for shakshuka.
# Run a hub first: python hub/cookwala_hub.py --recipes examples
import json, os
from cookwala.client import CookwalaClient, CookwalaProblem
def load(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
def pick(obj, path):
for part in path.replace("]", "").replace("[", ".").split("."):
obj = obj[int(part)] if part.isdigit() else (len(obj) if part == "length" else obj.get(part))
return obj
c = CookwalaClient(os.environ.get("COOKWALA_HUB", "http://localhost:7878"))
# Step 1: The presets carry the vendor name "Cookwala demo presets"; the basic hob robot has one sensor, its camera.
devices = c.get_devices()
assert pick(devices, "devices.demo-arm.actor.vendor") == "Cookwala demo presets", pick(devices, "devices.demo-arm.actor.vendor")
print("ok", "devices.demo-arm.actor.vendor", "=", "Cookwala demo presets")
assert pick(devices, "devices.demo-hob-robot-basic.capabilities.sensors.length") == 1, pick(devices, "devices.demo-hob-robot-basic.capabilities.sensors.length")
print("ok", "devices.demo-hob-robot-basic.capabilities.sensors.length", "=", 1)
# Step 2: Counter arm: cuts under its camera, cracks the eggs; the person heats and sautes.
arm = c.dry_run(recipe_id="shakshuka", device_id="demo-arm", human_present=True)
assert pick(arm, "state") == "accepted", pick(arm, "state")
print("ok", "state", "=", "accepted")
assert pick(arm, "plan[0].by") == "device", pick(arm, "plan[0].by")
print("ok", "plan[0].by", "=", "device")
assert pick(arm, "plan[4].op") == "cw.op.saute", pick(arm, "plan[4].op")
print("ok", "plan[4].op", "=", "cw.op.saute")
assert pick(arm, "plan[4].by") == "human", pick(arm, "plan[4].by")
print("ok", "plan[4].by", "=", "human")
# Step 3: Hob robot without thermometers: heating is a logged estimate.
basic = c.dry_run(recipe_id="shakshuka", device_id="demo-hob-robot-basic", human_present=True)
assert pick(basic, "state") == "accepted", pick(basic, "state")
print("ok", "state", "=", "accepted")
assert pick(basic, "plan[3].rung") == "model", pick(basic, "plan[3].rung")
print("ok", "plan[3].rung", "=", "model")
# Step 4: Hob robot with thermometers: heating is verified by the pan thermometer.
robot = c.dry_run(recipe_id="shakshuka", device_id="demo-hob-robot", human_present=True)
assert pick(robot, "state") == "accepted", pick(robot, "state")
print("ok", "state", "=", "accepted")
assert pick(robot, "plan[3].rung") == "cw.sense.pan_surface_temp", pick(robot, "plan[3].rung")
print("ok", "plan[3].rung", "=", "cw.sense.pan_surface_temp")
# Step 5: Smart oven: it lists cw.op.heat, so the dry run gives it the pan-heating step as an estimate and leaves the rest to the person. The dry run matches operations, not equipment; the recipe's equipment list is a separate check.
oven = c.dry_run(recipe_id="shakshuka", device_id="demo-oven", human_present=True)
assert pick(oven, "state") == "accepted", pick(oven, "state")
print("ok", "state", "=", "accepted")
assert pick(oven, "plan[3].by") == "device", pick(oven, "plan[3].by")
print("ok", "plan[3].by", "=", "device")
assert pick(oven, "plan[3].rung") == "model", pick(oven, "plan[3].rung")
print("ok", "plan[3].rung", "=", "model")
assert pick(oven, "plan[4].by") == "human", pick(oven, "plan[4].by")
print("ok", "plan[4].by", "=", "human")
# Step 6: Fryer robot: same as the hob robot for this dish, thirteen steps by the device.
fryer = c.dry_run(recipe_id="shakshuka", device_id="demo-fryer-robot", human_present=True)
assert pick(fryer, "state") == "accepted", pick(fryer, "state")
print("ok", "state", "=", "accepted")
assert pick(fryer, "plan.length") == 13, pick(fryer, "plan.length")
print("ok", "plan.length", "=", 13)
print("scenario complete")