Compare commits

..

4 Commits

Author SHA1 Message Date
Ninluc 7082ea3f8b Better camera settings + better rpompt
Build, push image, and notify Watchtower / build-image (push) Successful in 45s
Build, push image, and notify Watchtower / notify (push) Successful in 12s
2026-08-23 14:32:17 +02:00
Ninluc e802386adf Set to tidle when error requesting cooking plan 2026-08-23 14:22:59 +02:00
Ninluc 289124b7f8 Change State when microwave state is Idel 2026-08-23 14:22:39 +02:00
Ninluc 3bead59b23 Fix move to new messages 2026-08-23 13:04:16 +02:00
4 changed files with 39 additions and 21 deletions
+6 -6
View File
@@ -16,13 +16,13 @@ class DishSafetyResult(BaseModel):
def check_dish_safety(image_path: str) -> dict: def check_dish_safety(image_path: str) -> dict:
prompt = ( prompt = (
"You are a microwave safety vision inspector.\n\n" "You are a strict microwave safety vision inspector.\n\n"
"TASK: Check if this dish contains any REAL METAL CUTLERY (metal spoons, forks, knives) or ALUMINUM FOIL.\n\n" "TASK: Inspect the dish for ANY metal or plastic utensils (spoons, forks, knives, metal handles) or aluminum foil.\n\n"
"CRITICAL RULES:\n" "CRITICAL RULES:\n"
"1. Rice, curry, stew, potatoes, herbs, dark sauce, and ceramic/plastic/glass bowls are SAFE food items.\n" "1. Look closely at the edges and interior of the bowl for utensil handles sticking out, even if they appear dark or shadowed.\n"
"2. Camera image noise, shadows, and food textures are NOT metal objects.\n" "2. If you see ANY spoon handle, fork, knife, or utensil body—whether shiny silver, grey, or dark metal—mark is_safe = False.\n"
"3. Unless a shiny, metallic silver/gold utensil or foil sheet is clearly visible, mark is_safe = True and detected_hazards = [].\n" "3. ONLY mark is_safe = True if the bowl contains 100% food and rice with NO protruding utensils or foreign objects.\n"
"4. Do NOT invent or guess utensils if none are clearly visible." "4. If a spoon or utensil is detected, set detected_hazards = ['metal spoon'] and warning_message = 'Please remove the metal utensil before microwaving.'"
) )
response_raw = generate( response_raw = generate(
+3
View File
@@ -344,6 +344,9 @@ async def lora_process_task():
microwave_state = new_state microwave_state = new_state
if new_state not in (MicrowaveState.ALERT): # Not alert so we can show the error if new_state not in (MicrowaveState.ALERT): # Not alert so we can show the error
update_screen() update_screen()
if new_state == MicrowaveState.IDLE:
cooking_state = None
await asyncio.sleep_ms(20)
await asyncio.sleep_ms(100) await asyncio.sleep_ms(100)
+11 -14
View File
@@ -108,6 +108,15 @@ def on_microwave_change(state: MicrowaveState, field: MicrowaveStateFields):
# Send to LoRa device if state changed # Send to LoRa device if state changed
lora.send_reliable(payloads.lora_microwave_state(state.state), max_retries=5) lora.send_reliable(payloads.lora_microwave_state(state.state), max_retries=5)
if field == MicrowaveStateFields.COOKING_STATE:
update_lcd_microwave_count()
if state.cooking_state in (CookingStates.DONE, CookingStates.STIRRING_REQUIRED, CookingStates.ALERT):
# Trigger buzzer for user attention
beats = 5 if state.cooking_state == CookingStates.ALERT else (1 if state.cooking_state == CookingStates.STIRRING_REQUIRED else 3)
buzzer.buzzer_siren(beatsNb=beats)
elif state.cooking_state == CookingStates.DONE and state.state == MicrowaveState.COOKING:
state.set_state(MicrowaveState.DONE)
print(f"[{state.microwave_id}] Cooking finished. Waiting for user to remove dish.")
def set_cloud_alert(state: bool): def set_cloud_alert(state: bool):
"""Setter for cloud_alert that triggers a display update when changed.""" """Setter for cloud_alert that triggers a display update when changed."""
@@ -322,20 +331,6 @@ async def process_messages_task():
data = msg["data"] data = msg["data"]
if source == "LoRa": if source == "LoRa":
if "new_cooking_state" in data.get("data", {}):
mw_id = data["data"].get("id")
n_state = data["data"].get("new_cooking_state")
print(f"[LoRa] Microwave {mw_id} state changed to: {n_state}")
microwave_states[mw_id].set_cooking_state(n_state)
update_lcd_microwave_count() # Update the LCD with new microwave count
if n_state in (CookingStates.DONE, CookingStates.STIRRING_REQUIRED, CookingStates.ALERT):
beats = 5 if n_state == CookingStates.ALERT else (1 if n_state == CookingStates.STIRRING_REQUIRED else 3)
buzzer.buzzer_siren(beatsNb=beats)
if n_state == CookingStates.IDLE and microwave_states.get(mw_id).state == MicrowaveState.COOKING:
microwave_states[mw_id].set_state(MicrowaveState.DONE)
print(f"[{mw_id}] Cooking finished. Waiting for user to remove dish.")
if "action" in data.get("data", {}): if "action" in data.get("data", {}):
if type(data["data"]) != dict: if type(data["data"]) != dict:
print(f"[LoRa] Invalid data format received: {data['data']}") print(f"[LoRa] Invalid data format received: {data['data']}")
@@ -641,6 +636,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data):
for attempt in range(1, max_retries + 1): for attempt in range(1, max_retries + 1):
if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD: if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD:
print(f"[{microwave_id}] Dish removed or state changed. Aborting API request.") print(f"[{microwave_id}] Dish removed or state changed. Aborting API request.")
microwave_states[microwave_id].set_state(MicrowaveState.IDLE)
return return
try: try:
@@ -657,6 +653,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data):
# Abort if state changed while waiting for cloud AI response # Abort if state changed while waiting for cloud AI response
if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD: if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD:
print(f"[{microwave_id}] Dish removed during API request. Discarding API plan.") print(f"[{microwave_id}] Dish removed during API request. Discarding API plan.")
microwave_states[microwave_id].set_state(MicrowaveState.IDLE)
return return
response.raise_for_status() response.raise_for_status()
+19 -1
View File
@@ -6,7 +6,25 @@ import time
picam2 = Picamera2() picam2 = Picamera2()
camera_config = picam2.create_still_configuration() # Get maximum full-sensor dimensions
sensor_format = picam2.sensor_modes[0]
max_width = sensor_format['size'][0]
max_height = sensor_format['size'][1]
camera_config = picam2.create_still_configuration(
raw={"size": (1640, 1232)},
main={"size": (1640, 1232)}
)
picam2.set_controls({
"AeEnable": False, # Disable Auto Exposure to lock settings
"ExposureTime": 60000, # 40 ms shutter time (brightens without grain)
"AnalogueGain": 2.0, # Keep gain low to eliminate sensor noise
"AwbEnable": False, # Disable Auto White Balance fluctuation
"ColourGains": (1.4, 1.2), # Balanced RGB gains for indoor warm LED
"Brightness": 0.2, # Slight boost to shadow detail
"Contrast": 1.1, # Crisp edges around metallic surfaces
"ScalerCrop": [0, 0, max_width, max_height] # Uncrop the image
})
picam2.configure(camera_config) picam2.configure(camera_config)
picam2.start() picam2.start()