1441 lines
50 KiB
C#
1441 lines
50 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text;
|
||
using UnityEngine;
|
||
using GeneralTools;
|
||
|
||
public class DataService : MonoBehaviour, IDataService
|
||
{
|
||
public static DataService Instance { get; private set; }
|
||
|
||
public event Action<float> OnBFIValueUpdated;
|
||
// public event Action<float> OnBloodFlowValueUpdated;
|
||
// public event Action<float> OnBatteryLevelUpdated;
|
||
|
||
public float CurrentBFI { get; private set; }
|
||
public float CurrentBloodFlow { get; private set; }
|
||
public float CurrentBatteryLevel { get; private set; } = 80f; // 初始电量80%
|
||
|
||
public bool IsCollecting { get; private set; }
|
||
|
||
private Coroutine _dataCollectionCoroutine;
|
||
private Coroutine _batteryDrainCoroutine;
|
||
|
||
// 串口通信服务
|
||
private ISerialCommunicationService _serialCommunication;
|
||
private bool _useSerialCommunication = true; // 控制是否使用串口通信
|
||
private bool _enableSimulatedData = false; // 禁用模拟数据,改为使用真实串口数据
|
||
|
||
// BFI测试记录相关
|
||
private BFITestRecord _currentTestRecord;
|
||
private List<BFIDataPoint> _currentTestData;
|
||
private bool _isRecordingTest = false;
|
||
private const float MIN_VALID_BFI = 0f;
|
||
private const float MAX_VALID_BFI = 1000f;
|
||
|
||
// 自动记录相关
|
||
private DateTime _lastHeartbeatTime = DateTime.MinValue;
|
||
private const float HEARTBEAT_TIMEOUT_SECONDS = 5f; // 5秒没有心跳只标记断开,不切分记录
|
||
private static readonly TimeSpan MAX_RECORDING_DURATION = TimeSpan.FromHours(24);
|
||
private bool _hasReceivedHeartbeat = false;
|
||
private bool _autoRecordingEnabled = false;
|
||
private bool _heartbeatTimeoutLogged = false;
|
||
private Coroutine _heartbeatMonitorCoroutine;
|
||
private DateTime _lastCurrentRecordSnapshotTime = DateTime.MinValue;
|
||
private int _lastCurrentRecordSnapshotPointCount = 0;
|
||
private long _lastJournaledPointTicks = 0;
|
||
private float _lastJournaledPointBFI = float.NaN;
|
||
private static readonly TimeSpan CURRENT_RECORD_SNAPSHOT_INTERVAL = TimeSpan.FromMinutes(5);
|
||
private const string CURRENT_RECORDING_JOURNAL_FILE_NAME = "bfi_current_recording.jsonl";
|
||
private const string JOURNAL_ENTRY_START = "start";
|
||
private const string JOURNAL_ENTRY_POINT = "point";
|
||
private const string JOURNAL_ENTRY_PATIENT = "patient";
|
||
private const string JOURNAL_ENTRY_END = "end";
|
||
private string _currentRecordingJournalPath;
|
||
|
||
private void Awake()
|
||
{
|
||
Instance = this;
|
||
ServiceLocator.Register<IDataService>(this);
|
||
Debug.Log("DataService: 已注册到ServiceLocator");
|
||
}
|
||
|
||
void Start()
|
||
{
|
||
// 初始化安卓平台配置
|
||
InitializeAndroidConfig();
|
||
_currentRecordingJournalPath = ResolveCurrentRecordingJournalPath();
|
||
RecoverCurrentRecordingJournalIfNeeded("程序启动");
|
||
|
||
// 初始化串口通信服务
|
||
InitializeSerialCommunication();
|
||
|
||
// 订阅患者信息变更事件
|
||
var patientService = ServiceLocator.Get<IPatientInfoService>();
|
||
if (patientService != null)
|
||
{
|
||
patientService.OnPatientInfoChanged += OnPatientInfoChanged;
|
||
}
|
||
|
||
// 启动电池消耗协程
|
||
// _batteryDrainCoroutine = StartCoroutine(BatteryDrainCoroutine());
|
||
|
||
// 根据是否有串口通信决定数据采集方式
|
||
if (_enableSimulatedData && (!_useSerialCommunication || _serialCommunication == null))
|
||
{
|
||
// 只有明确启用模拟数据时才使用模拟数据采集
|
||
Debug.Log("DataService: 启用模拟数据采集");
|
||
StartDataCollection();
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("DataService: 等待真实串口数据,模拟数据已禁用");
|
||
}
|
||
|
||
// 启动心跳监控协程
|
||
_heartbeatMonitorCoroutine = StartCoroutine(MonitorHeartbeat());
|
||
}
|
||
|
||
public void StartDataCollection()
|
||
{
|
||
if (IsCollecting) return;
|
||
|
||
IsCollecting = true;
|
||
}
|
||
|
||
public void StopDataCollection()
|
||
{
|
||
if (!IsCollecting) return;
|
||
|
||
IsCollecting = false;
|
||
if (_dataCollectionCoroutine != null)
|
||
{
|
||
StopCoroutine(_dataCollectionCoroutine);
|
||
_dataCollectionCoroutine = null;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 初始化串口通信服务
|
||
/// </summary>
|
||
private void InitializeSerialCommunication()
|
||
{
|
||
try
|
||
{
|
||
_serialCommunication = ServiceLocator.Get<ISerialCommunicationService>();
|
||
if (_serialCommunication == null)
|
||
{
|
||
Debug.LogWarning("未找到串口通信服务,将使用模拟数据");
|
||
_useSerialCommunication = false;
|
||
return;
|
||
}
|
||
|
||
// 订阅串口通信事件
|
||
_serialCommunication.OnDeviceStatusReceived += OnDeviceStatusReceived;
|
||
// _serialCommunication.OnDeviceDataReceived += OnDeviceDataReceived;
|
||
// _serialCommunication.OnAlarmStatusReceived += OnAlarmStatusReceived;
|
||
_serialCommunication.OnCommunicationError += OnCommunicationError;
|
||
_serialCommunication.OnHandshakeCompleted += OnHandshakeCompleted;
|
||
|
||
// 从配置系统读取串口参数
|
||
string portName = GetPlatformSerialPortName(); //GetSerialPortName();
|
||
int baudRate = GetPlatformSerialBaudRate(); //GetSerialBaudRate();
|
||
|
||
Debug.Log($"尝试连接串口: {portName} @ {baudRate}");
|
||
|
||
// 尝试连接串口
|
||
if (!_serialCommunication.Connect(portName, baudRate))
|
||
{
|
||
Debug.LogWarning($"串口连接失败 ({portName}),将使用模拟数据");
|
||
_useSerialCommunication = false;
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("串口通信服务初始化成功");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"初始化串口通信服务失败: {ex.Message}");
|
||
_useSerialCommunication = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理设备状态数据(心跳报告)
|
||
/// 自动开始记录:收到第一个心跳就开始记录
|
||
/// </summary>
|
||
private void OnDeviceStatusReceived(DeviceStatusData status)
|
||
{
|
||
CurrentBFI = status.BFI;
|
||
CurrentBatteryLevel = status.BatteryLevel;
|
||
|
||
if (_autoRecordingEnabled && !_isRecordingTest)
|
||
{
|
||
AutoStartRecording();
|
||
}
|
||
|
||
// 更新心跳时间。心跳恢复时只继续向当前主界面会话记录写点,不新建记录。
|
||
_lastHeartbeatTime = DateTime.Now;
|
||
_hasReceivedHeartbeat = true;
|
||
_heartbeatTimeoutLogged = false;
|
||
|
||
OnBFIValueUpdated?.Invoke(CurrentBFI);
|
||
// OnBatteryLevelUpdated?.Invoke(CurrentBatteryLevel);
|
||
|
||
// 记录关键状态变化到文件日志
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
// 电量低于10%时记录关键信息
|
||
if (status.BatteryLevel <= 10 && status.PowerType == 1)
|
||
{
|
||
AndroidFileLogger.Instance?.LogError("DeviceStatus",
|
||
$"电量严重不足: {status.BatteryLevel}%, BFI: {status.BFI:F2}");
|
||
}
|
||
// BFI异常值时记录
|
||
else if (status.BFI <= 0 || status.BFI > 999 || float.IsNaN(status.BFI))
|
||
{
|
||
AndroidFileLogger.Instance?.LogError("DeviceStatus",
|
||
$"BFI值异常: {status.BFI}, 电量: {status.BatteryLevel}%");
|
||
}
|
||
// 正常情况下只偶尔记录
|
||
else if (Time.time % 60f < 1f) // 每分钟记录一次状态
|
||
{
|
||
AndroidFileLogger.Instance?.LogInfo("DeviceStatus",
|
||
$"设备正常: BFI={status.BFI:F2}, 电量={status.BatteryLevel}%");
|
||
}
|
||
#endif
|
||
|
||
// 只有在记录测试时才记录BFI数据,避免不必要的内存占用
|
||
if (_autoRecordingEnabled && _isRecordingTest)
|
||
{
|
||
RolloverRecordingIfNeeded("记录超过24小时");
|
||
RecordBFIData(status.BFI);
|
||
}
|
||
|
||
// 通过DCSAlarmManager检查设备状态
|
||
if (DCSAlarmManager.Instance != null)
|
||
{
|
||
DCSAlarmManager.Instance.CheckAndSendAlarms(status);
|
||
DCSAlarmManager.Instance.CheckBFIAlarm(CurrentBFI);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 处理通信错误
|
||
/// </summary>
|
||
private void OnCommunicationError()
|
||
{
|
||
// 触发通信异常报警(代码0x34)
|
||
DCSAlarmManager.Instance?.SendAlarmCommand(0x34, AlarmPriority.High, true);
|
||
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
AndroidFileLogger.Instance?.LogError("Communication",
|
||
$"串口通信异常,时间: {System.DateTime.Now:HH:mm:ss}");
|
||
#endif
|
||
|
||
// 通信错误时的处理(不再自动切换到模拟模式)
|
||
if (_useSerialCommunication)
|
||
{
|
||
Debug.LogWarning("通信异常,等待重新连接");
|
||
_useSerialCommunication = false;
|
||
|
||
// 只有明确启用模拟数据时才启动模拟采集
|
||
if (_enableSimulatedData)
|
||
{
|
||
Debug.Log("启用模拟数据作为备用");
|
||
StartDataCollection();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 握手完成处理
|
||
/// </summary>
|
||
private void OnHandshakeCompleted()
|
||
{
|
||
Debug.Log("设备握手完成,开始接收数据");
|
||
IsCollecting = true;
|
||
}
|
||
|
||
|
||
void OnDestroy()
|
||
{
|
||
// 停止数据采集
|
||
StopDataCollection();
|
||
|
||
// 如果有正在进行的BFI测试记录,保存数据
|
||
SaveCurrentRecordingSnapshotIfNeeded(true, "程序退出");
|
||
SaveCurrentRecordingIfNeeded("程序退出");
|
||
|
||
if (_batteryDrainCoroutine != null)
|
||
{
|
||
StopCoroutine(_batteryDrainCoroutine);
|
||
}
|
||
|
||
// 停止心跳监控协程
|
||
if (_heartbeatMonitorCoroutine != null)
|
||
{
|
||
StopCoroutine(_heartbeatMonitorCoroutine);
|
||
}
|
||
|
||
// 取消患者信息事件订阅
|
||
var patientService = ServiceLocator.Get<IPatientInfoService>();
|
||
if (patientService != null)
|
||
{
|
||
patientService.OnPatientInfoChanged -= OnPatientInfoChanged;
|
||
}
|
||
|
||
// 清理串口通信服务
|
||
if (_serialCommunication != null)
|
||
{
|
||
_serialCommunication.OnDeviceStatusReceived -= OnDeviceStatusReceived;
|
||
// _serialCommunication.OnDeviceDataReceived -= OnDeviceDataReceived;
|
||
// _serialCommunication.OnAlarmStatusReceived -= OnAlarmStatusReceived;
|
||
_serialCommunication.OnCommunicationError -= OnCommunicationError;
|
||
_serialCommunication.OnHandshakeCompleted -= OnHandshakeCompleted;
|
||
_serialCommunication.Disconnect();
|
||
}
|
||
}
|
||
|
||
private void OnApplicationPause(bool pauseStatus)
|
||
{
|
||
if (pauseStatus)
|
||
{
|
||
SaveCurrentRecordingSnapshotIfNeeded(true, "应用进入后台");
|
||
SaveCurrentRecordingIfNeeded("应用进入后台");
|
||
}
|
||
}
|
||
|
||
private void OnApplicationQuit()
|
||
{
|
||
SaveCurrentRecordingSnapshotIfNeeded(true, "应用退出");
|
||
SaveCurrentRecordingIfNeeded("应用退出");
|
||
}
|
||
|
||
public void SaveCurrentRecordingIfNeeded(string reason)
|
||
{
|
||
if (_isRecordingTest)
|
||
{
|
||
Debug.Log($"[{reason}] 检测到正在进行的BFI测试记录,自动保存数据");
|
||
StopBFITestRecording();
|
||
}
|
||
}
|
||
|
||
public void SetAutoBFIRecordingEnabled(bool enabled, string reason = "")
|
||
{
|
||
if (_autoRecordingEnabled == enabled)
|
||
{
|
||
if (enabled && !_isRecordingTest)
|
||
{
|
||
AutoStartRecording();
|
||
}
|
||
return;
|
||
}
|
||
|
||
_autoRecordingEnabled = enabled;
|
||
|
||
if (!enabled)
|
||
{
|
||
SaveCurrentRecordingIfNeeded(string.IsNullOrEmpty(reason) ? "关闭自动记录" : reason);
|
||
_hasReceivedHeartbeat = false;
|
||
_lastHeartbeatTime = DateTime.MinValue;
|
||
Debug.Log($"[自动记录] 已关闭自动BFI记录: {reason}");
|
||
}
|
||
else
|
||
{
|
||
_hasReceivedHeartbeat = false;
|
||
_lastHeartbeatTime = DateTime.MinValue;
|
||
_heartbeatTimeoutLogged = false;
|
||
if (!_isRecordingTest)
|
||
{
|
||
AutoStartRecording();
|
||
}
|
||
Debug.Log($"[自动记录] 已启用自动BFI记录: {reason}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化安卓平台配置
|
||
/// </summary>
|
||
private void InitializeAndroidConfig()
|
||
{
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
try
|
||
{
|
||
Debug.Log("开始初始化安卓配置...");
|
||
|
||
// 确保目标目录存在
|
||
var persistentPath = Application.persistentDataPath;
|
||
if (!Directory.Exists(persistentPath))
|
||
{
|
||
Directory.CreateDirectory(persistentPath);
|
||
}
|
||
|
||
// 复制安卓配置文件
|
||
var sourceConfigPath = Path.Combine(Application.streamingAssetsPath, "config_android.ini");
|
||
var targetConfigPath = Path.Combine(persistentPath, "config_android.ini");
|
||
|
||
// 如果目标文件不存在,则从StreamingAssets复制
|
||
if (!File.Exists(targetConfigPath))
|
||
{
|
||
if (File.Exists(sourceConfigPath))
|
||
{
|
||
File.Copy(sourceConfigPath, targetConfigPath, true);
|
||
Debug.Log($"安卓配置文件已复制到: {targetConfigPath}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("未找到安卓配置文件,创建默认配置");
|
||
CreateDefaultAndroidConfig(targetConfigPath);
|
||
}
|
||
}
|
||
|
||
Debug.Log("安卓配置初始化完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"安卓配置初始化失败: {ex.Message}");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
private void CreateDefaultAndroidConfig(string configPath)
|
||
{
|
||
try
|
||
{
|
||
var defaultConfig = @"[SerialPort]
|
||
serialPortName=/dev/ttyS4
|
||
serialBaudRate=115200
|
||
serialDataBits=8
|
||
serialStopBits=1
|
||
serialParity=None
|
||
serialFlowControl=false
|
||
heartbeatInterval=1000
|
||
communicationTimeout=5000
|
||
autoReconnect=true
|
||
maxReconnectAttempts=5
|
||
shutdownBroadcastAction=com.dcx.ruiyiweiux.ACTION_SHUTDOWN";
|
||
|
||
File.WriteAllText(configPath, defaultConfig);
|
||
Debug.Log($"创建默认安卓配置文件: {configPath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"创建默认安卓配置文件失败: {ex.Message}");
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 根据平台获取合适的串口名称
|
||
/// </summary>
|
||
/// <returns>串口名称</returns>
|
||
public string GetPlatformSerialPortName()
|
||
{
|
||
// 优先从配置文件读取
|
||
try
|
||
{
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
// 安卓平台串口配置
|
||
var androidConfigPath = Path.Combine(Application.persistentDataPath, "config_android.ini");
|
||
if (File.Exists(androidConfigPath))
|
||
{
|
||
var lines = File.ReadAllLines(androidConfigPath);
|
||
foreach (var line in lines)
|
||
{
|
||
if (line.StartsWith("serialPortName="))
|
||
{
|
||
var portName = line.Substring("serialPortName=".Length).Trim();
|
||
if (!string.IsNullOrEmpty(portName))
|
||
{
|
||
Debug.Log($"从安卓配置文件读取串口名称: {portName}");
|
||
return portName;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
// 桌面平台配置文件读取
|
||
var configPath = Path.Combine(Application.streamingAssetsPath, "config.ini");
|
||
if (File.Exists(configPath))
|
||
{
|
||
var lines = File.ReadAllLines(configPath);
|
||
foreach (var line in lines)
|
||
{
|
||
if (line.StartsWith("serialPortName="))
|
||
{
|
||
var portName = line.Substring("serialPortName=".Length).Trim();
|
||
if (!string.IsNullOrEmpty(portName))
|
||
{
|
||
Debug.Log($"从配置文件读取串口名称: {portName}");
|
||
return portName;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"读取串口配置失败: {ex.Message}");
|
||
}
|
||
|
||
// 如果配置文件不存在或读取失败,使用平台默认值
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
// 安卓平台常用串口设备路径
|
||
string[] androidPorts = { "/dev/ttyS4", "/dev/ttyUSB1", "/dev/ttyACM0", "/dev/ttyACM1" };
|
||
foreach (var port in androidPorts)
|
||
{
|
||
if (File.Exists(port))
|
||
{
|
||
Debug.Log($"检测到安卓串口设备: {port}");
|
||
return port;
|
||
}
|
||
}
|
||
Debug.LogWarning("未检测到安卓串口设备,使用默认路径");
|
||
return "/dev/ttyS4";
|
||
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
|
||
// Windows平台默认串口
|
||
return "COM1";
|
||
#elif UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX
|
||
// Linux平台默认串口
|
||
return "/dev/ttyS4";
|
||
#elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
|
||
// macOS平台默认串口
|
||
return "/dev/cu.usbserial-1410";
|
||
#else
|
||
// 其他平台默认
|
||
return "COM1";
|
||
#endif
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据平台获取合适的串口波特率
|
||
/// </summary>
|
||
/// <returns>波特率</returns>
|
||
private int GetPlatformSerialBaudRate()
|
||
{
|
||
try
|
||
{
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
// 安卓平台配置读取
|
||
var androidConfigPath = Path.Combine(Application.persistentDataPath, "config_android.ini");
|
||
if (File.Exists(androidConfigPath))
|
||
{
|
||
var lines = File.ReadAllLines(androidConfigPath);
|
||
foreach (var line in lines)
|
||
{
|
||
if (line.StartsWith("serialBaudRate="))
|
||
{
|
||
var baudRateStr = line.Substring("serialBaudRate=".Length).Trim();
|
||
if (int.TryParse(baudRateStr, out int baudRate))
|
||
{
|
||
Debug.Log($"从安卓配置文件读取波特率: {baudRate}");
|
||
return baudRate;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
// 桌面平台配置读取
|
||
var configPath = Path.Combine(Application.streamingAssetsPath, "config.ini");
|
||
if (File.Exists(configPath))
|
||
{
|
||
var lines = File.ReadAllLines(configPath);
|
||
foreach (var line in lines)
|
||
{
|
||
if (line.StartsWith("baudRate="))
|
||
{
|
||
var baudRateStr = line.Substring("baudRate=".Length).Trim();
|
||
if (int.TryParse(baudRateStr, out int baudRate))
|
||
{
|
||
Debug.Log($"从配置文件读取波特率: {baudRate}");
|
||
return baudRate;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"读取波特率配置失败: {ex.Message}");
|
||
}
|
||
|
||
// 默认波特率(DCS协议标准)
|
||
return 115200;
|
||
}
|
||
|
||
#region 自动记录管理
|
||
|
||
/// <summary>
|
||
/// 患者信息变更处理
|
||
/// </summary>
|
||
private void OnPatientInfoChanged(PatientInfo patient)
|
||
{
|
||
if (_isRecordingTest && _currentTestRecord != null && patient != null)
|
||
{
|
||
UpdateCurrentRecordPatientInfo(patient.HospitalId, patient.Name, patient.Gender, patient.Age, patient.Height, patient.Weight);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动开始记录(收到心跳时触发)
|
||
/// </summary>
|
||
private void AutoStartRecording()
|
||
{
|
||
if (_isRecordingTest)
|
||
{
|
||
Debug.Log("已有测试正在记录中,无需重复开始");
|
||
return;
|
||
}
|
||
|
||
// 获取当前患者信息
|
||
var patientService = ServiceLocator.Get<IPatientInfoService>();
|
||
var patient = patientService?.GetCurrentPatient();
|
||
|
||
string patientId = "";
|
||
string patientName = "未知患者";
|
||
string patientGender = "";
|
||
int patientAge = 0;
|
||
float patientHeight = 0f;
|
||
float patientWeight = 0f;
|
||
|
||
if (patient != null)
|
||
{
|
||
patientId = patient.HospitalId;
|
||
patientName = patient.Name;
|
||
patientGender = patient.Gender;
|
||
patientAge = patient.Age;
|
||
patientHeight = patient.Height;
|
||
patientWeight = patient.Weight;
|
||
}
|
||
|
||
string testName = $"BFI测试_{DateTime.Now:yyyyMMdd_HHmmss}";
|
||
StartBFITestRecording(testName, patientId, patientName, patientGender, patientAge, patientHeight, patientWeight);
|
||
|
||
Debug.Log($"[自动记录] 收到心跳,开始记录 - 患者: {patientName}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 心跳监控协程
|
||
/// </summary>
|
||
private IEnumerator MonitorHeartbeat()
|
||
{
|
||
while (true)
|
||
{
|
||
yield return new WaitForSeconds(1f); // 每秒检查一次
|
||
|
||
if (_autoRecordingEnabled && _isRecordingTest)
|
||
{
|
||
RolloverRecordingIfNeeded("记录超过24小时");
|
||
FinalizeLastJournalPoint();
|
||
|
||
// 检查心跳超时
|
||
var timeSinceLastHeartbeat = (DateTime.Now - _lastHeartbeatTime).TotalSeconds;
|
||
if (_hasReceivedHeartbeat && timeSinceLastHeartbeat > HEARTBEAT_TIMEOUT_SECONDS && !_heartbeatTimeoutLogged)
|
||
{
|
||
Debug.LogWarning($"[心跳监控] 心跳超时 ({timeSinceLastHeartbeat:F1}秒),保持当前BFI记录,等待心跳恢复");
|
||
_heartbeatTimeoutLogged = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void RolloverRecordingIfNeeded(string reason)
|
||
{
|
||
if (!_autoRecordingEnabled || !_isRecordingTest || _currentTestRecord == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (DateTime.Now - _currentTestRecord.StartTime < MAX_RECORDING_DURATION)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Debug.Log($"[自动记录] {reason},保存当前BFI记录并继续新记录");
|
||
StopBFITestRecording();
|
||
AutoStartRecording();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新当前记录的患者信息(当用户在记录过程中设置患者信息时)
|
||
/// </summary>
|
||
public void UpdateCurrentRecordPatientInfo(string patientId, string patientName)
|
||
{
|
||
UpdateCurrentRecordPatientInfo(patientId, patientName, string.Empty, 0, 0f, 0f);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新当前记录的完整患者信息(当用户在记录过程中更新患者信息时)
|
||
/// </summary>
|
||
public void UpdateCurrentRecordPatientInfo(string patientId, string patientName, string patientGender, int patientAge, float patientHeight, float patientWeight)
|
||
{
|
||
if (_isRecordingTest && _currentTestRecord != null)
|
||
{
|
||
_currentTestRecord.PatientId = patientId;
|
||
_currentTestRecord.PatientName = patientName;
|
||
_currentTestRecord.PatientGender = patientGender;
|
||
_currentTestRecord.PatientAge = patientAge;
|
||
_currentTestRecord.PatientHeight = patientHeight;
|
||
_currentTestRecord.PatientWeight = patientWeight;
|
||
AppendCurrentRecordingJournalEntry(CreateJournalPatientEntry(_currentTestRecord));
|
||
SaveCurrentRecordingSnapshotIfNeeded(true, "更新患者信息");
|
||
Debug.Log($"[自动记录] 更新患者信息: {patientName} ({patientId})");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region BFI测试记录管理
|
||
|
||
/// <summary>
|
||
/// 开始BFI测试记录
|
||
/// </summary>
|
||
public void StartBFITestRecording(string testName = "", string patientId = "", string patientName = "", string patientGender = "", int patientAge = 0, float patientHeight = 0f, float patientWeight = 0f)
|
||
{
|
||
try
|
||
{
|
||
if (_isRecordingTest)
|
||
{
|
||
Debug.LogWarning("已有测试正在记录中,先停止当前测试");
|
||
StopBFITestRecording();
|
||
}
|
||
|
||
// 创建新的测试记录
|
||
var startTime = DateTime.Now;
|
||
_currentTestRecord = new BFITestRecord
|
||
{
|
||
TestId = Guid.NewGuid().ToString(),
|
||
TestName = string.IsNullOrEmpty(testName) ? $"BFI测试_{startTime:yyyyMMdd_HHmmss}" : testName,
|
||
PatientId = patientId,
|
||
PatientName = patientName,
|
||
PatientGender = patientGender,
|
||
PatientAge = patientAge,
|
||
PatientHeight = patientHeight,
|
||
PatientWeight = patientWeight,
|
||
StartTime = startTime,
|
||
StartTimeTicks = startTime.Ticks,
|
||
EndTime = DateTime.MinValue,
|
||
EndTimeTicks = 0,
|
||
DataPoints = new List<BFIDataPoint>()
|
||
};
|
||
|
||
_currentTestData = new List<BFIDataPoint>();
|
||
_isRecordingTest = true;
|
||
_lastCurrentRecordSnapshotTime = DateTime.MinValue;
|
||
_lastCurrentRecordSnapshotPointCount = 0;
|
||
_lastJournaledPointTicks = 0;
|
||
_lastJournaledPointBFI = float.NaN;
|
||
ResetCurrentRecordingJournal();
|
||
AppendCurrentRecordingJournalEntry(CreateJournalStartEntry(_currentTestRecord));
|
||
|
||
Debug.Log($"开始BFI测试记录: {_currentTestRecord.TestName}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"开始BFI测试记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止BFI测试记录并保存
|
||
/// </summary>
|
||
public void StopBFITestRecording()
|
||
{
|
||
try
|
||
{
|
||
if (!_isRecordingTest || _currentTestRecord == null)
|
||
{
|
||
Debug.LogWarning("没有正在进行的测试记录");
|
||
return;
|
||
}
|
||
|
||
// 完成测试记录
|
||
var endTime = DateTime.Now;
|
||
_currentTestRecord.EndTime = endTime;
|
||
_currentTestRecord.EndTimeTicks = endTime.Ticks;
|
||
_currentTestRecord.DataPoints = new List<BFIDataPoint>(_currentTestData);
|
||
|
||
// 计算统计信息
|
||
_currentTestRecord.EndTest(endTime);
|
||
FinalizeLastJournalPoint();
|
||
AppendCurrentRecordingJournalEntry(CreateJournalEndEntry(_currentTestRecord));
|
||
|
||
// 保存到历史记录
|
||
bool savedToHistory = false;
|
||
if (BFIHistoryManager.Instance != null)
|
||
{
|
||
bool savedToDisk = BFIHistoryManager.Instance.AddTestRecord(_currentTestRecord);
|
||
var savedRecord = BFIHistoryManager.Instance.GetRecord(_currentTestRecord.TestId);
|
||
int expectedPointCount = _currentTestData?.Count ?? 0;
|
||
savedToHistory = savedToDisk &&
|
||
savedRecord != null &&
|
||
(savedRecord.DataPoints?.Count ?? 0) >= expectedPointCount &&
|
||
BFIHistoryManager.Instance.IsRecordPersisted(_currentTestRecord.TestId, expectedPointCount);
|
||
if (savedToHistory)
|
||
{
|
||
Debug.Log($"BFI测试记录已保存: {_currentTestRecord.TestName}, 数据点: {_currentTestData.Count}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"BFI测试记录保存失败或未确认落盘: {_currentTestRecord.TestName}, 数据点: {_currentTestData.Count}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("BFIHistoryManager未找到,无法保存测试记录");
|
||
}
|
||
|
||
if (savedToHistory)
|
||
{
|
||
DeleteCurrentRecordingJournal();
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("BFI测试记录未确认写入历史索引,保留实时日志以便下次启动恢复");
|
||
}
|
||
|
||
// 清理
|
||
_isRecordingTest = false;
|
||
_currentTestRecord = null;
|
||
_currentTestData?.Clear();
|
||
_lastCurrentRecordSnapshotTime = DateTime.MinValue;
|
||
_lastCurrentRecordSnapshotPointCount = 0;
|
||
_lastJournaledPointTicks = 0;
|
||
_lastJournaledPointBFI = float.NaN;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"停止BFI测试记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 记录BFI数据点
|
||
/// </summary>
|
||
private void RecordBFIData(float bfiValue)
|
||
{
|
||
if (!_isRecordingTest || _currentTestData == null) return;
|
||
|
||
try
|
||
{
|
||
if (float.IsNaN(bfiValue) || float.IsInfinity(bfiValue) || bfiValue < MIN_VALID_BFI || bfiValue > MAX_VALID_BFI)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 丢弃异常BFI数据点: {bfiValue}");
|
||
return;
|
||
}
|
||
|
||
// 记录粒度按秒:同一秒内只保留一个数据点(更新为最新值)
|
||
var timestamp = TruncateToSecond(DateTime.Now);
|
||
|
||
if (_currentTestData.Count > 0)
|
||
{
|
||
var lastPoint = _currentTestData[_currentTestData.Count - 1];
|
||
if (lastPoint != null && lastPoint.Timestamp == timestamp)
|
||
{
|
||
lastPoint.BFI = bfiValue;
|
||
lastPoint.Status = "正常";
|
||
lastPoint.AlarmLevel = 0;
|
||
lastPoint.TimestampTicks = timestamp.Ticks;
|
||
return;
|
||
}
|
||
|
||
FinalizeJournalPoint(lastPoint);
|
||
}
|
||
|
||
var dataPoint = new BFIDataPoint
|
||
{
|
||
Timestamp = timestamp,
|
||
TimestampTicks = timestamp.Ticks,
|
||
BFI = bfiValue,
|
||
Status = "正常",
|
||
AlarmLevel = 0
|
||
};
|
||
|
||
_currentTestData.Add(dataPoint);
|
||
AppendJournalPointIfChanged(dataPoint);
|
||
SaveCurrentRecordingSnapshotIfNeeded(false, "新增BFI数据点");
|
||
|
||
// 严格限制数据点数量,防止安卓板内存溢出
|
||
const int MAX_DATA_POINTS = 90000; // 24小时每秒1点约86400个,保留完整日记录
|
||
if (_currentTestData.Count > MAX_DATA_POINTS)
|
||
{
|
||
// 移除前1/3数据,保留后2/3,减少频繁操作
|
||
int removeCount = _currentTestData.Count / 3;
|
||
_currentTestData.RemoveRange(0, removeCount);
|
||
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
AndroidFileLogger.Instance?.LogInfo("DataService",
|
||
$"BFI数据清理: 删除{removeCount}个旧数据点,剩余{_currentTestData.Count}个");
|
||
#endif
|
||
|
||
// 减少日志输出频率,避免内存积压
|
||
if (_currentTestData.Count % 300 == 0) // 每300个数据点记录一次
|
||
{
|
||
Debug.LogWarning($"[内存管理] BFI数据达到{MAX_DATA_POINTS}个,已清理{removeCount}个旧数据点");
|
||
}
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"记录BFI数据失败: {ex.Message}");
|
||
|
||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
AndroidFileLogger.Instance?.LogError("DataService", $"记录BFI数据异常: {ex.Message}");
|
||
#endif
|
||
}
|
||
}
|
||
|
||
private DateTime TruncateToSecond(DateTime time)
|
||
{
|
||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second, time.Kind);
|
||
}
|
||
|
||
private void FinalizeLastJournalPoint()
|
||
{
|
||
if (_currentTestData == null || _currentTestData.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
FinalizeJournalPoint(_currentTestData[_currentTestData.Count - 1]);
|
||
}
|
||
|
||
private void FinalizeJournalPoint(BFIDataPoint point)
|
||
{
|
||
AppendJournalPointIfChanged(point);
|
||
}
|
||
|
||
private void AppendJournalPointIfChanged(BFIDataPoint point)
|
||
{
|
||
if (point == null || point.TimestampTicks == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (point.TimestampTicks == _lastJournaledPointTicks && Mathf.Approximately(point.BFI, _lastJournaledPointBFI))
|
||
{
|
||
return;
|
||
}
|
||
|
||
AppendCurrentRecordingJournalEntry(CreateJournalPointEntry(point));
|
||
_lastJournaledPointTicks = point.TimestampTicks;
|
||
_lastJournaledPointBFI = point.BFI;
|
||
}
|
||
|
||
private void SaveCurrentRecordingSnapshotIfNeeded(bool force, string reason)
|
||
{
|
||
if (!_isRecordingTest || _currentTestRecord == null || _currentTestData == null || _currentTestData.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
DateTime now = DateTime.Now;
|
||
bool intervalReached = now - _lastCurrentRecordSnapshotTime >= CURRENT_RECORD_SNAPSHOT_INTERVAL;
|
||
bool pointCountChanged = _currentTestData.Count != _lastCurrentRecordSnapshotPointCount;
|
||
|
||
if (!force && (!intervalReached || !pointCountChanged))
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var snapshot = CreateCurrentRecordingSnapshot(now);
|
||
bool saved = force
|
||
? BFIHistoryManager.Instance.AddTestRecord(snapshot)
|
||
: BFIHistoryManager.Instance.AddTestRecordSnapshot(snapshot);
|
||
|
||
if (!saved)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 当前BFI记录快照未确认写入磁盘: {reason}");
|
||
_lastCurrentRecordSnapshotTime = now;
|
||
return;
|
||
}
|
||
|
||
_lastCurrentRecordSnapshotTime = now;
|
||
_lastCurrentRecordSnapshotPointCount = _currentTestData.Count;
|
||
if (force || _currentTestData.Count % 300 == 0)
|
||
{
|
||
Debug.Log($"[自动记录] 当前BFI记录快照已保存: {reason}, 数据点: {_currentTestData.Count}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 保存当前BFI记录快照失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
public bool FlushCurrentBFIRecordingSnapshot(string reason = "")
|
||
{
|
||
int beforePointCount = _lastCurrentRecordSnapshotPointCount;
|
||
SaveCurrentRecordingSnapshotIfNeeded(true, string.IsNullOrEmpty(reason) ? "手动刷新当前BFI记录" : reason);
|
||
return _lastCurrentRecordSnapshotPointCount >= beforePointCount &&
|
||
_currentTestData != null &&
|
||
_lastCurrentRecordSnapshotPointCount == _currentTestData.Count;
|
||
}
|
||
|
||
public void RecoverPendingBFIRecordingJournal(string reason = "")
|
||
{
|
||
RecoverCurrentRecordingJournalIfNeeded(string.IsNullOrEmpty(reason) ? "手动恢复当前BFI实时日志" : reason);
|
||
}
|
||
|
||
public void ResetCurrentBFIRecordingAfterHistoryClear(string reason = "")
|
||
{
|
||
bool shouldContinueRecording = _autoRecordingEnabled && _isRecordingTest;
|
||
|
||
_isRecordingTest = false;
|
||
_currentTestRecord = null;
|
||
_currentTestData?.Clear();
|
||
_lastCurrentRecordSnapshotTime = DateTime.MinValue;
|
||
_lastCurrentRecordSnapshotPointCount = 0;
|
||
_lastJournaledPointTicks = 0;
|
||
_lastJournaledPointBFI = float.NaN;
|
||
DeleteCurrentRecordingJournal();
|
||
|
||
Debug.Log($"[自动记录] 已重置当前BFI记录缓存: {reason}");
|
||
|
||
if (shouldContinueRecording)
|
||
{
|
||
AutoStartRecording();
|
||
}
|
||
}
|
||
|
||
private BFITestRecord CreateCurrentRecordingSnapshot(DateTime snapshotTime)
|
||
{
|
||
var snapshot = new BFITestRecord
|
||
{
|
||
TestId = _currentTestRecord.TestId,
|
||
TestName = _currentTestRecord.TestName,
|
||
PatientId = _currentTestRecord.PatientId,
|
||
PatientName = _currentTestRecord.PatientName,
|
||
PatientGender = _currentTestRecord.PatientGender,
|
||
PatientAge = _currentTestRecord.PatientAge,
|
||
PatientHeight = _currentTestRecord.PatientHeight,
|
||
PatientWeight = _currentTestRecord.PatientWeight,
|
||
LowThreshold = _currentTestRecord.LowThreshold,
|
||
HighThreshold = _currentTestRecord.HighThreshold,
|
||
AlarmPriority = _currentTestRecord.AlarmPriority,
|
||
AlarmEnabled = _currentTestRecord.AlarmEnabled,
|
||
StartTime = _currentTestRecord.StartTime,
|
||
StartTimeTicks = _currentTestRecord.StartTimeTicks,
|
||
EndTime = snapshotTime,
|
||
EndTimeTicks = snapshotTime.Ticks,
|
||
DataPoints = new List<BFIDataPoint>(_currentTestData)
|
||
};
|
||
|
||
snapshot.EndTest(snapshotTime);
|
||
return snapshot;
|
||
}
|
||
|
||
private string ResolveCurrentRecordingJournalPath()
|
||
{
|
||
return Path.Combine(Application.persistentDataPath, CURRENT_RECORDING_JOURNAL_FILE_NAME);
|
||
}
|
||
|
||
private void RecoverCurrentRecordingJournalIfNeeded(string reason)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(_currentRecordingJournalPath))
|
||
{
|
||
_currentRecordingJournalPath = ResolveCurrentRecordingJournalPath();
|
||
}
|
||
|
||
if (_isRecordingTest || !File.Exists(_currentRecordingJournalPath))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!TryReadCurrentRecordingJournal(out var recoveredRecord))
|
||
{
|
||
Debug.LogWarning($"[自动记录] 未能恢复当前BFI实时日志,保留文件等待下次尝试: {_currentRecordingJournalPath}");
|
||
return;
|
||
}
|
||
|
||
int pointCount = recoveredRecord.DataPoints?.Count ?? 0;
|
||
if (pointCount == 0)
|
||
{
|
||
DeleteCurrentRecordingJournal();
|
||
Debug.Log("[自动记录] 当前BFI实时日志没有数据点,已清理");
|
||
return;
|
||
}
|
||
|
||
bool savedToDisk = BFIHistoryManager.Instance.AddTestRecord(recoveredRecord);
|
||
var savedRecord = BFIHistoryManager.Instance.GetRecord(recoveredRecord.TestId);
|
||
if (savedToDisk &&
|
||
savedRecord != null &&
|
||
(savedRecord.DataPoints?.Count ?? 0) >= pointCount &&
|
||
BFIHistoryManager.Instance.IsRecordPersisted(recoveredRecord.TestId, pointCount))
|
||
{
|
||
DeleteCurrentRecordingJournal();
|
||
Debug.Log($"[自动记录] 已从实时日志恢复BFI记录: {reason}, 数据点: {pointCount}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[自动记录] 实时日志恢复后未确认写入历史索引,继续保留日志: {_currentRecordingJournalPath}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 恢复当前BFI实时日志失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private bool TryReadCurrentRecordingJournal(out BFITestRecord record)
|
||
{
|
||
record = null;
|
||
long endTimeTicks = 0;
|
||
|
||
try
|
||
{
|
||
foreach (var line in File.ReadLines(_currentRecordingJournalPath, Encoding.UTF8))
|
||
{
|
||
if (string.IsNullOrWhiteSpace(line))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
BFIRecordingJournalEntry entry;
|
||
try
|
||
{
|
||
entry = JsonUtility.FromJson<BFIRecordingJournalEntry>(line);
|
||
}
|
||
catch
|
||
{
|
||
// 突然断电可能留下半行,跳过无法解析的最后一行。
|
||
continue;
|
||
}
|
||
|
||
if (entry == null || string.IsNullOrEmpty(entry.Type))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (entry.Type == JOURNAL_ENTRY_START)
|
||
{
|
||
record = CreateRecordFromJournalStart(entry);
|
||
}
|
||
else if (entry.Type == JOURNAL_ENTRY_PATIENT)
|
||
{
|
||
if (record == null)
|
||
{
|
||
record = CreateRecordFromJournalStart(entry);
|
||
}
|
||
|
||
ApplyJournalPatientInfo(record, entry);
|
||
}
|
||
else if (entry.Type == JOURNAL_ENTRY_POINT)
|
||
{
|
||
if (record == null)
|
||
{
|
||
record = CreateRecordFromJournalStart(entry);
|
||
}
|
||
|
||
AddOrUpdateJournalPoint(record, entry);
|
||
}
|
||
else if (entry.Type == JOURNAL_ENTRY_END)
|
||
{
|
||
endTimeTicks = entry.EndTimeTicks;
|
||
}
|
||
}
|
||
|
||
if (record == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
record.RestoreAfterDeserialization();
|
||
DateTime endTime = ResolveRecoveredEndTime(record, endTimeTicks);
|
||
record.EndTest(endTime);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 读取当前BFI实时日志失败: {ex.Message}");
|
||
record = null;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private BFITestRecord CreateRecordFromJournalStart(BFIRecordingJournalEntry entry)
|
||
{
|
||
long startTicks = entry.StartTimeTicks > 0 ? entry.StartTimeTicks : DateTime.Now.Ticks;
|
||
var startTime = new DateTime(startTicks, DateTimeKind.Local);
|
||
|
||
return new BFITestRecord
|
||
{
|
||
TestId = string.IsNullOrEmpty(entry.TestId) ? Guid.NewGuid().ToString() : entry.TestId,
|
||
TestName = string.IsNullOrEmpty(entry.TestName) ? $"BFI测试_{startTime:yyyyMMdd_HHmmss}" : entry.TestName,
|
||
PatientId = entry.PatientId ?? string.Empty,
|
||
PatientName = entry.PatientName ?? string.Empty,
|
||
PatientGender = entry.PatientGender ?? string.Empty,
|
||
PatientAge = entry.PatientAge,
|
||
PatientHeight = entry.PatientHeight,
|
||
PatientWeight = entry.PatientWeight,
|
||
LowThreshold = entry.LowThreshold,
|
||
HighThreshold = entry.HighThreshold,
|
||
AlarmPriority = entry.AlarmPriority,
|
||
AlarmEnabled = entry.AlarmEnabled,
|
||
StartTime = startTime,
|
||
StartTimeTicks = startTicks,
|
||
EndTime = DateTime.MinValue,
|
||
EndTimeTicks = 0,
|
||
DataPoints = new List<BFIDataPoint>()
|
||
};
|
||
}
|
||
|
||
private void ApplyJournalPatientInfo(BFITestRecord record, BFIRecordingJournalEntry entry)
|
||
{
|
||
record.PatientId = entry.PatientId ?? string.Empty;
|
||
record.PatientName = entry.PatientName ?? string.Empty;
|
||
record.PatientGender = entry.PatientGender ?? string.Empty;
|
||
record.PatientAge = entry.PatientAge;
|
||
record.PatientHeight = entry.PatientHeight;
|
||
record.PatientWeight = entry.PatientWeight;
|
||
}
|
||
|
||
private void AddOrUpdateJournalPoint(BFITestRecord record, BFIRecordingJournalEntry entry)
|
||
{
|
||
if (record.DataPoints == null)
|
||
{
|
||
record.DataPoints = new List<BFIDataPoint>();
|
||
}
|
||
|
||
long timestampTicks = entry.TimestampTicks > 0 ? entry.TimestampTicks : DateTime.Now.Ticks;
|
||
var existingPoint = record.DataPoints.Find(point => point != null && point.TimestampTicks == timestampTicks);
|
||
if (existingPoint != null)
|
||
{
|
||
existingPoint.BFI = entry.BFI;
|
||
existingPoint.Status = string.IsNullOrEmpty(entry.Status) ? "正常" : entry.Status;
|
||
existingPoint.Notes = entry.Notes ?? string.Empty;
|
||
existingPoint.AlarmLevel = entry.AlarmLevel;
|
||
existingPoint.Timestamp = new DateTime(timestampTicks, DateTimeKind.Local);
|
||
return;
|
||
}
|
||
|
||
record.DataPoints.Add(new BFIDataPoint
|
||
{
|
||
Timestamp = new DateTime(timestampTicks, DateTimeKind.Local),
|
||
TimestampTicks = timestampTicks,
|
||
BFI = entry.BFI,
|
||
Status = string.IsNullOrEmpty(entry.Status) ? "正常" : entry.Status,
|
||
Notes = entry.Notes ?? string.Empty,
|
||
AlarmLevel = entry.AlarmLevel
|
||
});
|
||
}
|
||
|
||
private DateTime ResolveRecoveredEndTime(BFITestRecord record, long endTimeTicks)
|
||
{
|
||
if (endTimeTicks > 0)
|
||
{
|
||
return new DateTime(endTimeTicks, DateTimeKind.Local);
|
||
}
|
||
|
||
if (record.DataPoints != null && record.DataPoints.Count > 0)
|
||
{
|
||
var lastPoint = record.DataPoints[record.DataPoints.Count - 1];
|
||
if (lastPoint != null && lastPoint.TimestampTicks > 0)
|
||
{
|
||
return new DateTime(lastPoint.TimestampTicks, DateTimeKind.Local);
|
||
}
|
||
}
|
||
|
||
return DateTime.Now;
|
||
}
|
||
|
||
private void ResetCurrentRecordingJournal()
|
||
{
|
||
DeleteCurrentRecordingJournal();
|
||
}
|
||
|
||
private void DeleteCurrentRecordingJournal()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(_currentRecordingJournalPath))
|
||
{
|
||
_currentRecordingJournalPath = ResolveCurrentRecordingJournalPath();
|
||
}
|
||
|
||
if (File.Exists(_currentRecordingJournalPath))
|
||
{
|
||
File.Delete(_currentRecordingJournalPath);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 删除当前BFI实时日志失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void AppendCurrentRecordingJournalEntry(BFIRecordingJournalEntry entry)
|
||
{
|
||
try
|
||
{
|
||
if (entry == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(_currentRecordingJournalPath))
|
||
{
|
||
_currentRecordingJournalPath = ResolveCurrentRecordingJournalPath();
|
||
}
|
||
|
||
string directory = Path.GetDirectoryName(_currentRecordingJournalPath);
|
||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
string line = JsonUtility.ToJson(entry, false) + Environment.NewLine;
|
||
byte[] bytes = Encoding.UTF8.GetBytes(line);
|
||
using (var stream = new FileStream(_currentRecordingJournalPath, FileMode.Append, FileAccess.Write, FileShare.Read, 4096))
|
||
{
|
||
stream.Write(bytes, 0, bytes.Length);
|
||
FlushJournalStream(stream);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[自动记录] 写入当前BFI实时日志失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void FlushJournalStream(FileStream stream)
|
||
{
|
||
try
|
||
{
|
||
stream.Flush(true);
|
||
}
|
||
catch
|
||
{
|
||
stream.Flush();
|
||
}
|
||
}
|
||
|
||
private BFIRecordingJournalEntry CreateJournalStartEntry(BFITestRecord record)
|
||
{
|
||
return new BFIRecordingJournalEntry
|
||
{
|
||
Type = JOURNAL_ENTRY_START,
|
||
TestId = record.TestId,
|
||
TestName = record.TestName,
|
||
PatientId = record.PatientId,
|
||
PatientName = record.PatientName,
|
||
PatientGender = record.PatientGender,
|
||
PatientAge = record.PatientAge,
|
||
PatientHeight = record.PatientHeight,
|
||
PatientWeight = record.PatientWeight,
|
||
LowThreshold = record.LowThreshold,
|
||
HighThreshold = record.HighThreshold,
|
||
AlarmPriority = record.AlarmPriority,
|
||
AlarmEnabled = record.AlarmEnabled,
|
||
StartTimeTicks = record.StartTimeTicks
|
||
};
|
||
}
|
||
|
||
private BFIRecordingJournalEntry CreateJournalPointEntry(BFIDataPoint point)
|
||
{
|
||
return new BFIRecordingJournalEntry
|
||
{
|
||
Type = JOURNAL_ENTRY_POINT,
|
||
TestId = _currentTestRecord?.TestId,
|
||
StartTimeTicks = _currentTestRecord?.StartTimeTicks ?? 0,
|
||
TimestampTicks = point.TimestampTicks,
|
||
BFI = point.BFI,
|
||
Status = point.Status,
|
||
Notes = point.Notes,
|
||
AlarmLevel = point.AlarmLevel
|
||
};
|
||
}
|
||
|
||
private BFIRecordingJournalEntry CreateJournalPatientEntry(BFITestRecord record)
|
||
{
|
||
return new BFIRecordingJournalEntry
|
||
{
|
||
Type = JOURNAL_ENTRY_PATIENT,
|
||
TestId = record.TestId,
|
||
StartTimeTicks = record.StartTimeTicks,
|
||
PatientId = record.PatientId,
|
||
PatientName = record.PatientName,
|
||
PatientGender = record.PatientGender,
|
||
PatientAge = record.PatientAge,
|
||
PatientHeight = record.PatientHeight,
|
||
PatientWeight = record.PatientWeight
|
||
};
|
||
}
|
||
|
||
private BFIRecordingJournalEntry CreateJournalEndEntry(BFITestRecord record)
|
||
{
|
||
return new BFIRecordingJournalEntry
|
||
{
|
||
Type = JOURNAL_ENTRY_END,
|
||
TestId = record.TestId,
|
||
StartTimeTicks = record.StartTimeTicks,
|
||
EndTimeTicks = record.EndTimeTicks
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前测试记录状态
|
||
/// </summary>
|
||
public bool IsRecordingBFITest()
|
||
{
|
||
return _isRecordingTest;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前测试记录信息
|
||
/// </summary>
|
||
public BFITestRecord GetCurrentTestRecord()
|
||
{
|
||
return _currentTestRecord;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前测试数据点数量
|
||
/// </summary>
|
||
public int GetCurrentTestDataCount()
|
||
{
|
||
return _currentTestData?.Count ?? 0;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class BFIRecordingJournalEntry
|
||
{
|
||
public string Type;
|
||
public string TestId;
|
||
public string TestName;
|
||
public string PatientId;
|
||
public string PatientName;
|
||
public string PatientGender;
|
||
public int PatientAge;
|
||
public float PatientHeight;
|
||
public float PatientWeight;
|
||
public float LowThreshold;
|
||
public float HighThreshold;
|
||
public int AlarmPriority;
|
||
public bool AlarmEnabled;
|
||
public long StartTimeTicks;
|
||
public long EndTimeTicks;
|
||
public long TimestampTicks;
|
||
public float BFI;
|
||
public string Status;
|
||
public string Notes;
|
||
public int AlarmLevel;
|
||
}
|