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

531 lines
22 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, request, current_app, g
import sqlite3
import datetime
from dateutil.relativedelta import relativedelta
import logging
import random
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
bp = Blueprint('ph_data', __name__, url_prefix='/ph_data')
# 数据缓存字典,格式: {time_range: (seed, cached_data)}
ph_data_cache = {}
def get_db():
"""获取数据库连接"""
if 'db' not in g:
try:
g.db = sqlite3.connect(
current_app.config.get('DATABASE', 'agriculture.db'),
check_same_thread=False,
detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
)
g.db.row_factory = sqlite3.Row
logger.info("数据库连接成功")
except Exception as e:
logger.error(f"数据库连接失败: {str(e)}")
raise
return g.db
def close_db(e=None):
"""关闭数据库连接"""
db = g.pop('db', None)
if db is not None:
db.close()
logger.info("数据库连接已关闭")
@bp.route('/get_time_ranges', methods=['GET'])
def get_time_ranges():
"""获取时间范围选项"""
try:
time_ranges = [
{'value': 'today', 'label': '今日'},
{'value': 'last_three_days', 'label': '前三天'},
{'value': 'next_two_days', 'label': '后两天'},
]
logger.info("成功返回时间范围选项")
return jsonify({'time_ranges': time_ranges})
except Exception as e:
logger.error(f"获取时间范围失败: {str(e)}")
return jsonify({'error': '获取时间范围失败'}), 500
def increase_ph_fluctuation(data_list, time_range, amplitude=1.5):
"""
增加PH值的波动幅度使用固定随机种子确保相同时间范围生成相同波动数据
:param data_list: 包含PH值的数据列表
:param time_range: 时间范围参数
:param amplitude: 波动幅度默认±1.5
:return: 修改后的列表
"""
# 检查缓存
if time_range in ph_data_cache:
logger.info(f"使用缓存的波动数据 for {time_range}")
return ph_data_cache[time_range]
# 为特定时间范围设置固定随机种子
seed = hash(time_range)
random.seed(seed)
logger.info(f"为时间范围 {time_range} 设置随机种子: {seed}")
# 应用波动
modified_data = []
for item in data_list:
original_ph = item.get('ph') or item.get('avg_ph')
if original_ph is not None:
# 随机波动(可正可负)
fluctuation = random.uniform(-amplitude, amplitude)
new_ph = original_ph + fluctuation
# 确保PH值在合理范围0-14
new_ph = max(0, min(14, new_ph))
# 更新数据
modified_item = item.copy()
if 'ph' in modified_item:
modified_item['ph'] = round(new_ph, 2)
else:
modified_item['avg_ph'] = round(new_ph, 2)
modified_data.append(modified_item)
# 缓存波动后的数据
ph_data_cache[time_range] = modified_data
return modified_data
@bp.route('/get_ph_data', methods=['GET'])
def get_ph_data():
"""获取指定时间范围的PH值数据合并所有设备"""
time_range = request.args.get('time_range', 'today')
sample_method = request.args.get('sample_method', 'fixed') # 采样方式fixed(固定点)或hourly(每小时)
logger.info(f"请求PH数据时间范围: {time_range},采样方式: {sample_method}")
if time_range not in ['today', 'last_three_days', 'next_two_days', 'all']:
logger.warning(f"无效的时间范围: {time_range}")
return jsonify({'error': '无效的时间范围'}), 400
try:
db = get_db()
# 硬编码今日为2025-05-27
today = datetime.datetime(2025, 5, 27)
today_str = today.strftime('%Y-%m-%d')
result = []
if time_range == 'today':
# 今日数据查询逻辑
start_date = today_str + ' 00:00:00'
end_date = today_str + ' 23:59:59'
if sample_method == 'hourly':
# 每小时采样
for hour in range(0, 24):
sample_time = f"{today_str} {hour:02d}:00:00"
start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
- relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
+ relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))''',
(start_time, end_time, sample_time)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'timestamp': sample_time,
'ph': round(avg_ph, 2)
})
else:
# 固定点采样
cursor = db.execute(
'''SELECT timestamp, AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))
GROUP BY timestamp
ORDER BY timestamp''',
(start_date, end_date, today_str)
)
data = cursor.fetchall()
result = [{'timestamp': row['timestamp'], 'ph': float(row['avg_ph'])} for row in data]
logger.info(f"查询到今日PH数据记录: {len(result)}")
elif time_range == 'last_three_days':
# 前三天数据查询
if sample_method == 'hourly':
# 每小时采样
for i in range(3, 0, -1):
date = today - relativedelta(days=i)
date_str = date.strftime('%Y-%m-%d')
for hour in range(0, 24):
sample_time = f"{date_str} {hour:02d}:00:00"
start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
- relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
+ relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))''',
(start_time, end_time, sample_time)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'timestamp': sample_time,
'ph': round(avg_ph, 2)
})
else:
# 固定点采样每天6个点
sample_hours = [4, 8, 12, 16, 20, 23]
for i in range(3, 0, -1):
date = today - relativedelta(days=i)
date_str = date.strftime('%Y-%m-%d')
for hour in sample_hours:
if hour == 23:
sample_time = f"{date_str} {hour:02d}:00:00"
else:
sample_time = f"{date_str} {hour:02d}:00:00"
start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
- relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
+ relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))''',
(start_time, end_time, sample_time)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'timestamp': sample_time,
'ph': round(avg_ph, 2)
})
logger.info(f"查询到前三天PH数据记录: {len(result)}")
elif time_range == 'next_two_days':
# 后两天数据查询
if sample_method == 'hourly':
for i in range(1, 3):
date = today + relativedelta(days=i)
date_str = date.strftime('%Y-%m-%d')
for hour in range(0, 24):
sample_time = f"{date_str} {hour:02d}:00:00"
start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
- relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
+ relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))''',
(start_time, end_time, sample_time)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'timestamp': sample_time,
'ph': round(avg_ph, 2)
})
else:
sample_hours = [8, 12, 16, 20]
for i in range(1, 3):
date = today + relativedelta(days=i)
date_str = date.strftime('%Y-%m-%d')
for hour in sample_hours:
sample_time = f"{date_str} {hour:02d}:00:00"
start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
- relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
+ relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))''',
(start_time, end_time, sample_time)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'timestamp': sample_time,
'ph': round(avg_ph, 2)
})
logger.info(f"查询到后两天PH数据记录: {len(result)}")
elif time_range == 'all':
# 查询所有PH数据
cursor = db.execute(
'''SELECT timestamp, AVG(ph) as avg_ph
FROM temperature_data
WHERE ph IS NOT NULL
GROUP BY timestamp
ORDER BY timestamp'''
)
data = cursor.fetchall()
result = [{'timestamp': row['timestamp'], 'ph': float(row['avg_ph'])} for row in data]
logger.info(f"查询到所有PH数据记录: {len(result)}")
# 关键修改:使用带随机种子的波动函数,并缓存结果
result = increase_ph_fluctuation(result, time_range, amplitude=2.0)
return jsonify({
'time_range': time_range,
'sample_method': sample_method,
'data': result
})
except sqlite3.Error as e:
logger.error(f"数据库查询错误: {str(e)}")
return jsonify({'error': '数据库查询错误'}), 500
except Exception as e:
logger.error(f"获取PH数据异常: {str(e)}")
return jsonify({'error': '服务器内部错误'}), 500
finally:
close_db()
@bp.route('/get_ph_data_by_date_range', methods=['GET'])
def get_ph_data_by_date_range():
"""获取指定日期范围内的PH值数据合并所有设备"""
start_date_str = request.args.get('start_date')
end_date_str = request.args.get('end_date')
sample_method = request.args.get('sample_method', 'daily') # 采样方式daily(每日)或hourly(每小时)
# 验证日期格式
try:
start_date = datetime.datetime.strptime(start_date_str, '%Y-%m-%d')
end_date = datetime.datetime.strptime(end_date_str, '%Y-%m-%d')
except ValueError:
logger.warning(f"无效的日期格式需要YYYY-MM-DD格式实际传入: {start_date_str}, {end_date_str}")
return jsonify({'error': '无效的日期格式需要YYYY-MM-DD格式'}), 400
# 验证日期范围
if start_date > end_date:
logger.warning(f"开始日期不能大于结束日期: {start_date_str} > {end_date_str}")
return jsonify({'error': '开始日期不能大于结束日期'}), 400
logger.info(f"请求PH数据日期范围: {start_date_str}{end_date_str},采样方式: {sample_method}")
try:
db = get_db()
result = []
if sample_method == 'hourly':
# 每小时采样
current_date = start_date
while current_date <= end_date:
date_str = current_date.strftime('%Y-%m-%d')
for hour in range(0, 24):
sample_time = f"{date_str} {hour:02d}:00:00"
start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
- relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S')
+ relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE (timestamp BETWEEN ? AND ?)
OR (DATE(timestamp) = DATE(?))''',
(start_time, end_time, sample_time)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'timestamp': sample_time,
'ph': round(avg_ph, 2)
})
current_date += relativedelta(days=1)
else:
# 每日采样
current_date = start_date
while current_date <= end_date:
date_str = current_date.strftime('%Y-%m-%d')
cursor = db.execute(
'''SELECT AVG(ph) as avg_ph
FROM temperature_data
WHERE DATE(timestamp) = ?''',
(date_str,)
)
row = cursor.fetchone()
avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0
result.append({
'date': date_str,
'avg_ph': round(avg_ph, 2)
})
current_date += relativedelta(days=1)
logger.info(f"查询到{start_date_str}{end_date_str}的PH数据记录: {len(result)}")
# 增加PH值的波动幅度使用日期范围字符串作为种子
range_key = f"{start_date_str}_{end_date_str}"
result = increase_ph_fluctuation(result, range_key, amplitude=2.0)
return jsonify({
'start_date': start_date_str,
'end_date': end_date_str,
'sample_method': sample_method,
'data': result
})
except sqlite3.Error as e:
logger.error(f"数据库查询错误: {str(e)}")
return jsonify({'error': '数据库查询错误'}), 500
except Exception as e:
logger.error(f"获取PH数据异常: {str(e)}")
return jsonify({'error': '服务器内部错误'}), 500
finally:
close_db()
@bp.route('/get_ph_today', methods=['GET'])
def get_ph_today():
"""获取2025年5月27日设备1的所有PH值数据"""
logger.info("请求获取2025年5月27日设备1的PH数据")
try:
db = get_db()
target_date = datetime.datetime(2025, 5, 27)
start = target_date.strftime('%Y-%m-%d 00:00:00')
end = target_date.strftime('%Y-%m-%d 23:59:59')
logger.info(f"查询范围: {start} ~ {end}设备ID: 1")
cursor = db.execute(
'''SELECT timestamp, ph
FROM temperature_data
WHERE timestamp BETWEEN ? AND ?
AND ph IS NOT NULL
AND device_id = 1 -- 只查询设备1的数据
ORDER BY timestamp''',
(start, end)
)
data = cursor.fetchall()
if not data:
logger.warning("2025-05-27设备1无PH数据查询最近10条调试")
debug_cursor = db.execute('''
SELECT timestamp, ph, device_id
FROM temperature_data
ORDER BY timestamp DESC
LIMIT 10
''')
recent = debug_cursor.fetchall()
logger.info("数据库最近10条记录:")
for record in recent:
logger.info(f" timestamp: {record['timestamp']}, ph: {record['ph']}, device_id: {record['device_id']}")
return jsonify({
'date': '2025-05-27',
'device_id': 1,
'message': '未找到设备1的PH数据',
'data': []
})
result = []
for i, row in enumerate(data):
try:
# 提取原始数据
raw_ts = row['timestamp']
raw_ph = row['ph']
# 验证时间格式
if isinstance(raw_ts, datetime.datetime):
dt = raw_ts
else:
dt = datetime.datetime.strptime(raw_ts, '%Y-%m-%d %H:%M:%S')
# 验证PH值
if raw_ph is None:
continue # 跳过空值
if isinstance(raw_ph, (float, int)):
ph_value = raw_ph
elif isinstance(raw_ph, str):
ph_str = raw_ph.strip()
if 'pH' in ph_str:
ph_str = ph_str.replace('pH', '').strip()
ph_value = float(ph_str)
else:
ph_value = float(raw_ph)
result.append({
'timestamp': raw_ts.strftime('%Y-%m-%d %H:%M:%S') if isinstance(raw_ts,
datetime.datetime) else raw_ts,
'ph': round(ph_value, 2),
'formatted_time': dt.strftime('%H:%M')
})
except ValueError as ve:
logger.error(f"{i + 1} 条记录格式错误: {str(ve)}")
logger.error(f" 原始数据: timestamp={raw_ts}, ph={raw_ph}")
except TypeError as te:
logger.error(f"{i + 1} 条记录类型错误: {str(te)}")
logger.error(f" 原始数据: timestamp={raw_ts}, ph={raw_ph}")
except Exception as e:
logger.error(f"处理第 {i + 1} 条记录时未知错误: {str(e)}")
if not result:
logger.warning("设备1的所有记录处理后无有效数据")
return jsonify({
'date': '2025-05-27',
'device_id': 1,
'message': '数据格式错误无有效PH值',
'data': []
})
logger.info(f"成功处理设备1的 {len(result)} 条有效数据")
return jsonify({
'date': '2025-05-27',
'device_id': 1,
'total_records': len(result),
'data': result
})
except sqlite3.OperationalError as oe:
logger.error(f"数据库操作错误: {str(oe)}", exc_info=True)
return jsonify({
'date': '2025-05-27',
'device_id': 1,
'error': f'数据库操作错误: {str(oe)}'
}), 500
except sqlite3.Error as se:
logger.error(f"数据库错误: {str(se)}", exc_info=True)
return jsonify({
'date': '2025-05-27',
'device_id': 1,
'error': f'数据库错误: {str(se)}'
}), 500
except Exception as e:
logger.error(f"未知异常: {str(e)}", exc_info=True)
return jsonify({
'date': '2025-05-27',
'device_id': 1,
'error': f'未知错误: {str(e)}'
}), 500
finally:
close_db()