177 lines
6.0 KiB
Python
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)) |