1693 lines
56 KiB
Dart
1693 lines
56 KiB
Dart
import 'dart:async';
|
||
import 'dart:convert';
|
||
// import 'dart:math';
|
||
|
||
import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
||
|
||
import '../const/CommonVariables.dart';
|
||
import '../model/WebSocketMessage.dart';
|
||
import '../repository/AreaRepository.dart';
|
||
// import 'package:http/http.dart' as http;
|
||
|
||
import '../const/ServiceConstant.dart';
|
||
import '../util/requestWithLog.dart';
|
||
import '../enum/ReportStatus.dart';
|
||
|
||
class AreaService {
|
||
final AreaRepository areaRepository = AreaRepository();
|
||
|
||
Map<String, List<Map<String, dynamic>>> bodyDataCache =
|
||
{}; // 存储原始实时数据 mac -> list实时数据列表
|
||
Map<String, List<Map<String, dynamic>>> processedDataCache =
|
||
{}; // 存储处理过的数据 mac -> list处理数据列表
|
||
Map<String, Map<String, dynamic>> userInfoMap = {}; // 存储用户信息 mac -> 用户信息
|
||
Map<String, String> deviceUserMap = {}; // 设备与用户的映射 mac -> openId
|
||
Map<String, int> macProcess = {}; // 存储检测进度 mac -> 进度百分比
|
||
Map<String, int> deviceStartTimeMap = {}; // mac -> 开始体验的时间戳(毫秒)
|
||
Map<String, Timer?> deviceTimers = {}; // 设备超时定时器 mac -> Timer
|
||
Map<String, int> lastValidDataTime = {}; // 最后有效数据时间 mac -> timestamp
|
||
Map<String, int> lastBedStatusTime = {}; // 最后离床状态开始时间 mac -> timestamp
|
||
|
||
// 新增:进度相关
|
||
Map<String, Timer?> _progressTimers = {}; // 进度更新定时器
|
||
Map<String, int> _lastProgressTime = {}; // 最后更新进度的时间
|
||
|
||
// 新增:记录初始数据处理状态
|
||
Map<String, bool> _initialDataProcessed = {}; // 是否已处理过初始数据
|
||
|
||
// 新增:心率直线检测相关
|
||
Map<String, List<Map<String, dynamic>>> _heartRateSequenceCache =
|
||
{}; // 存储心率连续相同序列
|
||
Map<String, bool> _inStraightLineMode = {}; // 是否处于直线检测模式
|
||
Map<String, Map<String, dynamic>> _lastStraightLineStart = {}; // 直线开始的第一个数据
|
||
|
||
// 新增:报告生成状态管理
|
||
Map<String, bool> _reportGeneratingMap = {}; // mac -> 是否正在生成报告
|
||
Map<String, bool> _reportGeneratedMap = {}; // mac -> 是否已生成报告
|
||
|
||
int dataLength = 300; // 目标有效数据长度
|
||
int initialDataLength = 180; // 初始需要的数据长度(3分钟)
|
||
int noDataTimeout = 20 * 1000; // 20秒无数据超时
|
||
int bedOffTimeout = 20 * 1000; // 20秒离床超时
|
||
int totalTimeout = 20 * 60 * 1000; // 20分钟总超时
|
||
int totalHeartPercent = 80; // 心率有效占比
|
||
int standardThreshold = 5; // 允许跳动范围
|
||
int heartStillTime = 10; // 心率直线判断阈值
|
||
|
||
Timer? _offlineCheckTimer;
|
||
bool _isTimerRunning = false;
|
||
|
||
// Redis 相关常量
|
||
final int _redisExpiryTime = 120; // Redis 缓存过期时间(秒)
|
||
|
||
AreaService() {
|
||
// 从Redis恢复体验中的设备
|
||
_recoverExperiences();
|
||
// 启动离线检测定时器
|
||
_startOfflineCheckTimer();
|
||
}
|
||
|
||
// 从Redis恢复体验中的设备
|
||
Future<void> _recoverExperiences() async {
|
||
try {
|
||
// 使用keys方法获取所有体验中的设备
|
||
final keys = await edm.EasyDartModule.redis.scanKeys("experience_*");
|
||
|
||
for (var key in keys) {
|
||
try {
|
||
final experienceData = await edm.EasyDartModule.redis.get(key);
|
||
if (experienceData != null) {
|
||
final data = jsonDecode(experienceData);
|
||
final mac = data['mac'];
|
||
final openId = data['openId'];
|
||
final startTime = data['startTime'];
|
||
final now = DateTime.now().millisecondsSinceEpoch;
|
||
|
||
// 如果体验时间超过20分钟,结束体验
|
||
if (now - startTime > totalTimeout) {
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "超时自动结束");
|
||
await _callRemoteEndExperience(mac, openId, "超时自动结束",
|
||
ReportStatus.reportExceptionClose.value);
|
||
await edm.EasyDartModule.redis.delete(key);
|
||
} else {
|
||
// 恢复设备体验状态
|
||
deviceStartTimeMap[mac] = startTime;
|
||
deviceUserMap[mac] = openId;
|
||
|
||
// 恢复用户信息
|
||
final userInfoKey = "user_info_$mac";
|
||
final userInfoData =
|
||
await edm.EasyDartModule.redis.get(userInfoKey);
|
||
if (userInfoData != null) {
|
||
userInfoMap[mac] = jsonDecode(userInfoData);
|
||
}
|
||
|
||
// 恢复初始数据处理状态
|
||
_initialDataProcessed[mac] = false;
|
||
|
||
// 初始化心率序列缓存
|
||
_heartRateSequenceCache[mac] = [];
|
||
_inStraightLineMode[mac] = false;
|
||
|
||
// 初始化报告状态
|
||
_reportGeneratingMap[mac] = false;
|
||
_reportGeneratedMap[mac] = false;
|
||
|
||
// 启动设备超时定时器
|
||
_startDeviceTimeoutTimer(mac);
|
||
|
||
// 恢复进度定时器
|
||
_startProgressTimer(mac);
|
||
|
||
print("从Redis恢复设备体验: $mac, openId: $openId");
|
||
}
|
||
}
|
||
} catch (e) {
|
||
print("恢复设备体验数据失败: $e");
|
||
}
|
||
}
|
||
} catch (e) {
|
||
print("从Redis恢复体验失败: $e");
|
||
}
|
||
}
|
||
|
||
// 保存体验信息到Redis
|
||
Future<void> _saveExperienceToRedis(String mac, Map<String, dynamic> userInfo,
|
||
String openId, int startTime) async {
|
||
try {
|
||
final key = "experience_$mac";
|
||
final data = {
|
||
'mac': mac,
|
||
'openId': openId,
|
||
'startTime': startTime,
|
||
'saveTime': DateTime.now().millisecondsSinceEpoch,
|
||
};
|
||
|
||
// 保存体验信息
|
||
await edm.EasyDartModule.redis.set(key, jsonEncode(data));
|
||
|
||
// 保存用户信息
|
||
final userInfoKey = "user_info_$mac";
|
||
await edm.EasyDartModule.redis.set(userInfoKey, jsonEncode(userInfo));
|
||
|
||
print("保存体验信息到Redis: $mac, openId: $openId");
|
||
} catch (e) {
|
||
print("保存体验信息到Redis失败: $e");
|
||
}
|
||
}
|
||
|
||
// 从Redis删除体验信息
|
||
Future<void> _removeExperienceFromRedis(String mac) async {
|
||
try {
|
||
await edm.EasyDartModule.redis.delete("experience_$mac");
|
||
await edm.EasyDartModule.redis.delete("user_info_$mac");
|
||
print("从Redis删除体验信息: $mac");
|
||
} catch (e) {
|
||
print("从Redis删除体验信息失败: $e");
|
||
}
|
||
}
|
||
|
||
// 保存process为100的数据到Redis
|
||
Future<void> _saveProcess100DataToRedis(String mac, String openId,
|
||
String reportId, Map<String, dynamic> data) async {
|
||
try {
|
||
final key = "report_${openId}_${mac}";
|
||
final dataToSave = Map<String, dynamic>.from(data);
|
||
dataToSave['reportId'] = reportId;
|
||
|
||
await edm.EasyDartModule.redis.setWithExpiry(
|
||
key,
|
||
jsonEncode(dataToSave),
|
||
_redisExpiryTime,
|
||
);
|
||
|
||
print("保存process为100的数据到Redis: key=$key, reportId=$reportId");
|
||
} catch (e) {
|
||
print("保存process为100的数据到Redis失败: $e");
|
||
}
|
||
}
|
||
|
||
// 从Redis读取process为100的数据
|
||
Future<Map<String, dynamic>?> _getProcess100DataFromRedis(
|
||
String mac, String openId) async {
|
||
try {
|
||
final key = "report_${openId}_${mac}";
|
||
final data = await edm.EasyDartModule.redis.get(key);
|
||
|
||
if (data != null) {
|
||
print("从Redis读取process为100的数据: key=$key");
|
||
return jsonDecode(data);
|
||
}
|
||
|
||
return null;
|
||
} catch (e) {
|
||
print("从Redis读取process为100的数据失败: $e");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 启动设备超时定时器
|
||
void _startDeviceTimeoutTimer(String mac) {
|
||
// 先取消现有的定时器
|
||
_stopDeviceTimeoutTimer(mac);
|
||
|
||
// 启动新的定时器
|
||
deviceTimers[mac] = Timer(Duration(milliseconds: totalTimeout), () {
|
||
_handleDeviceTimeout(mac);
|
||
});
|
||
}
|
||
|
||
// 停止设备超时定时器
|
||
void _stopDeviceTimeoutTimer(String mac) {
|
||
if (deviceTimers.containsKey(mac) && deviceTimers[mac] != null) {
|
||
deviceTimers[mac]!.cancel();
|
||
deviceTimers[mac] = null;
|
||
}
|
||
}
|
||
|
||
// 处理设备超时
|
||
void _handleDeviceTimeout(String mac) async {
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
print("设备超时: $mac, openId: $openId");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "体验超时");
|
||
await _endExperience(
|
||
mac, openId, "体验超时", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
}
|
||
|
||
// 启动进度更新定时器
|
||
void _startProgressTimer(String mac) {
|
||
// 先取消现有的定时器
|
||
_stopProgressTimer(mac);
|
||
|
||
// 每秒更新一次进度
|
||
_progressTimers[mac] = Timer.periodic(Duration(seconds: 1), (timer) {
|
||
_updateProgress(mac);
|
||
});
|
||
|
||
_lastProgressTime[mac] = DateTime.now().millisecondsSinceEpoch;
|
||
}
|
||
|
||
// 停止进度更新定时器
|
||
void _stopProgressTimer(String mac) {
|
||
if (_progressTimers.containsKey(mac) && _progressTimers[mac] != null) {
|
||
_progressTimers[mac]!.cancel();
|
||
_progressTimers[mac] = null;
|
||
}
|
||
_lastProgressTime.remove(mac);
|
||
}
|
||
|
||
// 更新进度
|
||
void _updateProgress(String mac) {
|
||
try {
|
||
// 如果设备不在体验中,停止定时器
|
||
if (!checkUsing(mac)) {
|
||
_stopProgressTimer(mac);
|
||
return;
|
||
}
|
||
|
||
final startTime = deviceStartTimeMap[mac];
|
||
if (startTime == null) return;
|
||
|
||
final now = DateTime.now().millisecondsSinceEpoch;
|
||
final duration = now - startTime;
|
||
|
||
// 计算基于时间的进度(20分钟 = 1200秒)
|
||
int timeBasedProgress = (duration / (20 * 60 * 1000) * 100).toInt();
|
||
|
||
// 计算基于数据的进度
|
||
final processedCount = processedDataCache[mac]?.length ?? 0;
|
||
int dataBasedProgress = (processedCount / dataLength * 100).toInt();
|
||
|
||
// 综合进度策略:
|
||
// 1. 前期(前2分钟)以时间进度为主
|
||
// 2. 中期(2-5分钟)时间和数据进度结合
|
||
// 3. 后期(5分钟后)以数据进度为主
|
||
|
||
int finalProgress;
|
||
|
||
if (duration < 2 * 60 * 1000) {
|
||
// 前2分钟
|
||
// 80%时间进度 + 20%数据进度
|
||
finalProgress =
|
||
(timeBasedProgress * 0.8 + dataBasedProgress * 0.2).toInt();
|
||
} else if (duration < 5 * 60 * 1000) {
|
||
// 2-5分钟
|
||
// 50%时间进度 + 50%数据进度
|
||
finalProgress =
|
||
(timeBasedProgress * 0.5 + dataBasedProgress * 0.5).toInt();
|
||
} else {
|
||
// 5分钟后
|
||
// 20%时间进度 + 80%数据进度
|
||
finalProgress =
|
||
(timeBasedProgress * 0.2 + dataBasedProgress * 0.8).toInt();
|
||
}
|
||
|
||
// 确保进度不会减少(用户体验角度)
|
||
final int currentProgress = macProcess[mac] ?? 0;
|
||
if (finalProgress > currentProgress) {
|
||
macProcess[mac] = finalProgress.clamp(0, 100);
|
||
} else if (duration > 30 * 1000) {
|
||
// 30秒后允许轻微下降,避免卡住
|
||
// 如果有数据但进度不增长,缓慢增加时间进度
|
||
if (processedCount > 0 && currentProgress < 10) {
|
||
macProcess[mac] = (currentProgress + 1).clamp(0, 100);
|
||
}
|
||
}
|
||
|
||
// 如果报告已生成或正在生成,进度设为100%
|
||
if ((_reportGeneratingMap[mac] ?? false) ||
|
||
(_reportGeneratedMap[mac] ?? false)) {
|
||
macProcess[mac] = 100;
|
||
}
|
||
|
||
// 记录最后更新时间
|
||
_lastProgressTime[mac] = now;
|
||
} catch (e) {
|
||
print("更新进度异常: $e, MAC=$mac");
|
||
}
|
||
}
|
||
|
||
// 析构函数,清理定时器
|
||
void dispose() {
|
||
_stopOfflineCheckTimer();
|
||
|
||
// 清理所有设备定时器
|
||
deviceTimers.forEach((mac, timer) {
|
||
if (timer != null) {
|
||
timer.cancel();
|
||
}
|
||
});
|
||
deviceTimers.clear();
|
||
|
||
// 清理所有进度定时器
|
||
_progressTimers.forEach((mac, timer) {
|
||
if (timer != null) {
|
||
timer.cancel();
|
||
}
|
||
});
|
||
_progressTimers.clear();
|
||
|
||
// 清理心率序列缓存
|
||
_heartRateSequenceCache.clear();
|
||
_inStraightLineMode.clear();
|
||
_lastStraightLineStart.clear();
|
||
|
||
// 清理报告状态
|
||
_reportGeneratingMap.clear();
|
||
_reportGeneratedMap.clear();
|
||
}
|
||
|
||
// 开始离线检测定时器
|
||
void _startOfflineCheckTimer() {
|
||
if (_isTimerRunning) return;
|
||
|
||
_isTimerRunning = true;
|
||
_offlineCheckTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||
_checkDeviceOfflineStatus();
|
||
});
|
||
|
||
edm.EasyDartModule.logger.info("离线检测定时器已启动");
|
||
}
|
||
|
||
// 停止离线检测定时器
|
||
void _stopOfflineCheckTimer() {
|
||
if (_offlineCheckTimer != null) {
|
||
_offlineCheckTimer!.cancel();
|
||
_offlineCheckTimer = null;
|
||
}
|
||
_isTimerRunning = false;
|
||
edm.EasyDartModule.logger.info("离线检测定时器已停止");
|
||
}
|
||
|
||
// 检查设备离线状态
|
||
void _checkDeviceOfflineStatus() {
|
||
try {
|
||
final now = DateTime.now().millisecondsSinceEpoch;
|
||
|
||
// 检查无数据超时
|
||
lastValidDataTime.forEach((mac, lastTime) {
|
||
if (now - lastTime > noDataTimeout) {
|
||
print("设备无数据超时: $mac");
|
||
_handleNoDataTimeout(mac);
|
||
}
|
||
});
|
||
|
||
// 检查离床状态超时
|
||
lastBedStatusTime.forEach((mac, offBedTime) {
|
||
if (now - offBedTime > bedOffTimeout) {
|
||
print("设备离床超时: $mac");
|
||
_handleBedOffTimeout(mac);
|
||
}
|
||
});
|
||
} catch (e) {
|
||
print("检测离线异常: $e");
|
||
}
|
||
}
|
||
|
||
// 处理无数据超时
|
||
void _handleNoDataTimeout(String mac) async {
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "无数据超时");
|
||
await _endExperience(
|
||
mac, openId, "无数据超时", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
}
|
||
|
||
// 处理离床超时
|
||
void _handleBedOffTimeout(String mac) async {
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "离床超时");
|
||
await _endExperience(
|
||
mac, openId, "离床超时", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
}
|
||
|
||
// 开始快检体验
|
||
Future<Map<String, dynamic>> startKuaijian(Map<String, dynamic> data) async {
|
||
String msg = "";
|
||
int flag = 1;
|
||
|
||
try {
|
||
String mac = _normalizeMac(data['mac']);
|
||
String openId = data['openId']?.toString() ?? '';
|
||
|
||
if (openId.isEmpty) {
|
||
return {"msg": "openId不能为空", "flag": 0};
|
||
}
|
||
|
||
bool haveRun = await checkHaveRun(data);
|
||
if (haveRun) {
|
||
print(
|
||
"[调试][${DateTime.now().toIso8601String()}]: 已经在运行,跳过 ${data['mac']}");
|
||
msg = "该设备正在体验中";
|
||
flag = 0;
|
||
return {"msg": msg, "flag": flag};
|
||
}
|
||
|
||
// 检查该mac是否正在体验中
|
||
bool using = checkUsing(mac);
|
||
if (using) {
|
||
msg = "该设备正在体验中";
|
||
flag = 0;
|
||
return {"msg": msg, "flag": flag};
|
||
}
|
||
|
||
bool state = await checkDeviceState(mac);
|
||
if (!state) {
|
||
msg = "设备离线";
|
||
flag = 0;
|
||
return {"msg": msg, "flag": flag};
|
||
}
|
||
|
||
// 提取并处理用户信息(设置默认值)
|
||
final userInfo = _processUserInfo(data);
|
||
|
||
// 2. 记录开始体验时间
|
||
final startTime = DateTime.now().millisecondsSinceEpoch;
|
||
deviceStartTimeMap[mac] = startTime;
|
||
deviceUserMap[mac] = openId;
|
||
userInfoMap[mac] = userInfo;
|
||
lastValidDataTime[mac] = startTime; // 初始化最后有效数据时间
|
||
_initialDataProcessed[mac] = false; // 初始化初始数据处理标志
|
||
|
||
// 3. 初始化心率序列相关缓存
|
||
_heartRateSequenceCache[mac] = [];
|
||
_inStraightLineMode[mac] = false;
|
||
|
||
// 4. 初始化报告状态
|
||
_reportGeneratingMap[mac] = false;
|
||
_reportGeneratedMap[mac] = false;
|
||
|
||
// 5. 保存到Redis
|
||
await _saveExperienceToRedis(mac, userInfo, openId, startTime);
|
||
|
||
// 6. 启动设备超时定时器
|
||
_startDeviceTimeoutTimer(mac);
|
||
|
||
// 7. 启动进度定时器
|
||
_startProgressTimer(mac);
|
||
|
||
edm.EasyDartModule.logger
|
||
.info("记录设备开始体验时间: $mac, openId: $openId, 时间: $startTime");
|
||
|
||
// 8. 调用服务号后端通知开始体验
|
||
await rpcStartKuaijian(data);
|
||
|
||
// 9. 开始记录实时数据
|
||
startGetBodyData(data);
|
||
|
||
//10.更新远程api
|
||
await rpcStart(data);
|
||
} catch (e) {
|
||
print("申请体验报告失败: $e");
|
||
return {"msg": "系统异常", "flag": 0};
|
||
}
|
||
|
||
return {"msg": "开始体验成功", "flag": flag};
|
||
}
|
||
|
||
// 处理用户信息,设置默认值
|
||
Map<String, dynamic> _processUserInfo(Map<String, dynamic> data) {
|
||
// 设置默认值
|
||
final defaultValues = {
|
||
'username': '体验用户',
|
||
'age': 30,
|
||
'height': 170,
|
||
'weight': 60,
|
||
'gender': 1, // 1:男, 2:女
|
||
'phoneNo': '未知',
|
||
'deviceNo': data['mac'] ?? '',
|
||
};
|
||
|
||
// 处理用户信息,空值或无效值使用默认值
|
||
final userInfo = {
|
||
'username':
|
||
_getValueOrDefault(data['username'], defaultValues['username']),
|
||
'age': _getIntValueOrDefault(data['age'], defaultValues['age']),
|
||
'height': _getIntValueOrDefault(data['height'], defaultValues['height']),
|
||
'weight': _getIntValueOrDefault(data['weight'], defaultValues['weight']),
|
||
'gender': _getIntValueOrDefault(data['gender'], defaultValues['gender']),
|
||
'phoneNo': _getValueOrDefault(data['phoneNo'], defaultValues['phoneNo']),
|
||
'deviceNo': _getValueOrDefault(
|
||
data['deviceNo'] ?? data['mac'], defaultValues['deviceNo']),
|
||
};
|
||
|
||
// 验证年龄、身高、体重范围 - 修复类型转换问题
|
||
userInfo['age'] = _validateRange(
|
||
userInfo['age'] as int, 1, 120, defaultValues['age'] as int);
|
||
|
||
userInfo['height'] = _validateRange(
|
||
userInfo['height'] as int, 50, 250, defaultValues['height'] as int);
|
||
|
||
userInfo['weight'] = _validateRange(
|
||
userInfo['weight'] as int, 10, 300, defaultValues['weight'] as int);
|
||
|
||
userInfo['gender'] = _validateRange(
|
||
userInfo['gender'] as int, 1, 2, defaultValues['gender'] as int);
|
||
|
||
print("处理后的用户信息: $userInfo");
|
||
return userInfo;
|
||
}
|
||
|
||
// 获取字符串值或默认值
|
||
String _getValueOrDefault(dynamic value, String defaultValue) {
|
||
if (value == null) return defaultValue;
|
||
|
||
final strValue = value.toString().trim();
|
||
if (strValue.isEmpty) return defaultValue;
|
||
|
||
return strValue;
|
||
}
|
||
|
||
// 获取整数值或默认值
|
||
int _getIntValueOrDefault(dynamic value, int defaultValue) {
|
||
if (value == null) return defaultValue;
|
||
|
||
try {
|
||
if (value is String) {
|
||
final trimmed = value.trim();
|
||
if (trimmed.isEmpty) return defaultValue;
|
||
return int.tryParse(trimmed) ?? defaultValue;
|
||
} else if (value is int) {
|
||
return value;
|
||
} else if (value is double) {
|
||
return value.toInt();
|
||
} else if (value is num) {
|
||
return value.toInt();
|
||
}
|
||
} catch (e) {
|
||
print("转换整数值失败: $value, $e");
|
||
}
|
||
|
||
return defaultValue;
|
||
}
|
||
|
||
// 验证数值范围
|
||
int _validateRange(int value, int min, int max, int defaultValue) {
|
||
if (value >= min && value <= max) {
|
||
return value;
|
||
}
|
||
return defaultValue;
|
||
}
|
||
|
||
// 结束快检体验
|
||
Future<Map<String, dynamic>> endKuaijian(Map<String, dynamic> data) async {
|
||
String msg = "";
|
||
int flag = 1;
|
||
|
||
try {
|
||
String mac = _normalizeMac(data['mac']);
|
||
String openId = data['openId']?.toString() ?? '';
|
||
|
||
if (openId.isEmpty) {
|
||
return {"msg": "openId不能为空", "flag": 0};
|
||
}
|
||
|
||
// 检查该设备是否在体验中
|
||
if (!checkUsing(mac)) {
|
||
return {"msg": "该设备未在体验中", "flag": 0};
|
||
}
|
||
|
||
// 检查是否是同一个用户
|
||
if (deviceUserMap[mac] != openId) {
|
||
return {"msg": "无权结束该设备的体验", "flag": 0};
|
||
}
|
||
|
||
// 检查是否已经在生成报告
|
||
if (_reportGeneratingMap[mac] ?? false) {
|
||
return {"msg": "报告正在生成中,请稍候", "flag": 0};
|
||
}
|
||
|
||
// 检查是否已经生成报告
|
||
if (_reportGeneratedMap[mac] ?? false) {
|
||
return {"msg": "报告已生成,请查看结果", "flag": 0};
|
||
}
|
||
|
||
// 获取当前有效数据量
|
||
final processedCount = processedDataCache[mac]?.length ?? 0;
|
||
|
||
if (processedCount >= dataLength) {
|
||
// 正常完成体验,生成报告
|
||
final reportId = await _requestSleepAnalytics(mac, openId);
|
||
if (reportId != null) {
|
||
msg = "体验完成,报告ID: $reportId";
|
||
await _callRemoteCompleteExperience(
|
||
mac, openId, "正常完成", reportId, ReportStatus.completed.value);
|
||
} else {
|
||
msg = "体验完成,但生成报告失败";
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "报告生成失败");
|
||
await _endExperience(
|
||
mac, openId, "报告生成失败", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
} else {
|
||
// 提前结束体验
|
||
await updateDeviceConnectStatus(ReportStatus.appNormalClose.value, mac,
|
||
remark: "用户主动结束");
|
||
await _endExperience(
|
||
mac, openId, "用户主动结束", ReportStatus.appNormalClose.value);
|
||
msg = "体验已提前结束";
|
||
}
|
||
} catch (e) {
|
||
print("结束体验失败: $e");
|
||
return {"msg": "系统异常", "flag": 0};
|
||
}
|
||
|
||
return {"msg": msg, "flag": flag};
|
||
}
|
||
|
||
// 结束体验的通用方法
|
||
Future<void> _endExperience(
|
||
String mac, String openId, String reason, int reportStatus,
|
||
{bool rpc = true}) async {
|
||
try {
|
||
print("结束体验: MAC=$mac, openId=$openId, 原因=$reason");
|
||
|
||
// 1. 调用远程结束接口
|
||
if (rpc) {
|
||
await _callRemoteEndExperience(mac, openId, reason, reportStatus);
|
||
}
|
||
|
||
// 2. 清理本地数据
|
||
deviceStartTimeMap.remove(mac);
|
||
deviceUserMap.remove(mac);
|
||
userInfoMap.remove(mac);
|
||
bodyDataCache.remove(mac);
|
||
processedDataCache.remove(mac);
|
||
macProcess.remove(mac);
|
||
lastValidDataTime.remove(mac);
|
||
lastBedStatusTime.remove(mac);
|
||
_initialDataProcessed.remove(mac);
|
||
|
||
// 3. 清理心率序列相关缓存
|
||
_heartRateSequenceCache.remove(mac);
|
||
_inStraightLineMode.remove(mac);
|
||
_lastStraightLineStart.remove(mac);
|
||
|
||
// 4. 清理报告状态
|
||
_reportGeneratingMap.remove(mac);
|
||
_reportGeneratedMap.remove(mac);
|
||
|
||
// 5. 停止定时器
|
||
_stopDeviceTimeoutTimer(mac);
|
||
_stopProgressTimer(mac);
|
||
|
||
// 6. 从Redis删除
|
||
await _removeExperienceFromRedis(mac);
|
||
|
||
// 7. 停止监听设备数据
|
||
_stopListeningDeviceData(mac);
|
||
|
||
// 8. 通知前端
|
||
_notifyExperienceEnd(mac, openId, reason);
|
||
|
||
edm.EasyDartModule.logger.info("体验已结束: $mac, 原因: $reason");
|
||
} catch (e) {
|
||
edm.EasyDartModule.logger.error("结束体验异常: $e, MAC=$mac");
|
||
}
|
||
}
|
||
|
||
// 请求睡眠分析报告
|
||
Future<String?> _requestSleepAnalytics(String mac, String openId) async {
|
||
try {
|
||
// 检查是否已经在生成报告
|
||
if (_reportGeneratingMap[mac] ?? false) {
|
||
print("报告已经在生成中,跳过重复请求: $mac");
|
||
return null;
|
||
}
|
||
|
||
// 检查是否已经生成报告
|
||
if (_reportGeneratedMap[mac] ?? false) {
|
||
print("报告已经生成,跳过重复请求: $mac");
|
||
return null;
|
||
}
|
||
|
||
final userInfo = userInfoMap[mac];
|
||
final processedData = processedDataCache[mac];
|
||
|
||
if (userInfo == null ||
|
||
processedData == null ||
|
||
processedData.length < dataLength) {
|
||
print("请求报告失败: 数据不足或用户信息缺失");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "数据不足或用户信息缺失");
|
||
return null;
|
||
}
|
||
|
||
// 设置报告正在生成中
|
||
_reportGeneratingMap[mac] = true;
|
||
print("开始生成报告: $mac, openId: $openId, 数据量: ${processedData.length}");
|
||
|
||
// 准备请求数据
|
||
final Map<String, dynamic> requestData = {
|
||
"username": userInfo['username'],
|
||
"age": userInfo['age'],
|
||
"height": userInfo['height'],
|
||
"weight": userInfo['weight'],
|
||
"gender": userInfo['gender'],
|
||
"deviceNo": userInfo['deviceNo'],
|
||
"phoneNo": userInfo['phoneNo'],
|
||
"dataList": processedData,
|
||
};
|
||
|
||
print(
|
||
"用户信息: ${userInfo['username']}, 年龄: ${userInfo['age']}, 电话: ${userInfo['phoneNo']}");
|
||
|
||
// 发送HTTP请求
|
||
final reportId = await _sendReportRequest(requestData, mac, openId);
|
||
|
||
// 报告生成完成
|
||
_reportGeneratingMap[mac] = false;
|
||
|
||
if (reportId != null) {
|
||
_reportGeneratedMap[mac] = true;
|
||
print("报告生成成功: $reportId");
|
||
return reportId;
|
||
} else {
|
||
print("报告生成失败");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "报告生成失败");
|
||
return null;
|
||
}
|
||
} catch (e) {
|
||
print("请求睡眠分析异常: $e");
|
||
_reportGeneratingMap[mac] = false;
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "请求睡眠分析异常: $e");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 发送报告请求
|
||
Future<String?> _sendReportRequest(
|
||
Map<String, dynamic> data, String mac, String openId) async {
|
||
try {
|
||
// 处理 dataList 的数据转换
|
||
if (data['dataList'] != null && data['dataList'] is List) {
|
||
List<Map<String, dynamic>> originalList =
|
||
List<Map<String, dynamic>>.from(data['dataList']);
|
||
|
||
// 将原始数据转换为新的格式
|
||
List<Map<String, dynamic>> transformedList = originalList.map((item) {
|
||
// 从原始数据中提取 heartRate 和 breathRate
|
||
// 如果字段不存在或为null,则使用默认值0
|
||
int heartRate = item['heartRate'] is int
|
||
? item['heartRate']
|
||
: (item['heartRate'] is double
|
||
? (item['heartRate'] as double).toInt()
|
||
: 0);
|
||
|
||
int breathRate = item['breathRate'] is int
|
||
? item['breathRate']
|
||
: (item['breathRate'] is double
|
||
? (item['breathRate'] as double).toInt()
|
||
: 0);
|
||
|
||
// 创建新的数据结构
|
||
return {
|
||
'breath': breathRate,
|
||
'heart': heartRate,
|
||
};
|
||
}).toList();
|
||
|
||
// 替换原始 dataList 为转换后的数据
|
||
data['dataList'] = transformedList;
|
||
}
|
||
|
||
// 打印转换后的数据用于调试
|
||
print('转换后的数据: ${jsonEncode(data)}');
|
||
|
||
await requestSleepAnalytics(data, mac, openId);
|
||
return null; // 由于是异步调用,这里返回null,实际结果在回调中处理
|
||
} catch (e) {
|
||
print("发送报告请求异常: $e");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "发送报告请求异常: $e");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 调用远程完成接口
|
||
Future<void> _callRemoteCompleteExperience(String mac, String openId,
|
||
String reason, String reportId, int reportStatus) async {
|
||
try {
|
||
// 调用您的远程完成接口
|
||
print("调用远程完成接口: $mac, openId: $openId, 原因: $reason, 报告ID: $reportId");
|
||
// 结束体验
|
||
await _endExperience(
|
||
mac, openId, "$reason, 报告ID: $reportId", reportStatus);
|
||
} catch (e) {
|
||
print("调用远程完成接口失败: $e");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "调用远程完成接口失败: $e");
|
||
}
|
||
}
|
||
|
||
// 调用远程结束接口
|
||
Future<void> _callRemoteEndExperience(
|
||
String mac, String openId, String reason, int reportStatus) async {
|
||
try {
|
||
// 调用您的远程完成接口
|
||
print("调用远程结束接口: $mac, openId: $openId, 原因: $reason");
|
||
// TODO: 实现具体的远程调用
|
||
await updateDeviceConnectStatus(reportStatus, mac, remark: reason);
|
||
} catch (e) {
|
||
print("调用远程结束接口失败: $e");
|
||
}
|
||
}
|
||
|
||
// 通知体验结束
|
||
void _notifyExperienceEnd(String mac, String openId, String reason) {
|
||
try {
|
||
edm.EasyDartModule.websocket.sendData(jsonEncode({
|
||
"type": "experience_end",
|
||
"mac": mac,
|
||
"openId": openId,
|
||
"reason": reason,
|
||
"timestamp": DateTime.now().millisecondsSinceEpoch,
|
||
"message": "体验已结束: $reason"
|
||
}));
|
||
} catch (e) {
|
||
print("发送体验结束通知失败: $e");
|
||
}
|
||
}
|
||
|
||
// 检查是否体验中
|
||
bool checkUsing(String mac) {
|
||
String normalizedMac = _normalizeMac(mac);
|
||
return deviceStartTimeMap.containsKey(normalizedMac);
|
||
}
|
||
|
||
// 远程通知分析开始
|
||
Future<void> rpcStartKuaijian(data) async {
|
||
// TODO: 实现远程开始接口
|
||
}
|
||
|
||
// 开始获取实时数据
|
||
void startGetBodyData(data) {
|
||
try {
|
||
String mac = _normalizeMac(data['mac']);
|
||
data['mac'] = mac; // 统一格式
|
||
|
||
// 监听 WebSocket 实时数据
|
||
CommonVariables.callMap["/vsbs/web/rt/marttress"] = (receivedData) {
|
||
_handleWebSocketData(receivedData);
|
||
};
|
||
|
||
print("开始监听体征数据");
|
||
edm.EasyDartModule.websocket.sendData(jsonEncode(WebSocketMessage(
|
||
path: "/vsbs/web/rt/marttress", type: 1, data: {"mac": mac})));
|
||
} catch (e) {
|
||
edm.EasyDartModule.logger.error("[websocket]获取体征数据异常: $e");
|
||
}
|
||
}
|
||
|
||
// 处理WebSocket数据
|
||
Future<void> _handleWebSocketData(dynamic receivedData) async {
|
||
try {
|
||
print("当前心率为: ${receivedData['heartRate']}");
|
||
print("当前有效数据为: ${processedDataCache['${receivedData['mac']}']?.length}");
|
||
if (receivedData is! Map) return;
|
||
|
||
// 将Map<dynamic, dynamic>转换为Map<String, dynamic>
|
||
final Map<String, dynamic> data = {};
|
||
receivedData.forEach((key, value) {
|
||
data[key.toString()] = value;
|
||
});
|
||
|
||
// 为数据添加时间戳
|
||
data['timestamp'] = DateTime.now().millisecondsSinceEpoch;
|
||
|
||
// 统一mac格式
|
||
if (data.containsKey('mac')) {
|
||
String mac = _normalizeMac(data['mac']);
|
||
data['mac'] = mac;
|
||
|
||
// 检查设备是否在体验中
|
||
if (!checkUsing(mac)) {
|
||
return; // 不在体验中的设备数据直接忽略
|
||
}
|
||
|
||
// 检查是否已经生成报告,如果已经生成则不再处理数据
|
||
if (_reportGeneratedMap[mac] ?? false) {
|
||
print("报告已生成,不再处理数据: $mac");
|
||
return;
|
||
}
|
||
|
||
// 更新最后有效数据时间
|
||
lastValidDataTime[mac] = data['timestamp'];
|
||
|
||
// 处理离床状态
|
||
_handleBedStatus(mac, data);
|
||
|
||
// 处理离线状态
|
||
if (data['status'] == "离线") {
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "设备离线");
|
||
_endExperience(
|
||
mac, openId, "设备离线", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 存储原始数据
|
||
storeInstantData(data);
|
||
|
||
// 处理数据(包含心率直线检测)
|
||
_processDataWithHeartRateDetection(mac, data);
|
||
|
||
edm.EasyDartModule.logger.info("[websocket]实时体征数据-->$data");
|
||
}
|
||
} catch (e) {
|
||
print("处理WebSocket数据异常: $e");
|
||
}
|
||
}
|
||
|
||
// 处理离床状态
|
||
void _handleBedStatus(String mac, Map<String, dynamic> data) {
|
||
if (data["inBed"] == "离床") {
|
||
// 记录离床开始时间
|
||
if (!lastBedStatusTime.containsKey(mac)) {
|
||
lastBedStatusTime[mac] = data['timestamp'];
|
||
}
|
||
} else {
|
||
// 清除离床计时
|
||
lastBedStatusTime.remove(mac);
|
||
}
|
||
}
|
||
|
||
// 存储原始实时数据
|
||
void storeInstantData(Map<String, dynamic> data) {
|
||
try {
|
||
String mac = data['mac'];
|
||
|
||
// 如果不存在该 mac,则先创建一个空 list
|
||
bodyDataCache.putIfAbsent(mac, () => []);
|
||
|
||
// 往对应的 list 中添加数据
|
||
bodyDataCache[mac]!.add(Map<String, dynamic>.from(data));
|
||
|
||
// 限制数据长度(保留足够的数据用于处理)
|
||
if (bodyDataCache[mac]!.length > dataLength * 2) {
|
||
bodyDataCache[mac]!.removeAt(0);
|
||
}
|
||
} catch (e) {
|
||
print("storeInstantData异常: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> _processDataWithHeartRateDetection(
|
||
String mac, Map<String, dynamic> data) async {
|
||
try {
|
||
// 检查是否已经生成报告,如果已经生成则不再处理数据
|
||
if (_reportGeneratedMap[mac] ?? false) {
|
||
return;
|
||
}
|
||
|
||
// 初始化处理数据缓存
|
||
processedDataCache.putIfAbsent(mac, () => []);
|
||
_heartRateSequenceCache.putIfAbsent(mac, () => []);
|
||
|
||
final processedList = processedDataCache[mac]!;
|
||
final sequenceCache = _heartRateSequenceCache[mac]!;
|
||
|
||
// 检查数据有效性
|
||
if (data['heartRate'] == -1 ||
|
||
data['heartRate'] == 0 ||
|
||
data['breathRate'] == -1 ||
|
||
data['breathRate'] == 0 ||
|
||
data['bodyMotion'] != 0 ||
|
||
data['inBed'] != '在床') {
|
||
// 无效数据,重置序列
|
||
if (sequenceCache.isNotEmpty) {
|
||
sequenceCache.clear();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 获取当前心率
|
||
int currentHeartRate = data['heartRate'] is int
|
||
? data['heartRate']
|
||
: (data['heartRate'] is double
|
||
? (data['heartRate'] as double).toInt()
|
||
: 0);
|
||
|
||
// === 修正的核心逻辑 ===
|
||
|
||
// 如果序列为空或当前心率与序列最后心率相同
|
||
if (sequenceCache.isEmpty ||
|
||
(sequenceCache.isNotEmpty &&
|
||
sequenceCache.last['heartRate'] == currentHeartRate)) {
|
||
// 添加到连续相同心率序列
|
||
sequenceCache.add({
|
||
'heartRate': currentHeartRate,
|
||
'data': data,
|
||
'timestamp': data['timestamp'],
|
||
});
|
||
} else {
|
||
// 心率发生变化 - 处理之前的序列
|
||
_processHeartRateSequence(mac, sequenceCache, processedList);
|
||
|
||
// 清空序列,开始新的序列
|
||
sequenceCache.clear();
|
||
sequenceCache.add({
|
||
'heartRate': currentHeartRate,
|
||
'data': data,
|
||
'timestamp': data['timestamp'],
|
||
});
|
||
}
|
||
|
||
// === 修正的核心逻辑结束 ===
|
||
|
||
// 处理初始数据(只执行一次)
|
||
if (processedList.length == initialDataLength &&
|
||
!_initialDataProcessed[mac]!) {
|
||
print("开始处理初始数据,数据量: ${processedList.length}");
|
||
_processInitialData(mac);
|
||
_initialDataProcessed[mac] = true;
|
||
}
|
||
|
||
// 检查是否达到目标数据量 - 修改为只触发一次
|
||
if (processedList.length >= dataLength &&
|
||
!(_reportGeneratingMap[mac] ?? false) &&
|
||
!(_reportGeneratedMap[mac] ?? false)) {
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
print("达到目标数据量,开始生成报告: $mac, 数据量: ${processedList.length}");
|
||
|
||
// 异步生成报告,避免阻塞数据处理
|
||
Future.delayed(Duration.zero, () {
|
||
_requestSleepAnalytics(mac, openId).then((reportId) {
|
||
if (reportId != null) {
|
||
_callRemoteCompleteExperience(mac, openId, "数据采集完成", reportId,
|
||
ReportStatus.completed.value);
|
||
} else {
|
||
// 报告生成失败,继续收集数据
|
||
print("报告生成失败,继续收集数据: $mac");
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
print("当前有效数据为: ${processedDataCache['$mac']?.length}");
|
||
} catch (e) {
|
||
print("处理数据异常: $e, MAC=$mac");
|
||
// 处理数据异常时更新状态
|
||
if (checkUsing(mac)) {
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "处理数据异常: $e");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 新增:处理心率序列的辅助方法
|
||
void _processHeartRateSequence(
|
||
String mac,
|
||
List<Map<String, dynamic>> sequenceCache,
|
||
List<Map<String, dynamic>> processedList) {
|
||
if (sequenceCache.isEmpty) return;
|
||
|
||
if (sequenceCache.length >= heartStillTime) {
|
||
// 连续相同心率达到或超过10次 - 只保留第一个和最后一个
|
||
print("连续相同心率达到${sequenceCache.length}次(>=${heartStillTime}),进行去重处理");
|
||
|
||
// 第一个数据点
|
||
final firstData = sequenceCache.first['data'];
|
||
final firstProcessedData = {
|
||
'mac': mac,
|
||
'heartRate': firstData['heartRate'],
|
||
'breathRate': firstData['breathRate'],
|
||
'timestamp': firstData['timestamp'],
|
||
'bodyMotion': firstData['bodyMotion'],
|
||
};
|
||
|
||
// 检查是否重复(与上一个处理的数据比较)
|
||
if (processedList.isEmpty ||
|
||
processedList.last['heartRate'] != firstProcessedData['heartRate'] ||
|
||
processedList.last['breathRate'] !=
|
||
firstProcessedData['breathRate']) {
|
||
processedList.add(firstProcessedData);
|
||
print("添加直线开始数据点: 心率 ${firstProcessedData['heartRate']}");
|
||
}
|
||
|
||
// 最后一个数据点
|
||
final lastData = sequenceCache.last['data'];
|
||
final lastProcessedData = {
|
||
'mac': mac,
|
||
'heartRate': lastData['heartRate'],
|
||
'breathRate': lastData['breathRate'],
|
||
'timestamp': lastData['timestamp'],
|
||
'bodyMotion': lastData['bodyMotion'],
|
||
};
|
||
|
||
// 检查是否重复(与上一个处理的数据比较)
|
||
if (processedList.isEmpty ||
|
||
processedList.last['heartRate'] != lastProcessedData['heartRate'] ||
|
||
processedList.last['breathRate'] != lastProcessedData['breathRate']) {
|
||
processedList.add(lastProcessedData);
|
||
print("添加直线结束数据点: 心率 ${lastProcessedData['heartRate']}");
|
||
}
|
||
} else {
|
||
// 连续相同心率少于10次 - 全部保留
|
||
print("连续相同心率${sequenceCache.length}次(<${heartStillTime}),全部保留");
|
||
|
||
for (var seqData in sequenceCache) {
|
||
final processedData = {
|
||
'mac': mac,
|
||
'heartRate': seqData['data']['heartRate'],
|
||
'breathRate': seqData['data']['breathRate'],
|
||
'timestamp': seqData['data']['timestamp'],
|
||
'bodyMotion': seqData['data']['bodyMotion'],
|
||
};
|
||
|
||
// 检查是否重复(与上一个处理的数据比较)
|
||
// 这里注意:对于连续相同的数据,我们不应该去重,所以去掉这个检查
|
||
processedList.add(processedData);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理初始数据(前3分钟)
|
||
void _processInitialData(String mac) async {
|
||
try {
|
||
final processedList = processedDataCache[mac]!;
|
||
|
||
// 1. 检查数据量是否足够
|
||
if (processedList.length < initialDataLength ~/ 2) {
|
||
// 数据量少于一半,结束体验
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
print("初始数据不足,结束体验: $mac, 数据量: ${processedList.length}");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "初始数据不足");
|
||
_endExperience(
|
||
mac, openId, "初始数据不足", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 2. 计算平均心率
|
||
int totalHeartRate = 0;
|
||
for (var data in processedList) {
|
||
final heartRate = data['heartRate'];
|
||
if (heartRate is int) {
|
||
totalHeartRate += heartRate;
|
||
} else if (heartRate is num) {
|
||
totalHeartRate += heartRate.toInt();
|
||
} else if (heartRate is String) {
|
||
totalHeartRate += int.tryParse(heartRate) ?? 0;
|
||
}
|
||
}
|
||
|
||
double avgHeartRate = totalHeartRate / processedList.length;
|
||
|
||
// 3. 计算在平均值±5范围内的数据百分比
|
||
int inRangeCount = 0;
|
||
for (var data in processedList) {
|
||
final heartRate = data['heartRate'];
|
||
double? hrValue;
|
||
|
||
if (heartRate is int) {
|
||
hrValue = heartRate.toDouble();
|
||
} else if (heartRate is num) {
|
||
hrValue = heartRate.toDouble();
|
||
} else if (heartRate is String) {
|
||
hrValue = double.tryParse(heartRate);
|
||
}
|
||
|
||
if (hrValue != null &&
|
||
hrValue >= avgHeartRate - standardThreshold &&
|
||
hrValue <= avgHeartRate + standardThreshold) {
|
||
inRangeCount++;
|
||
}
|
||
}
|
||
|
||
double inRangePercent = (inRangeCount / processedList.length) * 100;
|
||
|
||
// 4. 如果小于阈值,结束体验
|
||
if (inRangePercent < totalHeartPercent) {
|
||
final openId = deviceUserMap[mac];
|
||
if (openId != null) {
|
||
print(
|
||
"心率数据不稳定,结束体验: $mac, 平均心率: $avgHeartRate, 在范围比例: $inRangePercent%");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "心率数据不稳定");
|
||
_endExperience(
|
||
mac, openId, "心率数据不稳定", ReportStatus.reportExceptionClose.value);
|
||
}
|
||
return;
|
||
}
|
||
|
||
print("初始数据处理通过: $mac, 平均心率: $avgHeartRate, 在范围比例: $inRangePercent%");
|
||
} catch (e) {
|
||
print("处理初始数据异常: $e, MAC=$mac");
|
||
// 处理初始数据异常时更新状态
|
||
if (checkUsing(mac)) {
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "处理初始数据异常: $e");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取实时数据 - 修改后的版本
|
||
Future<Map<String, dynamic>> getInstantData(String mac, String openId) async {
|
||
String normalizedMac = _normalizeMac(mac);
|
||
String msg = "";
|
||
int flag = 1;
|
||
var data;
|
||
|
||
// 检查设备是否在体验中
|
||
if (!deviceStartTimeMap.containsKey(normalizedMac)) {
|
||
flag = -1;
|
||
msg = "该设备未开始体验";
|
||
data = null;
|
||
final redisData =
|
||
await _getProcess100DataFromRedis(normalizedMac, openId);
|
||
if (redisData != null) {
|
||
data = redisData;
|
||
flag = 1;
|
||
msg = "从Redis读取数据成功";
|
||
} else {
|
||
flag = 0;
|
||
msg = "该设备暂无数据";
|
||
data = null;
|
||
}
|
||
}
|
||
// 检查用户是否匹配
|
||
else if (deviceUserMap[normalizedMac] != openId) {
|
||
flag = -2;
|
||
msg = "无权查看该设备数据";
|
||
data = null;
|
||
}
|
||
// 检查是否有缓存数据
|
||
else if (!bodyDataCache.containsKey(normalizedMac) ||
|
||
bodyDataCache[normalizedMac]!.isEmpty) {
|
||
// 如果没有本地缓存数据,尝试从Redis读取
|
||
final redisData =
|
||
await _getProcess100DataFromRedis(normalizedMac, openId);
|
||
if (redisData != null) {
|
||
data = redisData;
|
||
flag = 1;
|
||
msg = "从Redis读取数据成功";
|
||
} else {
|
||
flag = 0;
|
||
msg = "该设备暂无数据";
|
||
data = null;
|
||
}
|
||
} else {
|
||
// 取最新一次数据
|
||
data = Map<String, dynamic>.from(bodyDataCache[normalizedMac]!.last);
|
||
|
||
// 添加进度信息
|
||
data['process'] = macProcess[normalizedMac] ?? 0;
|
||
data['openId'] = openId;
|
||
|
||
// 添加体验时长
|
||
final startTime = deviceStartTimeMap[normalizedMac]!;
|
||
final duration = DateTime.now().millisecondsSinceEpoch - startTime;
|
||
data['experienceDuration'] = duration ~/ 1000; // 转换为秒
|
||
|
||
// 添加有效数据量
|
||
final processedCount = processedDataCache[normalizedMac]?.length ?? 0;
|
||
data['processedDataCount'] = processedCount;
|
||
data['targetDataCount'] = dataLength;
|
||
|
||
// 添加报告状态
|
||
data['reportGenerating'] = _reportGeneratingMap[normalizedMac] ?? false;
|
||
data['reportGenerated'] = _reportGeneratedMap[normalizedMac] ?? false;
|
||
|
||
// 添加用户信息(可选)
|
||
data['userInfo'] = userInfoMap[normalizedMac];
|
||
|
||
// 添加空的reportId字段
|
||
data['reportId'] = '';
|
||
}
|
||
|
||
return {"msg": msg, "flag": flag, "data": data};
|
||
}
|
||
|
||
// 停止监听设备数据
|
||
void _stopListeningDeviceData(String mac) {
|
||
try {
|
||
edm.EasyDartModule.websocket.sendData(jsonEncode(
|
||
WebSocketMessage(path: "/vsbs/web/rt/marttress", type: 2)));
|
||
} catch (e) {
|
||
print("停止监听设备数据失败: $e");
|
||
}
|
||
}
|
||
|
||
// 获取所有正在体验的设备列表
|
||
List<Map<String, dynamic>> getActiveDevices() {
|
||
return deviceStartTimeMap.entries.map((entry) {
|
||
final mac = entry.key;
|
||
return {
|
||
'mac': mac,
|
||
'openId': deviceUserMap[mac],
|
||
'startTime': entry.value,
|
||
'progress': macProcess[mac] ?? 0,
|
||
'processedData': processedDataCache[mac]?.length ?? 0,
|
||
'reportGenerating': _reportGeneratingMap[mac] ?? false,
|
||
'reportGenerated': _reportGeneratedMap[mac] ?? false,
|
||
'userInfo': userInfoMap[mac],
|
||
};
|
||
}).toList();
|
||
}
|
||
|
||
// 获取设备体验时长
|
||
int? getDeviceExperienceDuration(String mac) {
|
||
String normalizedMac = _normalizeMac(mac);
|
||
if (deviceStartTimeMap.containsKey(normalizedMac)) {
|
||
final now = DateTime.now().millisecondsSinceEpoch;
|
||
return now - deviceStartTimeMap[normalizedMac]!;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 统一mac地址格式
|
||
String _normalizeMac(String mac) {
|
||
if (mac == null || mac.isEmpty) return '';
|
||
|
||
String normalized =
|
||
mac.toString().replaceAll(RegExp(r'[:-\s]'), '').toUpperCase().trim();
|
||
|
||
return normalized;
|
||
}
|
||
|
||
// 调试方法:打印当前状态
|
||
void printStatus() {
|
||
print("=== AreaService 状态 ===");
|
||
print("正在体验的设备:");
|
||
deviceStartTimeMap.forEach((mac, startTime) {
|
||
final openId = deviceUserMap[mac];
|
||
final progress = macProcess[mac] ?? 0;
|
||
final processedCount = processedDataCache[mac]?.length ?? 0;
|
||
final reportGenerating = _reportGeneratingMap[mac] ?? false;
|
||
final reportGenerated = _reportGeneratedMap[mac] ?? false;
|
||
final userInfo = userInfoMap[mac];
|
||
print(" $mac (用户: $openId) - 进度: $progress%, 有效数据: $processedCount");
|
||
print(" 报告状态: 生成中=$reportGenerating, 已生成=$reportGenerated");
|
||
print(
|
||
" 用户信息: ${userInfo?['username']}, 年龄: ${userInfo?['age']}, 电话: ${userInfo?['phoneNo']}");
|
||
});
|
||
print("======================");
|
||
}
|
||
|
||
checkHaveRun(data) async {
|
||
data['deviceNo'] = data['mac'];
|
||
String serviceAddress = ServiceConstant.service_address;
|
||
String serviceName = ServiceConstant.app_server_service;
|
||
String serviceApi = "/checkreport/${data['mac']}";
|
||
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||
bool flag = false;
|
||
await requestWithLog(
|
||
logTitle: "APP端提检查快检报告开始体验",
|
||
method: MyHttpMethod.get,
|
||
queryUrl: queryUrl,
|
||
data: data['data'],
|
||
onSuccess: (res) {
|
||
flag = res.data;
|
||
print("[调试][成功][${DateTime.now().toIso8601String()}]: 接收到数据结果为${flag}");
|
||
},
|
||
onFailure: (res) {
|
||
flag = true;
|
||
print("[调试][失败][${DateTime.now().toIso8601String()}]: 接收到数据结果为${flag}");
|
||
},
|
||
);
|
||
return flag;
|
||
}
|
||
|
||
Future<void> rpcStart(Map<String, dynamic> data) async {
|
||
try {
|
||
String serviceAddress = ServiceConstant.service_address;
|
||
String serviceName = ServiceConstant.app_server_service;
|
||
String serviceApi = "/reportsubmit";
|
||
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||
data['openid'] = data['openId'];
|
||
data['status'] = ReportStatus.experienceing.value;
|
||
await requestWithLog(
|
||
logTitle: "APP端提交快检报告开始体验",
|
||
method: MyHttpMethod.post,
|
||
queryUrl: queryUrl,
|
||
data: data,
|
||
onSuccess: (res) {
|
||
print("通知体验开始成功");
|
||
},
|
||
onFailure: (res) {
|
||
print("提交快检报告开始体验失败");
|
||
},
|
||
);
|
||
} catch (e) {
|
||
print("远程调用失败:$e");
|
||
}
|
||
}
|
||
|
||
Future<void> requestSleepAnalytics(
|
||
Map<String, dynamic> data, String mac, String openId) async {
|
||
try {
|
||
String queryUrl =
|
||
"https://health.ciotcp.com/api/api/h5/getReportByOtherData";
|
||
int retryCount = 0;
|
||
bool isSuccess = false;
|
||
String? reportId;
|
||
|
||
while (retryCount < 3 && !isSuccess) {
|
||
await requestWithLog(
|
||
logTitle: "请求实时分析",
|
||
method: MyHttpMethod.post,
|
||
data: data,
|
||
queryUrl: queryUrl,
|
||
onSuccess: (res) async {
|
||
if (res.data == null || res.data == "") {
|
||
retryCount++;
|
||
if (retryCount < 3) {
|
||
print("请求失败,正在尝试第${retryCount}次重试...");
|
||
} else {
|
||
print("请求失败,已尝试3次,停止重试");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "报告请求失败");
|
||
}
|
||
} else {
|
||
isSuccess = true;
|
||
reportId = res.data;
|
||
print("请求成功,报告ID: $reportId");
|
||
print(
|
||
"报告地址: https://health.ciotcp.com/mobile/quickDetail?type=quickCheck&useFrom=Quansi&reportId=$reportId");
|
||
|
||
// 保存process为100的数据到Redis
|
||
if (bodyDataCache.containsKey(mac) &&
|
||
bodyDataCache[mac]!.isNotEmpty) {
|
||
final lastData = bodyDataCache[mac]!.last;
|
||
final process100Data = Map<String, dynamic>.from(lastData);
|
||
process100Data['process'] = 100; // 确保process为100
|
||
await _saveProcess100DataToRedis(
|
||
mac, openId, reportId!, process100Data);
|
||
}
|
||
|
||
await finishDevice(reportId!, mac);
|
||
await _endExperience(
|
||
mac, openId, "体验结束", ReportStatus.reportExceptionClose.value,
|
||
rpc: false);
|
||
}
|
||
},
|
||
onFailure: (res) async {
|
||
if (res.code == 20000 && res.data != null) {
|
||
// 请求成功,通常不应该进入这里,但为了确保
|
||
isSuccess = true;
|
||
reportId = res.data;
|
||
print("请求成功,报告ID: $reportId");
|
||
|
||
// 保存process为100的数据到Redis
|
||
if (bodyDataCache.containsKey(mac) &&
|
||
bodyDataCache[mac]!.isNotEmpty) {
|
||
final lastData = bodyDataCache[mac]!.last;
|
||
final process100Data = Map<String, dynamic>.from(lastData);
|
||
process100Data['process'] = 100; // 确保process为100
|
||
await _saveProcess100DataToRedis(
|
||
mac, openId, reportId!, process100Data);
|
||
}
|
||
|
||
await finishDevice(reportId!, mac);
|
||
} else {
|
||
// 请求失败,重新尝试
|
||
retryCount++;
|
||
if (retryCount < 3) {
|
||
print("请求失败,正在尝试第${retryCount}次重试...");
|
||
} else {
|
||
print("请求失败,已尝试3次,停止重试");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "报告请求失败");
|
||
}
|
||
}
|
||
},
|
||
);
|
||
}
|
||
if (!isSuccess) {
|
||
print("最终请求失败,无法获取报告数据");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "最终请求失败,无法获取报告数据");
|
||
}
|
||
} catch (e) {
|
||
print("[请求报告失败]:报告异常:$e");
|
||
print("[微信]:报告异常:$e");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "请求报告失败异常: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> finishDevice(String reportId, String mac) async {
|
||
try {
|
||
// 从 deviceUserMap 中获取对应的 openid
|
||
String? openid = deviceUserMap[mac];
|
||
|
||
if (openid == null) {
|
||
print("无法找到设备 $mac 对应的用户信息");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "无法找到用户信息");
|
||
return;
|
||
}
|
||
|
||
String serviceAddress = ServiceConstant.service_address;
|
||
String serviceName = ServiceConstant.app_server_service;
|
||
String serviceApi = "/reportfinish";
|
||
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||
|
||
// 从 userInfoMap 中获取 deviceNo 或使用 mac
|
||
final userInfo = userInfoMap[mac];
|
||
String deviceNo = userInfo?['deviceNo'] ?? mac;
|
||
|
||
Map<String, dynamic> finishData = {
|
||
"deviceNo": deviceNo,
|
||
"openid": openid,
|
||
"reportId": reportId,
|
||
};
|
||
|
||
print("发送报告完成请求: deviceNo=$deviceNo, openid=$openid, reportId=$reportId");
|
||
|
||
await requestWithLog(
|
||
logTitle: "更新完成状态",
|
||
method: MyHttpMethod.post,
|
||
data: finishData,
|
||
queryUrl: queryUrl,
|
||
onSuccess: (res) {
|
||
print("[微信]:报告完成成功 $finishData");
|
||
},
|
||
onFailure: (res) {
|
||
print("[微信]:报告完成失败 $finishData");
|
||
// 报告完成失败时也要更新状态
|
||
updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "报告完成失败");
|
||
},
|
||
);
|
||
} catch (e) {
|
||
print("发送报告完成请求异常: $e");
|
||
await updateDeviceConnectStatus(
|
||
ReportStatus.reportExceptionClose.value, mac,
|
||
remark: "发送报告完成请求异常: $e");
|
||
}
|
||
}
|
||
|
||
//更新设备状态
|
||
Future<void> updateDeviceConnectStatus(int status, String mac,
|
||
{String? remark}) async {
|
||
try {
|
||
String serviceAddress = ServiceConstant.service_address;
|
||
String serviceName = ServiceConstant.app_server_service;
|
||
String serviceApi = "/reportupdatestatus";
|
||
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||
|
||
String? openid = deviceUserMap[mac];
|
||
|
||
if (openid == null) {
|
||
print("无法找到设备 $mac 对应的用户信息");
|
||
return;
|
||
}
|
||
// 初始化 data
|
||
Map<String, dynamic> data = {
|
||
"deviceNo": mac,
|
||
"openid": openid,
|
||
"status": status,
|
||
};
|
||
|
||
// 如果 remark 不为空,则添加到 data 中
|
||
if (remark != null && remark.isNotEmpty) {
|
||
data['remark'] = remark;
|
||
}
|
||
|
||
await requestWithLog(
|
||
logTitle: "更新设备处理状态",
|
||
method: MyHttpMethod.post,
|
||
data: data,
|
||
queryUrl: queryUrl,
|
||
onSuccess: (res) {
|
||
print("[微信]:更新状态成功为$data");
|
||
},
|
||
onFailure: (res) {
|
||
//更新失败
|
||
print("[微信]:更新状态失败,数据为$data");
|
||
},
|
||
);
|
||
} catch (e) {
|
||
print("[微信]:更新失状态败-》$e");
|
||
}
|
||
}
|
||
|
||
checkDeviceState(String mac) async {
|
||
try {
|
||
bool flag = false;
|
||
String queryUrl =
|
||
"https://iot.he-info.cn/api/iot/device/info/status?did=${mac}&token=M5Y3B4Frr6T1ACtwMfTNaSaxE2DT16Tn";
|
||
await requestWithLog(
|
||
logTitle: "查询设备状态",
|
||
method: MyHttpMethod.get,
|
||
queryUrl: queryUrl,
|
||
onSuccess: (res) {
|
||
flag = (res.data[0]['state']) == 3 || (res.data[0]['state']) == 4
|
||
? true
|
||
: false;
|
||
},
|
||
onFailure: (res) {
|
||
//更新失败
|
||
flag = (res.data[0]['state']) == 3 || (res.data[0]['state']) == 4
|
||
? true
|
||
: false;
|
||
},
|
||
);
|
||
return flag;
|
||
} catch (e) {
|
||
print("获取设备状态失败,数据为$e");
|
||
return false;
|
||
}
|
||
}
|
||
}
|