Files
nonye/back/blueprints/device_warning.py
2025-07-17 23:13:04 +08:00

325 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from flask import Blueprint, jsonify, current_app, g
import sqlite3
import datetime
bp = Blueprint('device_warning', __name__, url_prefix='/api/warning')
def get_db():
"""获取数据库连接"""
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
"""关闭数据库连接"""
db = g.pop('db', None)
if db is not None:
db.close()
def is_data_constant(data_points, threshold=0.5, time_threshold_hours=2):
"""
检测数据是否长时间保持不变
data_points: 数据点列表,每个元素为(timestamp, value)
threshold: 数值变化阈值,小于此值视为不变
time_threshold_hours: 时间阈值单位小时默认2小时
"""
if len(data_points) < 2:
return False, None
# 检查时间跨度是否达到阈值
first_time = datetime.datetime.strptime(data_points[0][0], '%Y-%m-%d %H:%M:%S')
last_time = datetime.datetime.strptime(data_points[-1][0], '%Y-%m-%d %H:%M:%S')
time_diff = last_time - first_time
if time_diff.total_seconds() < time_threshold_hours * 3600:
return False, None
# 检查数值变化是否小于阈值
first_value = data_points[0][1]
for time, value in data_points[1:]:
if abs(value - first_value) > threshold:
return False, None
return True, first_value
def check_device_warnings():
"""检查所有设备是否有数据长时间不变的情况"""
db = get_db()
cursor = db.cursor()
# 获取所有设备
cursor.execute("SELECT id, device_name, device_code FROM device")
devices = cursor.fetchall()
warning_updates = []
for device in devices:
device_id = device['id']
device_name = device['device_name']
device_code = device['device_code']
# 检查设备当前状态
cursor.execute("SELECT status FROM device WHERE id = ?", (device_id,))
device_status = cursor.fetchone()
# 如果设备已经是故障状态,则跳过检测
if device_status['status'] == 'Faulty':
continue
warning_type = None
warning_value = None
# 检查温度数据使用2小时阈值
cursor.execute(
"""SELECT timestamp, temperature FROM temperature_data
WHERE device_id = ?
ORDER BY timestamp DESC
LIMIT 24""", # 取最近24条数据
(device_id,)
)
temp_data = cursor.fetchall()
if temp_data:
is_constant, value = is_data_constant(
[(item['timestamp'], item['temperature']) for item in temp_data]
)
if is_constant:
warning_type = 'temperature_constant'
warning_value = value
# 如果温度没有问题检查湿度数据使用2小时阈值
if not warning_type:
cursor.execute(
"""SELECT timestamp, humidity FROM temperature_data
WHERE device_id = ?
ORDER BY timestamp DESC
LIMIT 24""",
(device_id,)
)
humidity_data = cursor.fetchall()
if humidity_data:
is_constant, value = is_data_constant(
[(item['timestamp'], item['humidity']) for item in humidity_data]
)
if is_constant:
warning_type = 'humidity_constant'
warning_value = value
# 如果温度和湿度都没有问题检查pH值数据使用1小时阈值
if not warning_type:
cursor.execute(
"""SELECT timestamp, ph FROM temperature_data
WHERE device_id = ?
ORDER BY timestamp DESC
LIMIT 24""",
(device_id,)
)
ph_data = cursor.fetchall()
if ph_data:
is_constant, value = is_data_constant(
[(item['timestamp'], item['ph']) for item in ph_data],
threshold=0.2, # pH变化阈值更小
time_threshold_hours=1 # pH检查时间阈值更短
)
if is_constant:
warning_type = 'ph_constant'
warning_value = value
# 更新设备状态
if warning_type:
# 如果设备之前不是警告状态,则更新
if device_status['status'] != 'Faulty':
warning_updates.append({
'device_id': device_id,
'status': 'warning',
'warning_type': warning_type,
'warning_value': warning_value,
'warning_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
else:
# 如果设备之前是警告状态,但现在不是了,则恢复为正常状态
if device_status['status'] == 'warning':
warning_updates.append({
'device_id': device_id,
'status': 'normal',
'warning_type': None,
'warning_value': None,
'warning_time': None
})
# 批量更新设备状态
for update in warning_updates:
cursor.execute(
"""
UPDATE device
SET status = ?,
warning_type = ?,
warning_value = ?,
warning_time = ?
WHERE id = ?
""",
(
update['status'],
update['warning_type'],
update['warning_value'],
update['warning_time'],
update['device_id']
)
)
db.commit()
return warning_updates
@bp.route('/check', methods=['GET'])
def check_warnings():
"""检查并更新设备警告状态"""
try:
updates = check_device_warnings()
return jsonify({
'status': 'success',
'data': updates,
'message': f'更新了{len(updates)}个设备状态'
})
except Exception as e:
return jsonify({
'status': 'error',
'message': f'检查设备警告失败: {str(e)}'
}), 500
@bp.route('/list', methods=['GET'])
def get_warning_list():
"""获取警告设备列表(优先使用存储的故障描述)"""
try:
db = get_db()
cursor = db.cursor()
# 尝试获取完整警告信息包括fault_description字段
try:
cursor.execute(
"""
SELECT
id,
device_name,
device_code,
status,
warning_type,
warning_value,
warning_time,
fault_description # 显式查询存储的故障描述
FROM device
WHERE status = 'Faulty' OR status = 'warning'
ORDER BY warning_time DESC
"""
)
warnings = cursor.fetchall()
warning_list = []
for warning in warnings:
warning_dict = dict(warning)
# 优先使用数据库中存储的fault_description
stored_description = warning_dict.get('fault_description')
if stored_description:
warning_dict['fault_description'] = stored_description
else:
# 如果没有存储描述则根据warning_type生成默认描述
warning_type = warning_dict.get('warning_type')
if warning_type == 'temperature_constant':
warning_dict['fault_description'] = '温度持续异常'
elif warning_type == 'humidity_constant':
warning_dict['fault_description'] = '湿度持续异常'
elif warning_type == 'ph_constant':
warning_dict['fault_description'] = 'pH值持续异常'
else:
warning_dict['fault_description'] = '环境数据异常'
warning_list.append(warning_dict)
return jsonify({
'status': 'success',
'data': warning_list,
'message': f'获取到{len(warning_list)}个警告设备'
})
except sqlite3.OperationalError as e:
# 如果表结构不完整缺少fault_description等字段回退到简单查询
cursor.execute(
"""
SELECT
id,
device_name,
device_code,
status
FROM device
WHERE status = 'Faulty' OR status = 'warning'
ORDER BY created_at DESC
"""
)
warnings = cursor.fetchall()
# 简单查询结果只能提供默认描述
warning_list = [dict(warning, fault_description='环境数据异常')
for warning in warnings]
return jsonify({
'status': 'success',
'data': warning_list,
'message': f'获取到{len(warning_list)}个警告设备(简化模式)'
})
except Exception as e:
return jsonify({
'status': 'error',
'message': f'获取警告设备列表失败: {str(e)}'
}), 500
@bp.route('/resolve/<int:device_id>', methods=['POST'])
def resolve_warning(device_id):
"""解决设备警告"""
try:
db = get_db()
cursor = db.cursor()
cursor.execute(
"""
UPDATE device
SET status = 'Online',
warning_type = NULL,
warning_value = NULL,
warning_time = NULL
WHERE id = ?
""",
(device_id,)
)
db.commit()
return jsonify({
'status': 'success',
'message': '设备警告已解除'
})
except Exception as e:
return jsonify({
'status': 'error',
'message': f'解除设备警告失败: {str(e)}'
}), 500
# 初始化数据库表结构(如需在代码中初始化)
def init_db():
db = get_db()
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))