Files
Smartwave/cloud/app.py
T
Ninluc 4abefa5b29
Build, push image, and notify Watchtower / build-image (push) Successful in 34s
Build, push image, and notify Watchtower / notify (push) Successful in 10s
Added some error logging
2026-08-10 16:43:24 +02:00

177 lines
6.0 KiB
Python

import os
import asyncio
import base64
import uuid
import datetime
import sys
import json
from flask import Flask, request, jsonify
from pymongo import MongoClient
from APIs import generate, EdamamAPI
from APIs.mqtt import send_command
from microwaveCookPlanner import MicrowaveCookPlanner
import safety_checker
sys.path.insert(0, '..')
try:
from shared import config
except ImportError:
from ..shared import config
app = Flask(__name__)
# ---------------------------------------------------------
# Configuration & Setup
# ---------------------------------------------------------
# Configure MongoDB connection
MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:27017/")
client = MongoClient(MONGO_URI)
db = client["microwave_network_db"]
cooking_collection = db["cooking_parameters"]
telemetry_collection = db["telemetry_data"]
# Ensure the camera image storage directory exists when the app starts
CAMERA_IMAGE_DIR = "storage/dishPhotos"
os.makedirs(CAMERA_IMAGE_DIR, exist_ok=True)
# ---------------------------------------------------------
# Classes
# ---------------------------------------------------------
microwave_cook_planner = MicrowaveCookPlanner()
# ---------------------------------------------------------
# Routes
# ---------------------------------------------------------
@app.route("/")
def hello_world():
gen = generate(prompt="Say Hello, to the user !")
print(gen)
return f"<p>{gen}</p>"
@app.route("/cooking-params", methods=["POST"])
async def cooking_params():
try:
data = request.get_json()
if not data:
return jsonify({"error": "Invalid or missing JSON payload"}), 400
# Extract user or device parameters
height_cm = float(data.get("dish_height", 4.0))
initial_temp_c = float(data.get("ir_initial_temp", 20.0))
microwave_wattage = int(data.get("microwave_wattage", 900))
defrost_mode = bool(data.get("defrost_mode", False))
# 1. Save Camera Image
camera_image_b64 = data.get("camera_image")
if not camera_image_b64:
return jsonify({"error": "Missing required field 'camera_image'"}), 400
filename = f"dish_{uuid.uuid4().hex}.jpg"
filepath = os.path.join(CAMERA_IMAGE_DIR, filename)
try:
with open(filepath, "wb") as f:
f.write(base64.b64decode(camera_image_b64))
data["camera_image"] = filepath
except Exception as e:
print(f"[Error] Failed to save camera image: {str(e)}")
return jsonify({"error": f"Failed to save camera image: {str(e)}"}), 500
# 2. Run Vision Safety Check & Cook Planner Concurrently
try:
safety_task = asyncio.to_thread(safety_checker.check_dish_safety, filepath)
planner_task = asyncio.to_thread(
microwave_cook_planner.generate_plan,
image_path=filepath,
height_cm=height_cm,
initial_temp_c=initial_temp_c,
microwave_wattage=microwave_wattage,
defrost_mode=defrost_mode
)
# Execute both concurrently and await results
safety_result, cook_plan = await asyncio.gather(safety_task, planner_task)
except Exception as e:
print(f"[Error] Exception during safety check or cook planning: {str(e)}")
return jsonify({"error": f"Task execution failed: {str(e)}"}), 500
# 3. Evaluate Safety Result
data["safety_check"] = safety_result
if not safety_result.get("is_safe", True):
print(f"[Safety Warning] Unsafe dish detected: {safety_result}")
return jsonify({
"error": "Safety hazard detected in microwave area",
"is_safe": False,
"warning": safety_result.get("warning_message", "Unsafe materials detected."),
"detected_hazards": safety_result.get("detected_hazards", [])
}), 200
# 4. Attach Cooking Plan & Save to MongoDB
data["analysis_results"] = cook_plan
try:
cooking_collection.insert_one(data)
data.pop("_id", None)
except Exception as e:
return jsonify({"error": f"Database error: {str(e)}"}), 500
return jsonify(cook_plan), 201
except Exception as e:
print(f"[Error] Exception in /cooking-params: {str(e)}")
@app.route("/telemetry", methods=["POST"])
def telemetry():
data = request.get_json()
if data is None:
return jsonify({"error": "Invalid or missing JSON payload"}), 400
# If the payload was double-encoded as a string, deserialize it
if isinstance(data, str):
try:
data = json.loads(data)
except (json.JSONDecodeError, TypeError):
return jsonify({"error": "String payload could not be parsed as JSON"}), 400
if not isinstance(data, dict):
return jsonify({"error": "Expected a JSON object/dictionary"}), 400
# Stamp UTC timestamp for Node-RED queries
data["received_at"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
try:
telemetry_collection.insert_one(data)
return jsonify({"status": "success", "message": "Telemetry saved"}), 200
except Exception as e:
return jsonify({"error": f"Database error: {str(e)}"}), 500
@app.route("/debug", methods=["GET"])
def debug():
# Construct external HTTP endpoint dynamically based on incoming request host
telemetry_url = f"{request.host_url.rstrip('/')}/telemetry"
cmd_payload = {
"action": "request_telemetry",
"endpoint": telemetry_url
}
try:
send_command(topic="cmd/all", payload=cmd_payload)
return jsonify({
"status": "Telemetry command sent to cmd/all",
"published_payload": cmd_payload
}), 200
except Exception as e:
return jsonify({"error": f"Failed to publish MQTT command: {str(e)}"}), 500
if __name__ == "__main__":
app.run(debug=getattr(config, "DEBUG", True))