RemoteAgent / app /app_device_routes.py
jeongsoo's picture
Initial setup
2382288
"""
RAG 검색 챗봇 μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ - μž₯치 관리 API 라우트 μ •μ˜ (API 경둜 μˆ˜μ •λ¨)
"""
import logging
import requests
from flask import request, jsonify
# 둜거 κ°€μ Έμ˜€κΈ°
logger = logging.getLogger(__name__)
def register_device_routes(app, login_required, DEVICE_SERVER_URL):
"""Flask μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μž₯치 관리 κ΄€λ ¨ 라우트 등둝"""
# μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ URL λ³€μˆ˜
custom_device_url = None
# URL μ„€μ • ν•¨μˆ˜
def get_device_url():
# μ‚¬μš©μž μ§€μ • URL이 있으면 μ‚¬μš©, μ—†μœΌλ©΄ ν™˜κ²½λ³€μˆ˜ κ°’ μ‚¬μš©
return custom_device_url or DEVICE_SERVER_URL
@app.route('/api/device/connect', methods=['POST'])
@login_required
def connect_device_server():
"""μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ URL μ—°κ²° API"""
nonlocal custom_device_url # μƒμœ„ μŠ€μ½”ν”„μ˜ λ³€μˆ˜ μ°Έμ‘°
try:
# μš”μ²­μ—μ„œ URL κ°€μ Έμ˜€κΈ°
request_data = request.get_json()
if not request_data or 'url' not in request_data:
logger.error("URL이 μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
return jsonify({
"success": False,
"error": "URL이 μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."
}), 400 # Bad Request
new_url = request_data['url'].strip()
if not new_url:
logger.error("URL이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€.")
return jsonify({
"success": False,
"error": "URL이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€."
}), 400 # Bad Request
# URL μ„€μ •
logger.info(f"μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ URL μ„€μ •: {new_url}")
custom_device_url = new_url
# μ„€μ •λœ URL둜 μƒνƒœ 확인 μ‹œλ„
try:
api_path = "/api/status"
logger.info(f"μž₯치 μ„œλ²„ μƒνƒœ 확인 μš”μ²­: {custom_device_url}{api_path}")
response = requests.get(f"{custom_device_url}{api_path}", timeout=5)
if response.status_code == 200:
try:
data = response.json()
logger.info(f"μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ μ—°κ²° 성곡. 응닡 데이터: {data}")
return jsonify({
"success": True,
"message": "μž₯치 μ„œλ²„ 연결에 μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.",
"server_status": data.get("status", "정상")
})
except requests.exceptions.JSONDecodeError:
logger.error("μž₯치 μ„œλ²„ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."
}), 502 # Bad Gateway
else:
error_message = f"μž₯치 μ„œλ²„ 응닡 였λ₯˜: {response.status_code}"
try:
error_detail = response.json().get("error", response.text)
error_message += f" - {error_detail}"
except Exception:
error_message += f" - {response.text}"
logger.warning(error_message)
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": error_message
}), 502 # Bad Gateway
except requests.exceptions.Timeout:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ ({custom_device_url})")
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ. μ„œλ²„ 응닡이 λ„ˆλ¬΄ λŠλ¦½λ‹ˆλ‹€."
}), 504 # Gateway Timeout
except requests.exceptions.ConnectionError:
logger.error(f"μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨ ({custom_device_url})")
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지, URL이 μ •ν™•ν•œμ§€ ν™•μΈν•΄μ£Όμ„Έμš”."
}), 502 # Bad Gateway
except Exception as e:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
except Exception as e:
logger.error(f"/api/device/connect 처리 쀑 λ‚΄λΆ€ μ„œλ²„ 였λ₯˜: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"λ‚΄λΆ€ μ„œλ²„ 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
@app.route('/api/device/status', methods=['GET'])
@login_required
def device_status():
"""μž₯치 관리 μ„œλ²„μ˜ μƒνƒœλ₯Ό ν™•μΈν•˜λŠ” API μ—”λ“œν¬μΈνŠΈ"""
try:
# requests λΌμ΄λΈŒλŸ¬λ¦¬λŠ” ν•¨μˆ˜ λ‚΄μ—μ„œ μž„ν¬νŠΈν•  ν•„μš” μ—†μŒ (상단 μž„ν¬νŠΈ μ‚¬μš©)
# json, jsonify도 Flaskμ—μ„œ 이미 μž„ν¬νŠΈλ¨ (상단 μž„ν¬νŠΈ μ‚¬μš©)
# μ—°κ²° νƒ€μž„μ•„μ›ƒ μ„€μ •
timeout = 5 # 5초둜 νƒ€μž„μ•„μ›ƒ μ„€μ •
try:
# μž₯치 μ„œλ²„ μƒνƒœ 확인 - 경둜: /api/status
current_device_url = get_device_url()
api_path = "/api/status"
logger.info(f"μž₯치 μ„œλ²„ μƒνƒœ 확인 μš”μ²­: {current_device_url}{api_path}")
response = requests.get(f"{current_device_url}{api_path}", timeout=timeout)
# 응닡 μƒνƒœ μ½”λ“œ 및 λ‚΄μš© λ‘œκΉ… (디버깅 κ°•ν™”)
logger.debug(f"μž₯치 μ„œλ²„ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
try:
logger.debug(f"μž₯치 μ„œλ²„ 응닡 λ‚΄μš©: {response.text[:200]}...") # λ„ˆλ¬΄ κΈΈλ©΄ μž˜λΌμ„œ λ‘œκΉ…
except Exception:
logger.debug("μž₯치 μ„œλ²„ 응닡 λ‚΄μš© λ‘œκΉ… μ‹€νŒ¨ (ν…μŠ€νŠΈ ν˜•μ‹ μ•„λ‹˜?)")
if response.status_code == 200:
# 성곡 μ‹œ 응닡 데이터 ꡬ쑰 확인 및 λ‘œκΉ…
try:
data = response.json()
logger.info(f"μž₯치 μ„œλ²„ μƒνƒœ 확인 성곡. 응닡 데이터: {data}")
return jsonify({"success": True, "status": "connected", "data": data})
except requests.exceptions.JSONDecodeError:
logger.error("μž₯치 μ„œλ²„ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."
}), 502 # Bad Gateway (μ—…μŠ€νŠΈλ¦Ό μ„œλ²„ 였λ₯˜)
else:
# μ‹€νŒ¨ μ‹œ 였λ₯˜ λ©”μ‹œμ§€ 포함 λ‘œκΉ…
error_message = f"μž₯치 μ„œλ²„ 응닡 였λ₯˜: {response.status_code}"
try:
# μ„œλ²„μ—μ„œ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό json으둜 λ³΄λ‚΄λŠ” 경우 포함
error_detail = response.json().get("error", response.text)
error_message += f" - {error_detail}"
except Exception:
error_message += f" - {response.text}" # JSON νŒŒμ‹± μ‹€νŒ¨ μ‹œ 원본 ν…μŠ€νŠΈ
logger.warning(error_message) # κ²½κ³  레벨둜 λ‘œκΉ…
return jsonify({
"success": False,
"error": error_message
}), 502 # Bad Gateway
except requests.exceptions.Timeout:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ ({DEVICE_SERVER_URL})")
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ. μ„œλ²„ 응닡이 λ„ˆλ¬΄ λŠλ¦½λ‹ˆλ‹€."
}), 504 # Gateway Timeout
except requests.exceptions.ConnectionError:
current_device_url = get_device_url()
logger.error(f"μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨ ({current_device_url})")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지, URL이 μ •ν™•ν•œμ§€ ν™•μΈν•΄μ£Όμ„Έμš”."
}), 502 # Bad Gateway (μ—°κ²° μ‹€νŒ¨λ„ μ—…μŠ€νŠΈλ¦Ό 문제둜 κ°„μ£Ό)
except Exception as e:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜ λ°œμƒ: {e}", exc_info=True) # 상세 μŠ€νƒ 트레이슀 λ‘œκΉ…
return jsonify({
"success": False,
"error": f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error (Flask μ•± λ‚΄λΆ€ λ˜λŠ” requests 라이브러리 문제 κ°€λŠ₯μ„±)
except Exception as e:
# 이 try-except 블둝은 /api/device/status 라우트 자체의 λ‚΄λΆ€ 였λ₯˜ 처리
logger.error(f"/api/device/status 처리 쀑 λ‚΄λΆ€ μ„œλ²„ 였λ₯˜: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"λ‚΄λΆ€ μ„œλ²„ 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
@app.route('/api/device/list', methods=['GET'])
@login_required
def device_list():
"""μž₯치 λͺ©λ‘ 쑰회 API"""
logger.info("μž₯치 λͺ©λ‘ 쑰회 μš”μ²­")
try:
current_device_url = get_device_url()
api_path = "/api/devices"
logger.info(f"μž₯치 λͺ©λ‘ 쑰회 μš”μ²­: {current_device_url}{api_path}")
response = requests.get(f"{current_device_url}{api_path}", timeout=5)
logger.debug(f"μž₯치 λͺ©λ‘ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
devices = data.get("devices", [])
logger.info(f"μž₯치 λͺ©λ‘ 쑰회 성곡: {len(devices)}개 μž₯치")
return jsonify({
"success": True,
"devices": devices
})
except requests.exceptions.JSONDecodeError:
logger.error("μž₯치 λͺ©λ‘ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({"success": False, "error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡"}), 502
else:
error_message = f"μž₯치 λͺ©λ‘ 쑰회 μ‹€νŒ¨: {response.status_code}"
try: error_message += f" - {response.json().get('error', response.text)}"
except Exception: error_message += f" - {response.text}"
logger.warning(error_message)
return jsonify({
"success": False,
"error": error_message
}), 502
except requests.exceptions.Timeout:
logger.error("μž₯치 λͺ©λ‘ 쑰회 μ‹œκ°„ 초과")
return jsonify({
"success": False,
"error": "μž₯치 λͺ©λ‘ 쑰회 μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
}), 504
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503 # Service Unavailable
except Exception as e:
logger.error(f"μž₯치 λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"μž₯치 λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500
@app.route('/api/device/programs', methods=['GET'])
@login_required
def device_programs():
"""μ‹€ν–‰ κ°€λŠ₯ν•œ ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 API"""
logger.info("ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μš”μ²­")
try:
current_device_url = get_device_url()
api_path = "/api/programs"
logger.info(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μš”μ²­: {current_device_url}{api_path}")
response = requests.get(f"{current_device_url}{api_path}", timeout=5)
logger.debug(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
programs = data.get("programs", [])
logger.info(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 성곡: {len(programs)}개 ν”„λ‘œκ·Έλž¨")
return jsonify({
"success": True,
"programs": programs
})
except requests.exceptions.JSONDecodeError:
logger.error("ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({"success": False, "error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡"}), 502
else:
error_message = f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μ‹€νŒ¨: {response.status_code}"
try: error_message += f" - {response.json().get('error', response.text)}"
except Exception: error_message += f" - {response.text}"
logger.warning(error_message)
return jsonify({
"success": False,
"error": error_message
}), 502
except requests.exceptions.Timeout:
logger.error("ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μ‹œκ°„ 초과")
return jsonify({
"success": False,
"error": "ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
}), 504
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503
except Exception as e:
logger.error(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500
@app.route('/api/device/programs/<program_id>/execute', methods=['POST'])
@login_required
def execute_program(program_id):
"""ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ API"""
logger.info(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­: {program_id}")
try:
current_device_url = get_device_url()
api_path = f"/api/programs/{program_id}/execute"
logger.info(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­: {current_device_url}{api_path}")
response = requests.post(
f"{current_device_url}{api_path}",
json={}, # ν•„μš”μ‹œ 여기에 νŒŒλΌλ―Έν„° μΆ”κ°€ κ°€λŠ₯
timeout=10 # ν”„λ‘œκ·Έλž¨ μ‹€ν–‰μ—λŠ” 더 κΈ΄ μ‹œκ°„ λΆ€μ—¬
)
logger.debug(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
success = data.get("success", False)
message = data.get("message", "")
logger.info(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡: {success}, {message}")
return jsonify(data) # μ„œλ²„ 응닡 κ·ΈλŒ€λ‘œ λ°˜ν™˜
except requests.exceptions.JSONDecodeError:
logger.error("ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
# 성곡(200)ν–ˆμ§€λ§Œ 응닡이 비정상적인 경우
return jsonify({
"success": False, # νŒŒμ‹± μ‹€νŒ¨ν–ˆμœΌλ―€λ‘œ False둜 κ°„μ£Ό
"error": "ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡"
}), 502
else:
error_message = f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­ μ‹€νŒ¨: {response.status_code}"
try: error_message += f" - {response.json().get('error', response.text)}"
except Exception: error_message += f" - {response.text}"
logger.warning(error_message)
return jsonify({
"success": False,
"error": error_message
}), 502 # λ˜λŠ” response.status_code λ₯Ό κ·ΈλŒ€λ‘œ λ°˜ν™˜ν•˜λŠ” 것도 κ³ λ €
except requests.exceptions.Timeout:
logger.error("ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­ μ‹œκ°„ 초과")
return jsonify({
"success": False,
"error": "ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­ μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
}), 504
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503
except Exception as e:
logger.error(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500