import 'dart:async'; import 'dart:convert'; import 'package:EasyDartModule/EasyDartModule.dart' as edm; import '../const/CommonVariables.dart'; import '../model/WebSocketMessage.dart'; import '../const/ServiceConstant.dart'; import '../util/requestWithLog.dart'; import '../enum/ReportStatus.dart'; class AreaService { Map>> bodyDataCache = {}; // 存储原始实时数据 mac -> list实时数据列表 Map>> processedDataCache = {}; // 存储处理过的数据 mac -> list处理数据列表 Map> userInfoMap = {}; // 存储用户信息 mac -> 用户信息 Map deviceUserMap = {}; // 设备与用户的映射 mac -> openId Map macProcess = {}; // 存储检测进度 mac -> 进度百分比 Map deviceStartTimeMap = {}; // mac -> 开始体验的时间戳(毫秒) Map deviceTimers = {}; // 设备超时定时器 mac -> Timer Map lastValidDataTime = {}; // 最后有效数据时间 mac -> timestamp Map lastBedStatusTime = {}; // 最后离床状态开始时间 mac -> timestamp // 新增:进度相关 Map _progressTimers = {}; // 进度更新定时器 Map _lastProgressTime = {}; // 最后更新进度的时间 // 新增:记录初始数据处理状态 Map _initialDataProcessed = {}; // 是否已处理过初始数据 // 新增:心率直线检测相关 Map>> _heartRateSequenceCache = {}; // 存储心率连续相同序列 Map _inStraightLineMode = {}; // 是否处于直线检测模式 Map> _lastStraightLineStart = {}; // 直线开始的第一个数据 // 新增:报告生成状态管理 Map _reportGeneratingMap = {}; // mac -> 是否正在生成报告 Map _reportGeneratedMap = {}; // mac -> 是否已生成报告 int dataLength = 300; // 目标有效数据长度 int initialDataLength = 180; // 初始需要的数据长度(3分钟) int noDataTimeout = 60 * 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 缓存过期时间(秒) Map exceptionCache = {}; AreaService() { // 从Redis恢复体验中的设备 _recoverExperiences(); // 启动离线检测定时器 _startOfflineCheckTimer(); } // 从Redis恢复体验中的设备 Future _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 _saveExperienceToRedis(String mac, Map 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 _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 _saveProcess100DataToRedis(String mac, String openId, String reportId, Map data) async { try { final key = "report_${openId}_${mac}"; final dataToSave = Map.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?> _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) { final openId = deviceUserMap[mac]; print("设备无数据超时: $mac"); _handleNoDataTimeout(mac); } }); // 检查离床状态超时 lastBedStatusTime.forEach((mac, offBedTime) { if (now - offBedTime > bedOffTimeout) { final openId = deviceUserMap[mac]; print("设备离床超时: $mac"); _handleBedOffTimeout(mac); } }); } catch (e) { print("检测离线异常: $e"); } } // 处理无数据超时 void _handleNoDataTimeout(String mac) async { final openId = deviceUserMap[mac]; if (openId != null) { addExceptionEnd(mac, openId!, "设备长时间无数据,请检查设备后重试"); 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) { addExceptionEnd(mac, openId!, "长时间离床,本次体验提前结束"); await updateDeviceConnectStatus( ReportStatus.reportExceptionClose.value, mac, remark: "离床超时"); await _endExperience( mac, openId, "离床超时", ReportStatus.reportExceptionClose.value); } } // 开始快检体验 Future> startKuaijian(Map 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 _processUserInfo(Map 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> endKuaijian(Map 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 _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 _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 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 _sendReportRequest( Map data, String mac, String openId) async { try { // 处理 dataList 的数据转换 if (data['dataList'] != null && data['dataList'] is List) { List> originalList = List>.from(data['dataList']); // 将原始数据转换为新的格式 List> 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 _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 _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 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 _handleWebSocketData(dynamic receivedData) async { try { print("当前心率为: ${receivedData['heartRate']}"); print("当前有效数据为: ${processedDataCache['${receivedData['mac']}']?.length}"); if (receivedData is! Map) return; // 将Map转换为Map final Map 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) { addExceptionEnd(mac, openId!, "当前设备已离线,本次体验提前结束"); 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 data) { if (data["inBed"] == "离床") { // 记录离床开始时间 if (!lastBedStatusTime.containsKey(mac)) { lastBedStatusTime[mac] = data['timestamp']; } } else { // 清除离床计时 lastBedStatusTime.remove(mac); } } // 存储原始实时数据 void storeInstantData(Map data) { try { String mac = data['mac']; // 如果不存在该 mac,则先创建一个空 list bodyDataCache.putIfAbsent(mac, () => []); // 往对应的 list 中添加数据 bodyDataCache[mac]!.add(Map.from(data)); // 限制数据长度(保留足够的数据用于处理) if (bodyDataCache[mac]!.length > dataLength * 2) { bodyDataCache[mac]!.removeAt(0); } } catch (e) { print("storeInstantData异常: $e"); } } Future _processDataWithHeartRateDetection( String mac, Map 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> sequenceCache, List> 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%"); addExceptionEnd(mac, openId, "心率数据不稳定,请请保持平静重新测试"); 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> 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.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'] = ''; } String? info; if (flag != 1) { info = fillExceptionInfo(mac, openId); // 如果info不为空,用info覆盖msg if (info != null && info.isNotEmpty) { msg = info; } } 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, data: {"mac": mac}))); } catch (e) { print("停止监听设备数据失败: $e"); } } // 获取所有正在体验的设备列表 List> 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 rpcStart(Map 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 requestSleepAnalytics( Map 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.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.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 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 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 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 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; } } void addExceptionEnd(String mac, String openId, String message) { exceptionCache[mac + openId] = message; } String? fillExceptionInfo(String mac, String openId) { String key = "$mac$openId"; // 创建缓存key String? info = exceptionCache[key]; // 获取异常信息 if (info != null && info.isNotEmpty) { // 获取后立即删除,确保只返回一次 exceptionCache.remove(key); print("获取异常信息后删除缓存: key=$key, info=$info"); } return info; } }