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;
|
|||
|
|
}
|