초기화
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# equipment package marker
|
||||
from .db_manager import (
|
||||
add_equipment_to_db,
|
||||
update_equipment_in_db,
|
||||
delete_equipment_from_db,
|
||||
get_all_equipment_from_db,
|
||||
get_equipment_details_from_db,
|
||||
save_to_db,
|
||||
get_all_machine_statuses_joined,
|
||||
get_machine_status_detail_joined
|
||||
)
|
||||
from .router import router
|
||||
@@ -0,0 +1,365 @@
|
||||
import mysql.connector
|
||||
from database.connection import get_db_connection
|
||||
import logging
|
||||
import json
|
||||
|
||||
# 설비 추가
|
||||
def add_equipment_to_db(equip_data):
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
# DB 테이블에 만약 equipment_type 컬럼이 추가되었는지 파악
|
||||
# 없을 경우 기본값을 채워 안전하게 동작
|
||||
sql = """
|
||||
INSERT INTO equipment (name, yarn_bobbin_count, dryer_type, power_consumption, max_cutting_speed, max_feed_speed)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(sql, (
|
||||
equip_data.get('name'),
|
||||
equip_data.get('yarn_bobbin_count'),
|
||||
equip_data.get('dryer_type'),
|
||||
equip_data.get('power_consumption'),
|
||||
equip_data.get('max_cutting_speed'),
|
||||
equip_data.get('max_feed_speed')
|
||||
))
|
||||
conn.commit()
|
||||
|
||||
new_equipment_id = cursor.lastrowid
|
||||
if new_equipment_id:
|
||||
init_status_sql = "INSERT INTO machine_status (equipment_id, device_state, sync_state) VALUES (%s, %s, %s)"
|
||||
cursor.execute(init_status_sql, (new_equipment_id, "OFFLINE", "IDLE"))
|
||||
conn.commit()
|
||||
return new_equipment_id
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error adding equipment: {err}")
|
||||
return None
|
||||
|
||||
# 설비 수정
|
||||
def update_equipment_in_db(equip_data):
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
sql = """
|
||||
UPDATE equipment SET
|
||||
name = %s,
|
||||
yarn_bobbin_count = %s,
|
||||
dryer_type = %s,
|
||||
power_consumption = %s,
|
||||
max_cutting_speed = %s,
|
||||
max_feed_speed = %s
|
||||
WHERE id = %s
|
||||
"""
|
||||
cursor.execute(sql, (
|
||||
equip_data.get('name'),
|
||||
equip_data.get('yarn_bobbin_count'),
|
||||
equip_data.get('dryer_type'),
|
||||
equip_data.get('power_consumption'),
|
||||
equip_data.get('max_cutting_speed'),
|
||||
equip_data.get('max_feed_speed'),
|
||||
equip_data.get('id')
|
||||
))
|
||||
conn.commit()
|
||||
return True
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error updating equipment: {err}")
|
||||
return False
|
||||
|
||||
# 설비 삭제
|
||||
def delete_equipment_from_db(equipment_id):
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
cursor.execute("DELETE FROM machine_status WHERE equipment_id = %s", (equipment_id,))
|
||||
cursor.execute("DELETE FROM plan_assignments WHERE equipment_id = %s", (equipment_id,))
|
||||
cursor.execute("DELETE FROM equipment WHERE id = %s", (equipment_id,))
|
||||
conn.commit()
|
||||
return True
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error deleting equipment: {err}")
|
||||
return False
|
||||
|
||||
# 모든 설비 목록 조회
|
||||
def get_all_equipment_from_db():
|
||||
# --- DEBUG LOG ---
|
||||
print("[DB DEBUG] get_all_equipment_from_db function starts.")
|
||||
# -----------------
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
# machine_status 테이블과 JOIN하여 실시간 장치 상태(device_state)를 함께 조회
|
||||
sql = """
|
||||
SELECT e.*, ms.device_state
|
||||
FROM equipment e
|
||||
LEFT JOIN machine_status ms ON e.id = ms.equipment_id
|
||||
"""
|
||||
cursor.execute(sql)
|
||||
results = cursor.fetchall()
|
||||
# --- DEBUG LOG ---
|
||||
print(f"[DB DEBUG] Query executed. Raw rows fetched: {len(results)}")
|
||||
# -----------------
|
||||
for r in results:
|
||||
# device_state가 'ONLINE'이면 is_online을 True로 설정
|
||||
r['is_online'] = (r.get('device_state') == 'ONLINE')
|
||||
|
||||
if 'equipment_type' not in r or not r['equipment_type']:
|
||||
r['equipment_type'] = 'CC_MC_TYPE_A'
|
||||
# 프론트엔드가 카드를 렌더링할 때 요구하는 필수 필드 보완 (방어 코드)
|
||||
if 'type' not in r or not r['type']:
|
||||
r['type'] = 'cutter'
|
||||
if 'ip_address' not in r or not r['ip_address']:
|
||||
r['ip_address'] = '127.0.0.1'
|
||||
if 'line_prefix' not in r or not r['line_prefix']:
|
||||
r['line_prefix'] = 'line1'
|
||||
return results
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error getting all equipment: {err}")
|
||||
# --- DEBUG LOG ---
|
||||
print(f"[DB DEBUG] Error fetching equipment: {err}")
|
||||
# -----------------
|
||||
return []
|
||||
|
||||
|
||||
|
||||
# 특정 설비 상세 정보 조회
|
||||
def get_equipment_details_from_db(equipment_id):
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
cursor.execute("SELECT * FROM equipment WHERE id = %s", (equipment_id,))
|
||||
equipment_data = cursor.fetchone()
|
||||
if not equipment_data:
|
||||
return None
|
||||
if 'equipment_type' not in equipment_data:
|
||||
equipment_data['equipment_type'] = 'CC_MC_TYPE_A'
|
||||
|
||||
return {
|
||||
"equipment": equipment_data
|
||||
}
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error getting equipment details: {err}")
|
||||
return None
|
||||
|
||||
# 실시간 설비 상태 DB에 저장/업데이트 (UPSERT)
|
||||
def save_to_db(data):
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
sql = """
|
||||
INSERT INTO machine_status (
|
||||
equipment_id, timestamp, device_state, operation_mode,
|
||||
yarn_sensors, dryer_states, temperatures, pump_states,
|
||||
cutting_motor_is_running, cutting_motor_value,
|
||||
transfer_motor_is_running, transfer_motor_value,
|
||||
fan_state, door_sensors, water_level_sensor
|
||||
) VALUES (
|
||||
%(equipment_id)s, %(timestamp)s, %(device_state)s, %(operation_mode)s,
|
||||
%(yarn_sensors)s, %(dryer_states)s, %(temperatures)s, %(pump_states)s,
|
||||
%(cutting_motor_is_running)s, %(cutting_motor_value)s,
|
||||
%(transfer_motor_is_running)s, %(transfer_motor_value)s,
|
||||
%(fan_state)s, %(door_sensors)s, %(water_level_sensor)s
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
timestamp = VALUES(timestamp),
|
||||
device_state = VALUES(device_state),
|
||||
operation_mode = VALUES(operation_mode),
|
||||
yarn_sensors = VALUES(yarn_sensors),
|
||||
dryer_states = VALUES(dryer_states),
|
||||
temperatures = VALUES(temperatures),
|
||||
pump_states = VALUES(pump_states),
|
||||
cutting_motor_is_running = VALUES(cutting_motor_is_running),
|
||||
cutting_motor_value = VALUES(cutting_motor_value),
|
||||
transfer_motor_is_running = VALUES(transfer_motor_is_running),
|
||||
transfer_motor_value = VALUES(transfer_motor_value),
|
||||
fan_state = VALUES(fan_state),
|
||||
door_sensors = VALUES(door_sensors),
|
||||
water_level_sensor = VALUES(water_level_sensor)
|
||||
"""
|
||||
params = data.copy()
|
||||
for key, value in params.items():
|
||||
if isinstance(value, list) or isinstance(value, dict):
|
||||
params[key] = json.dumps(value)
|
||||
|
||||
cursor.execute(sql, params)
|
||||
conn.commit()
|
||||
logging.info(f"Successfully saved status for equipment_id: {data['equipment_id']}")
|
||||
return True
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"DB Error in save_to_db: {err}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(f"An unexpected error occurred in save_to_db: {e}")
|
||||
return False
|
||||
|
||||
# 모든 설비의 현재 상태를 설비 정보와 JOIN하여 조회
|
||||
def get_all_machine_statuses_joined():
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
sql = """
|
||||
SELECT
|
||||
e.id as equipment_id,
|
||||
e.name,
|
||||
ms.timestamp,
|
||||
ms.device_state,
|
||||
ms.d_read_g0,
|
||||
ms.d_read_g1,
|
||||
ms.d_read_g2
|
||||
FROM equipment e
|
||||
LEFT JOIN machine_status ms ON e.id = ms.equipment_id
|
||||
ORDER BY e.id
|
||||
"""
|
||||
cursor.execute(sql)
|
||||
results = cursor.fetchall()
|
||||
for r in results:
|
||||
if 'equipment_type' not in r:
|
||||
r['equipment_type'] = 'CC_MC_TYPE_A'
|
||||
return results
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error in get_all_machine_statuses_joined: {err}")
|
||||
return []
|
||||
|
||||
# 특정 설비의 상세 상태를 설비 정보와 JOIN하여 조회
|
||||
def get_machine_status_detail_joined(equipment_id):
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
sql = """
|
||||
SELECT
|
||||
e.*,
|
||||
ms.timestamp,
|
||||
ms.device_state,
|
||||
ms.sync_state,
|
||||
ms.m_read_raw,
|
||||
ms.m_write_raw,
|
||||
ms.d_read_g0, ms.d_read_g1, ms.d_read_g2, ms.d_read_g3, ms.d_read_g4,
|
||||
ms.d_read_g5, ms.d_read_g6, ms.d_read_g7, ms.d_read_g8, ms.d_read_g9,
|
||||
ms.d_write_g0, ms.d_write_g1, ms.d_write_g2, ms.d_write_g3
|
||||
FROM equipment e
|
||||
LEFT JOIN machine_status ms ON e.id = ms.equipment_id
|
||||
WHERE e.id = %s
|
||||
"""
|
||||
cursor.execute(sql, (equipment_id,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
if 'equipment_type' not in result:
|
||||
result['equipment_type'] = 'CC_MC_TYPE_A'
|
||||
# JSON 컬럼 자동 파싱 처리
|
||||
for key in ["m_read_raw", "m_write_raw"]:
|
||||
val = result.get(key)
|
||||
if isinstance(val, str):
|
||||
try:
|
||||
result[key] = json.loads(val)
|
||||
except json.JSONDecodeError:
|
||||
logging.warning(f"Could not decode JSON for key {key}: {val}")
|
||||
return result
|
||||
except mysql.connector.Error as err:
|
||||
logging.error(f"Error in get_machine_status_detail_joined: {err}")
|
||||
return None
|
||||
|
||||
def update_m_write_in_db(equipment_id: int, payload: list):
|
||||
"""M영역 쓰기 설정값을 machine_status의 m_write_raw JSON에 병합 업데이트합니다."""
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
# 기존 데이터 가져오기
|
||||
cursor.execute("SELECT m_write_raw FROM machine_status WHERE equipment_id = %s", (equipment_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
m_write = {}
|
||||
if row and row.get("m_write_raw"):
|
||||
try:
|
||||
m_write = json.loads(row["m_write_raw"])
|
||||
except:
|
||||
pass
|
||||
|
||||
# 새로운 값 병합
|
||||
for item in payload:
|
||||
if len(item) >= 2:
|
||||
bit_id = str(item[0])
|
||||
val = int(item[1])
|
||||
m_write[bit_id] = val
|
||||
|
||||
# 업데이트 수행
|
||||
sql = "UPDATE machine_status SET m_write_raw = %s WHERE equipment_id = %s"
|
||||
cursor.execute(sql, (json.dumps(m_write), equipment_id))
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error in update_m_write_in_db: {e}")
|
||||
return False
|
||||
|
||||
def update_d_write_in_db(equipment_id: int, payload: list):
|
||||
"""D영역 쓰기 설정값을 machine_status의 d_write_g0~g3 개별 컬럼에 업데이트합니다."""
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
for item in payload:
|
||||
if len(item) >= 2:
|
||||
group_id = int(item[0])
|
||||
val = float(item[1])
|
||||
|
||||
if 0 <= group_id <= 3:
|
||||
sql = f"UPDATE machine_status SET d_write_g{group_id} = %s WHERE equipment_id = %s"
|
||||
cursor.execute(sql, (val, equipment_id))
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error in update_d_write_in_db: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def save_control_command(equipment_id: int, command_id: str, cmd_type: str, requested_value: list, status: str):
|
||||
"""제어 명령 요청을 생성하여 control_commands 테이블에 기록합니다."""
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
sql = """
|
||||
INSERT INTO control_commands (equipment_id, command_id, type, requested_value, command_status)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(sql, (equipment_id, command_id, cmd_type, json.dumps(requested_value), status))
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error in save_control_command: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def update_control_command_status(command_id: str, status: str, applied_value: list = None):
|
||||
"""수신된 ACK 신호나 타임아웃에 기반하여 제어 명령 상태를 업데이트합니다."""
|
||||
try:
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
if applied_value is not None:
|
||||
sql = """
|
||||
UPDATE control_commands
|
||||
SET command_status = %s, applied_value = %s, applied_at = CURRENT_TIMESTAMP
|
||||
WHERE command_id = %s
|
||||
"""
|
||||
cursor.execute(sql, (status, json.dumps(applied_value), command_id))
|
||||
elif status == "PLC_APPLIED":
|
||||
sql = """
|
||||
UPDATE control_commands
|
||||
SET command_status = %s, applied_value = requested_value,
|
||||
applied_at = CURRENT_TIMESTAMP
|
||||
WHERE command_id = %s
|
||||
"""
|
||||
cursor.execute(sql, (status, command_id))
|
||||
elif status in ("FAILED", "TIMEOUT"):
|
||||
sql = """
|
||||
UPDATE control_commands
|
||||
SET command_status = %s, applied_at = CURRENT_TIMESTAMP
|
||||
WHERE command_id = %s
|
||||
"""
|
||||
cursor.execute(sql, (status, command_id))
|
||||
else:
|
||||
cursor.execute(
|
||||
"UPDATE control_commands SET command_status = %s WHERE command_id = %s",
|
||||
(status, command_id)
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error in update_control_command_status: {e}")
|
||||
return False
|
||||
@@ -0,0 +1,316 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
import logging
|
||||
import math
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger("equipment_router")
|
||||
router = APIRouter(prefix="/equipment", tags=["equipment"])
|
||||
|
||||
class EquipInfo(BaseModel):
|
||||
equip_id: str
|
||||
equip_name: str
|
||||
equip_type: str
|
||||
description: Optional[str] = ""
|
||||
|
||||
class ControlRequestM(BaseModel):
|
||||
bit: Optional[int] = None
|
||||
value: int
|
||||
|
||||
from .db_manager import (
|
||||
get_all_equipment_from_db,
|
||||
get_equipment_details_from_db,
|
||||
get_all_machine_statuses_joined,
|
||||
get_machine_status_detail_joined,
|
||||
save_control_command
|
||||
)
|
||||
|
||||
def update_m_write_in_db(equipment_id: int, payload: list):
|
||||
try:
|
||||
from database.connection import get_db_connection
|
||||
import json
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor(dictionary=True) as cursor:
|
||||
cursor.execute("SELECT m_write_raw FROM machine_status WHERE equipment_id = %s", (equipment_id,))
|
||||
row = cursor.fetchone()
|
||||
m_write = {}
|
||||
if row and row.get("m_write_raw"):
|
||||
try:
|
||||
m_write = json.loads(row["m_write_raw"])
|
||||
except:
|
||||
pass
|
||||
for item in payload:
|
||||
if len(item) >= 2:
|
||||
m_write[str(item[0])] = int(item[1])
|
||||
cursor.execute(
|
||||
"UPDATE machine_status SET m_write_raw = %s WHERE equipment_id = %s",
|
||||
(json.dumps(m_write), equipment_id)
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"[DB Error in update_m_write_in_db]: {e}")
|
||||
return False
|
||||
|
||||
def update_d_write_in_db(equipment_id: int, payload: list):
|
||||
try:
|
||||
from database.connection import get_db_connection
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
for item in payload:
|
||||
if len(item) >= 2:
|
||||
group_id = int(item[0])
|
||||
val = float(item[1])
|
||||
if 0 <= group_id <= 3:
|
||||
cursor.execute(
|
||||
f"UPDATE machine_status SET d_write_g{group_id} = %s WHERE equipment_id = %s",
|
||||
(val, equipment_id)
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"[DB Error in update_d_write_in_db]: {e}")
|
||||
return False
|
||||
|
||||
def _get_line_prefix_by_id(equipment_id: int) -> str:
|
||||
if equipment_id == 8:
|
||||
return "line1"
|
||||
return f"line{equipment_id}"
|
||||
|
||||
|
||||
def _ensure_equipment_is_ready(equipment_id: int):
|
||||
status = get_machine_status_detail_joined(equipment_id)
|
||||
if not status:
|
||||
raise HTTPException(status_code=404, detail="Machine status detail not found")
|
||||
if status.get("sync_state") == "SYNCING" or status.get("device_state") != "ONLINE":
|
||||
raise HTTPException(status_code=423, detail="Equipment initialization is in progress")
|
||||
|
||||
|
||||
@router.get("", response_model=List[dict])
|
||||
def list_equipment_root():
|
||||
return get_all_equipment_from_db()
|
||||
|
||||
|
||||
@router.get("/status/all", response_model=List[dict])
|
||||
def list_machine_statuses():
|
||||
return get_all_machine_statuses_joined()
|
||||
|
||||
|
||||
@router.get("/monitoring/metrics", response_model=dict)
|
||||
def get_performance_metrics():
|
||||
from monitoring.metrics import get_metrics_summary
|
||||
return get_metrics_summary()
|
||||
|
||||
|
||||
@router.get("/{equipment_id}", response_model=dict)
|
||||
def get_equipment_details(equipment_id: int):
|
||||
details = get_equipment_details_from_db(equipment_id)
|
||||
if not details:
|
||||
raise HTTPException(status_code=404, detail="Equipment not found")
|
||||
return details
|
||||
|
||||
|
||||
@router.get("/{equipment_id}/status", response_model=dict)
|
||||
def get_machine_status_detail(equipment_id: int):
|
||||
status = get_machine_status_detail_joined(equipment_id)
|
||||
if not status:
|
||||
raise HTTPException(status_code=404, detail="Machine status detail not found")
|
||||
return status
|
||||
|
||||
|
||||
@router.post("/{equipment_id}/control/m", response_model=dict, status_code=202)
|
||||
def send_momentary_control_m(equipment_id: int, payload: List[List[int]]):
|
||||
from communication.mqtt_client import _client
|
||||
import json
|
||||
|
||||
logger.info(f"[REST API WRITE M] Started. equipment_id: {equipment_id} | Payload: {payload}")
|
||||
_ensure_equipment_is_ready(equipment_id)
|
||||
|
||||
if not payload:
|
||||
raise HTTPException(status_code=400, detail="Empty payload not allowed")
|
||||
|
||||
seen_ids = set()
|
||||
for item in payload:
|
||||
if len(item) < 2:
|
||||
raise HTTPException(status_code=400, detail="Invalid payload format: each item must be [id, value]")
|
||||
bit_id, val = item[0], item[1]
|
||||
if bit_id in seen_ids:
|
||||
raise HTTPException(status_code=400, detail=f"Duplicate bit ID in payload: {bit_id}")
|
||||
seen_ids.add(bit_id)
|
||||
if not (0 <= bit_id <= 187):
|
||||
raise HTTPException(status_code=400, detail=f"M-bit ID {bit_id} out of bounds (0~187)")
|
||||
if val not in (0, 1):
|
||||
raise HTTPException(status_code=400, detail=f"Invalid M-bit value {val} (must be 0 or 1)")
|
||||
|
||||
if _client is None or not _client.is_connected():
|
||||
logger.error("[REST API WRITE M ERROR] MQTT client is offline")
|
||||
raise HTTPException(status_code=503, detail="MQTT broker connection is offline")
|
||||
|
||||
command_id = str(uuid.uuid4())
|
||||
|
||||
db_ok = save_control_command(equipment_id, command_id, "m_write", payload, "ACCEPTED")
|
||||
if not db_ok:
|
||||
logger.error("[REST API WRITE M ERROR] Failed to save control command in database")
|
||||
raise HTTPException(status_code=500, detail="Database transaction failed (Command not queued)")
|
||||
|
||||
ok = update_m_write_in_db(equipment_id, payload)
|
||||
if not ok:
|
||||
logger.error("[REST API WRITE M ERROR] Failed to update machine status write buffer in database")
|
||||
from .db_manager import update_control_command_status
|
||||
update_control_command_status(command_id, "FAILED")
|
||||
raise HTTPException(status_code=500, detail="Database write buffer update failed")
|
||||
|
||||
prefix = _get_line_prefix_by_id(equipment_id)
|
||||
topic = f"/{prefix}/m/out"
|
||||
|
||||
envelope = {
|
||||
"command_id": command_id,
|
||||
"type": "m_write",
|
||||
"items": payload,
|
||||
"requested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
try:
|
||||
msg_payload = json.dumps(envelope)
|
||||
result = _client.publish(topic, msg_payload, qos=1)
|
||||
if result.rc != 0:
|
||||
raise Exception(f"Paho MQTT publish returned error code: {result.rc}")
|
||||
logger.info(f"[REST API WRITE M SUCCESS] Published to topic: {topic} | Payload: {msg_payload}")
|
||||
except Exception as e:
|
||||
logger.error(f"[REST API WRITE M MQTT ERROR] Publish failed: {e}", exc_info=True)
|
||||
from .db_manager import update_control_command_status
|
||||
update_control_command_status(command_id, "FAILED")
|
||||
raise HTTPException(status_code=500, detail=f"MQTT publish failed: {e}")
|
||||
|
||||
return {
|
||||
"status": "accepted",
|
||||
"command_id": command_id,
|
||||
"message": "Command queued, ACK pending"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{equipment_id}/control/d", response_model=dict, status_code=202)
|
||||
def send_register_control_d(equipment_id: int, payload: List[List[int]]):
|
||||
from communication.mqtt_client import _client
|
||||
import json
|
||||
|
||||
logger.info(f"[REST API WRITE D] Started. equipment_id: {equipment_id} | Payload: {payload}")
|
||||
_ensure_equipment_is_ready(equipment_id)
|
||||
|
||||
if not payload:
|
||||
raise HTTPException(status_code=400, detail="Empty payload not allowed")
|
||||
|
||||
float_payload = []
|
||||
seen_ids = set()
|
||||
for item in payload:
|
||||
if len(item) < 2:
|
||||
raise HTTPException(status_code=400, detail="Invalid payload format: each item must be [id, value]")
|
||||
group_id, val = int(item[0]), float(item[1])
|
||||
if group_id in seen_ids:
|
||||
raise HTTPException(status_code=400, detail=f"Duplicate group ID in payload: {group_id}")
|
||||
seen_ids.add(group_id)
|
||||
if not (0 <= group_id <= 3):
|
||||
raise HTTPException(status_code=400, detail=f"D-group ID {group_id} out of bounds (0~3)")
|
||||
if not math.isfinite(val):
|
||||
raise HTTPException(status_code=400, detail=f"Invalid REAL value {val}")
|
||||
float_payload.append([group_id, val])
|
||||
|
||||
if _client is None or not _client.is_connected():
|
||||
logger.error("[REST API WRITE D ERROR] MQTT client is offline")
|
||||
raise HTTPException(status_code=503, detail="MQTT broker connection is offline")
|
||||
|
||||
command_id = str(uuid.uuid4())
|
||||
|
||||
db_ok = save_control_command(equipment_id, command_id, "d_write", float_payload, "ACCEPTED")
|
||||
if not db_ok:
|
||||
logger.error("[REST API WRITE D ERROR] Failed to save control command in database")
|
||||
raise HTTPException(status_code=500, detail="Database transaction failed (Command not queued)")
|
||||
|
||||
ok = update_d_write_in_db(equipment_id, float_payload)
|
||||
if not ok:
|
||||
logger.error("[REST API WRITE D ERROR] Failed to update machine status write buffer in database")
|
||||
from .db_manager import update_control_command_status
|
||||
update_control_command_status(command_id, "FAILED")
|
||||
raise HTTPException(status_code=500, detail="Database write buffer update failed")
|
||||
|
||||
prefix = _get_line_prefix_by_id(equipment_id)
|
||||
topic = f"/{prefix}/d/out"
|
||||
|
||||
envelope = {
|
||||
"command_id": command_id,
|
||||
"type": "d_write",
|
||||
"items": float_payload,
|
||||
"requested_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
try:
|
||||
msg_payload = json.dumps(envelope)
|
||||
result = _client.publish(topic, msg_payload, qos=1)
|
||||
if result.rc != 0:
|
||||
raise Exception(f"Paho MQTT publish returned error code: {result.rc}")
|
||||
logger.info(f"[REST API WRITE D SUCCESS] Published to topic: {topic} | Payload: {msg_payload}")
|
||||
except Exception as e:
|
||||
logger.error(f"[REST API WRITE D MQTT ERROR] Publish failed: {e}", exc_info=True)
|
||||
from .db_manager import update_control_command_status
|
||||
update_control_command_status(command_id, "FAILED")
|
||||
raise HTTPException(status_code=500, detail=f"MQTT publish failed: {e}")
|
||||
|
||||
return {
|
||||
"status": "accepted",
|
||||
"command_id": command_id,
|
||||
"message": "Command queued, ACK pending"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{equipment_id}/sync", response_model=dict)
|
||||
def trigger_manual_sync(equipment_id: int):
|
||||
from communication.mqtt_client import _client
|
||||
import json
|
||||
|
||||
logger.info(f"[REST API SYNC] Manual Sync requested for equipment_id: {equipment_id}")
|
||||
|
||||
if _client is None or not _client.is_connected():
|
||||
logger.error("[REST API SYNC ERROR] MQTT client is offline")
|
||||
raise HTTPException(status_code=503, detail="MQTT broker connection is offline")
|
||||
|
||||
prefix = _get_line_prefix_by_id(equipment_id)
|
||||
|
||||
initial_m_write = json.dumps({str(i): 0 for i in range(188)})
|
||||
try:
|
||||
from database.connection import get_db_connection
|
||||
with get_db_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"UPDATE machine_status SET m_write_raw = %s, device_state = 'SYNCING' WHERE equipment_id = %s",
|
||||
(initial_m_write, equipment_id)
|
||||
)
|
||||
conn.commit()
|
||||
logger.info(f"[REST API SYNC] Successfully cleared database status values for equipment_id: {equipment_id}")
|
||||
except Exception as dbe:
|
||||
logger.error(f"[REST API SYNC ERROR] DB status reset failed: {dbe}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Database reset transaction failed")
|
||||
|
||||
m_reset_payload = [[i, 0] for i in range(188)]
|
||||
|
||||
try:
|
||||
result_m = _client.publish(f"/{prefix}/m/out", json.dumps(m_reset_payload), qos=1)
|
||||
if result_m.rc != 0:
|
||||
raise Exception(f"M output reset publish failed with rc: {result_m.rc}")
|
||||
logger.info(f"[REST API SYNC] Published M output RESET payload to /{prefix}/m/out")
|
||||
except Exception as e:
|
||||
logger.error(f"[REST API SYNC ERROR] M reset payload publish failed: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"MQTT reset publish failed: {e}")
|
||||
|
||||
cmd_topic = f"/{prefix}/cmd"
|
||||
try:
|
||||
result_cmd = _client.publish(cmd_topic, "sync", qos=1)
|
||||
if result_cmd.rc != 0:
|
||||
raise Exception(f"Sync command publish failed with rc: {result_cmd.rc}")
|
||||
logger.info(f"[REST API SYNC SUCCESS] Published 'sync' command to topic: {cmd_topic}")
|
||||
except Exception as e:
|
||||
logger.error(f"[REST API SYNC ERROR] Sync command publish failed: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"MQTT sync command publish failed: {e}")
|
||||
|
||||
return {"status": "success", "reset_topic_m": f"/{prefix}/m/out", "reset_topic_d": "", "cmd_topic": cmd_topic}
|
||||
Reference in New Issue
Block a user